diff --git a/.storybook/preview.ts b/.storybook/preview.ts index a0cf42df3d..ea8075a630 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -7,7 +7,10 @@ import { markdownItPlugin } from "@/plugins/markdownItPlugin"; import "@quasar/extras/material-icons/material-icons.css"; import "quasar/dist/quasar.sass"; -import "../src/styles/_index.scss"; +import "@/styles/_index.scss"; +import { UnreachableError } from "@/type/utility"; +import { setThemeToCss, setFontToCss } from "@/domain/dom"; +import { themes } from "@/domain/theme"; setup((app) => { app.use(Quasar, { @@ -61,6 +64,42 @@ const preview: Preview = { defaultTheme: "light", attributeName: "is-dark-theme", }), + + // テーマの設定をCSSへ反映する + () => { + let observer: MutationObserver | undefined = undefined; + return { + async mounted() { + setFontToCss("default"); + + const root = document.documentElement; + let lastIsDark: boolean | undefined = undefined; + observer = new MutationObserver(() => { + const isDark = root.getAttribute("is-dark-theme") === "true"; + if (lastIsDark === isDark) return; + lastIsDark = isDark; + + const theme = themes.find((theme) => theme.isDark === isDark); + if (!theme) + throw new UnreachableError("assert: theme !== undefined"); + + setThemeToCss(theme); + }); + + observer.observe(root, { + attributes: true, + attributeFilter: ["is-dark-theme"], + }); + }, + unmounted() { + if (observer) { + observer.disconnect(); + } + }, + + template: ``, + }; + }, ], argTypesEnhancers: [addActionsWithEmits], }; diff --git a/public/themes/dark.json b/public/themes/dark.json deleted file mode 100644 index a3bef2b9bf..0000000000 --- a/public/themes/dark.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "Dark", - "displayName": "ダーク", - "order": 2, - "isDark": true, - "colors": { - "primary": "#86C591", - "display": "#E1E1E1", - "display-on-primary": "#1F1F1F", - "display-hyperlink": "#58A6FF", - "background": "#1F1F1F", - "surface": "#2B2B2B", - "warning": "#F27483", - "text-splitter-hover": "#394152", - "active-point-focus": "#292F38", - "active-point-hover": "#272A2F" - } -} diff --git a/public/themes/default.json b/public/themes/default.json deleted file mode 100644 index 9d04773fd8..0000000000 --- a/public/themes/default.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "Default", - "displayName": "デフォルト", - "order": 1, - "isDark": false, - "colors": { - "primary": "#A5D4AD", - "display": "#121212", - "display-on-primary": "#121212", - "display-hyperlink": "#0969DA", - "background": "#FFFFFF", - "surface": "#EEEEEE", - "warning": "#C10015", - "text-splitter-hover": "#CCDDFF", - "active-point-focus": "#E0EAFF", - "active-point-hover": "#EEF3FF" - } -} diff --git a/src/backend/browser/sandbox.ts b/src/backend/browser/sandbox.ts index 5e00b6e398..69552e8b7c 100644 --- a/src/backend/browser/sandbox.ts +++ b/src/backend/browser/sandbox.ts @@ -16,7 +16,6 @@ import { EngineSettings, HotkeySettingType, Sandbox, - ThemeConf, } from "@/type/preload"; import { AssetTextFileNames } from "@/type/staticResources"; @@ -245,16 +244,6 @@ export const api: Sandbox = { // TODO: Impl return; }, - async getAvailableThemes() { - // NOTE: Electron版では起動時にテーマ情報が必要なので、 - // この実装とは違って起動時に読み込んだキャッシュを返すだけになっている。 - return Promise.all( - // FIXME: themeファイルのいい感じのパスの設定 - ["/themes/default.json", "/themes/dark.json"].map((url) => - fetch(url).then((res) => res.json() as Promise), - ), - ); - }, vuexReady() { // NOTE: 何もしなくて良さそう return Promise.resolve(); diff --git a/src/backend/electron/main.ts b/src/backend/electron/main.ts index 9669602438..42a908ecca 100644 --- a/src/backend/electron/main.ts +++ b/src/backend/electron/main.ts @@ -31,7 +31,6 @@ import { EngineAndVvppController } from "./engineAndVvppController"; import { failure, success } from "@/type/result"; import { AssetTextFileNames } from "@/type/staticResources"; import { - ThemeConf, EngineInfo, SystemError, defaultHotkeySettings, @@ -40,6 +39,7 @@ import { EngineId, TextAsset, } from "@/type/preload"; +import { themes } from "@/domain/theme"; type SingleInstanceLockData = { filePath: string | undefined; @@ -226,20 +226,6 @@ function checkMultiEngineEnabled(): boolean { return enabled; } -// テーマの読み込み -const themes = readThemeFiles(); -function readThemeFiles() { - const themes: ThemeConf[] = []; - const dir = path.join(__static, "themes"); - for (const file of fs.readdirSync(dir)) { - const theme = JSON.parse( - fs.readFileSync(path.join(dir, file)).toString(), - ) as ThemeConf; - themes.push(theme); - } - return themes; -} - const appState = { willQuit: false, }; @@ -652,10 +638,6 @@ registerIpcMainHandle({ } }, - GET_AVAILABLE_THEMES: () => { - return themes; - }, - OPEN_LOG_DIRECTORY: () => { void shell.openPath(app.getPath("logs")); }, diff --git a/src/backend/electron/preload.ts b/src/backend/electron/preload.ts index 28d7127505..87d493e877 100644 --- a/src/backend/electron/preload.ts +++ b/src/backend/electron/preload.ts @@ -174,10 +174,6 @@ const api: Sandbox = { void ipcRendererInvokeProxy.SET_NATIVE_THEME(source); }, - getAvailableThemes: () => { - return ipcRendererInvokeProxy.GET_AVAILABLE_THEMES(); - }, - vuexReady: () => { void ipcRendererInvokeProxy.ON_VUEX_READY(); }, diff --git a/src/domain/theme/index.ts b/src/domain/theme/index.ts new file mode 100644 index 0000000000..6d4f71280a --- /dev/null +++ b/src/domain/theme/index.ts @@ -0,0 +1,41 @@ +import { ThemeConf } from "@/type/preload"; + +const light = { + name: "Default", + displayName: "デフォルト", + order: 1, + isDark: false, + colors: { + primary: "#A5D4AD", + display: "#121212", + "display-on-primary": "#121212", + "display-hyperlink": "#0969DA", + background: "#FFFFFF", + surface: "#EEEEEE", + warning: "#C10015", + "text-splitter-hover": "#CCDDFF", + "active-point-focus": "#E0EAFF", + "active-point-hover": "#EEF3FF", + }, +} as const satisfies ThemeConf; + +const dark = { + name: "Dark", + displayName: "ダーク", + order: 2, + isDark: true, + colors: { + primary: "#86C591", + display: "#E1E1E1", + "display-on-primary": "#1F1F1F", + "display-hyperlink": "#58A6FF", + background: "#1F1F1F", + surface: "#2B2B2B", + warning: "#F27483", + "text-splitter-hover": "#394152", + "active-point-focus": "#292F38", + "active-point-hover": "#272A2F", + }, +} as const satisfies ThemeConf; + +export const themes = [light, dark]; diff --git a/src/store/setting.ts b/src/store/setting.ts index 8c1e21ac3f..312057241d 100644 --- a/src/store/setting.ts +++ b/src/store/setting.ts @@ -1,6 +1,7 @@ import { SettingStoreState, SettingStoreTypes } from "./type"; import { createDotNotationUILockAction as createUILockAction } from "./ui"; import { createDotNotationPartialStore as createPartialStore } from "./vuex"; +import { themes } from "@/domain/theme"; import { showAlertDialog, showQuestionDialog, @@ -83,7 +84,7 @@ export const settingStore = createPartialStore({ }); mutations.SET_AVAILABLE_THEMES({ - themes: await window.backend.getAvailableThemes(), + themes, }); void actions.SET_CURRENT_THEME_SETTING({ currentTheme: await window.backend.getSetting("currentTheme"), diff --git a/src/store/ui.ts b/src/store/ui.ts index d338134735..9f17a9be11 100644 --- a/src/store/ui.ts +++ b/src/store/ui.ts @@ -373,6 +373,10 @@ export const uiStore = createPartialStore({ }, }, + /** + * 選択可能なテーマをセットする。 + * NOTE: カスタムテーマが導入された場合を見越して残している。 + */ SET_AVAILABLE_THEMES: { mutation(state, { themes }) { state.availableThemes = themes; diff --git a/src/styles/_index.scss b/src/styles/_index.scss index b73fe71697..297ac8030d 100644 --- a/src/styles/_index.scss +++ b/src/styles/_index.scss @@ -9,11 +9,15 @@ // 優先度を強引に上げる body:not(#dummy) { user-select: none; - border-left: solid #{vars.$window-border-width} #{colors.$splitter}; - border-right: solid #{vars.$window-border-width} #{colors.$splitter}; - border-bottom: solid #{vars.$window-border-width} #{colors.$splitter}; color: colors.$display; background: colors.$background; + + // Storybookでは枠線を付けない + &:not(.sb-show-main) { + border-left: solid #{vars.$window-border-width} #{colors.$splitter}; + border-right: solid #{vars.$window-border-width} #{colors.$splitter}; + border-bottom: solid #{vars.$window-border-width} #{colors.$splitter}; + } } body[data-editor-font="default"] { diff --git a/src/type/ipc.ts b/src/type/ipc.ts index 9b3c407a1c..e0af36fddb 100644 --- a/src/type/ipc.ts +++ b/src/type/ipc.ts @@ -9,7 +9,6 @@ import { MessageBoxReturnValue, NativeThemeType, TextAsset, - ThemeConf, ToolbarSettingType, } from "@/type/preload"; import { AltPortInfos } from "@/store/type"; @@ -169,11 +168,6 @@ export type IpcIHData = { return: ToolbarSettingType; }; - GET_AVAILABLE_THEMES: { - args: []; - return: ThemeConf[]; - }; - ON_VUEX_READY: { args: []; return: void; diff --git a/src/type/preload.ts b/src/type/preload.ts index 31abcbc904..973ea58505 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -277,7 +277,6 @@ export interface Sandbox { getDefaultHotkeySettings(): Promise; getDefaultToolbarSetting(): Promise; setNativeTheme(source: NativeThemeType): void; - getAvailableThemes(): Promise; vuexReady(): void; getSetting(key: Key): Promise; setSetting( diff --git "a/tests/e2e/storybook/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.mts-snapshots/components-base-basedocumentview--default-storybook-win32.png" "b/tests/e2e/storybook/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.mts-snapshots/components-base-basedocumentview--default-storybook-win32.png" index a9b71e56e3..54a0a22ac9 100644 Binary files "a/tests/e2e/storybook/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.mts-snapshots/components-base-basedocumentview--default-storybook-win32.png" and "b/tests/e2e/storybook/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.mts-snapshots/components-base-basedocumentview--default-storybook-win32.png" differ diff --git "a/tests/e2e/storybook/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.mts-snapshots/components-dialog-settingdialog-filenametemplatedialog--opened-storybook-win32.png" "b/tests/e2e/storybook/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.mts-snapshots/components-dialog-settingdialog-filenametemplatedialog--opened-storybook-win32.png" index d2112ef92c..7731c4c0a8 100644 Binary files "a/tests/e2e/storybook/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.mts-snapshots/components-dialog-settingdialog-filenametemplatedialog--opened-storybook-win32.png" and "b/tests/e2e/storybook/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.mts-snapshots/components-dialog-settingdialog-filenametemplatedialog--opened-storybook-win32.png" differ diff --git "a/tests/e2e/storybook/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.mts-snapshots/components-dialog-updatenotificationdialog--opened-storybook-win32.png" "b/tests/e2e/storybook/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.mts-snapshots/components-dialog-updatenotificationdialog--opened-storybook-win32.png" index dfec2a005c..d06a074474 100644 Binary files "a/tests/e2e/storybook/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.mts-snapshots/components-dialog-updatenotificationdialog--opened-storybook-win32.png" and "b/tests/e2e/storybook/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.mts-snapshots/components-dialog-updatenotificationdialog--opened-storybook-win32.png" differ