feat(core-api/UserSettings): Ported across the UserSettings API from internal and convert to ts

This commit is contained in:
blam
2020-05-29 00:11:29 +02:00
parent 828e6675c8
commit 9437832616
3 changed files with 176 additions and 0 deletions
@@ -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];
}
}