chore(ui): store selected projects in settings (#21737)
This commit is contained in:
parent
403a194ac7
commit
b0bda92f9e
|
@ -15,7 +15,6 @@ const TODO_ITEMS = [
|
||||||
'book a doctors appointment'
|
'book a doctors appointment'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
test.describe('New Todo', () => {
|
test.describe('New Todo', () => {
|
||||||
test('should allow me to add todo items', async ({ page }) => {
|
test('should allow me to add todo items', async ({ page }) => {
|
||||||
// create a new todo locator
|
// create a new todo locator
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { isUnderTest } from 'playwright-core/lib/utils';
|
||||||
import type { Page } from '../page';
|
import type { Page } from '../page';
|
||||||
import { registryDirectory } from '../registry';
|
import { registryDirectory } from '../registry';
|
||||||
import type { CRPage } from './crPage';
|
import type { CRPage } from './crPage';
|
||||||
|
@ -29,23 +30,21 @@ export async function installAppIcon(page: Page) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function syncLocalStorageWithSettings(page: Page, appName: string) {
|
export async function syncLocalStorageWithSettings(page: Page, appName: string) {
|
||||||
|
if (isUnderTest())
|
||||||
|
return;
|
||||||
const settingsFile = path.join(registryDirectory, '.settings', `${appName}.json`);
|
const settingsFile = path.join(registryDirectory, '.settings', `${appName}.json`);
|
||||||
await page.exposeBinding('saveSettings', false, (_, settings: any) => {
|
await page.exposeBinding('_saveSerializedSettings', false, (_, settings) => {
|
||||||
fs.mkdirSync(path.dirname(settingsFile), { recursive: true });
|
fs.mkdirSync(path.dirname(settingsFile), { recursive: true });
|
||||||
fs.writeFileSync(settingsFile, settings);
|
fs.writeFileSync(settingsFile, settings);
|
||||||
});
|
});
|
||||||
|
|
||||||
const settings = await fs.promises.readFile(settingsFile, 'utf-8').catch(() => ('{}'));
|
const settings = await fs.promises.readFile(settingsFile, 'utf-8').catch(() => ('{}'));
|
||||||
await page.addInitScript(`(${String((settings: any) => {
|
await page.addInitScript(
|
||||||
|
`(${String((settings: any) => {
|
||||||
Object.entries(settings).map(([k, v]) => localStorage[k] = v);
|
Object.entries(settings).map(([k, v]) => localStorage[k] = v);
|
||||||
|
(window as any).saveSettings = () => {
|
||||||
let lastValue = JSON.stringify(localStorage);
|
(window as any)._saveSerializedSettings(JSON.stringify({ ...localStorage }));
|
||||||
setInterval(() => {
|
};
|
||||||
const value = JSON.stringify(localStorage);
|
})})(${settings});
|
||||||
if (value !== lastValue) {
|
`);
|
||||||
lastValue = value;
|
|
||||||
window.saveSettings(value);
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
})})(${settings})`);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import '@web/common.css';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { TreeView } from '@web/components/treeView';
|
import { TreeView } from '@web/components/treeView';
|
||||||
import type { TreeState } from '@web/components/treeView';
|
import type { TreeState } from '@web/components/treeView';
|
||||||
import { TeleReporterReceiver, TeleSuite } from '@testIsomorphic/teleReceiver';
|
import { baseFullConfig, TeleReporterReceiver, TeleSuite } from '@testIsomorphic/teleReceiver';
|
||||||
import type { TeleTestCase } from '@testIsomorphic/teleReceiver';
|
import type { TeleTestCase } from '@testIsomorphic/teleReceiver';
|
||||||
import type { FullConfig, Suite, TestCase, TestResult, Location } from '../../../playwright-test/types/testReporter';
|
import type { FullConfig, Suite, TestCase, TestResult, Location } from '../../../playwright-test/types/testReporter';
|
||||||
import { SplitView } from '@web/components/splitView';
|
import { SplitView } from '@web/components/splitView';
|
||||||
|
@ -34,8 +34,9 @@ import { XtermWrapper } from '@web/components/xtermWrapper';
|
||||||
import { Expandable } from '@web/components/expandable';
|
import { Expandable } from '@web/components/expandable';
|
||||||
import { toggleTheme } from '@web/theme';
|
import { toggleTheme } from '@web/theme';
|
||||||
import { artifactsFolderName } from '@testIsomorphic/folders';
|
import { artifactsFolderName } from '@testIsomorphic/folders';
|
||||||
|
import { settings } from '@web/uiUtils';
|
||||||
|
|
||||||
let updateRootSuite: (rootSuite: Suite, progress: Progress) => void = () => {};
|
let updateRootSuite: (config: FullConfig, rootSuite: Suite, progress: Progress) => void = () => {};
|
||||||
let runWatchedTests = (fileName: string) => {};
|
let runWatchedTests = (fileName: string) => {};
|
||||||
let xtermSize = { cols: 80, rows: 24 };
|
let xtermSize = { cols: 80, rows: 24 };
|
||||||
|
|
||||||
|
@ -49,6 +50,11 @@ const xtermDataSource: XtermDataSource = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type TestModel = {
|
||||||
|
config: FullConfig | undefined;
|
||||||
|
rootSuite: Suite | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
export const WatchModeView: React.FC<{}> = ({
|
export const WatchModeView: React.FC<{}> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [filterText, setFilterText] = React.useState<string>('');
|
const [filterText, setFilterText] = React.useState<string>('');
|
||||||
|
@ -60,7 +66,7 @@ export const WatchModeView: React.FC<{}> = ({
|
||||||
['skipped', false],
|
['skipped', false],
|
||||||
]));
|
]));
|
||||||
const [projectFilters, setProjectFilters] = React.useState<Map<string, boolean>>(new Map());
|
const [projectFilters, setProjectFilters] = React.useState<Map<string, boolean>>(new Map());
|
||||||
const [rootSuite, setRootSuite] = React.useState<{ value: Suite | undefined }>({ value: undefined });
|
const [testModel, setTestModel] = React.useState<TestModel>({ config: undefined, rootSuite: undefined });
|
||||||
const [progress, setProgress] = React.useState<Progress>({ total: 0, passed: 0, failed: 0, skipped: 0 });
|
const [progress, setProgress] = React.useState<Progress>({ total: 0, passed: 0, failed: 0, skipped: 0 });
|
||||||
const [selectedTest, setSelectedTest] = React.useState<TestCase | undefined>(undefined);
|
const [selectedTest, setSelectedTest] = React.useState<TestCase | undefined>(undefined);
|
||||||
const [visibleTestIds, setVisibleTestIds] = React.useState<string[]>([]);
|
const [visibleTestIds, setVisibleTestIds] = React.useState<string[]>([]);
|
||||||
|
@ -71,7 +77,7 @@ export const WatchModeView: React.FC<{}> = ({
|
||||||
|
|
||||||
const reloadTests = () => {
|
const reloadTests = () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
updateRootSuite(new TeleSuite('', 'root'), { total: 0, passed: 0, failed: 0, skipped: 0 });
|
updateRootSuite(baseFullConfig, new TeleSuite('', 'root'), { total: 0, passed: 0, failed: 0, skipped: 0 });
|
||||||
refreshRootSuite(true).then(() => {
|
refreshRootSuite(true).then(() => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
});
|
});
|
||||||
|
@ -82,19 +88,20 @@ export const WatchModeView: React.FC<{}> = ({
|
||||||
reloadTests();
|
reloadTests();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
updateRootSuite = (rootSuite: Suite, newProgress: Progress) => {
|
updateRootSuite = (config: FullConfig, rootSuite: Suite, newProgress: Progress) => {
|
||||||
|
const selectedProjects = config.configFile ? settings.getObject<string[] | undefined>(config.configFile + ':projects', undefined) : undefined;
|
||||||
for (const projectName of projectFilters.keys()) {
|
for (const projectName of projectFilters.keys()) {
|
||||||
if (!rootSuite.suites.find(s => s.title === projectName))
|
if (!rootSuite.suites.find(s => s.title === projectName))
|
||||||
projectFilters.delete(projectName);
|
projectFilters.delete(projectName);
|
||||||
}
|
}
|
||||||
for (const projectSuite of rootSuite.suites) {
|
for (const projectSuite of rootSuite.suites) {
|
||||||
if (!projectFilters.has(projectSuite.title))
|
if (!projectFilters.has(projectSuite.title))
|
||||||
projectFilters.set(projectSuite.title, false);
|
projectFilters.set(projectSuite.title, !!selectedProjects?.includes(projectSuite.title));
|
||||||
}
|
}
|
||||||
if (projectFilters.size && ![...projectFilters.values()].includes(true))
|
if (!selectedProjects && projectFilters.size && ![...projectFilters.values()].includes(true))
|
||||||
projectFilters.set(projectFilters.entries().next().value[0], true);
|
projectFilters.set(projectFilters.entries().next().value[0], true);
|
||||||
|
|
||||||
setRootSuite({ value: rootSuite });
|
setTestModel({ config, rootSuite });
|
||||||
setProjectFilters(new Map(projectFilters));
|
setProjectFilters(new Map(projectFilters));
|
||||||
setProgress(newProgress);
|
setProgress(newProgress);
|
||||||
};
|
};
|
||||||
|
@ -103,11 +110,11 @@ export const WatchModeView: React.FC<{}> = ({
|
||||||
// Clear test results.
|
// Clear test results.
|
||||||
{
|
{
|
||||||
const testIdSet = new Set(testIds);
|
const testIdSet = new Set(testIds);
|
||||||
for (const test of rootSuite.value?.allTests() || []) {
|
for (const test of testModel.rootSuite?.allTests() || []) {
|
||||||
if (testIdSet.has(test.id))
|
if (testIdSet.has(test.id))
|
||||||
(test as TeleTestCase)._createTestResult('pending');
|
(test as TeleTestCase)._createTestResult('pending');
|
||||||
}
|
}
|
||||||
setRootSuite({ ...rootSuite });
|
setTestModel({ ...testModel });
|
||||||
}
|
}
|
||||||
|
|
||||||
const time = ' [' + new Date().toLocaleTimeString() + ']';
|
const time = ' [' + new Date().toLocaleTimeString() + ']';
|
||||||
|
@ -154,6 +161,7 @@ export const WatchModeView: React.FC<{}> = ({
|
||||||
setStatusFilters={setStatusFilters}
|
setStatusFilters={setStatusFilters}
|
||||||
projectFilters={projectFilters}
|
projectFilters={projectFilters}
|
||||||
setProjectFilters={setProjectFilters}
|
setProjectFilters={setProjectFilters}
|
||||||
|
testModel={testModel}
|
||||||
runTests={() => runTests(visibleTestIds)} />
|
runTests={() => runTests(visibleTestIds)} />
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<div className='section-title'>Tests</div>
|
<div className='section-title'>Tests</div>
|
||||||
|
@ -165,7 +173,7 @@ export const WatchModeView: React.FC<{}> = ({
|
||||||
statusFilters={statusFilters}
|
statusFilters={statusFilters}
|
||||||
projectFilters={projectFilters}
|
projectFilters={projectFilters}
|
||||||
filterText={filterText}
|
filterText={filterText}
|
||||||
rootSuite={rootSuite}
|
testModel={testModel}
|
||||||
runningState={runningState}
|
runningState={runningState}
|
||||||
runTests={runTests}
|
runTests={runTests}
|
||||||
onTestSelected={setSelectedTest}
|
onTestSelected={setSelectedTest}
|
||||||
|
@ -191,8 +199,9 @@ const FiltersView: React.FC<{
|
||||||
setStatusFilters: (filters: Map<string, boolean>) => void;
|
setStatusFilters: (filters: Map<string, boolean>) => void;
|
||||||
projectFilters: Map<string, boolean>;
|
projectFilters: Map<string, boolean>;
|
||||||
setProjectFilters: (filters: Map<string, boolean>) => void;
|
setProjectFilters: (filters: Map<string, boolean>) => void;
|
||||||
|
testModel: TestModel | undefined,
|
||||||
runTests: () => void;
|
runTests: () => void;
|
||||||
}> = ({ filterText, setFilterText, statusFilters, setStatusFilters, projectFilters, setProjectFilters, runTests }) => {
|
}> = ({ filterText, setFilterText, statusFilters, setStatusFilters, projectFilters, setProjectFilters, testModel, runTests }) => {
|
||||||
const [expanded, setExpanded] = React.useState(false);
|
const [expanded, setExpanded] = React.useState(false);
|
||||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
@ -235,6 +244,9 @@ const FiltersView: React.FC<{
|
||||||
const copy = new Map(projectFilters);
|
const copy = new Map(projectFilters);
|
||||||
copy.set(projectName, !copy.get(projectName));
|
copy.set(projectName, !copy.get(projectName));
|
||||||
setProjectFilters(copy);
|
setProjectFilters(copy);
|
||||||
|
const configFile = testModel?.config?.configFile;
|
||||||
|
if (configFile)
|
||||||
|
settings.setObject(configFile + ':projects', [...copy.entries()].filter(([_, v]) => v).map(([k]) => k));
|
||||||
}}/>
|
}}/>
|
||||||
<div>{projectName}</div>
|
<div>{projectName}</div>
|
||||||
</label>
|
</label>
|
||||||
|
@ -254,18 +266,18 @@ const TestList: React.FC<{
|
||||||
statusFilters: Map<string, boolean>,
|
statusFilters: Map<string, boolean>,
|
||||||
projectFilters: Map<string, boolean>,
|
projectFilters: Map<string, boolean>,
|
||||||
filterText: string,
|
filterText: string,
|
||||||
rootSuite: { value: Suite | undefined },
|
testModel: { rootSuite: Suite | undefined, config: FullConfig | undefined },
|
||||||
runTests: (testIds: string[]) => void,
|
runTests: (testIds: string[]) => void,
|
||||||
runningState?: { testIds: Set<string>, itemSelectedByUser?: boolean },
|
runningState?: { testIds: Set<string>, itemSelectedByUser?: boolean },
|
||||||
setVisibleTestIds: (testIds: string[]) => void,
|
setVisibleTestIds: (testIds: string[]) => void,
|
||||||
onTestSelected: (test: TestCase | undefined) => void,
|
onTestSelected: (test: TestCase | undefined) => void,
|
||||||
}> = ({ statusFilters, projectFilters, filterText, rootSuite, runTests, runningState, onTestSelected, setVisibleTestIds }) => {
|
}> = ({ statusFilters, projectFilters, filterText, testModel, runTests, runningState, onTestSelected, setVisibleTestIds }) => {
|
||||||
const [treeState, setTreeState] = React.useState<TreeState>({ expandedItems: new Map() });
|
const [treeState, setTreeState] = React.useState<TreeState>({ expandedItems: new Map() });
|
||||||
const [selectedTreeItemId, setSelectedTreeItemId] = React.useState<string | undefined>();
|
const [selectedTreeItemId, setSelectedTreeItemId] = React.useState<string | undefined>();
|
||||||
const [watchedTreeIds] = React.useState<Set<string>>(new Set());
|
const [watchedTreeIds] = React.useState<Set<string>>(new Set());
|
||||||
|
|
||||||
const { rootItem, treeItemMap } = React.useMemo(() => {
|
const { rootItem, treeItemMap } = React.useMemo(() => {
|
||||||
const rootItem = createTree(rootSuite.value, projectFilters);
|
const rootItem = createTree(testModel.rootSuite, projectFilters);
|
||||||
filterTree(rootItem, filterText, statusFilters);
|
filterTree(rootItem, filterText, statusFilters);
|
||||||
hideOnlyTests(rootItem);
|
hideOnlyTests(rootItem);
|
||||||
const treeItemMap = new Map<string, TreeItem>();
|
const treeItemMap = new Map<string, TreeItem>();
|
||||||
|
@ -279,7 +291,7 @@ const TestList: React.FC<{
|
||||||
visit(rootItem);
|
visit(rootItem);
|
||||||
setVisibleTestIds([...visibleTestIds]);
|
setVisibleTestIds([...visibleTestIds]);
|
||||||
return { rootItem, treeItemMap };
|
return { rootItem, treeItemMap };
|
||||||
}, [filterText, rootSuite, statusFilters, projectFilters, setVisibleTestIds]);
|
}, [filterText, testModel, statusFilters, projectFilters, setVisibleTestIds]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// Look for a first failure within the run batch to select it.
|
// Look for a first failure within the run batch to select it.
|
||||||
|
@ -439,15 +451,15 @@ declare global {
|
||||||
let receiver: TeleReporterReceiver | undefined;
|
let receiver: TeleReporterReceiver | undefined;
|
||||||
|
|
||||||
let throttleTimer: NodeJS.Timeout | undefined;
|
let throttleTimer: NodeJS.Timeout | undefined;
|
||||||
let throttleData: { rootSuite: Suite, progress: Progress } | undefined;
|
let throttleData: { config: FullConfig, rootSuite: Suite, progress: Progress } | undefined;
|
||||||
const throttledAction = () => {
|
const throttledAction = () => {
|
||||||
clearTimeout(throttleTimer);
|
clearTimeout(throttleTimer);
|
||||||
throttleTimer = undefined;
|
throttleTimer = undefined;
|
||||||
updateRootSuite(throttleData!.rootSuite, throttleData!.progress);
|
updateRootSuite(throttleData!.config, throttleData!.rootSuite, throttleData!.progress);
|
||||||
};
|
};
|
||||||
|
|
||||||
const throttleUpdateRootSuite = (rootSuite: Suite, progress: Progress, immediate = false) => {
|
const throttleUpdateRootSuite = (config: FullConfig, rootSuite: Suite, progress: Progress, immediate = false) => {
|
||||||
throttleData = { rootSuite, progress };
|
throttleData = { config, rootSuite, progress };
|
||||||
if (immediate)
|
if (immediate)
|
||||||
throttledAction();
|
throttledAction();
|
||||||
else if (!throttleTimer)
|
else if (!throttleTimer)
|
||||||
|
@ -465,23 +477,25 @@ const refreshRootSuite = (eraseResults: boolean): Promise<void> => {
|
||||||
failed: 0,
|
failed: 0,
|
||||||
skipped: 0,
|
skipped: 0,
|
||||||
};
|
};
|
||||||
|
let config: FullConfig;
|
||||||
receiver = new TeleReporterReceiver({
|
receiver = new TeleReporterReceiver({
|
||||||
onBegin: (config: FullConfig, suite: Suite) => {
|
onBegin: (c: FullConfig, suite: Suite) => {
|
||||||
if (!rootSuite)
|
if (!rootSuite)
|
||||||
rootSuite = suite;
|
rootSuite = suite;
|
||||||
|
config = c;
|
||||||
progress.total = suite.allTests().length;
|
progress.total = suite.allTests().length;
|
||||||
progress.passed = 0;
|
progress.passed = 0;
|
||||||
progress.failed = 0;
|
progress.failed = 0;
|
||||||
progress.skipped = 0;
|
progress.skipped = 0;
|
||||||
throttleUpdateRootSuite(rootSuite, progress, true);
|
throttleUpdateRootSuite(config, rootSuite, progress, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
onEnd: () => {
|
onEnd: () => {
|
||||||
throttleUpdateRootSuite(rootSuite, progress, true);
|
throttleUpdateRootSuite(config, rootSuite, progress, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
onTestBegin: () => {
|
onTestBegin: () => {
|
||||||
throttleUpdateRootSuite(rootSuite, progress);
|
throttleUpdateRootSuite(config, rootSuite, progress);
|
||||||
},
|
},
|
||||||
|
|
||||||
onTestEnd: (test: TestCase) => {
|
onTestEnd: (test: TestCase) => {
|
||||||
|
@ -491,7 +505,7 @@ const refreshRootSuite = (eraseResults: boolean): Promise<void> => {
|
||||||
++progress.failed;
|
++progress.failed;
|
||||||
else
|
else
|
||||||
++progress.passed;
|
++progress.passed;
|
||||||
throttleUpdateRootSuite(rootSuite, progress);
|
throttleUpdateRootSuite(config, rootSuite, progress);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return sendMessage('list', {});
|
return sendMessage('list', {});
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { settings } from './uiUtils';
|
||||||
|
|
||||||
export function applyTheme() {
|
export function applyTheme() {
|
||||||
if ((document as any).playwrightThemeInitialized)
|
if ((document as any).playwrightThemeInitialized)
|
||||||
return;
|
return;
|
||||||
|
@ -26,14 +28,14 @@ export function applyTheme() {
|
||||||
document.body.classList.add('inactive');
|
document.body.classList.add('inactive');
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
const currentTheme = localStorage.getItem('theme');
|
const currentTheme = settings.getString('theme', 'light-mode');
|
||||||
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
|
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
if (currentTheme === 'dark-mode' || prefersDarkScheme.matches)
|
if (currentTheme === 'dark-mode' || prefersDarkScheme.matches)
|
||||||
document.body.classList.add('dark-mode');
|
document.body.classList.add('dark-mode');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toggleTheme() {
|
export function toggleTheme() {
|
||||||
const oldTheme = localStorage.getItem('theme');
|
const oldTheme = settings.getString('theme', 'light-mode');
|
||||||
let newTheme: string;
|
let newTheme: string;
|
||||||
if (oldTheme === 'dark-mode')
|
if (oldTheme === 'dark-mode')
|
||||||
newTheme = 'light-mode';
|
newTheme = 'light-mode';
|
||||||
|
@ -43,7 +45,7 @@ export function toggleTheme() {
|
||||||
if (oldTheme)
|
if (oldTheme)
|
||||||
document.body.classList.remove(oldTheme);
|
document.body.classList.remove(oldTheme);
|
||||||
document.body.classList.add(newTheme);
|
document.body.classList.add(newTheme);
|
||||||
localStorage.setItem('theme', newTheme);
|
settings.setString('theme', newTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isDarkTheme() {
|
export function isDarkTheme() {
|
||||||
|
|
|
@ -80,15 +80,41 @@ export function copy(text: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSetting<S>(name: string, defaultValue: S): [S, React.Dispatch<React.SetStateAction<S>>] {
|
export function useSetting<S>(name: string, defaultValue: S): [S, React.Dispatch<React.SetStateAction<S>>] {
|
||||||
const string = localStorage.getItem(name);
|
const value = settings.getObject(name, defaultValue);
|
||||||
let value = defaultValue;
|
|
||||||
if (string !== null)
|
|
||||||
value = JSON.parse(string);
|
|
||||||
|
|
||||||
const [state, setState] = React.useState<S>(value);
|
const [state, setState] = React.useState<S>(value);
|
||||||
const setStateWrapper = (value: React.SetStateAction<S>) => {
|
const setStateWrapper = (value: React.SetStateAction<S>) => {
|
||||||
localStorage.setItem(name, JSON.stringify(value));
|
settings.setObject(name, value);
|
||||||
setState(value);
|
setState(value);
|
||||||
};
|
};
|
||||||
return [state, setStateWrapper];
|
return [state, setStateWrapper];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Settings {
|
||||||
|
getString(name: string, defaultValue: string): string {
|
||||||
|
return localStorage[name] || defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
setString(name: string, value: string) {
|
||||||
|
localStorage[name] = value;
|
||||||
|
if ((window as any).saveSettings)
|
||||||
|
(window as any).saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
getObject<T>(name: string, defaultValue: T): T {
|
||||||
|
if (!localStorage[name])
|
||||||
|
return defaultValue;
|
||||||
|
try {
|
||||||
|
return JSON.parse(localStorage[name]);
|
||||||
|
} catch {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setObject<T>(name: string, value: T) {
|
||||||
|
localStorage[name] = JSON.stringify(value);
|
||||||
|
if ((window as any).saveSettings)
|
||||||
|
(window as any).saveSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const settings = new Settings();
|
||||||
|
|
Loading…
Reference in New Issue