aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-02-27 00:57:44 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-03-06 18:56:46 +0100
commitf05d54406c9bc4b69609a4935132ff17b8e28824 (patch)
treee7ffde8f8b3433e004932a6e068dedbb4f2196da
parentdesign: Simpler message count indicators (diff)
downloadsophie-f05d54406c9bc4b69609a4935132ff17b8e28824.tar.gz
sophie-f05d54406c9bc4b69609a4935132ff17b8e28824.tar.zst
sophie-f05d54406c9bc4b69609a4935132ff17b8e28824.zip
refactor: Shared model type factories
Allows customization of stores both in the renderer and in the main process. Instead of exposing a basic model type from the shared module (which was be overwritted with more specific props in the main package), we expose factory function that can create specific model types in both the renderer and the main process. Using these package-specific customization to stores, the renderer package can attach IPC calls directly to store objects, which the main package can attach the handlers for IPC calls and other internal actions. Signed-off-by: Kristóf Marussy <kristof@marussy.com>
-rw-r--r--packages/main/src/stores/GlobalSettings.ts12
-rw-r--r--packages/main/src/stores/Profile.ts7
-rw-r--r--packages/main/src/stores/Service.ts7
-rw-r--r--packages/main/src/stores/ServiceSettings.ts10
-rw-r--r--packages/main/src/stores/SharedStore.ts14
-rw-r--r--packages/main/src/stores/config/loadConfig.ts2
-rw-r--r--packages/main/src/utils/overrideProps.ts62
-rw-r--r--packages/renderer/src/components/locationBar/LocationTextField.tsx3
-rw-r--r--packages/renderer/src/components/locationBar/NavigationButtons.tsx3
-rw-r--r--packages/renderer/src/components/sidebar/ServiceIcon.tsx3
-rw-r--r--packages/renderer/src/components/sidebar/ServiceSwitcher.tsx9
-rw-r--r--packages/renderer/src/components/sidebar/ToggleDarkModeButton.tsx8
-rw-r--r--packages/renderer/src/components/sidebar/ToggleLocationBarButton.tsx8
-rw-r--r--packages/renderer/src/stores/GlobalSettings.ts63
-rw-r--r--packages/renderer/src/stores/Profile.ts (renamed from packages/main/src/stores/ProfileSettings.ts)18
-rw-r--r--packages/renderer/src/stores/RendererStore.ts41
-rw-r--r--packages/renderer/src/stores/Service.ts34
-rw-r--r--packages/renderer/src/stores/ServiceSettings.ts (renamed from packages/shared/src/stores/ServiceSettings.ts)20
-rw-r--r--packages/renderer/src/stores/SharedStore.ts (renamed from packages/shared/src/stores/SharedStore.ts)47
-rw-r--r--packages/shared/src/contextBridge/SophieRenderer.ts2
-rw-r--r--packages/shared/src/index.ts26
-rw-r--r--packages/shared/src/stores/GlobalSettingsBase.ts (renamed from packages/shared/src/stores/GlobalSettings.ts)32
-rw-r--r--packages/shared/src/stores/ServiceBase.ts (renamed from packages/shared/src/stores/Service.ts)20
-rw-r--r--packages/shared/src/stores/ServiceSettingsBase.ts57
-rw-r--r--packages/shared/src/stores/SharedStoreBase.ts70
25 files changed, 348 insertions, 230 deletions
diff --git a/packages/main/src/stores/GlobalSettings.ts b/packages/main/src/stores/GlobalSettings.ts
index 2af9da2..31b7e12 100644
--- a/packages/main/src/stores/GlobalSettings.ts
+++ b/packages/main/src/stores/GlobalSettings.ts
@@ -18,22 +18,16 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { 21import { defineGlobalSettingsModel, ThemeSource } from '@sophie/shared';
22 GlobalSettings as GlobalSettingsBase, 22import { Instance, resolveIdentifier } from 'mobx-state-tree';
23 ThemeSource,
24} from '@sophie/shared';
25import { Instance, resolveIdentifier, types } from 'mobx-state-tree';
26 23
27import { getLogger } from '../utils/log'; 24import { getLogger } from '../utils/log';
28import overrideProps from '../utils/overrideProps';
29 25
30import Service from './Service'; 26import Service from './Service';
31 27
32const log = getLogger('sharedStore'); 28const log = getLogger('sharedStore');
33 29
34const GlobalSettings = overrideProps(GlobalSettingsBase, { 30const GlobalSettings = defineGlobalSettingsModel(Service).actions((self) => ({
35 selectedService: types.safeReference(Service),
36}).actions((self) => ({
37 setThemeSource(mode: ThemeSource): void { 31 setThemeSource(mode: ThemeSource): void {
38 self.themeSource = mode; 32 self.themeSource = mode;
39 }, 33 },
diff --git a/packages/main/src/stores/Profile.ts b/packages/main/src/stores/Profile.ts
index 0fd486e..836f4a8 100644
--- a/packages/main/src/stores/Profile.ts
+++ b/packages/main/src/stores/Profile.ts
@@ -21,14 +21,9 @@
21import { Profile as ProfileBase } from '@sophie/shared'; 21import { Profile as ProfileBase } from '@sophie/shared';
22import { getSnapshot, Instance } from 'mobx-state-tree'; 22import { getSnapshot, Instance } from 'mobx-state-tree';
23 23
24import overrideProps from '../utils/overrideProps';
25
26import ProfileSettings from './ProfileSettings';
27import type ProfileConfig from './config/ProfileConfig'; 24import type ProfileConfig from './config/ProfileConfig';
28 25
29const Profile = overrideProps(ProfileBase, { 26const Profile = ProfileBase.views((self) => ({
30 settings: ProfileSettings,
31}).views((self) => ({
32 get config(): ProfileConfig { 27 get config(): ProfileConfig {
33 const { id, settings } = self; 28 const { id, settings } = self;
34 return { ...getSnapshot(settings), id }; 29 return { ...getSnapshot(settings), id };
diff --git a/packages/main/src/stores/Service.ts b/packages/main/src/stores/Service.ts
index 5302dd4..abef7c2 100644
--- a/packages/main/src/stores/Service.ts
+++ b/packages/main/src/stores/Service.ts
@@ -19,18 +19,15 @@
19 */ 19 */
20 20
21import type { UnreadCount } from '@sophie/service-shared'; 21import type { UnreadCount } from '@sophie/service-shared';
22import { Service as ServiceBase } from '@sophie/shared'; 22import { defineServiceModel } from '@sophie/shared';
23import { Instance, getSnapshot } from 'mobx-state-tree'; 23import { Instance, getSnapshot } from 'mobx-state-tree';
24 24
25import type { ServiceView } from '../infrastructure/electron/types'; 25import type { ServiceView } from '../infrastructure/electron/types';
26import overrideProps from '../utils/overrideProps';
27 26
28import ServiceSettings from './ServiceSettings'; 27import ServiceSettings from './ServiceSettings';
29import type ServiceConfig from './config/ServiceConfig'; 28import type ServiceConfig from './config/ServiceConfig';
30 29
31const Service = overrideProps(ServiceBase, { 30const Service = defineServiceModel(ServiceSettings)
32 settings: ServiceSettings,
33})
34 .views((self) => ({ 31 .views((self) => ({
35 get config(): ServiceConfig { 32 get config(): ServiceConfig {
36 const { id, settings } = self; 33 const { id, settings } = self;
diff --git a/packages/main/src/stores/ServiceSettings.ts b/packages/main/src/stores/ServiceSettings.ts
index e6f48c6..5d37347 100644
--- a/packages/main/src/stores/ServiceSettings.ts
+++ b/packages/main/src/stores/ServiceSettings.ts
@@ -18,16 +18,12 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { ServiceSettings as ServiceSettingsBase } from '@sophie/shared'; 21import { defineServiceSettingsModel } from '@sophie/shared';
22import { Instance, types } from 'mobx-state-tree'; 22import { Instance } from 'mobx-state-tree';
23
24import overrideProps from '../utils/overrideProps';
25 23
26import Profile from './Profile'; 24import Profile from './Profile';
27 25
28const ServiceSettings = overrideProps(ServiceSettingsBase, { 26const ServiceSettings = defineServiceSettingsModel(Profile);
29 profile: types.reference(Profile),
30});
31 27
32/* 28/*
33 eslint-disable-next-line @typescript-eslint/no-redeclare -- 29 eslint-disable-next-line @typescript-eslint/no-redeclare --
diff --git a/packages/main/src/stores/SharedStore.ts b/packages/main/src/stores/SharedStore.ts
index 182b693..d72c532 100644
--- a/packages/main/src/stores/SharedStore.ts
+++ b/packages/main/src/stores/SharedStore.ts
@@ -18,10 +18,8 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { SharedStore as SharedStoreBase } from '@sophie/shared'; 21import { defineSharedStoreModel } from '@sophie/shared';
22import { getSnapshot, Instance, types } from 'mobx-state-tree'; 22import { getSnapshot, Instance } from 'mobx-state-tree';
23
24import overrideProps from '../utils/overrideProps';
25 23
26import GlobalSettings from './GlobalSettings'; 24import GlobalSettings from './GlobalSettings';
27import Profile from './Profile'; 25import Profile from './Profile';
@@ -33,13 +31,7 @@ function getConfigs<T>(models: { config: T }[]): T[] | undefined {
33 return models.length === 0 ? undefined : models.map((model) => model.config); 31 return models.length === 0 ? undefined : models.map((model) => model.config);
34} 32}
35 33
36const SharedStore = overrideProps(SharedStoreBase, { 34const SharedStore = defineSharedStoreModel(GlobalSettings, Profile, Service)
37 settings: types.optional(GlobalSettings, {}),
38 profilesById: types.map(Profile),
39 profiles: types.array(types.reference(Profile)),
40 servicesById: types.map(Service),
41 services: types.array(types.reference(Service)),
42})
43 .views((self) => ({ 35 .views((self) => ({
44 get config(): Config { 36 get config(): Config {
45 const { settings, profiles, services } = self; 37 const { settings, profiles, services } = self;
diff --git a/packages/main/src/stores/config/loadConfig.ts b/packages/main/src/stores/config/loadConfig.ts
index 55d15c8..2463084 100644
--- a/packages/main/src/stores/config/loadConfig.ts
+++ b/packages/main/src/stores/config/loadConfig.ts
@@ -18,6 +18,7 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { ProfileSettingsSnapshotIn } from '@sophie/shared';
21import { 22import {
22 applySnapshot, 23 applySnapshot,
23 IMSTArray, 24 IMSTArray,
@@ -31,7 +32,6 @@ import slug from 'slug';
31 32
32import GlobalSettings from '../GlobalSettings'; 33import GlobalSettings from '../GlobalSettings';
33import type Profile from '../Profile'; 34import type Profile from '../Profile';
34import type { ProfileSettingsSnapshotIn } from '../ProfileSettings';
35import type Service from '../Service'; 35import type Service from '../Service';
36import type { ServiceSettingsSnapshotIn } from '../ServiceSettings'; 36import type { ServiceSettingsSnapshotIn } from '../ServiceSettings';
37 37
diff --git a/packages/main/src/utils/overrideProps.ts b/packages/main/src/utils/overrideProps.ts
deleted file mode 100644
index c626408..0000000
--- a/packages/main/src/utils/overrideProps.ts
+++ /dev/null
@@ -1,62 +0,0 @@
1/*
2 * Copyright (C) 2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21/**
22 * @file This file implements a technique to force-override properties of a model.
23 *
24 * The overridden properties must conform to the SnapshotIt and SnapshotOut format
25 * of the original model. Essentially, this means that only views and actions can
26 * be added safely.
27 *
28 * @see https://github.com/mobxjs/mobx-state-tree/issues/1403#issuecomment-940843087
29 */
30
31import {
32 IAnyModelType,
33 IModelType,
34 ModelProperties,
35 SnapshotIn,
36 SnapshotOut,
37} from 'mobx-state-tree';
38
39export type IUnsafeOverriddenModelType<
40 BASE extends IAnyModelType,
41 PROPS extends ModelProperties,
42> = BASE extends IModelType<infer P, infer O, infer CC, infer CS>
43 ? IModelType<Omit<P, keyof PROPS> & PROPS, O, CC, CS>
44 : never;
45
46export type IOverriddenModelType<
47 BASE extends IAnyModelType,
48 PROPS extends ModelProperties,
49> = SnapshotIn<BASE> extends SnapshotIn<IUnsafeOverriddenModelType<BASE, PROPS>>
50 ? SnapshotOut<
51 IUnsafeOverriddenModelType<BASE, PROPS>
52 > extends SnapshotOut<BASE>
53 ? IUnsafeOverriddenModelType<BASE, PROPS>
54 : never
55 : never;
56
57export default function overrideProps<
58 BASE extends IAnyModelType,
59 PROPS extends ModelProperties,
60>(base: BASE, props: PROPS): IOverriddenModelType<BASE, PROPS> {
61 return base.props(props) as IOverriddenModelType<BASE, PROPS>;
62}
diff --git a/packages/renderer/src/components/locationBar/LocationTextField.tsx b/packages/renderer/src/components/locationBar/LocationTextField.tsx
index f436bf0..e6da59f 100644
--- a/packages/renderer/src/components/locationBar/LocationTextField.tsx
+++ b/packages/renderer/src/components/locationBar/LocationTextField.tsx
@@ -20,11 +20,12 @@
20 20
21import FilledInput from '@mui/material/FilledInput'; 21import FilledInput from '@mui/material/FilledInput';
22import { styled } from '@mui/material/styles'; 22import { styled } from '@mui/material/styles';
23import { Service } from '@sophie/shared';
24import { autorun } from 'mobx'; 23import { autorun } from 'mobx';
25import { observer } from 'mobx-react-lite'; 24import { observer } from 'mobx-react-lite';
26import React, { useCallback, useEffect, useState } from 'react'; 25import React, { useCallback, useEffect, useState } from 'react';
27 26
27import Service from '../../stores/Service';
28
28import GoAdornment from './GoAdornment'; 29import GoAdornment from './GoAdornment';
29import LocationOverlayInput from './LocationOverlayInput'; 30import LocationOverlayInput from './LocationOverlayInput';
30import UrlAdornment from './UrlAdornment'; 31import UrlAdornment from './UrlAdornment';
diff --git a/packages/renderer/src/components/locationBar/NavigationButtons.tsx b/packages/renderer/src/components/locationBar/NavigationButtons.tsx
index 77b02b6..ce59692 100644
--- a/packages/renderer/src/components/locationBar/NavigationButtons.tsx
+++ b/packages/renderer/src/components/locationBar/NavigationButtons.tsx
@@ -26,10 +26,11 @@ import IconRefresh from '@mui/icons-material/Refresh';
26import { useTheme } from '@mui/material'; 26import { useTheme } from '@mui/material';
27import Box from '@mui/material/Box'; 27import Box from '@mui/material/Box';
28import IconButton from '@mui/material/IconButton'; 28import IconButton from '@mui/material/IconButton';
29import { Service } from '@sophie/shared';
30import { observer } from 'mobx-react-lite'; 29import { observer } from 'mobx-react-lite';
31import React from 'react'; 30import React from 'react';
32 31
32import Service from '../../stores/Service';
33
33function NavigationButtons({ 34function NavigationButtons({
34 service, 35 service,
35}: { 36}: {
diff --git a/packages/renderer/src/components/sidebar/ServiceIcon.tsx b/packages/renderer/src/components/sidebar/ServiceIcon.tsx
index 144f860..5db8a0c 100644
--- a/packages/renderer/src/components/sidebar/ServiceIcon.tsx
+++ b/packages/renderer/src/components/sidebar/ServiceIcon.tsx
@@ -20,10 +20,11 @@
20 20
21import Badge from '@mui/material/Badge'; 21import Badge from '@mui/material/Badge';
22import { styled, useTheme } from '@mui/material/styles'; 22import { styled, useTheme } from '@mui/material/styles';
23import { Service } from '@sophie/shared';
24import { observer } from 'mobx-react-lite'; 23import { observer } from 'mobx-react-lite';
25import React, { useEffect, useState } from 'react'; 24import React, { useEffect, useState } from 'react';
26 25
26import type Service from '../../stores/Service';
27
27const ServiceIconRoot = styled('div', { 28const ServiceIconRoot = styled('div', {
28 name: 'ServiceIcon', 29 name: 'ServiceIcon',
29 slot: 'Root', 30 slot: 'Root',
diff --git a/packages/renderer/src/components/sidebar/ServiceSwitcher.tsx b/packages/renderer/src/components/sidebar/ServiceSwitcher.tsx
index 2241476..eba21b5 100644
--- a/packages/renderer/src/components/sidebar/ServiceSwitcher.tsx
+++ b/packages/renderer/src/components/sidebar/ServiceSwitcher.tsx
@@ -65,11 +65,8 @@ const ServiceSwitcherTab = styled(Tab, {
65})); 65}));
66 66
67function ServiceSwitcher(): JSX.Element { 67function ServiceSwitcher(): JSX.Element {
68 const store = useStore(); 68 const { settings, services } = useStore();
69 const { 69 const { selectedService } = settings;
70 settings: { selectedService },
71 services,
72 } = store;
73 70
74 return ( 71 return (
75 <ServiceSwitcherRoot 72 <ServiceSwitcherRoot
@@ -77,7 +74,7 @@ function ServiceSwitcher(): JSX.Element {
77 orientation="vertical" 74 orientation="vertical"
78 value={selectedService === undefined ? false : selectedService.id} 75 value={selectedService === undefined ? false : selectedService.id}
79 onChange={(_event, newValue: string) => 76 onChange={(_event, newValue: string) =>
80 store.setSelectedServiceId(newValue) 77 settings.setSelectedServiceId(newValue)
81 } 78 }
82 > 79 >
83 {services.map((service) => ( 80 {services.map((service) => (
diff --git a/packages/renderer/src/components/sidebar/ToggleDarkModeButton.tsx b/packages/renderer/src/components/sidebar/ToggleDarkModeButton.tsx
index 164b066..bacbf07 100644
--- a/packages/renderer/src/components/sidebar/ToggleDarkModeButton.tsx
+++ b/packages/renderer/src/components/sidebar/ToggleDarkModeButton.tsx
@@ -27,15 +27,13 @@ import React from 'react';
27import { useStore } from '../StoreProvider'; 27import { useStore } from '../StoreProvider';
28 28
29export default observer(() => { 29export default observer(() => {
30 const store = useStore(); 30 const { shared } = useStore();
31 const { 31 const { shouldUseDarkColors } = shared;
32 shared: { shouldUseDarkColors },
33 } = store;
34 32
35 return ( 33 return (
36 <IconButton 34 <IconButton
37 aria-label="Toggle dark mode" 35 aria-label="Toggle dark mode"
38 onClick={() => store.toggleDarkMode()} 36 onClick={() => shared.toggleDarkMode()}
39 > 37 >
40 {shouldUseDarkColors ? <LightModeIcon /> : <DarkModeIcon />} 38 {shouldUseDarkColors ? <LightModeIcon /> : <DarkModeIcon />}
41 </IconButton> 39 </IconButton>
diff --git a/packages/renderer/src/components/sidebar/ToggleLocationBarButton.tsx b/packages/renderer/src/components/sidebar/ToggleLocationBarButton.tsx
index 60033b0..d2f0745 100644
--- a/packages/renderer/src/components/sidebar/ToggleLocationBarButton.tsx
+++ b/packages/renderer/src/components/sidebar/ToggleLocationBarButton.tsx
@@ -45,17 +45,15 @@ function ToggleLocationBarIcon({
45} 45}
46 46
47function ToggleLocationBarButton(): JSX.Element { 47function ToggleLocationBarButton(): JSX.Element {
48 const store = useStore(); 48 const { settings } = useStore();
49 const { 49 const { selectedService, showLocationBar } = settings;
50 settings: { selectedService, showLocationBar },
51 } = store;
52 50
53 return ( 51 return (
54 <IconButton 52 <IconButton
55 aria-pressed={showLocationBar} 53 aria-pressed={showLocationBar}
56 aria-controls={LOCATION_BAR_ID} 54 aria-controls={LOCATION_BAR_ID}
57 aria-label="Show location bar" 55 aria-label="Show location bar"
58 onClick={() => store.toggleLocationBar()} 56 onClick={() => settings.toggleLocationBar()}
59 > 57 >
60 <ToggleLocationBarIcon 58 <ToggleLocationBarIcon
61 loading={selectedService?.state === 'loading'} 59 loading={selectedService?.state === 'loading'}
diff --git a/packages/renderer/src/stores/GlobalSettings.ts b/packages/renderer/src/stores/GlobalSettings.ts
new file mode 100644
index 0000000..79815ba
--- /dev/null
+++ b/packages/renderer/src/stores/GlobalSettings.ts
@@ -0,0 +1,63 @@
1/*
2 * Copyright (C) 2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import { defineGlobalSettingsModel, ThemeSource } from '@sophie/shared';
22import { Instance } from 'mobx-state-tree';
23
24import getEnv from '../env/getEnv';
25
26import Service from './Service';
27
28const GlobalSettings = defineGlobalSettingsModel(Service).actions((self) => ({
29 setSelectedServiceId(serviceId: string): void {
30 getEnv(self).dispatchMainAction({
31 action: 'set-selected-service-id',
32 serviceId,
33 });
34 },
35 setThemeSource(themeSource: ThemeSource): void {
36 getEnv(self).dispatchMainAction({
37 action: 'set-theme-source',
38 themeSource,
39 });
40 },
41 setShowLocationBar(showLocationBar: boolean): void {
42 getEnv(self).dispatchMainAction({
43 action: 'set-show-location-bar',
44 showLocationBar,
45 });
46 },
47 toggleLocationBar(): void {
48 this.setShowLocationBar(!self.showLocationBar);
49 },
50}));
51
52/*
53 eslint-disable-next-line @typescript-eslint/no-redeclare --
54 Intentionally naming the type the same as the store definition.
55*/
56interface GlobalSettings extends Instance<typeof GlobalSettings> {}
57
58export default GlobalSettings;
59
60export type {
61 GlobalSettingsSnapshotIn,
62 GlobalSettingsSnapshotOut,
63} from '@sophie/shared';
diff --git a/packages/main/src/stores/ProfileSettings.ts b/packages/renderer/src/stores/Profile.ts
index eed51e3..20a3a17 100644
--- a/packages/main/src/stores/ProfileSettings.ts
+++ b/packages/renderer/src/stores/Profile.ts
@@ -18,13 +18,15 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { ProfileSettings } from '@sophie/shared'; 21import { Profile as ProfileBase } from '@sophie/shared';
22import { Instance } from 'mobx-state-tree';
22 23
23// TODO Export a modified ProfileSettings once we need to add actions to it. 24const Profile = ProfileBase;
24// eslint-disable-next-line unicorn/prefer-export-from -- Can't export from default.
25export default ProfileSettings;
26 25
27export type { 26/*
28 ProfileSettingsSnapshotIn, 27 eslint-disable-next-line @typescript-eslint/no-redeclare --
29 ProfileSettingsSnapshotOut, 28 Intentionally naming the type the same as the store definition.
30} from '@sophie/shared'; 29*/
30interface Profile extends Instance<typeof Profile> {}
31
32export default Profile;
diff --git a/packages/renderer/src/stores/RendererStore.ts b/packages/renderer/src/stores/RendererStore.ts
index 1acc605..8f424f6 100644
--- a/packages/renderer/src/stores/RendererStore.ts
+++ b/packages/renderer/src/stores/RendererStore.ts
@@ -18,20 +18,17 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { 21import { BrowserViewBounds, SophieRenderer } from '@sophie/shared';
22 BrowserViewBounds,
23 SharedStore,
24 Service,
25 SophieRenderer,
26 ThemeSource,
27 GlobalSettings,
28} from '@sophie/shared';
29import { applySnapshot, applyPatch, Instance, types } from 'mobx-state-tree'; 22import { applySnapshot, applyPatch, Instance, types } from 'mobx-state-tree';
30 23
31import RendererEnv from '../env/RendererEnv'; 24import RendererEnv from '../env/RendererEnv';
32import getEnv from '../env/getEnv'; 25import getEnv from '../env/getEnv';
33import { getLogger } from '../utils/log'; 26import { getLogger } from '../utils/log';
34 27
28import GlobalSettings from './GlobalSettings';
29import Service from './Service';
30import SharedStore from './SharedStore';
31
35const log = getLogger('RendererStore'); 32const log = getLogger('RendererStore');
36 33
37const RendererStore = types 34const RendererStore = types
@@ -47,40 +44,12 @@ const RendererStore = types
47 }, 44 },
48 })) 45 }))
49 .actions((self) => ({ 46 .actions((self) => ({
50 setSelectedServiceId(serviceId: string): void {
51 getEnv(self).dispatchMainAction({
52 action: 'set-selected-service-id',
53 serviceId,
54 });
55 },
56 setBrowserViewBounds(browserViewBounds: BrowserViewBounds): void { 47 setBrowserViewBounds(browserViewBounds: BrowserViewBounds): void {
57 getEnv(self).dispatchMainAction({ 48 getEnv(self).dispatchMainAction({
58 action: 'set-browser-view-bounds', 49 action: 'set-browser-view-bounds',
59 browserViewBounds, 50 browserViewBounds,
60 }); 51 });
61 }, 52 },
62 setThemeSource(themeSource: ThemeSource): void {
63 getEnv(self).dispatchMainAction({
64 action: 'set-theme-source',
65 themeSource,
66 });
67 },
68 toggleDarkMode(): void {
69 if (self.shared.shouldUseDarkColors) {
70 this.setThemeSource('light');
71 } else {
72 this.setThemeSource('dark');
73 }
74 },
75 setShowLocationBar(showLocationBar: boolean): void {
76 getEnv(self).dispatchMainAction({
77 action: 'set-show-location-bar',
78 showLocationBar,
79 });
80 },
81 toggleLocationBar(): void {
82 this.setShowLocationBar(!self.settings.showLocationBar);
83 },
84 })); 53 }));
85 54
86/* 55/*
diff --git a/packages/renderer/src/stores/Service.ts b/packages/renderer/src/stores/Service.ts
new file mode 100644
index 0000000..c2c938a
--- /dev/null
+++ b/packages/renderer/src/stores/Service.ts
@@ -0,0 +1,34 @@
1/*
2 * Copyright (C) 2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import { defineServiceModel } from '@sophie/shared';
22import { Instance } from 'mobx-state-tree';
23
24import ServiceSettings from './ServiceSettings';
25
26const Service = defineServiceModel(ServiceSettings);
27
28/*
29 eslint-disable-next-line @typescript-eslint/no-redeclare --
30 Intentionally naming the type the same as the store definition.
31*/
32interface Service extends Instance<typeof Service> {}
33
34export default Service;
diff --git a/packages/shared/src/stores/ServiceSettings.ts b/packages/renderer/src/stores/ServiceSettings.ts
index a5811f5..5d37347 100644
--- a/packages/shared/src/stores/ServiceSettings.ts
+++ b/packages/renderer/src/stores/ServiceSettings.ts
@@ -18,17 +18,12 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { Instance, types, SnapshotIn, SnapshotOut } from 'mobx-state-tree'; 21import { defineServiceSettingsModel } from '@sophie/shared';
22import { Instance } from 'mobx-state-tree';
22 23
23import Profile from './Profile'; 24import Profile from './Profile';
24 25
25const ServiceSettings = /* @__PURE__ */ (() => 26const ServiceSettings = defineServiceSettingsModel(Profile);
26 types.model('ServiceSettings', {
27 name: types.string,
28 profile: types.reference(Profile),
29 // TODO: Remove this once recipes are added.
30 url: types.string,
31 }))();
32 27
33/* 28/*
34 eslint-disable-next-line @typescript-eslint/no-redeclare -- 29 eslint-disable-next-line @typescript-eslint/no-redeclare --
@@ -38,8 +33,7 @@ interface ServiceSettings extends Instance<typeof ServiceSettings> {}
38 33
39export default ServiceSettings; 34export default ServiceSettings;
40 35
41export interface ServiceSettingsSnapshotIn 36export type {
42 extends SnapshotIn<typeof ServiceSettings> {} 37 ServiceSettingsSnapshotIn,
43 38 ServiceSettingsSnapshotOut,
44export interface ServiceSettingsSnapshotOut 39} from '@sophie/shared';
45 extends SnapshotOut<typeof ServiceSettings> {}
diff --git a/packages/shared/src/stores/SharedStore.ts b/packages/renderer/src/stores/SharedStore.ts
index d81a3d3..062479d 100644
--- a/packages/shared/src/stores/SharedStore.ts
+++ b/packages/renderer/src/stores/SharedStore.ts
@@ -1,5 +1,5 @@
1/* 1/*
2 * Copyright (C) 2021-2022 Kristóf Marussy <kristof@marussy.com> 2 * Copyright (C) 2022 Kristóf Marussy <kristof@marussy.com>
3 * 3 *
4 * This file is part of Sophie. 4 * This file is part of Sophie.
5 * 5 *
@@ -18,27 +18,26 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { 21import { defineSharedStoreModel } from '@sophie/shared';
22 IJsonPatch, 22import { Instance } from 'mobx-state-tree';
23 Instance,
24 types,
25 SnapshotIn,
26 SnapshotOut,
27} from 'mobx-state-tree';
28 23
29import GlobalSettings from './GlobalSettings'; 24import GlobalSettings from './GlobalSettings';
30import Profile from './Profile'; 25import Profile from './Profile';
31import Service from './Service'; 26import Service from './Service';
32 27
33const SharedStore = /* @__PURE__ */ (() => 28const SharedStore = defineSharedStoreModel(
34 types.model('SharedStore', { 29 GlobalSettings,
35 settings: types.optional(GlobalSettings, {}), 30 Profile,
36 profilesById: types.map(Profile), 31 Service,
37 profiles: types.array(types.reference(Profile)), 32).actions((self) => ({
38 servicesById: types.map(Service), 33 toggleDarkMode(): void {
39 services: types.array(types.reference(Service)), 34 if (self.shouldUseDarkColors) {
40 shouldUseDarkColors: false, 35 self.settings.setThemeSource('light');
41 }))(); 36 } else {
37 self.settings.setThemeSource('dark');
38 }
39 },
40}));
42 41
43/* 42/*
44 eslint-disable-next-line @typescript-eslint/no-redeclare -- 43 eslint-disable-next-line @typescript-eslint/no-redeclare --
@@ -48,13 +47,7 @@ interface SharedStore extends Instance<typeof SharedStore> {}
48 47
49export default SharedStore; 48export default SharedStore;
50 49
51export interface SharedStoreSnapshotIn extends SnapshotIn<typeof SharedStore> {} 50export type {
52 51 SharedStoreSnapshotIn,
53export interface SharedStoreSnapshotOut 52 SharedStoreSnapshotOut,
54 extends SnapshotOut<typeof SharedStore> {} 53} from '@sophie/shared';
55
56export interface SharedStoreListener {
57 onSnapshot(snapshot: SharedStoreSnapshotIn): void;
58
59 onPatch(patches: IJsonPatch[]): void;
60}
diff --git a/packages/shared/src/contextBridge/SophieRenderer.ts b/packages/shared/src/contextBridge/SophieRenderer.ts
index 28dc0b7..9e087da 100644
--- a/packages/shared/src/contextBridge/SophieRenderer.ts
+++ b/packages/shared/src/contextBridge/SophieRenderer.ts
@@ -19,7 +19,7 @@
19 */ 19 */
20 20
21import { Action } from '../schemas'; 21import { Action } from '../schemas';
22import { SharedStoreListener } from '../stores/SharedStore'; 22import { SharedStoreListener } from '../stores/SharedStoreBase';
23 23
24export default interface SophieRenderer { 24export default interface SophieRenderer {
25 onSharedStoreChange(this: void, listener: SharedStoreListener): Promise<void>; 25 onSharedStoreChange(this: void, listener: SharedStoreListener): Promise<void>;
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
index 3d30488..66debf7 100644
--- a/packages/shared/src/index.ts
+++ b/packages/shared/src/index.ts
@@ -27,8 +27,11 @@ export { Action, BrowserViewBounds, ThemeSource } from './schemas';
27export type { 27export type {
28 GlobalSettingsSnapshotIn, 28 GlobalSettingsSnapshotIn,
29 GlobalSettingsSnapshotOut, 29 GlobalSettingsSnapshotOut,
30} from './stores/GlobalSettings'; 30} from './stores/GlobalSettingsBase';
31export { default as GlobalSettings } from './stores/GlobalSettings'; 31export {
32 default as GlobalSettingsBase,
33 defineGlobalSettingsModel,
34} from './stores/GlobalSettingsBase';
32 35
33export { default as Profile } from './stores/Profile'; 36export { default as Profile } from './stores/Profile';
34 37
@@ -38,17 +41,26 @@ export type {
38} from './stores/ProfileSettings'; 41} from './stores/ProfileSettings';
39export { default as ProfileSettings } from './stores/ProfileSettings'; 42export { default as ProfileSettings } from './stores/ProfileSettings';
40 43
41export { default as Service } from './stores/Service'; 44export {
45 default as ServiceBase,
46 defineServiceModel,
47} from './stores/ServiceBase';
42 48
43export type { 49export type {
44 ServiceSettingsSnapshotIn, 50 ServiceSettingsSnapshotIn,
45 ServiceSettingsSnapshotOut, 51 ServiceSettingsSnapshotOut,
46} from './stores/ServiceSettings'; 52} from './stores/ServiceSettingsBase';
47export { default as ServiceSettings } from './stores/ServiceSettings'; 53export {
54 default as ServiceSettingsBase,
55 defineServiceSettingsModel,
56} from './stores/ServiceSettingsBase';
48 57
49export type { 58export type {
50 SharedStoreListener, 59 SharedStoreListener,
51 SharedStoreSnapshotIn, 60 SharedStoreSnapshotIn,
52 SharedStoreSnapshotOut, 61 SharedStoreSnapshotOut,
53} from './stores/SharedStore'; 62} from './stores/SharedStoreBase';
54export { default as SharedStore } from './stores/SharedStore'; 63export {
64 default as SharedStoreBase,
65 defineSharedStoreModel,
66} from './stores/SharedStoreBase';
diff --git a/packages/shared/src/stores/GlobalSettings.ts b/packages/shared/src/stores/GlobalSettingsBase.ts
index f316af9..48092fd 100644
--- a/packages/shared/src/stores/GlobalSettings.ts
+++ b/packages/shared/src/stores/GlobalSettingsBase.ts
@@ -18,32 +18,44 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { Instance, types, SnapshotIn, SnapshotOut } from 'mobx-state-tree'; 21import {
22 Instance,
23 types,
24 SnapshotIn,
25 SnapshotOut,
26 IAnyModelType,
27} from 'mobx-state-tree';
22 28
23import { ThemeSource } from '../schemas'; 29import { ThemeSource } from '../schemas';
24 30
25import Service from './Service'; 31import ServiceBase from './ServiceBase';
26 32
27const GlobalSettings = /* @__PURE__ */ (() => 33export function defineGlobalSettingsModel<TS extends IAnyModelType>(
28 types.model('GlobalSettings', { 34 service: TS,
35) {
36 return types.model('GlobalSettings', {
29 themeSource: types.optional( 37 themeSource: types.optional(
30 types.enumeration(ThemeSource.options), 38 types.enumeration(ThemeSource.options),
31 'system', 39 'system',
32 ), 40 ),
33 showLocationBar: false, 41 showLocationBar: false,
34 selectedService: types.safeReference(Service), 42 selectedService: types.safeReference(service),
35 }))(); 43 });
44}
45
46const GlobalSettingsBase = /* @__PURE__ */ (() =>
47 defineGlobalSettingsModel(ServiceBase))();
36 48
37/* 49/*
38 eslint-disable-next-line @typescript-eslint/no-redeclare -- 50 eslint-disable-next-line @typescript-eslint/no-redeclare --
39 Intentionally naming the type the same as the store definition. 51 Intentionally naming the type the same as the store definition.
40*/ 52*/
41interface GlobalSettings extends Instance<typeof GlobalSettings> {} 53interface GlobalSettingsBase extends Instance<typeof GlobalSettingsBase> {}
42 54
43export default GlobalSettings; 55export default GlobalSettingsBase;
44 56
45export interface GlobalSettingsSnapshotIn 57export interface GlobalSettingsSnapshotIn
46 extends SnapshotIn<typeof GlobalSettings> {} 58 extends SnapshotIn<typeof GlobalSettingsBase> {}
47 59
48export interface GlobalSettingsSnapshotOut 60export interface GlobalSettingsSnapshotOut
49 extends SnapshotOut<typeof GlobalSettings> {} 61 extends SnapshotOut<typeof GlobalSettingsBase> {}
diff --git a/packages/shared/src/stores/Service.ts b/packages/shared/src/stores/ServiceBase.ts
index a4e3c92..cde403b 100644
--- a/packages/shared/src/stores/Service.ts
+++ b/packages/shared/src/stores/ServiceBase.ts
@@ -18,14 +18,14 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { Instance, types } from 'mobx-state-tree'; 21import { IAnyModelType, Instance, types } from 'mobx-state-tree';
22 22
23import ServiceSettings from './ServiceSettings'; 23import ServiceSettingsBase from './ServiceSettingsBase';
24 24
25const Service = /* @__PURE__ */ (() => 25export function defineServiceModel<TS extends IAnyModelType>(settings: TS) {
26 types.model('Service', { 26 return types.model('Service', {
27 id: types.identifier, 27 id: types.identifier,
28 settings: ServiceSettings, 28 settings,
29 currentUrl: types.maybe(types.string), 29 currentUrl: types.maybe(types.string),
30 canGoBack: false, 30 canGoBack: false,
31 canGoForward: false, 31 canGoForward: false,
@@ -36,12 +36,16 @@ const Service = /* @__PURE__ */ (() =>
36 ), 36 ),
37 directMessageCount: 0, 37 directMessageCount: 0,
38 indirectMessageCount: 0, 38 indirectMessageCount: 0,
39 }))(); 39 });
40}
41
42const ServiceBase = /* @__PURE__ */ (() =>
43 defineServiceModel(ServiceSettingsBase))();
40 44
41/* 45/*
42 eslint-disable-next-line @typescript-eslint/no-redeclare -- 46 eslint-disable-next-line @typescript-eslint/no-redeclare --
43 Intentionally naming the type the same as the store definition. 47 Intentionally naming the type the same as the store definition.
44*/ 48*/
45interface Service extends Instance<typeof Service> {} 49interface ServiceBase extends Instance<typeof ServiceBase> {}
46 50
47export default Service; 51export default ServiceBase;
diff --git a/packages/shared/src/stores/ServiceSettingsBase.ts b/packages/shared/src/stores/ServiceSettingsBase.ts
new file mode 100644
index 0000000..45eb15d
--- /dev/null
+++ b/packages/shared/src/stores/ServiceSettingsBase.ts
@@ -0,0 +1,57 @@
1/*
2 * Copyright (C) 2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import {
22 Instance,
23 types,
24 SnapshotIn,
25 SnapshotOut,
26 IAnyModelType,
27} from 'mobx-state-tree';
28
29import ProfileBase from './Profile';
30
31export function defineServiceSettingsModel<TP extends IAnyModelType>(
32 profile: TP,
33) {
34 return types.model('ServiceSettings', {
35 name: types.string,
36 profile: types.reference(profile),
37 // TODO: Remove this once recipes are added.
38 url: types.string,
39 });
40}
41
42const ServiceSettingsBase = /* @__PURE__ */ (() =>
43 defineServiceSettingsModel(ProfileBase))();
44
45/*
46 eslint-disable-next-line @typescript-eslint/no-redeclare --
47 Intentionally naming the type the same as the store definition.
48*/
49interface ServiceSettingsBase extends Instance<typeof ServiceSettingsBase> {}
50
51export default ServiceSettingsBase;
52
53export interface ServiceSettingsSnapshotIn
54 extends SnapshotIn<typeof ServiceSettingsBase> {}
55
56export interface ServiceSettingsSnapshotOut
57 extends SnapshotOut<typeof ServiceSettingsBase> {}
diff --git a/packages/shared/src/stores/SharedStoreBase.ts b/packages/shared/src/stores/SharedStoreBase.ts
new file mode 100644
index 0000000..8d6624b
--- /dev/null
+++ b/packages/shared/src/stores/SharedStoreBase.ts
@@ -0,0 +1,70 @@
1/*
2 * Copyright (C) 2021-2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import {
22 IJsonPatch,
23 Instance,
24 types,
25 SnapshotIn,
26 SnapshotOut,
27 IAnyModelType,
28} from 'mobx-state-tree';
29
30import GlobalSettingsBase from './GlobalSettingsBase';
31import ProfileBase from './Profile';
32import ServiceBase from './ServiceBase';
33
34export function defineSharedStoreModel<
35 TG extends IAnyModelType,
36 TP extends IAnyModelType,
37 TS extends IAnyModelType,
38>(globalSettings: TG, profile: TP, service: TS) {
39 return types.model('SharedStore', {
40 settings: types.optional(globalSettings, {}),
41 profilesById: types.map(profile),
42 profiles: types.array(types.reference(profile)),
43 servicesById: types.map(service),
44 services: types.array(types.reference(service)),
45 shouldUseDarkColors: false,
46 });
47}
48
49const SharedStoreBase = /* @__PURE__ */ (() =>
50 defineSharedStoreModel(GlobalSettingsBase, ProfileBase, ServiceBase))();
51
52/*
53 eslint-disable-next-line @typescript-eslint/no-redeclare --
54 Intentionally naming the type the same as the store definition.
55*/
56interface SharedStoreBase extends Instance<typeof SharedStoreBase> {}
57
58export default SharedStoreBase;
59
60export interface SharedStoreSnapshotIn
61 extends SnapshotIn<typeof SharedStoreBase> {}
62
63export interface SharedStoreSnapshotOut
64 extends SnapshotOut<typeof SharedStoreBase> {}
65
66export interface SharedStoreListener {
67 onSnapshot(snapshot: SharedStoreSnapshotIn): void;
68
69 onPatch(patches: IJsonPatch[]): void;
70}