Release notes dialog (#2726)

* Move manual docs
* Move changelog docs
* Add migration notes
* Move changelog to settings
* Add release notes dialog
* Add new changelog
This commit is contained in:
WithoutPants 2022-07-13 12:57:53 +10:00
parent 964b559309
commit 30877c75fb
59 changed files with 229 additions and 57 deletions

View file

@ -9,7 +9,11 @@ import LightboxProvider from "src/hooks/Lightbox/context";
import { initPolyfills } from "src/polyfills";
import locales from "src/locales";
import { useConfiguration, useSystemStatus } from "src/core/StashService";
import {
useConfiguration,
useConfigureUI,
useSystemStatus,
} from "src/core/StashService";
import { flattenMessages } from "src/utils";
import Mousetrap from "mousetrap";
import MousetrapPause from "mousetrap-pause";
@ -22,6 +26,9 @@ import { LoadingIndicator, TITLE_SUFFIX } from "./components/Shared";
import { ConfigurationProvider } from "./hooks/Config";
import { ManualProvider } from "./components/Help/context";
import { InteractiveProvider } from "./hooks/Interactive/context";
import { ReleaseNotesDialog } from "./components/Dialogs/ReleaseNotesDialog";
import { IUIConfig } from "./core/config";
import { releaseNotes } from "./docs/en/ReleaseNotes";
const Performers = lazy(() => import("./components/Performers/Performers"));
const FrontPage = lazy(() => import("./components/FrontPage/FrontPage"));
@ -62,6 +69,8 @@ function languageMessageString(language: string) {
export const App: React.FC = () => {
const config = useConfiguration();
const [saveUI] = useConfigureUI();
const { data: systemStatusData } = useSystemStatus();
const language =
@ -161,6 +170,32 @@ export const App: React.FC = () => {
);
}
function maybeRenderReleaseNotes() {
const lastNoteSeen = (config.data?.configuration.ui as IUIConfig)
?.lastNoteSeen;
const notes = releaseNotes.filter((n) => {
return !lastNoteSeen || n.date > lastNoteSeen;
});
if (notes.length === 0) return;
return (
<ReleaseNotesDialog
notes={notes.map((n) => n.content)}
onClose={() => {
saveUI({
variables: {
input: {
...config.data?.configuration.ui,
lastNoteSeen: notes[0].date,
},
},
});
}}
/>
);
}
return (
<ErrorBoundary>
{messages ? (
@ -173,6 +208,7 @@ export const App: React.FC = () => {
configuration={config.data?.configuration}
loading={config.loading}
>
{maybeRenderReleaseNotes()}
<ToastProvider>
<Suspense fallback={<LoadingIndicator />}>
<LightboxProvider>

View file

@ -1,26 +1,27 @@
import React from "react";
import { useChangelogStorage } from "src/hooks";
import Version from "./Version";
import V010 from "./versions/v010.md";
import V011 from "./versions/v011.md";
import V020 from "./versions/v020.md";
import V021 from "./versions/v021.md";
import V030 from "./versions/v030.md";
import V040 from "./versions/v040.md";
import V050 from "./versions/v050.md";
import V060 from "./versions/v060.md";
import V070 from "./versions/v070.md";
import V080 from "./versions/v080.md";
import V090 from "./versions/v090.md";
import V0100 from "./versions/v0100.md";
import V0110 from "./versions/v0110.md";
import V0120 from "./versions/v0120.md";
import V0130 from "./versions/v0130.md";
import V0131 from "./versions/v0131.md";
import V0140 from "./versions/v0140.md";
import V0150 from "./versions/v0150.md";
import V0160 from "./versions/v0160.md";
import V0161 from "./versions/v0161.md";
import V010 from "src/docs/en/Changelog/v010.md";
import V011 from "src/docs/en/Changelog/v011.md";
import V020 from "src/docs/en/Changelog/v020.md";
import V021 from "src/docs/en/Changelog/v021.md";
import V030 from "src/docs/en/Changelog/v030.md";
import V040 from "src/docs/en/Changelog/v040.md";
import V050 from "src/docs/en/Changelog/v050.md";
import V060 from "src/docs/en/Changelog/v060.md";
import V070 from "src/docs/en/Changelog/v070.md";
import V080 from "src/docs/en/Changelog/v080.md";
import V090 from "src/docs/en/Changelog/v090.md";
import V0100 from "src/docs/en/Changelog/v0100.md";
import V0110 from "src/docs/en/Changelog/v0110.md";
import V0120 from "src/docs/en/Changelog/v0120.md";
import V0130 from "src/docs/en/Changelog/v0130.md";
import V0131 from "src/docs/en/Changelog/v0131.md";
import V0140 from "src/docs/en/Changelog/v0140.md";
import V0150 from "src/docs/en/Changelog/v0150.md";
import V0160 from "src/docs/en/Changelog/v0160.md";
import V0161 from "src/docs/en/Changelog/v0161.md";
import V0170 from "src/docs/en/Changelog/v0170.md";
import { MarkdownPage } from "../Shared/MarkdownPage";
// to avoid use of explicit any
@ -59,9 +60,9 @@ const Changelog: React.FC = () => {
// after new release:
// add entry to releases, using the current* fields
// then update the current fields.
const currentVersion = stashVersion || "v0.16.1";
const currentVersion = stashVersion || "v0.17.0";
const currentDate = buildDate;
const currentPage = V0161;
const currentPage = V0170;
const releases: IStashRelease[] = [
{
@ -70,6 +71,11 @@ const Changelog: React.FC = () => {
page: currentPage,
defaultOpen: true,
},
{
version: "v0.16.1",
date: "2022-07-26",
page: V0161,
},
{
version: "v0.16.0",
date: "2022-07-05",
@ -168,7 +174,7 @@ const Changelog: React.FC = () => {
];
return (
<>
<div className="changelog">
<h1 className="mb-4">Changelog:</h1>
{releases.map((r) => (
<Version
@ -182,7 +188,7 @@ const Changelog: React.FC = () => {
<MarkdownPage page={r.page} />
</Version>
))}
</>
</div>
);
};

View file

@ -1,6 +1,5 @@
.changelog {
margin-bottom: 4rem;
margin-top: 4rem;
.btn {
color: inherit;

View file

@ -0,0 +1,39 @@
import React from "react";
import { Form } from "react-bootstrap";
import { Modal } from "src/components/Shared";
import { faCogs } from "@fortawesome/free-solid-svg-icons";
import { useIntl } from "react-intl";
import { MarkdownPage } from "../Shared/MarkdownPage";
import { Module } from "src/docs/en/ReleaseNotes";
interface IReleaseNotesDialog {
notes: Module[];
onClose: () => void;
}
export const ReleaseNotesDialog: React.FC<IReleaseNotesDialog> = ({
notes,
onClose,
}) => {
const intl = useIntl();
return (
<Modal
show
icon={faCogs}
header={intl.formatMessage({ id: "release_notes" })}
accept={{
onClick: onClose,
text: intl.formatMessage({ id: "actions.close" }),
}}
>
<Form>
{notes.map((n, i) => (
<MarkdownPage page={n} key={i} />
))}
</Form>
</Modal>
);
};
export default ReleaseNotesDialog;

View file

@ -36,6 +36,7 @@ const FrontPage: React.FC = () => {
await saveUI({
variables: {
input: {
...configuration?.ui,
frontPageContent: content,
},
},

View file

@ -1,27 +1,27 @@
import React, { useState, useEffect } from "react";
import { Modal, Container, Row, Col, Nav, Tab } from "react-bootstrap";
import Introduction from "src/docs/en/Introduction.md";
import Tasks from "src/docs/en/Tasks.md";
import AutoTagging from "src/docs/en/AutoTagging.md";
import JSONSpec from "src/docs/en/JSONSpec.md";
import Configuration from "src/docs/en/Configuration.md";
import Interface from "src/docs/en/Interface.md";
import Galleries from "src/docs/en/Galleries.md";
import Scraping from "src/docs/en/Scraping.md";
import ScraperDevelopment from "src/docs/en/ScraperDevelopment.md";
import Plugins from "src/docs/en/Plugins.md";
import ExternalPlugins from "src/docs/en/ExternalPlugins.md";
import EmbeddedPlugins from "src/docs/en/EmbeddedPlugins.md";
import Tagger from "src/docs/en/Tagger.md";
import Contributing from "src/docs/en/Contributing.md";
import SceneFilenameParser from "src/docs/en/SceneFilenameParser.md";
import KeyboardShortcuts from "src/docs/en/KeyboardShortcuts.md";
import Help from "src/docs/en/Help.md";
import Deduplication from "src/docs/en/Deduplication.md";
import Interactive from "src/docs/en/Interactive.md";
import Captions from "src/docs/en/Captions.md";
import Identify from "src/docs/en/Identify.md";
import Browsing from "src/docs/en/Browsing.md";
import Introduction from "src/docs/en/Manual/Introduction.md";
import Tasks from "src/docs/en/Manual/Tasks.md";
import AutoTagging from "src/docs/en/Manual/AutoTagging.md";
import JSONSpec from "src/docs/en/Manual/JSONSpec.md";
import Configuration from "src/docs/en/Manual/Configuration.md";
import Interface from "src/docs/en/Manual/Interface.md";
import Galleries from "src/docs/en/Manual/Galleries.md";
import Scraping from "src/docs/en/Manual/Scraping.md";
import ScraperDevelopment from "src/docs/en/Manual/ScraperDevelopment.md";
import Plugins from "src/docs/en/Manual/Plugins.md";
import ExternalPlugins from "src/docs/en/Manual/ExternalPlugins.md";
import EmbeddedPlugins from "src/docs/en/Manual/EmbeddedPlugins.md";
import Tagger from "src/docs/en/Manual/Tagger.md";
import Contributing from "src/docs/en/Manual/Contributing.md";
import SceneFilenameParser from "src/docs/en/Manual/SceneFilenameParser.md";
import KeyboardShortcuts from "src/docs/en/Manual/KeyboardShortcuts.md";
import Help from "src/docs/en/Manual/Help.md";
import Deduplication from "src/docs/en/Manual/Deduplication.md";
import Interactive from "src/docs/en/Manual/Interactive.md";
import Captions from "src/docs/en/Manual/Captions.md";
import Identify from "src/docs/en/Manual/Identify.md";
import Browsing from "src/docs/en/Manual/Browsing.md";
import { MarkdownPage } from "../Shared/MarkdownPage";
interface IManualProps {

View file

@ -17,6 +17,7 @@ import { SettingsServicesPanel } from "./SettingsServicesPanel";
import { SettingsContext } from "./context";
import { SettingsLibraryPanel } from "./SettingsLibraryPanel";
import { SettingsSecurityPanel } from "./SettingsSecurityPanel";
import Changelog from "../Changelog/Changelog";
export const Settings: React.FC = () => {
const intl = useIntl();
@ -92,6 +93,11 @@ export const Settings: React.FC = () => {
<FormattedMessage id="config.categories.tools" />
</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link eventKey="changelog">
<FormattedMessage id="config.categories.changelog" />
</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link eventKey="about">
<FormattedMessage id="config.categories.about" />
@ -138,6 +144,9 @@ export const Settings: React.FC = () => {
<Tab.Pane eventKey="logs" unmountOnExit>
<SettingsLogsPanel />
</Tab.Pane>
<Tab.Pane eventKey="changelog" unmountOnExit>
<Changelog />
</Tab.Pane>
<Tab.Pane eventKey="about" unmountOnExit>
<SettingsAboutPanel />
</Tab.Pane>

View file

@ -1,9 +1,11 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useMemo, useState } from "react";
import { Button, Card, Container, Form } from "react-bootstrap";
import { useIntl, FormattedMessage } from "react-intl";
import * as GQL from "src/core/generated-graphql";
import { useSystemStatus, mutateMigrate } from "src/core/StashService";
import { migrationNotes } from "src/docs/en/MigrationNotes";
import { LoadingIndicator } from "../Shared";
import { MarkdownPage } from "../Shared/MarkdownPage";
export const Migrate: React.FC = () => {
const { data: systemStatus, loading } = useSystemStatus();
@ -45,8 +47,46 @@ export const Migrate: React.FC = () => {
}
}, [defaultBackupPath, backupPath]);
const status = systemStatus?.systemStatus;
const maybeMigrationNotes = useMemo(() => {
if (
!status ||
status.databaseSchema === undefined ||
status.databaseSchema === null ||
status.appSchema === undefined ||
status.appSchema === null
)
return;
const notes = [];
for (let i = status.databaseSchema; i <= status.appSchema; ++i) {
const note = migrationNotes[i];
if (note) {
notes.push(note);
}
}
if (notes.length === 0) return;
return (
<div className="migration-notes">
<h2>
<FormattedMessage id="setup.migrate.migration_notes" />
</h2>
<div>
{notes.map((n, i) => (
<div key={i}>
<MarkdownPage page={n} />
</div>
))}
</div>
</div>
);
}, [status]);
// only display setup wizard if system is not setup
if (loading || !systemStatus) {
if (loading || !systemStatus || !status) {
return <LoadingIndicator />;
}
@ -67,8 +107,6 @@ export const Migrate: React.FC = () => {
return <LoadingIndicator />;
}
const status = systemStatus.systemStatus;
async function onMigrate() {
try {
setMigrateLoading(true);
@ -148,6 +186,8 @@ export const Migrate: React.FC = () => {
</p>
</section>
{maybeMigrationNotes}
<section>
<Form.Group id="migrate">
<Form.Label>

View file

@ -0,0 +1,9 @@
.migration-notes {
margin: 1rem;
> div {
background-color: darken($color: $card-bg, $amount: 3);
border-radius: 3px;
padding: 16px;
}
}

View file

@ -2,7 +2,6 @@ import React from "react";
import { useStats } from "src/core/StashService";
import { FormattedMessage, FormattedNumber } from "react-intl";
import { LoadingIndicator } from "src/components/Shared";
import Changelog from "src/components/Changelog/Changelog";
import { TextUtils } from "src/utils";
export const Stats: React.FC = () => {
@ -115,9 +114,6 @@ export const Stats: React.FC = () => {
</p>
</div>
</div>
<div className="changelog col col-sm-8 mx-sm-auto">
<Changelog />
</div>
</div>
);
};

View file

@ -27,6 +27,7 @@ export type FrontPageContent = ISavedFilterRow | ICustomFilter;
export interface IUIConfig {
frontPageContent?: FrontPageContent[];
lastNoteSeen?: number;
}
function recentlyReleased(

View file

@ -0,0 +1,5 @@
### ✨ New Features
* Added release notes dialog. ([#](https://github.com/stashapp/stash/pull/))
### 🎨 Improvements
* Moved Changelogs to Settings page. ([#](https://github.com/stashapp/stash/pull/))

View file

@ -0,0 +1,9 @@
// import migration32 from "./32.md";
// type Module = typeof migration32;
// replace any with module once we add migration notes
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const migrationNotes: Record<number, any> = {
// 32: migration31,
};

View file

@ -0,0 +1,16 @@
import v0170 from "./v0170.md";
export type Module = typeof v0170;
interface IReleaseNotes {
// handle should be in the form of YYYYMMDD
date: number;
content: Module;
}
export const releaseNotes: IReleaseNotes[] = [
{
date: 20220707,
content: v0170,
},
];

View file

@ -0,0 +1 @@
* Changelog has been moved from the stats page to a section in the Settings page.

View file

@ -14,6 +14,7 @@
@import "src/components/SceneFilenameParser/styles.scss";
@import "src/components/ScenePlayer/styles.scss";
@import "src/components/Settings/styles.scss";
@import "src/components/Setup/styles.scss";
@import "src/components/Studios/styles.scss";
@import "src/components/Shared/styles.scss";
@import "src/components/Tags/styles.scss";

View file

@ -189,6 +189,7 @@
},
"categories": {
"about": "About",
"changelog": "Changelog",
"interface": "Interface",
"logs": "Logs",
"metadata_providers": "Metadata Providers",
@ -604,6 +605,7 @@
"edit_entity_title": "Edit {count, plural, one {{singularEntity}} other {{pluralEntity}}}",
"export_include_related_objects": "Include related objects in export",
"export_title": "Export",
"dont_show_until_updated": "Don't show until next update",
"lightbox": {
"delay": "Delay (Sec)",
"display_mode": {
@ -870,6 +872,7 @@
"rating": "Rating",
"recently_added_objects": "Recently Added {objects}",
"recently_released_objects": "Recently Released {objects}",
"release_notes": "Release Notes",
"resolution": "Resolution",
"scene": "Scene",
"sceneTagger": "Scene Tagger",
@ -919,9 +922,10 @@
"migration_failed_error": "The following error was encountered while migrating the database:",
"migration_failed_help": "Please make any necessary corrections and try again. Otherwise, raise a bug on the {githubLink} or seek help in the {discordLink}.",
"migration_irreversible_warning": "The schema migration process is not reversible. Once the migration is performed, your database will be incompatible with previous versions of stash.",
"migration_notes": "Migration Notes",
"migration_required": "Migration required",
"perform_schema_migration": "Perform schema migration",
"schema_too_old": "Your current stash database is schema version <strong>{databaseSchema}</strong> and needs to be migrated to version <strong>{appSchema}</strong>. This version of Stash will not function without migrating the database."
"schema_too_old": "Your current stash database is schema version <strong>{databaseSchema}</strong> and needs to be migrated to version <strong>{appSchema}</strong>. This version of Stash will not function without migrating the database. If you do not wish to migrate, you will need to downgrade to a version that matches your database schema."
},
"paths": {
"database_filename_empty_for_default": "database filename (empty for default)",