mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(devtools): implement settings store (#62429)
Introduces a set of services tasked with saving user settings. PR Close #62429
This commit is contained in:
committed by
Kristiyan Kostadinov
parent
986b0b1fc0
commit
1cdb54559d
@@ -15,4 +15,7 @@ export abstract class ApplicationOperations {
|
||||
abstract inspect(directivePosition: DirectivePosition, objectPath: string[], target: Frame): void;
|
||||
abstract inspectSignal(position: SignalNodePosition, target: Frame): void;
|
||||
abstract viewSourceFromRouter(name: string, type: string, target: Frame): void;
|
||||
abstract setStorageItems(items: {[key: string]: unknown}): Promise<void>;
|
||||
abstract getStorageItems(items: string[]): Promise<{[key: string]: unknown}>;
|
||||
abstract removeStorageItems(items: string[]): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
load("//devtools/tools:defaults.bzl", "ng_project")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
package(default_visibility = ["//devtools:__subpackages__"])
|
||||
|
||||
ng_project(
|
||||
name = "window",
|
||||
@@ -9,3 +9,14 @@ ng_project(
|
||||
"//:node_modules/@angular/core",
|
||||
],
|
||||
)
|
||||
|
||||
ng_project(
|
||||
name = "settings",
|
||||
srcs = ["settings_provider.ts"],
|
||||
deps = [
|
||||
"//:node_modules/@angular/core",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-operations",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-services:settings",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-services:settings_store",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {provideAppInitializer, inject, Provider, EnvironmentProviders} from '@angular/core';
|
||||
import {SETTINGS_STORE_KEY, SettingsStore} from '../application-services/settings_store';
|
||||
import {ApplicationOperations} from '../application-operations';
|
||||
import {Settings} from '../application-services/settings';
|
||||
|
||||
export function provideSettings(): (Provider | EnvironmentProviders)[] {
|
||||
let savedSettings: {[key: string]: unknown};
|
||||
|
||||
return [
|
||||
provideAppInitializer(async () => {
|
||||
const appOperations = inject(ApplicationOperations);
|
||||
const keyedItem = await appOperations.getStorageItems([SETTINGS_STORE_KEY]);
|
||||
savedSettings = (keyedItem[SETTINGS_STORE_KEY] ?? {}) as {[key: string]: unknown};
|
||||
}),
|
||||
{
|
||||
provide: SettingsStore,
|
||||
useFactory: () => new SettingsStore(savedSettings),
|
||||
},
|
||||
Settings,
|
||||
];
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
load("//devtools/tools:defaults.bzl", "ng_project", "ng_web_test_suite", "ts_test_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
package(default_visibility = ["//devtools:__subpackages__"])
|
||||
|
||||
ng_project(
|
||||
name = "browser_styles",
|
||||
@@ -34,18 +34,39 @@ ng_project(
|
||||
],
|
||||
)
|
||||
|
||||
ng_project(
|
||||
name = "settings_store",
|
||||
srcs = ["settings_store.ts"],
|
||||
deps = [
|
||||
"//devtools/projects/ng-devtools/src/lib/application-operations",
|
||||
"//packages/core",
|
||||
],
|
||||
)
|
||||
|
||||
ng_project(
|
||||
name = "settings",
|
||||
srcs = ["settings.ts"],
|
||||
deps = [
|
||||
":settings_store",
|
||||
"//packages/core",
|
||||
],
|
||||
)
|
||||
|
||||
ts_test_library(
|
||||
name = "test_application_services_lib",
|
||||
srcs = glob(["*_spec.ts"]),
|
||||
deps = [
|
||||
":browser_styles",
|
||||
":frame_manager",
|
||||
":settings_store",
|
||||
":theme",
|
||||
"//:node_modules/@angular/cdk",
|
||||
"//:node_modules/@angular/common",
|
||||
"//:node_modules/@angular/core",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-environment",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-providers:window",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-services/test-utils:app_operations_mock",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-services/test-utils:settings_store_mock",
|
||||
"//devtools/projects/protocol",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {inject} from '@angular/core';
|
||||
import {SettingsStore} from './settings_store';
|
||||
|
||||
export class Settings {
|
||||
private readonly settingsStore = inject(SettingsStore);
|
||||
|
||||
readonly dummy = this.settingsStore.create({
|
||||
key: 'dummy',
|
||||
category: 'general',
|
||||
initialValue: true,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {effect, inject, Injector, signal, WritableSignal} from '@angular/core';
|
||||
import {ApplicationOperations} from '../application-operations';
|
||||
|
||||
export const SETTINGS_STORE_KEY = 'ng-dt-settings-v1';
|
||||
|
||||
/** Provides an API for storing and preserving settings values. */
|
||||
export class SettingsStore {
|
||||
private readonly appOperations = inject(ApplicationOperations);
|
||||
private readonly injector = inject(Injector);
|
||||
private readonly signals = new Map<string, WritableSignal<unknown>>();
|
||||
|
||||
constructor(private data: {[key: string]: unknown}) {}
|
||||
|
||||
/**
|
||||
* Create a settings value a provided key, as a writable signal.
|
||||
* If the item doesn't exist, a new one will be created.
|
||||
* Updates to the signal value are automatically stored in the storage.
|
||||
*/
|
||||
create<T>(config: {key: string; category: string; initialValue: T}): WritableSignal<T> {
|
||||
const storeKey = `${config.key}@${config.category}`;
|
||||
const existing = this.signals.get(storeKey);
|
||||
if (existing) {
|
||||
return existing as WritableSignal<T>;
|
||||
}
|
||||
|
||||
const initialValue = storeKey in this.data ? (this.data[storeKey] as T) : config.initialValue;
|
||||
const value = signal<T>(initialValue);
|
||||
this.signals.set(storeKey, value);
|
||||
|
||||
effect(
|
||||
() => {
|
||||
this.data[storeKey] = value();
|
||||
this.appOperations.setStorageItems({[SETTINGS_STORE_KEY]: this.data});
|
||||
},
|
||||
{injector: this.injector},
|
||||
);
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {SettingsStore} from './settings_store';
|
||||
import {ApplicationOperations} from '../application-operations';
|
||||
import {AppOperationsMock} from './test-utils/app_operations_mock';
|
||||
import {ApplicationRef} from '@angular/core';
|
||||
|
||||
describe('SettingsStore', () => {
|
||||
let settingsStore: SettingsStore;
|
||||
let getStoredSettings: () => {[key: string]: unknown};
|
||||
|
||||
beforeEach(() => {
|
||||
const appOperationsMock = new AppOperationsMock();
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: ApplicationOperations, useValue: appOperationsMock},
|
||||
{
|
||||
provide: SettingsStore,
|
||||
useFactory: () => new SettingsStore({}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
settingsStore = TestBed.inject(SettingsStore);
|
||||
getStoredSettings = () => appOperationsMock.getStoredSettings();
|
||||
});
|
||||
|
||||
it('should return a settings item with an initial value', async () => {
|
||||
const item = settingsStore.create({
|
||||
key: 'item',
|
||||
category: 'test',
|
||||
initialValue: 'foo',
|
||||
});
|
||||
expect(item()).toEqual('foo');
|
||||
});
|
||||
|
||||
it('should set a settings item value', async () => {
|
||||
const value = settingsStore.create({
|
||||
key: 'item',
|
||||
category: 'test',
|
||||
initialValue: 'foo',
|
||||
});
|
||||
expect(value()).toEqual('foo');
|
||||
|
||||
value.set('bar');
|
||||
expect(value()).toBe('bar');
|
||||
|
||||
await TestBed.inject(ApplicationRef).whenStable();
|
||||
TestBed.tick();
|
||||
|
||||
expect(getStoredSettings()['item@test']).toEqual('bar');
|
||||
});
|
||||
|
||||
it('should set multiple values to a single settings item', async () => {
|
||||
const value = settingsStore.create({
|
||||
key: 'item',
|
||||
category: 'test',
|
||||
initialValue: 'foo',
|
||||
});
|
||||
expect(value()).toEqual('foo');
|
||||
|
||||
value.set('bar');
|
||||
await TestBed.inject(ApplicationRef).whenStable();
|
||||
TestBed.tick();
|
||||
expect(getStoredSettings()['item@test']).toEqual('bar');
|
||||
|
||||
value.set('baz');
|
||||
await TestBed.inject(ApplicationRef).whenStable();
|
||||
TestBed.tick();
|
||||
expect(getStoredSettings()['item@test']).toEqual('baz');
|
||||
});
|
||||
|
||||
it('should set values to multiple settings items', async () => {
|
||||
const first = settingsStore.create({
|
||||
key: 'first',
|
||||
category: 'test',
|
||||
initialValue: 'not_set',
|
||||
});
|
||||
const second = settingsStore.create({
|
||||
key: 'second',
|
||||
category: 'test',
|
||||
initialValue: 'not_set',
|
||||
});
|
||||
expect(first()).toEqual('not_set');
|
||||
expect(second()).toEqual('not_set');
|
||||
|
||||
first.set('1st');
|
||||
second.set('2nd');
|
||||
|
||||
await TestBed.inject(ApplicationRef).whenStable();
|
||||
TestBed.tick();
|
||||
|
||||
expect(getStoredSettings()['first@test']).toEqual('1st');
|
||||
expect(getStoredSettings()['second@test']).toEqual('2nd');
|
||||
});
|
||||
|
||||
it('should keep in sync multiple instances of the same settings item', async () => {
|
||||
const foo = settingsStore.create({
|
||||
key: 'item',
|
||||
category: 'test',
|
||||
initialValue: 'foo',
|
||||
});
|
||||
const bar = settingsStore.create({
|
||||
key: 'item',
|
||||
category: 'test',
|
||||
initialValue: 'bar',
|
||||
});
|
||||
expect(foo()).toEqual('foo');
|
||||
expect(bar()).toEqual('foo');
|
||||
|
||||
bar.set('baz');
|
||||
expect(foo()).toEqual('baz');
|
||||
expect(bar()).toEqual('baz');
|
||||
|
||||
await TestBed.inject(ApplicationRef).whenStable();
|
||||
TestBed.tick();
|
||||
|
||||
expect(getStoredSettings()['item@test']).toEqual('baz');
|
||||
});
|
||||
|
||||
it('should keep the latest signal value', async () => {
|
||||
const value = settingsStore.create({
|
||||
key: 'item',
|
||||
category: 'test',
|
||||
initialValue: 'foo',
|
||||
});
|
||||
expect(value()).toEqual('foo');
|
||||
|
||||
value.set('bar');
|
||||
value.set('baz');
|
||||
expect(value()).toBe('baz');
|
||||
|
||||
await TestBed.inject(ApplicationRef).whenStable();
|
||||
TestBed.tick();
|
||||
|
||||
expect(getStoredSettings()['item@test']).toEqual('baz');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
load("//devtools/tools:defaults.bzl", "ts_project")
|
||||
|
||||
package(default_visibility = ["//devtools:__subpackages__"])
|
||||
|
||||
ts_project(
|
||||
name = "settings_store_mock",
|
||||
srcs = [
|
||||
"settings_store_mock.ts",
|
||||
],
|
||||
deps = [
|
||||
"//:node_modules/@angular/core",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-services:settings_store",
|
||||
],
|
||||
)
|
||||
|
||||
ts_project(
|
||||
name = "app_operations_mock",
|
||||
srcs = [
|
||||
"app_operations_mock.ts",
|
||||
],
|
||||
deps = [
|
||||
"//devtools/projects/ng-devtools/src/lib/application-environment",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-operations",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-services:settings_store",
|
||||
"//devtools/projects/protocol",
|
||||
],
|
||||
)
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {DirectivePosition, ElementPosition, SignalNodePosition} from '../../../../../protocol';
|
||||
import {Frame} from '../../application-environment';
|
||||
import {ApplicationOperations} from '../../application-operations';
|
||||
import {SETTINGS_STORE_KEY} from '../settings_store';
|
||||
|
||||
export class AppOperationsMock extends ApplicationOperations {
|
||||
private storage: {[key: string]: unknown} = {};
|
||||
|
||||
/** Helper method – gives access to stored settings */
|
||||
getStoredSettings() {
|
||||
return this.storage[SETTINGS_STORE_KEY] as {[key: string]: unknown};
|
||||
}
|
||||
|
||||
override async setStorageItems(items: {[key: string]: unknown}): Promise<void> {
|
||||
this.storage = {
|
||||
...this.storage,
|
||||
...items,
|
||||
};
|
||||
}
|
||||
|
||||
override async getStorageItems(items: string[]): Promise<{[key: string]: unknown}> {
|
||||
const obj: {[key: string]: unknown} = {};
|
||||
for (const item of items) {
|
||||
obj[item] = this.storage[item];
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
override removeStorageItems(items: string[]): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
override viewSource(position: ElementPosition, target: Frame, directiveIndex?: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
override selectDomElement(position: ElementPosition, target: Frame): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
override inspect(
|
||||
directivePosition: DirectivePosition,
|
||||
objectPath: string[],
|
||||
target: Frame,
|
||||
): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
override inspectSignal(position: SignalNodePosition, target: Frame): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
override viewSourceFromRouter(name: string, type: string, target: Frame): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Provider, signal} from '@angular/core';
|
||||
import {SettingsStore} from '../settings_store';
|
||||
|
||||
export class SettingsStoreMock {
|
||||
private readonly signals = new Map<string, unknown>();
|
||||
|
||||
get(key: string, initialValue: unknown) {
|
||||
const value = this.signals.get(key) ?? signal(initialValue);
|
||||
this.signals.set(key, value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
export const SETTINGS_STORE_MOCK: Provider = {
|
||||
provide: SettingsStore,
|
||||
useClass: SettingsStoreMock,
|
||||
};
|
||||
@@ -25,7 +25,6 @@ ng_project(
|
||||
"//:node_modules/@angular/core",
|
||||
"//:node_modules/@angular/material",
|
||||
"//:node_modules/@angular/platform-browser",
|
||||
"//:node_modules/@types/chrome",
|
||||
"//:node_modules/rxjs",
|
||||
"//devtools/projects/ng-devtools",
|
||||
"//devtools/projects/ng-devtools-backend",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
/// <reference types="chrome"/>
|
||||
/// <reference types="firefox-webext-browser" />
|
||||
|
||||
import {Platform} from '@angular/cdk/platform';
|
||||
import {inject} from '@angular/core';
|
||||
@@ -16,21 +17,6 @@ import {DirectivePosition, ElementPosition, SignalNodePosition} from '../../../p
|
||||
export class ChromeApplicationOperations extends ApplicationOperations {
|
||||
platform = inject(Platform);
|
||||
|
||||
private runInInspectedWindow(script: string, target: Frame) {
|
||||
if (this.platform.FIREFOX && target.id !== TOP_LEVEL_FRAME_ID) {
|
||||
console.error(
|
||||
'[Angular DevTools]: This browser does not support targeting a specific frame for eval by URL.',
|
||||
);
|
||||
return;
|
||||
} else if (this.platform.FIREFOX) {
|
||||
chrome.devtools.inspectedWindow.eval(script);
|
||||
return;
|
||||
}
|
||||
|
||||
const frameURL = target.url;
|
||||
chrome.devtools.inspectedWindow.eval(script, {frameURL: frameURL?.toString?.()});
|
||||
}
|
||||
|
||||
override viewSource(position: ElementPosition, target: Frame, directiveIndex?: number): void {
|
||||
const viewSource = `inspect(inspectedApplication.findConstructorByPosition('${position}', ${directiveIndex}))`;
|
||||
this.runInInspectedWindow(viewSource, target);
|
||||
@@ -67,4 +53,38 @@ export class ChromeApplicationOperations extends ApplicationOperations {
|
||||
const viewSource = `inspect(inspectedApplication.findConstructorByNameForRouter('${name}', '${type}'))`;
|
||||
this.runInInspectedWindow(viewSource, target);
|
||||
}
|
||||
|
||||
override setStorageItems(items: {[key: string]: unknown}): Promise<void> {
|
||||
return this.storage.set(items);
|
||||
}
|
||||
|
||||
override getStorageItems(items: string[]): Promise<{[key: string]: unknown}> {
|
||||
return this.storage.get(items);
|
||||
}
|
||||
|
||||
override removeStorageItems(items: string[]): Promise<void> {
|
||||
return this.storage.remove(items);
|
||||
}
|
||||
|
||||
private runInInspectedWindow(script: string, target: Frame) {
|
||||
if (this.platform.FIREFOX && target.id !== TOP_LEVEL_FRAME_ID) {
|
||||
console.error(
|
||||
'[Angular DevTools]: This browser does not support targeting a specific frame for eval by URL.',
|
||||
);
|
||||
return;
|
||||
} else if (this.platform.FIREFOX) {
|
||||
chrome.devtools.inspectedWindow.eval(script);
|
||||
return;
|
||||
}
|
||||
|
||||
const frameURL = target.url;
|
||||
chrome.devtools.inspectedWindow.eval(script, {frameURL: frameURL?.toString?.()});
|
||||
}
|
||||
|
||||
private get storage(): typeof browser.storage.local {
|
||||
if (!this.platform.FIREFOX) {
|
||||
return chrome.storage.local;
|
||||
}
|
||||
return browser.storage.local;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
},
|
||||
"permissions": [
|
||||
"scripting",
|
||||
"activeTab"
|
||||
"activeTab",
|
||||
"storage"
|
||||
],
|
||||
"host_permissions": [
|
||||
"<all_urls>"
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
},
|
||||
"permissions": [
|
||||
"activeTab",
|
||||
"storage",
|
||||
"http://*/*",
|
||||
"https://*/*",
|
||||
"file:///*"
|
||||
|
||||
@@ -70,8 +70,12 @@ ng_project(
|
||||
|
||||
ng_project(
|
||||
name = "demo_application_operations",
|
||||
srcs = ["demo-application-operations.ts"],
|
||||
srcs = [
|
||||
"demo-application-operations.ts",
|
||||
"local-storage.ts",
|
||||
],
|
||||
deps = [
|
||||
"//:node_modules/@angular/core",
|
||||
"//devtools/projects/ng-devtools",
|
||||
"//devtools/projects/protocol",
|
||||
],
|
||||
|
||||
@@ -6,30 +6,87 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {inject} from '@angular/core';
|
||||
import {ApplicationOperations} from '../projects/ng-devtools';
|
||||
import {DirectivePosition, ElementPosition, SignalNodePosition} from '../projects/protocol';
|
||||
import {LOCAL_STORAGE} from './local-storage';
|
||||
|
||||
const STORAGE_KEY = 'ng-dt-storage-sim';
|
||||
|
||||
export class DemoApplicationOperations extends ApplicationOperations {
|
||||
private readonly localStorage = inject(LOCAL_STORAGE);
|
||||
|
||||
override viewSource(position: ElementPosition): void {
|
||||
console.warn('viewSource() is not implemented because the demo app runs in an Iframe');
|
||||
throw new Error('Not implemented in demo app.');
|
||||
}
|
||||
|
||||
override selectDomElement(position: ElementPosition): void {
|
||||
console.warn('selectDomElement() is not implemented because the demo app runs in an Iframe');
|
||||
throw new Error('Not implemented in demo app.');
|
||||
}
|
||||
|
||||
override inspect(directivePosition: DirectivePosition, keyPath: string[]): void {
|
||||
console.warn('inspect() is not implemented because the demo app runs in an Iframe');
|
||||
return;
|
||||
}
|
||||
|
||||
override inspectSignal(position: SignalNodePosition): void {
|
||||
console.warn('inspectSignal() is not implemented because the demo app runs in an Iframe');
|
||||
return;
|
||||
}
|
||||
|
||||
override viewSourceFromRouter(name: string, type: string): void {
|
||||
console.warn(
|
||||
'viewSourceFromRouter() is not implemented because the demo app runs in an Iframe',
|
||||
);
|
||||
throw new Error('Not implemented in demo app.');
|
||||
}
|
||||
|
||||
override async setStorageItems(items: {[key: string]: unknown}): Promise<void> {
|
||||
const currItems = this.getLsItems();
|
||||
this.setLsItems({...currItems, ...items});
|
||||
}
|
||||
|
||||
override async getStorageItems(items: string[]): Promise<{[key: string]: unknown}> {
|
||||
const currItems = this.getLsItems();
|
||||
const redundant = Object.keys(currItems).filter((prop) => !items.includes(prop));
|
||||
|
||||
for (const item of redundant) {
|
||||
delete currItems[item];
|
||||
}
|
||||
|
||||
return currItems;
|
||||
}
|
||||
|
||||
override async removeStorageItems(items: string[]): Promise<void> {
|
||||
const currItems = this.getLsItems();
|
||||
|
||||
for (const item of items) {
|
||||
delete currItems[item];
|
||||
}
|
||||
this.setLsItems(currItems);
|
||||
}
|
||||
|
||||
private getLsItems(): {[key: string]: unknown} {
|
||||
const storage = this.localStorage.getItem(STORAGE_KEY);
|
||||
|
||||
try {
|
||||
return JSON.parse(storage ?? '{}');
|
||||
} catch {
|
||||
this.localStorage.removeItem(STORAGE_KEY);
|
||||
console.error(
|
||||
'Unable to parse the data from the simulated storage. Cleaning the item and returning a default object',
|
||||
);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
private setLsItems(items: object) {
|
||||
try {
|
||||
this.localStorage.setItem(STORAGE_KEY, JSON.stringify(items));
|
||||
} catch {
|
||||
console.error('Unable to set item in the simulated storage.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {InjectionToken} from '@angular/core';
|
||||
|
||||
export const LOCAL_STORAGE = new InjectionToken<typeof localStorage>('LOCAL_STORAGE', {
|
||||
providedIn: 'root',
|
||||
factory: () => localStorage,
|
||||
});
|
||||
@@ -88,6 +88,7 @@
|
||||
"@types/chrome": "^0.1.0",
|
||||
"@types/convert-source-map": "^2.0.0",
|
||||
"@types/dom-navigation": "^1.0.5",
|
||||
"@types/firefox-webext-browser": "^120.0.4",
|
||||
"@types/hammerjs": "2.0.46",
|
||||
"@types/jasmine": "^5.0.0",
|
||||
"@types/jasminewd2": "^2.0.8",
|
||||
|
||||
Generated
+8
@@ -141,6 +141,9 @@ importers:
|
||||
'@types/dom-navigation':
|
||||
specifier: ^1.0.5
|
||||
version: 1.0.6
|
||||
'@types/firefox-webext-browser':
|
||||
specifier: ^120.0.4
|
||||
version: 120.0.4
|
||||
'@types/hammerjs':
|
||||
specifier: 2.0.46
|
||||
version: 2.0.46
|
||||
@@ -3663,6 +3666,9 @@ packages:
|
||||
'@types/filewriter@0.0.33':
|
||||
resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==}
|
||||
|
||||
'@types/firefox-webext-browser@120.0.4':
|
||||
resolution: {integrity: sha512-lBrpf08xhiZBigrtdQfUaqX1UauwZ+skbFiL8u2Tdra/rklkKadYmIzTwkNZSWtuZ7OKpFqbE2HHfDoFqvZf6w==}
|
||||
|
||||
'@types/geojson@7946.0.16':
|
||||
resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
|
||||
|
||||
@@ -14116,6 +14122,8 @@ snapshots:
|
||||
|
||||
'@types/filewriter@0.0.33': {}
|
||||
|
||||
'@types/firefox-webext-browser@120.0.4': {}
|
||||
|
||||
'@types/geojson@7946.0.16': {}
|
||||
|
||||
'@types/hammerjs@2.0.46': {}
|
||||
|
||||
Reference in New Issue
Block a user