diff --git a/src/bridges/settings.ts b/src/bridges/settings.ts new file mode 100644 index 0000000..bf564fd --- /dev/null +++ b/src/bridges/settings.ts @@ -0,0 +1,71 @@ +import { SourceGroup, ViewType, ThemeSettings } from "../schema-types" +import { ipcRenderer } from "electron" + +const SettingsBridge = { + saveGroups: (groups: SourceGroup[]) => { + ipcRenderer.invoke("set-groups", groups) + }, + loadGroups: (): SourceGroup[] => { + return ipcRenderer.sendSync("get-groups") + }, + + getDefaultMenu: (): boolean => { + return ipcRenderer.sendSync("get-menu") + }, + setDefaultMenu: (state: boolean) => { + ipcRenderer.invoke("set-menu", state) + }, + + getProxyStatus: (): boolean => { + return ipcRenderer.sendSync("get-proxy-status") + }, + toggleProxyStatus: () => { + ipcRenderer.send("toggle-proxy-status") + }, + getProxy: (): string => { + return ipcRenderer.sendSync("get-proxy") + }, + setProxy: (address: string = null) => { + ipcRenderer.invoke("set-proxy", address) + }, + + getDefaultView: (): ViewType => { + return ipcRenderer.sendSync("get-view") + }, + setDefaultView: (viewType: ViewType) => { + ipcRenderer.invoke("set-view", viewType) + }, + + getThemeSettings: (): ThemeSettings => { + return ipcRenderer.sendSync("get-theme") + }, + setThemeSettings: (theme: ThemeSettings) => { + ipcRenderer.invoke("set-theme", theme) + }, + shouldUseDarkColors: (): boolean => { + return ipcRenderer.sendSync("get-theme-dark-color") + }, + addThemeUpdateListener: (callback: (shouldDark: boolean) => any) => { + ipcRenderer.on("theme-updated", (_, shouldDark) => { + callback(shouldDark) + }) + }, + + setLocaleSettings: (option: string) => { + ipcRenderer.invoke("set-locale", option) + }, + getLocaleSettings: (): string => { + return ipcRenderer.sendSync("get-locale-settings") + }, + getCurrentLocale: (): string => { + return ipcRenderer.sendSync("get-locale") + } +} + +declare global { + interface Window { + settings: typeof SettingsBridge + } +} + +export default SettingsBridge \ No newline at end of file diff --git a/src/components/context-menu.tsx b/src/components/context-menu.tsx index c615e48..4359a5a 100644 --- a/src/components/context-menu.tsx +++ b/src/components/context-menu.tsx @@ -6,7 +6,7 @@ import { ContextualMenu, IContextualMenuItem, ContextualMenuItemType, Directiona import { ContextMenuType } from "../scripts/models/app" import { RSSItem } from "../scripts/models/item" import { ContextReduxProps } from "../containers/context-menu-container" -import { ViewType } from "../scripts/models/page" +import { ViewType } from "../schema-types" import { FilterType } from "../scripts/models/feed" export type ContextMenuProps = ContextReduxProps & { diff --git a/src/components/feeds/feed.tsx b/src/components/feeds/feed.tsx index 9101b3d..a5233b5 100644 --- a/src/components/feeds/feed.tsx +++ b/src/components/feeds/feed.tsx @@ -2,7 +2,7 @@ import * as React from "react" import { RSSItem } from "../../scripts/models/item" import { FeedReduxProps } from "../../containers/feed-container" import { RSSFeed } from "../../scripts/models/feed" -import { ViewType } from "../../scripts/models/page" +import { ViewType } from "../../schema-types" import CardsFeed from "./cards-feed" import ListFeed from "./list-feed" diff --git a/src/components/menu.tsx b/src/components/menu.tsx index 6b837d3..9262245 100644 --- a/src/components/menu.tsx +++ b/src/components/menu.tsx @@ -2,7 +2,7 @@ import * as React from "react" import intl from "react-intl-universal" import { Icon } from "@fluentui/react/lib/Icon" import { Nav, INavLink, INavLinkGroup } from "office-ui-fabric-react/lib/Nav" -import { SourceGroup } from "../scripts/models/group" +import { SourceGroup } from "../schema-types" import { SourceState, RSSSource } from "../scripts/models/source" import { ALL } from "../scripts/models/feed" import { AnimationClassNames, Stack } from "@fluentui/react" diff --git a/src/components/page.tsx b/src/components/page.tsx index 4befe09..4c607ce 100644 --- a/src/components/page.tsx +++ b/src/components/page.tsx @@ -2,7 +2,7 @@ import * as React from "react" import { FeedContainer } from "../containers/feed-container" import { AnimationClassNames, Icon, FocusTrapZone } from "@fluentui/react" import ArticleContainer from "../containers/article-container" -import { ViewType } from "../scripts/models/page" +import { ViewType } from "../schema-types" import ArticleSearch from "./utils/article-search" type PageProps = { diff --git a/src/components/settings/app.tsx b/src/components/settings/app.tsx index aa21825..58b57ab 100644 --- a/src/components/settings/app.tsx +++ b/src/components/settings/app.tsx @@ -1,7 +1,8 @@ import * as React from "react" import intl from "react-intl-universal" import { urlTest, byteToMB, calculateItemSize } from "../../scripts/utils" -import { getProxy, getProxyStatus, toggleProxyStatus, setProxy, getThemeSettings, setThemeSettings, ThemeSettings, getLocaleSettings, exportAll } from "../../scripts/settings" +import { ThemeSettings } from "../../schema-types" +import { getThemeSettings, setThemeSettings, exportAll } from "../../scripts/settings" import { Stack, Label, Toggle, TextField, DefaultButton, ChoiceGroup, IChoiceGroupOption, loadTheme, Dropdown, IDropdownOption, PrimaryButton } from "@fluentui/react" import { remote } from "electron" import DangerButton from "../utils/danger-button" @@ -25,8 +26,8 @@ class AppTab extends React.Component { constructor(props) { super(props) this.state = { - pacStatus: getProxyStatus(), - pacUrl: getProxy(), + pacStatus: window.settings.getProxyStatus(), + pacUrl: window.settings.getProxy(), themeSettings: getThemeSettings(), itemSize: null, cacheSize: null, @@ -86,10 +87,10 @@ class AppTab extends React.Component { ] toggleStatus = () => { - toggleProxyStatus() + window.settings.toggleProxyStatus() this.setState({ - pacStatus: getProxyStatus(), - pacUrl: getProxy() + pacStatus: window.settings.getProxyStatus(), + pacUrl: window.settings.getProxy() }) } @@ -101,7 +102,7 @@ class AppTab extends React.Component { setUrl = (event: React.FormEvent) => { event.preventDefault() - if (urlTest(this.state.pacUrl)) setProxy(this.state.pacUrl) + if (urlTest(this.state.pacUrl)) window.settings.setProxy(this.state.pacUrl) } onThemeChange = (_, option: IChoiceGroupOption) => { @@ -127,7 +128,7 @@ class AppTab extends React.Component { this.props.setLanguage(String(option.key))} style={{width: 200}} /> diff --git a/src/components/settings/groups.tsx b/src/components/settings/groups.tsx index 8a1fc32..d55f417 100644 --- a/src/components/settings/groups.tsx +++ b/src/components/settings/groups.tsx @@ -1,6 +1,6 @@ import * as React from "react" import intl from "react-intl-universal" -import { SourceGroup } from "../../scripts/models/group" +import { SourceGroup } from "../../schema-types" import { SourceState, RSSSource } from "../../scripts/models/source" import { IColumn, Selection, SelectionMode, DetailsList, Label, Stack, TextField, PrimaryButton, DefaultButton, Dropdown, IDropdownOption, CommandBarButton, MarqueeSelection, IDragDropEvents, IDragDropContext } from "@fluentui/react" diff --git a/src/containers/context-menu-container.tsx b/src/containers/context-menu-container.tsx index 6702346..f274d4c 100644 --- a/src/containers/context-menu-container.tsx +++ b/src/containers/context-menu-container.tsx @@ -4,8 +4,8 @@ import { RootState } from "../scripts/reducer" import { ContextMenuType, closeContextMenu, toggleSettings } from "../scripts/models/app" import { ContextMenu } from "../components/context-menu" import { RSSItem, markRead, markUnread, toggleStarred, toggleHidden, markAllRead } from "../scripts/models/item" -import { showItem, switchView, ViewType, switchFilter, toggleFilter } from "../scripts/models/page" -import { setDefaultView } from "../scripts/settings" +import { showItem, switchView, switchFilter, toggleFilter } from "../scripts/models/page" +import { ViewType } from "../schema-types" import { FilterType } from "../scripts/models/feed" const getContext = (state: RootState) => state.app.contextMenu @@ -57,7 +57,7 @@ const mapDispatchToProps = dispatch => { dispatch(toggleHidden(item)) }, switchView: (viewType: ViewType) => { - setDefaultView(viewType) + window.settings.setDefaultView(viewType) dispatch(switchView(viewType)) }, switchFilter: (filter: FilterType) => dispatch(switchFilter(filter)), diff --git a/src/containers/feed-container.tsx b/src/containers/feed-container.tsx index 6fe84ff..940cbb4 100644 --- a/src/containers/feed-container.tsx +++ b/src/containers/feed-container.tsx @@ -4,7 +4,8 @@ import { RootState } from "../scripts/reducer" import { markRead, RSSItem, itemShortcuts } from "../scripts/models/item" import { openItemMenu } from "../scripts/models/app" import { loadMore, RSSFeed } from "../scripts/models/feed" -import { showItem, ViewType } from "../scripts/models/page" +import { showItem } from "../scripts/models/page" +import { ViewType } from "../schema-types" import { Feed } from "../components/feeds/feed" interface FeedContainerProps { diff --git a/src/containers/menu-container.tsx b/src/containers/menu-container.tsx index 04fc778..8192319 100644 --- a/src/containers/menu-container.tsx +++ b/src/containers/menu-container.tsx @@ -3,8 +3,10 @@ import { createSelector } from "reselect" import { RootState } from "../scripts/reducer" import { Menu } from "../components/menu" import { toggleMenu, openGroupMenu } from "../scripts/models/app" -import { SourceGroup, toggleGroupExpansion } from "../scripts/models/group" -import { selectAllArticles, selectSources, toggleSearch, ViewType } from "../scripts/models/page" +import { toggleGroupExpansion } from "../scripts/models/group" +import { SourceGroup } from "../schema-types" +import { selectAllArticles, selectSources, toggleSearch } from "../scripts/models/page" +import { ViewType } from "../schema-types" import { initFeeds } from "../scripts/models/feed" import { RSSSource } from "../scripts/models/source" diff --git a/src/containers/nav-container.tsx b/src/containers/nav-container.tsx index f621c9a..c22b2a0 100644 --- a/src/containers/nav-container.tsx +++ b/src/containers/nav-container.tsx @@ -5,7 +5,8 @@ import { createSelector } from "reselect" import { RootState } from "../scripts/reducer" import { fetchItems, markAllRead } from "../scripts/models/item" import { toggleMenu, toggleLogMenu, toggleSettings, openViewMenu } from "../scripts/models/app" -import { ViewType, toggleSearch } from "../scripts/models/page" +import { toggleSearch } from "../scripts/models/page" +import { ViewType } from "../schema-types" import Nav from "../components/nav" const getState = (state: RootState) => state.app diff --git a/src/containers/settings/app-container.tsx b/src/containers/settings/app-container.tsx index abed10d..38e26a8 100644 --- a/src/containers/settings/app-container.tsx +++ b/src/containers/settings/app-container.tsx @@ -1,6 +1,6 @@ import intl from "react-intl-universal" import { connect } from "react-redux" -import { setLocaleSettings, importAll } from "../../scripts/settings" +import { importAll } from "../../scripts/settings" import { initIntl, saveSettings } from "../../scripts/models/app" import * as db from "../../scripts/db" import AppTab from "../../components/settings/app" @@ -9,7 +9,7 @@ import { remote } from "electron" const mapDispatchToProps = dispatch => ({ setLanguage: (option: string) => { - setLocaleSettings(option) + window.settings.setLocaleSettings(option) dispatch(initIntl()) }, deleteArticles: (days: number) => new Promise((resolve) => { diff --git a/src/containers/settings/groups-container.tsx b/src/containers/settings/groups-container.tsx index 0917939..3cef872 100644 --- a/src/containers/settings/groups-container.tsx +++ b/src/containers/settings/groups-container.tsx @@ -2,8 +2,9 @@ import { connect } from "react-redux" import { createSelector } from "reselect" import { RootState } from "../../scripts/reducer" import GroupsTab from "../../components/settings/groups" -import { createSourceGroup, SourceGroup, updateSourceGroup, addSourceToGroup, +import { createSourceGroup, updateSourceGroup, addSourceToGroup, deleteSourceGroup, removeSourceFromGroup, reorderSourceGroups } from "../../scripts/models/group" +import { SourceGroup } from "../../schema-types" const getSources = (state: RootState) => state.sources const getGroups = (state: RootState) => state.groups diff --git a/src/electron.ts b/src/electron.ts index c30a965..fece65b 100644 --- a/src/electron.ts +++ b/src/electron.ts @@ -1,7 +1,9 @@ import { app, ipcMain, BrowserWindow, Menu, nativeTheme } from "electron" import windowStateKeeper = require("electron-window-state") -import Store = require("electron-store") -import performUpdate from "./scripts/update-scripts" +import { ThemeSettings } from "./schema-types" +import { store, setThemeListener } from "./main/settings" +import performUpdate from "./main/update-scripts" +import path = require("path") if (!process.mas) { const locked = app.requestSingleInstanceLock() @@ -11,14 +13,12 @@ if (!process.mas) { } let mainWindow: BrowserWindow -let store: Store let restarting: boolean function init(setTheme = true) { restarting = false - store = new Store() performUpdate(store) - if (setTheme) nativeTheme.themeSource = store.get("theme", "system") + if (setTheme) nativeTheme.themeSource = store.get("theme", ThemeSettings.Default) } init() @@ -46,7 +46,8 @@ function createWindow() { webPreferences: { nodeIntegration: true, webviewTag: true, - enableRemoteModule: true + enableRemoteModule: true, + preload: path.join(app.getAppPath(), (app.isPackaged ? "dist/" : "") + "preload.js") } }) mainWindowState.manage(mainWindow) @@ -54,9 +55,10 @@ function createWindow() { mainWindow.show() mainWindow.focus() if (!app.isPackaged) mainWindow.webContents.openDevTools() - }); + }) + setThemeListener(mainWindow) // and load the index.html of the app. - mainWindow.loadFile((app.isPackaged ? "dist/" : "") + "index.html") + mainWindow.loadFile((app.isPackaged ? "dist/" : "") + "index.html", ) } if (process.platform === "darwin") { diff --git a/src/index.tsx b/src/index.tsx index 7844373..be0d6ea 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -7,10 +7,10 @@ import { initializeIcons } from "@fluentui/react/lib/Icons" import { rootReducer, RootState } from "./scripts/reducer" import Root from "./components/root" import { AppDispatch } from "./scripts/utils" -import { setProxy, applyThemeSettings } from "./scripts/settings" +import { applyThemeSettings } from "./scripts/settings" import { initApp } from "./scripts/models/app" -setProxy() +window.settings.setProxy() applyThemeSettings() initializeIcons("icons/") diff --git a/src/main/settings.ts b/src/main/settings.ts new file mode 100644 index 0000000..627f75f --- /dev/null +++ b/src/main/settings.ts @@ -0,0 +1,103 @@ +import Store = require("electron-store") +import { SchemaTypes, SourceGroup, ViewType, ThemeSettings } from "../schema-types" +import { ipcMain, session, nativeTheme, BrowserWindow, app } from "electron" + +export const store = new Store() + +const GROUPS_STORE_KEY = "sourceGroups" +ipcMain.handle("set-groups", (_, groups: SourceGroup[]) => { + store.set(GROUPS_STORE_KEY, groups) +}) +ipcMain.on("get-groups", (event) => { + event.returnValue = store.get(GROUPS_STORE_KEY, []) +}) + +const MENU_STORE_KEY = "menuOn" +ipcMain.on("get-menu", (event) => { + event.returnValue = store.get(MENU_STORE_KEY, false) +}) +ipcMain.handle("set-menu", (_, state: boolean) => { + store.set(MENU_STORE_KEY, state) +}) + +const PAC_STORE_KEY = "pac" +const PAC_STATUS_KEY = "pacOn" +function getProxyStatus() { + return store.get(PAC_STATUS_KEY, false) +} +function toggleProxyStatus() { + store.set(PAC_STATUS_KEY, !getProxyStatus()) + setProxy() +} +function getProxy() { + return store.get(PAC_STORE_KEY, "") +} +function setProxy(address = null) { + if (!address) { + address = getProxy() + } else { + store.set(PAC_STORE_KEY, address) + } + if (getProxyStatus()) { + let rules = { pacScript: address } + session.defaultSession.setProxy(rules) + session.fromPartition("sandbox").setProxy(rules) + } +} +ipcMain.on("get-proxy-status", (event) => { + event.returnValue = getProxyStatus() +}) +ipcMain.on("toggle-proxy-status", () => { + toggleProxyStatus() +}) +ipcMain.on("get-proxy", (event) => { + event.returnValue = getProxy() +}) +ipcMain.handle("set-proxy", (_, address = null) => { + setProxy(address) +}) + +const VIEW_STORE_KEY = "view" +ipcMain.on("get-view", (event) => { + event.returnValue = store.get(VIEW_STORE_KEY, ViewType.Cards) +}) +ipcMain.handle("set-view", (_, viewType: ViewType) => { + store.set(VIEW_STORE_KEY, viewType) +}) + +const THEME_STORE_KEY = "theme" +ipcMain.on("get-theme", (event) => { + event.returnValue = store.get(THEME_STORE_KEY, ThemeSettings.Default) +}) +ipcMain.handle("set-theme", (_, theme: ThemeSettings) => { + store.set(THEME_STORE_KEY, theme) + nativeTheme.themeSource = theme +}) +ipcMain.on("get-theme-dark-color", (event) => { + event.returnValue = nativeTheme.shouldUseDarkColors +}) +export function setThemeListener(window: BrowserWindow) { + nativeTheme.removeAllListeners() + nativeTheme.on("updated", () => { + let contents = window.webContents + if (!contents.isDestroyed()) { + contents.send("theme-updated", nativeTheme.shouldUseDarkColors) + } + }) +} + +const LOCALE_STORE_KEY = "locale" +ipcMain.handle("set-locale", (_, option: string) => { + store.set(LOCALE_STORE_KEY, option) +}) +function getLocaleSettings() { + return store.get(LOCALE_STORE_KEY, "default") +} +ipcMain.on("get-locale-settings", (event) => { + event.returnValue = getLocaleSettings() +}) +ipcMain.on("get-locale", (event) => { + let setting = getLocaleSettings() + let locale = setting === "default" ? app.getLocale() : setting + event.returnValue = locale +}) diff --git a/src/scripts/update-scripts.ts b/src/main/update-scripts.ts similarity index 68% rename from src/scripts/update-scripts.ts rename to src/main/update-scripts.ts index bcd94c5..6cf8cbf 100644 --- a/src/scripts/update-scripts.ts +++ b/src/main/update-scripts.ts @@ -1,7 +1,8 @@ import { app } from "electron" import Store = require("electron-store") +import { SchemaTypes } from "../schema-types" -export default function performUpdate(store: Store) { +export default function performUpdate(store: Store) { let version = store.get("version", null) let currentVersion = app.getVersion() diff --git a/src/preload.ts b/src/preload.ts new file mode 100644 index 0000000..59e4198 --- /dev/null +++ b/src/preload.ts @@ -0,0 +1,4 @@ +import { contextBridge } from "electron" +import SettingsBridge from "./bridges/settings" + +window.settings = SettingsBridge \ No newline at end of file diff --git a/src/schema-types.ts b/src/schema-types.ts new file mode 100644 index 0000000..d5a631b --- /dev/null +++ b/src/schema-types.ts @@ -0,0 +1,41 @@ +export class SourceGroup { + isMultiple: boolean + sids: number[] + name?: string + expanded?: boolean + index?: number // available only from groups tab container + + constructor(sids: number[], name: string = null) { + name = (name && name.trim()) || "订阅源组" + if (sids.length == 1) { + this.isMultiple = false + } else { + this.isMultiple = true + this.name = name + this.expanded = true + } + this.sids = sids + } +} + +export enum ViewType { + Cards, List, Customized +} + +export enum ThemeSettings { + Default = "system", + Light = "light", + Dark = "dark" +} + +export type SchemaTypes = { + version: string + theme: ThemeSettings + pac: string + pacOn: boolean + view: ViewType + locale: string + sourceGroups: SourceGroup[] + fontSize: number + menuOn: boolean +} diff --git a/src/scripts/PromiseConstructor.d.ts b/src/scripts/PromiseConstructor.d.ts deleted file mode 100644 index 767fc95..0000000 --- a/src/scripts/PromiseConstructor.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -interface PromiseFulfilledResult { - status: "fulfilled"; - value: T; -} - -interface PromiseRejectedResult { - status: "rejected"; - reason: any; -} - -type PromiseSettledResult = PromiseFulfilledResult | PromiseRejectedResult; - -declare interface PromiseConstructor { - /** - * Creates a Promise that is resolved with an array of results when all - * of the provided Promises resolve or reject. - * @param values An array of Promises. - * @returns A new Promise. - */ - allSettled(values: T): - Promise<{ -readonly [P in keyof T]: PromiseSettledResult ? U : T[P]> }>; -} \ No newline at end of file diff --git a/src/scripts/config-schema.ts b/src/scripts/config-schema.ts deleted file mode 100644 index 935b2de..0000000 --- a/src/scripts/config-schema.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ThemeSettings } from "./settings" -import { SourceGroup } from "./models/group" - -// Using schema breaks unsafe-eval. Unused. -/* export const schema: {[key in keyof schemaTypes]: Schema} = { - theme: { type: "string", default: "system" }, - pac: { type: "string", default: "" }, - pacOn: { type: "boolean", default: false }, - view: { type: "number", default: 0 }, - locale: { type: "string", default: "default" }, - sourceGroups: { type: "array", default: [] } -} */ - -export type schemaTypes = { - version: string - theme: ThemeSettings - pac: string - pacOn: boolean - view: number - locale: string - sourceGroups: SourceGroup[] - fontSize: number - menuOn: boolean -} diff --git a/src/scripts/models/app.ts b/src/scripts/models/app.ts index 55cde89..de5bbc4 100644 --- a/src/scripts/models/app.ts +++ b/src/scripts/models/app.ts @@ -5,7 +5,7 @@ import { ActionStatus, AppThunk, getWindowBreakpoint } from "../utils" import { INIT_FEEDS, FeedActionTypes, ALL, initFeeds } from "./feed" import { SourceGroupActionTypes, UPDATE_SOURCE_GROUP, ADD_SOURCE_TO_GROUP, DELETE_SOURCE_GROUP, REMOVE_SOURCE_FROM_GROUP, REORDER_SOURCE_GROUPS } from "./group" import { PageActionTypes, SELECT_PAGE, PageType, selectAllArticles } from "./page" -import { getCurrentLocale, setDefaultMenu, getDefaultMenu } from "../settings" +import { getCurrentLocale } from "../settings" import locales from "../i18n/_locales" import * as db from "../db" @@ -38,7 +38,7 @@ export class AppState { fetchingItems = false fetchingProgress = 0 fetchingTotal = 0 - menu = getWindowBreakpoint() && getDefaultMenu() + menu = getWindowBreakpoint() && window.settings.getDefaultMenu() menuKey = ALL title = "" settings = { @@ -152,7 +152,7 @@ export function openGroupMenu(sids: number[], event: React.MouseEvent): ContextM export function toggleMenu(): AppThunk { return (dispatch, getState) => { dispatch({ type: TOGGLE_MENU }) - setDefaultMenu(getState().app.menu) + window.settings.setDefaultMenu(getState().app.menu) } } diff --git a/src/scripts/models/group.ts b/src/scripts/models/group.ts index 311dc79..987f80b 100644 --- a/src/scripts/models/group.ts +++ b/src/scripts/models/group.ts @@ -1,43 +1,12 @@ import fs = require("fs") import intl from "react-intl-universal" import { SourceActionTypes, ADD_SOURCE, DELETE_SOURCE, addSource, RSSSource } from "./source" - +import { SourceGroup } from "../../schema-types" import { ActionStatus, AppThunk, domParser, AppDispatch } from "../utils" import { saveSettings } from "./app" -import { store } from "../settings" import { fetchItemsIntermediate, fetchItemsRequest, fetchItemsSuccess } from "./item" import { remote } from "electron" -const GROUPS_STORE_KEY = "sourceGroups" - -export class SourceGroup { - isMultiple: boolean - sids: number[] - name?: string - expanded?: boolean - index?: number // available only from groups tab container - - constructor(sids: number[], name: string = null) { - name = (name && name.trim()) || "订阅源组" - if (sids.length == 1) { - this.isMultiple = false - } else { - this.isMultiple = true - this.name = name - this.expanded = true - } - this.sids = sids - } - - static save(groups: SourceGroup[]) { - store.set(GROUPS_STORE_KEY, groups) - } - - static load(): SourceGroup[] { - return store.get(GROUPS_STORE_KEY, []) - } -} - export const CREATE_SOURCE_GROUP = "CREATE_SOURCE_GROUP" export const ADD_SOURCE_TO_GROUP = "ADD_SOURCE_TO_GROUP" export const REMOVE_SOURCE_FROM_GROUP = "REMOVE_SOURCE_FROM_GROUP" @@ -100,7 +69,7 @@ export function createSourceGroup(name: string): AppThunk { let group = new SourceGroup([], name) dispatch(createSourceGroupDone(group)) let groups = getState().groups - SourceGroup.save(groups) + window.settings.saveGroups(groups) return groups.length - 1 } } @@ -116,7 +85,7 @@ function addSourceToGroupDone(groupIndex: number, sid: number): SourceGroupActio export function addSourceToGroup(groupIndex: number, sid: number): AppThunk { return (dispatch, getState) => { dispatch(addSourceToGroupDone(groupIndex, sid)) - SourceGroup.save(getState().groups) + window.settings.saveGroups(getState().groups) } } @@ -131,7 +100,7 @@ function removeSourceFromGroupDone(groupIndex: number, sids: number[]): SourceGr export function removeSourceFromGroup(groupIndex: number, sids: number[]): AppThunk { return (dispatch, getState) => { dispatch(removeSourceFromGroupDone(groupIndex, sids)) - SourceGroup.save(getState().groups) + window.settings.saveGroups(getState().groups) } } @@ -145,7 +114,7 @@ function deleteSourceGroupDone(groupIndex: number): SourceGroupActionTypes { export function deleteSourceGroup(groupIndex: number): AppThunk { return (dispatch, getState) => { dispatch(deleteSourceGroupDone(groupIndex)) - SourceGroup.save(getState().groups) + window.settings.saveGroups(getState().groups) } } @@ -160,7 +129,7 @@ function updateSourceGroupDone(group: SourceGroup): SourceGroupActionTypes { export function updateSourceGroup(group: SourceGroup): AppThunk { return (dispatch, getState) => { dispatch(updateSourceGroupDone(group)) - SourceGroup.save(getState().groups) + window.settings.saveGroups(getState().groups) } } @@ -174,7 +143,7 @@ function reorderSourceGroupsDone(groups: SourceGroup[]): SourceGroupActionTypes export function reorderSourceGroups(groups: SourceGroup[]): AppThunk { return (dispatch, getState) => { dispatch(reorderSourceGroupsDone(groups)) - SourceGroup.save(getState().groups) + window.settings.saveGroups(getState().groups) } } @@ -184,7 +153,7 @@ export function toggleGroupExpansion(groupIndex: number): AppThunk { type: TOGGLE_GROUP_EXPANSION, groupIndex: groupIndex }) - SourceGroup.save(getState().groups) + window.settings.saveGroups(getState().groups) } } @@ -299,7 +268,7 @@ export function exportOPML(path: string): AppThunk { export type GroupState = SourceGroup[] export function groupReducer( - state = SourceGroup.load(), + state = window.settings.loadGroups(), action: SourceActionTypes | SourceGroupActionTypes ): GroupState { switch(action.type) { diff --git a/src/scripts/models/page.ts b/src/scripts/models/page.ts index 6f3a0a4..ca22372 100644 --- a/src/scripts/models/page.ts +++ b/src/scripts/models/page.ts @@ -1,9 +1,9 @@ import { ALL, SOURCE, loadMore, FeedFilter, FilterType, initFeeds, FeedActionTypes, INIT_FEED } from "./feed" import { getWindowBreakpoint, AppThunk, ActionStatus } from "../utils" -import { getDefaultView } from "../settings" import { RSSItem, markRead } from "./item" import { SourceActionTypes, DELETE_SOURCE } from "./source" import { toggleMenu } from "./app" +import { ViewType } from "../../schema-types" export const SELECT_PAGE = "SELECT_PAGE" export const SWITCH_VIEW = "SWITCH_VIEW" @@ -17,10 +17,6 @@ export enum PageType { AllArticles, Sources, Page } -export enum ViewType { - Cards, List, Customized -} - interface SelectPageAction { type: typeof SELECT_PAGE pageType: PageType @@ -205,7 +201,7 @@ export function performSearch(query: string): AppThunk { } export class PageState { - viewType = getDefaultView() + viewType = window.settings.getDefaultView() filter = new FeedFilter() feedId = ALL itemId = null as string diff --git a/src/scripts/models/source.ts b/src/scripts/models/source.ts index 9c948be..42ac3e2 100644 --- a/src/scripts/models/source.ts +++ b/src/scripts/models/source.ts @@ -3,7 +3,6 @@ import intl from "react-intl-universal" import * as db from "../db" import { fetchFavicon, ActionStatus, AppThunk, parseRSS } from "../utils" import { RSSItem, insertItems, ItemActionTypes, FETCH_ITEMS, MARK_READ, MARK_UNREAD, MARK_ALL_READ } from "./item" -import { SourceGroup } from "./group" import { saveSettings } from "./app" import { remote } from "electron" import { SourceRule } from "./rule" @@ -249,7 +248,7 @@ export function addSource(url: string, name: string = null, batch = false): AppT return RSSSource.checkItems(inserted, feed.items) .then(items => insertItems(items)) .then(() => { - SourceGroup.save(getState().groups) + window.settings.saveGroups(getState().groups) return inserted.sid }) }) @@ -310,7 +309,7 @@ export function deleteSource(source: RSSSource, batch = false): AppThunk() - -const MENU_STORE_KEY = "menuOn" -export function getDefaultMenu() { - return store.get(MENU_STORE_KEY, false) -} -export function setDefaultMenu(state: boolean) { - store.set(MENU_STORE_KEY, state) -} - -const PAC_STORE_KEY = "pac" -const PAC_STATUS_KEY = "pacOn" -export function getProxyStatus() { - return store.get(PAC_STATUS_KEY, false) -} -export function toggleProxyStatus() { - store.set(PAC_STATUS_KEY, !getProxyStatus()) - setProxy() -} -export function getProxy() { - return store.get(PAC_STORE_KEY, "") -} -export function setProxy(address = null) { - if (!address) { - address = getProxy() - } else { - store.set(PAC_STORE_KEY, address) - } - if (getProxyStatus()) { - let rules = { pacScript: address } - remote.getCurrentWebContents().session.setProxy(rules) - remote.session.fromPartition("sandbox").setProxy(rules) - } -} - -const VIEW_STORE_KEY = "view" -export const getDefaultView = (): ViewType => { - return store.get(VIEW_STORE_KEY, ViewType.Cards) -} -export const setDefaultView = (viewType: ViewType) => { - store.set(VIEW_STORE_KEY, viewType) -} +export const store = new Store() const lightTheme: IPartialTheme = { defaultFontStyle: { fontFamily: '"Segoe UI", "Source Han Sans SC Regular", "Microsoft YaHei", sans-serif' } @@ -56,63 +14,50 @@ const lightTheme: IPartialTheme = { const darkTheme: IPartialTheme = { ...lightTheme, palette: { - neutralLighterAlt: '#282828', - neutralLighter: '#313131', - neutralLight: '#3f3f3f', - neutralQuaternaryAlt: '#484848', - neutralQuaternary: '#4f4f4f', - neutralTertiaryAlt: '#6d6d6d', - neutralTertiary: '#c8c8c8', - neutralSecondary: '#d0d0d0', - neutralSecondaryAlt: '#d2d0ce', - neutralPrimaryAlt: '#dadada', - neutralPrimary: '#ffffff', - neutralDark: '#f4f4f4', - black: '#f8f8f8', - white: '#1f1f1f', - themePrimary: '#3a96dd', - themeLighterAlt: '#020609', - themeLighter: '#091823', - themeLight: '#112d43', - themeTertiary: '#235a85', - themeSecondary: '#3385c3', - themeDarkAlt: '#4ba0e1', - themeDark: '#65aee6', - themeDarker: '#8ac2ec', - accent: '#3a96dd' + neutralLighterAlt: "#282828", + neutralLighter: "#313131", + neutralLight: "#3f3f3f", + neutralQuaternaryAlt: "#484848", + neutralQuaternary: "#4f4f4f", + neutralTertiaryAlt: "#6d6d6d", + neutralTertiary: "#c8c8c8", + neutralSecondary: "#d0d0d0", + neutralSecondaryAlt: "#d2d0ce", + neutralPrimaryAlt: "#dadada", + neutralPrimary: "#ffffff", + neutralDark: "#f4f4f4", + black: "#f8f8f8", + white: "#1f1f1f", + themePrimary: "#3a96dd", + themeLighterAlt: "#020609", + themeLighter: "#091823", + themeLight: "#112d43", + themeTertiary: "#235a85", + themeSecondary: "#3385c3", + themeDarkAlt: "#4ba0e1", + themeDark: "#65aee6", + themeDarker: "#8ac2ec", + accent: "#3a96dd" } } -export enum ThemeSettings { - Default = "system", - Light = "light", - Dark = "dark" -} + const THEME_STORE_KEY = "theme" export function setThemeSettings(theme: ThemeSettings) { - store.set(THEME_STORE_KEY, theme) - remote.nativeTheme.themeSource = theme + window.settings.setThemeSettings(theme) applyThemeSettings() } export function getThemeSettings(): ThemeSettings { - return store.get(THEME_STORE_KEY, ThemeSettings.Default) + return window.settings.getThemeSettings() } export function applyThemeSettings() { - loadTheme(remote.nativeTheme.shouldUseDarkColors ? darkTheme : lightTheme) + loadTheme(window.settings.shouldUseDarkColors() ? darkTheme : lightTheme) } -remote.nativeTheme.on("updated", () => { - applyThemeSettings() +window.settings.addThemeUpdateListener((shouldDark) => { + loadTheme(shouldDark ? darkTheme : lightTheme) }) -const LOCALE_STORE_KEY = "locale" -export function setLocaleSettings(option: string) { - store.set(LOCALE_STORE_KEY, option) -} -export function getLocaleSettings() { - return store.get(LOCALE_STORE_KEY, "default") -} export function getCurrentLocale() { - let set = getLocaleSettings() - let locale = set === "default" ? remote.app.getLocale() : set + let locale = window.settings.getCurrentLocale() return (locale in locales) ? locale : "en-US" } diff --git a/webpack.config.js b/webpack.config.js index 4b00d3b..7f4c360 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -20,6 +20,25 @@ module.exports = [ filename: 'electron.js' } }, + { + mode: 'production', + entry: './src/preload.ts', + target: 'electron-preload', + module: { + rules: [{ + test: /\.ts$/, + include: /src/, + resolve: { + extensions: ['.ts', '.js'] + }, + use: [{ loader: 'ts-loader' }] + }] + }, + output: { + path: __dirname + '/dist', + filename: 'preload.js' + } + }, { mode: 'production', entry: './src/index.tsx',