feat(core-api/UserSettings): Ported across the UserSettings API from internal and convert to ts
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createApiRef } from '../ApiRef';
|
||||
|
||||
type setValue<T> = (value: T | null) => void;
|
||||
type removeValue = () => void;
|
||||
export type UserSettingsApi = {
|
||||
/**
|
||||
* Get persistent data.
|
||||
*
|
||||
* TODO: Replace with something less volatile than LocalStorage
|
||||
*
|
||||
* @param {String} storeName Name of the store.
|
||||
* @param {String?} key (Optional) Unique key associated with the data.
|
||||
* If not key is specified,the whole store is returned.
|
||||
* @param {String} key (Optional) Empty value to return if there is not data.
|
||||
* @return {Object} data The data that should is stored.
|
||||
*/
|
||||
getFromStore<T>(
|
||||
storeName: string,
|
||||
key: string,
|
||||
defaultValue: T | null,
|
||||
): T | null;
|
||||
/**
|
||||
* Remove persistent data.
|
||||
*
|
||||
* TODO: Replace with something less volatile than LocalStorage
|
||||
*
|
||||
* @param {String} storeName Name of the store.
|
||||
* @param {String} key Unique key associated with the data.
|
||||
*/
|
||||
removeFromStore(storeName: string, key: string): void;
|
||||
|
||||
/**
|
||||
* Save persistent data.
|
||||
*
|
||||
* TODO: Replace with something less volatile than LocalStorage
|
||||
*
|
||||
* @param {String} storeName Name of the store.
|
||||
* @param {String} key Unique key associated with the data.
|
||||
* @param {Object} data The data that should be stored.
|
||||
*/
|
||||
saveToStore(storeName: string, key: string, data: any): void;
|
||||
|
||||
/**
|
||||
* React hook that observes a single store value and provides functions for updating the value.
|
||||
*
|
||||
* @param {String} storeName Name of the store.
|
||||
* @param {String} key Unique key associated with the data.
|
||||
* @return {[value, setValue(value), removeValue()]} An array to be deconstructed,
|
||||
* The first element is the value, the second is a function to call to update the value,
|
||||
* and the third is a function to call to clear the value.
|
||||
*/
|
||||
useStoreValue<T>(
|
||||
storeName: string,
|
||||
key: string,
|
||||
): [T | null, setValue<T | null>, removeValue];
|
||||
};
|
||||
|
||||
export const userSettingsApiRef = createApiRef<UserSettingsApi>({
|
||||
id: 'core.user.settings',
|
||||
description:
|
||||
'Provides the ability to modify settings that are personalised to the user',
|
||||
});
|
||||
@@ -27,3 +27,4 @@ export * from './AppThemeApi';
|
||||
export * from './ErrorApi';
|
||||
export * from './FeatureFlagsApi';
|
||||
export * from './OAuthRequestApi';
|
||||
export * from './UserSettingsApi';
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { useState, useEffect } from 'react';
|
||||
import { UserSettingsApi } from '../../definitions';
|
||||
|
||||
export class UserSettings implements UserSettingsApi {
|
||||
getFromStore<T>(
|
||||
storeName: string,
|
||||
key: string = '',
|
||||
defaultValue: T | null,
|
||||
): T | null {
|
||||
let store;
|
||||
try {
|
||||
store = JSON.parse(localStorage.getItem(storeName)!) || {};
|
||||
} catch (e) {
|
||||
window.console.error(
|
||||
`Error when parsing JSON config from storage for: ${storeName}`,
|
||||
);
|
||||
return defaultValue as T;
|
||||
}
|
||||
|
||||
if (key) {
|
||||
return (store[key] ?? defaultValue) as T;
|
||||
}
|
||||
return store as T;
|
||||
}
|
||||
|
||||
saveToStore(storeName: string, key: string, data: any): void {
|
||||
const store = JSON.parse(localStorage.getItem(storeName)!) || {};
|
||||
store[key] = data;
|
||||
localStorage.setItem(storeName, JSON.stringify(store));
|
||||
}
|
||||
|
||||
removeFromStore(storeName: string, key: string): void {
|
||||
const store = JSON.parse(localStorage.getItem(storeName)!) || {};
|
||||
delete store[key];
|
||||
localStorage.setItem(storeName, JSON.stringify(store));
|
||||
}
|
||||
|
||||
useStoreValue<T>(
|
||||
storeName: string,
|
||||
key: string,
|
||||
): [T | null, (value: T | null) => void, () => void] {
|
||||
// We hardcode the emptyValue to null, since allowing objects or arrays would make
|
||||
// the useEffect params a lot more complex and it would easy to get stuck in a loop.
|
||||
const [value, setValue] = useState<T | null>(() =>
|
||||
this.getFromStore(storeName, key, null),
|
||||
);
|
||||
|
||||
// Listen to storage change events from other tabs
|
||||
useEffect(() => {
|
||||
const onChange = (event: StorageEvent) => {
|
||||
if (event.key === storeName) {
|
||||
// Avoid sending unnecessary updates when only the reference changes because of JSON.parse
|
||||
setValue((currentValue: T | null) => {
|
||||
const newValue = this.getFromStore(storeName, key, null);
|
||||
|
||||
// Fastest way to do a deep equals, since we know references will differ,
|
||||
// the values are serializable, and it's ok to signal a change if key order changes.
|
||||
if (JSON.stringify(currentValue) === JSON.stringify(newValue)) {
|
||||
return currentValue;
|
||||
}
|
||||
return newValue;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('storage', onChange);
|
||||
return () => window.removeEventListener('storage', onChange);
|
||||
}, [storeName, key]);
|
||||
|
||||
const saveValue = (newValue: T | null) => {
|
||||
this.saveToStore(storeName, key, newValue);
|
||||
setValue(this.getFromStore(storeName, key, null));
|
||||
};
|
||||
|
||||
const removeValue = () => {
|
||||
this.removeFromStore(storeName, key);
|
||||
setValue(null);
|
||||
};
|
||||
|
||||
return [value, saveValue, removeValue];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user