refactor: refacotr preference

This commit is contained in:
vben
2024-06-01 23:15:29 +08:00
parent f7b97e8a83
commit fed47f5e05
139 changed files with 2205 additions and 1450 deletions

View File

@@ -1 +1 @@
export * from './storage-cache';
export * from './storage-manager';

View File

@@ -1,104 +0,0 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { StorageCache } from './storage-cache';
describe('storageCache', () => {
let localStorageCache: StorageCache;
let sessionStorageCache: StorageCache;
beforeEach(() => {
localStorageCache = new StorageCache('prefix_', 'localStorage');
sessionStorageCache = new StorageCache('prefix_', 'sessionStorage');
localStorage.clear();
sessionStorage.clear();
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('should set and get an item with prefix in localStorage', () => {
localStorageCache.setItem('testKey', 'testValue');
const value = localStorageCache.getItem<string>('testKey');
expect(value).toBe('testValue');
expect(localStorage.getItem('prefix_testKey')).not.toBeNull();
});
it('should set and get an item with prefix in sessionStorage', () => {
sessionStorageCache.setItem('testKey', 'testValue');
const value = sessionStorageCache.getItem<string>('testKey');
expect(value).toBe('testValue');
expect(sessionStorage.getItem('prefix_testKey')).not.toBeNull();
});
it('should return null for expired item in localStorage', () => {
localStorageCache.setItem('testKey', 'testValue', 1 / 60); // 1 second expiry
vi.advanceTimersByTime(2000); // Fast-forward 2 seconds
const value = localStorageCache.getItem<string>('testKey');
expect(value).toBeNull();
});
it('should return null for expired item in sessionStorage', () => {
sessionStorageCache.setItem('testKey', 'testValue', 1 / 60); // 1 second expiry
vi.advanceTimersByTime(2000); // Fast-forward 2 seconds
const value = sessionStorageCache.getItem<string>('testKey');
expect(value).toBeNull();
});
it('should remove an item with prefix in localStorage', () => {
localStorageCache.setItem('testKey', 'testValue');
localStorageCache.removeItem('testKey');
const value = localStorageCache.getItem<string>('testKey');
expect(value).toBeNull();
expect(localStorage.getItem('prefix_testKey')).toBeNull();
});
it('should remove an item with prefix in sessionStorage', () => {
sessionStorageCache.setItem('testKey', 'testValue');
sessionStorageCache.removeItem('testKey');
const value = sessionStorageCache.getItem<string>('testKey');
expect(value).toBeNull();
expect(sessionStorage.getItem('prefix_testKey')).toBeNull();
});
it('should clear all items in localStorage', () => {
localStorageCache.setItem('testKey1', 'testValue1');
localStorageCache.setItem('testKey2', 'testValue2');
localStorageCache.clear();
expect(localStorageCache.length()).toBe(0);
});
it('should clear all items in sessionStorage', () => {
sessionStorageCache.setItem('testKey1', 'testValue1');
sessionStorageCache.setItem('testKey2', 'testValue2');
sessionStorageCache.clear();
expect(sessionStorageCache.length()).toBe(0);
});
it('should return correct length in localStorage', () => {
localStorageCache.setItem('testKey1', 'testValue1');
localStorageCache.setItem('testKey2', 'testValue2');
expect(localStorageCache.length()).toBe(2);
});
it('should return correct length in sessionStorage', () => {
sessionStorageCache.setItem('testKey1', 'testValue1');
sessionStorageCache.setItem('testKey2', 'testValue2');
expect(sessionStorageCache.length()).toBe(2);
});
it('should return correct key by index in localStorage', () => {
localStorageCache.setItem('testKey1', 'testValue1');
localStorageCache.setItem('testKey2', 'testValue2');
expect(localStorageCache.key(0)).toBe('prefix_testKey1');
expect(localStorageCache.key(1)).toBe('prefix_testKey2');
});
it('should return correct key by index in sessionStorage', () => {
sessionStorageCache.setItem('testKey1', 'testValue1');
sessionStorageCache.setItem('testKey2', 'testValue2');
expect(sessionStorageCache.key(0)).toBe('prefix_testKey1');
expect(sessionStorageCache.key(1)).toBe('prefix_testKey2');
});
});

View File

@@ -1,145 +0,0 @@
import type { IStorageCache, StorageType, StorageValue } from './types';
class StorageCache implements IStorageCache {
protected prefix: string;
protected storage: Storage;
constructor(prefix: string = '', storageType: StorageType = 'localStorage') {
this.prefix = prefix;
this.storage =
storageType === 'localStorage' ? localStorage : sessionStorage;
}
// 获取带前缀的键名
private getFullKey(key: string): string {
return this.prefix + key;
}
// 获取项之后的钩子方法
protected afterGetItem<T>(_key: string, _value: T | null): void {}
// 设置项之后的钩子方法
protected afterSetItem<T>(
_key: string,
_value: T,
_expiryInMinutes?: number,
): void {}
// 获取项之前的钩子方法
protected beforeGetItem(_key: string): void {}
// 设置项之前的钩子方法
protected beforeSetItem<T>(
_key: string,
_value: T,
_expiryInMinutes?: number,
): void {}
/**
* 清空存储
*/
clear(): void {
try {
this.storage.clear();
} catch (error) {
console.error('Error clearing storage', error);
}
}
/**
* 获取存储项
* @param key 存储键
* @returns 存储值或 null
*/
getItem<T>(key: string): T | null {
const fullKey = this.getFullKey(key);
this.beforeGetItem(fullKey);
let value: T | null = null;
try {
const item = this.storage.getItem(fullKey);
if (item) {
const storageValue: StorageValue<T> = JSON.parse(item);
if (storageValue.expiry && storageValue.expiry < Date.now()) {
this.storage.removeItem(fullKey);
} else {
value = storageValue.data;
}
}
} catch (error) {
console.error('Error getting item from storage', error);
}
this.afterGetItem(fullKey, value);
return value;
}
/**
* 获取存储中的键
* @param index 键的索引
* @returns 存储键或 null
*/
key(index: number): null | string {
try {
return this.storage.key(index);
} catch (error) {
console.error('Error getting key from storage', error);
return null;
}
}
/**
* 获取存储项的数量
* @returns 存储项的数量
*/
length(): number {
try {
return this.storage.length;
} catch (error) {
console.error('Error getting storage length', error);
return 0;
}
}
/**
* 删除存储项
* @param key 存储键
*/
removeItem(key: string): void {
const fullKey = this.getFullKey(key);
try {
this.storage.removeItem(fullKey);
} catch (error) {
console.error('Error removing item from storage', error);
}
}
/**
* 设置存储项
* @param key 存储键
* @param value 存储值
* @param expiryInMinutes 过期时间(分钟)
*/
setItem<T>(key: string, value: T, expiryInMinutes?: number): void {
const fullKey = this.getFullKey(key);
this.beforeSetItem(fullKey, value, expiryInMinutes);
const now = Date.now();
const expiry = expiryInMinutes ? now + expiryInMinutes * 60_000 : null;
const storageValue: StorageValue<T> = {
data: value,
expiry,
};
try {
this.storage.setItem(fullKey, JSON.stringify(storageValue));
} catch (error) {
console.error('Error setting item in storage', error);
}
this.afterSetItem(fullKey, value, expiryInMinutes);
}
}
export { StorageCache };

View File

@@ -0,0 +1,130 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { StorageManager } from './storage-manager';
describe('storageManager', () => {
let storageManager: StorageManager<{ age: number; name: string }>;
beforeEach(() => {
vi.useFakeTimers();
localStorage.clear();
storageManager = new StorageManager<{ age: number; name: string }>({
prefix: 'test_',
});
});
it('should set and get an item', () => {
storageManager.setItem('user', { age: 30, name: 'John Doe' });
const user = storageManager.getItem('user');
expect(user).toEqual({ age: 30, name: 'John Doe' });
});
it('should return default value if item does not exist', () => {
const user = storageManager.getItem('nonexistent', {
age: 0,
name: 'Default User',
});
expect(user).toEqual({ age: 0, name: 'Default User' });
});
it('should remove an item', () => {
storageManager.setItem('user', { age: 30, name: 'John Doe' });
storageManager.removeItem('user');
const user = storageManager.getItem('user');
expect(user).toBeNull();
});
it('should clear all items with the prefix', () => {
storageManager.setItem('user1', { age: 30, name: 'John Doe' });
storageManager.setItem('user2', { age: 25, name: 'Jane Doe' });
storageManager.clear();
expect(storageManager.getItem('user1')).toBeNull();
expect(storageManager.getItem('user2')).toBeNull();
});
it('should clear expired items', () => {
storageManager.setItem('user', { age: 30, name: 'John Doe' }, 1000); // 1秒过期
vi.advanceTimersByTime(1001); // 快进时间
storageManager.clearExpiredItems();
const user = storageManager.getItem('user');
expect(user).toBeNull();
});
it('should not clear non-expired items', () => {
storageManager.setItem('user', { age: 30, name: 'John Doe' }, 10_000); // 10秒过期
vi.advanceTimersByTime(5000); // 快进时间
storageManager.clearExpiredItems();
const user = storageManager.getItem('user');
expect(user).toEqual({ age: 30, name: 'John Doe' });
});
it('should handle JSON parse errors gracefully', () => {
localStorage.setItem('test_user', '{ invalid JSON }');
const user = storageManager.getItem('user', {
age: 0,
name: 'Default User',
});
expect(user).toEqual({ age: 0, name: 'Default User' });
});
it('should return null for non-existent items without default value', () => {
const user = storageManager.getItem('nonexistent');
expect(user).toBeNull();
});
it('should overwrite existing items', () => {
storageManager.setItem('user', { age: 30, name: 'John Doe' });
storageManager.setItem('user', { age: 25, name: 'Jane Doe' });
const user = storageManager.getItem('user');
expect(user).toEqual({ age: 25, name: 'Jane Doe' });
});
it('should handle items without expiry correctly', () => {
storageManager.setItem('user', { age: 30, name: 'John Doe' });
vi.advanceTimersByTime(5000);
const user = storageManager.getItem('user');
expect(user).toEqual({ age: 30, name: 'John Doe' });
});
it('should remove expired items when accessed', () => {
storageManager.setItem('user', { age: 30, name: 'John Doe' }, 1000); // 1秒过期
vi.advanceTimersByTime(1001); // 快进时间
const user = storageManager.getItem('user');
expect(user).toBeNull();
});
it('should not remove non-expired items when accessed', () => {
storageManager.setItem('user', { age: 30, name: 'John Doe' }, 10_000); // 10秒过期
vi.advanceTimersByTime(5000); // 快进时间
const user = storageManager.getItem('user');
expect(user).toEqual({ age: 30, name: 'John Doe' });
});
it('should handle multiple items with different expiry times', () => {
storageManager.setItem('user1', { age: 30, name: 'John Doe' }, 1000); // 1秒过期
storageManager.setItem('user2', { age: 25, name: 'Jane Doe' }, 2000); // 2秒过期
vi.advanceTimersByTime(1500); // 快进时间
storageManager.clearExpiredItems();
const user1 = storageManager.getItem('user1');
const user2 = storageManager.getItem('user2');
expect(user1).toBeNull();
expect(user2).toEqual({ age: 25, name: 'Jane Doe' });
});
it('should handle items with no expiry', () => {
storageManager.setItem('user', { age: 30, name: 'John Doe' });
vi.advanceTimersByTime(10_000); // 快进时间
storageManager.clearExpiredItems();
const user = storageManager.getItem('user');
expect(user).toEqual({ age: 30, name: 'John Doe' });
});
it('should clear all items correctly', () => {
storageManager.setItem('user1', { age: 30, name: 'John Doe' });
storageManager.setItem('user2', { age: 25, name: 'Jane Doe' });
storageManager.clear();
const user1 = storageManager.getItem('user1');
const user2 = storageManager.getItem('user2');
expect(user1).toBeNull();
expect(user2).toBeNull();
});
});

View File

@@ -0,0 +1,118 @@
type StorageType = 'localStorage' | 'sessionStorage';
interface StorageManagerOptions {
prefix?: string;
storageType?: StorageType;
}
interface StorageItem<T> {
expiry?: number;
value: T;
}
class StorageManager<T> {
private prefix: string;
private storage: Storage;
constructor({
prefix = '',
storageType = 'localStorage',
}: StorageManagerOptions = {}) {
this.prefix = prefix;
this.storage =
storageType === 'localStorage'
? window.localStorage
: window.sessionStorage;
}
/**
* 获取完整的存储键
* @param key 原始键
* @returns 带前缀的完整键
*/
private getFullKey(key: string): string {
return `${this.prefix}-${key}`;
}
/**
* 清除所有带前缀的存储项
*/
clear(): void {
const keysToRemove: string[] = [];
for (let i = 0; i < this.storage.length; i++) {
const key = this.storage.key(i);
if (key && key.startsWith(this.prefix)) {
keysToRemove.push(key);
}
}
keysToRemove.forEach((key) => this.storage.removeItem(key));
}
/**
* 清除所有过期的存储项
*/
clearExpiredItems(): void {
for (let i = 0; i < this.storage.length; i++) {
const key = this.storage.key(i);
if (key && key.startsWith(this.prefix)) {
const shortKey = key.replace(this.prefix, '');
this.getItem(shortKey); // 调用 getItem 方法检查并移除过期项
}
}
}
/**
* 获取存储项
* @param key 键
* @param defaultValue 当项不存在或已过期时返回的默认值
* @returns 值,如果项已过期或解析错误则返回默认值
*/
getItem(key: string, defaultValue: T | null = null): T | null {
const fullKey = this.getFullKey(key);
const itemStr = this.storage.getItem(fullKey);
if (!itemStr) {
return defaultValue;
}
try {
const item: StorageItem<T> = JSON.parse(itemStr);
if (item.expiry && Date.now() > item.expiry) {
this.storage.removeItem(fullKey);
return defaultValue;
}
return item.value;
} catch (error) {
console.error(`Error parsing item with key "${fullKey}":`, error);
this.storage.removeItem(fullKey); // 如果解析失败,删除该项
return defaultValue;
}
}
/**
* 移除存储项
* @param key 键
*/
removeItem(key: string): void {
const fullKey = this.getFullKey(key);
this.storage.removeItem(fullKey);
}
/**
* 设置存储项
* @param key 键
* @param value 值
* @param ttl 存活时间(毫秒)
*/
setItem(key: string, value: T, ttl?: number): void {
const fullKey = this.getFullKey(key);
const expiry = ttl ? Date.now() + ttl : undefined;
const item: StorageItem<T> = { expiry, value };
try {
this.storage.setItem(fullKey, JSON.stringify(item));
} catch (error) {
console.error(`Error setting item with key "${fullKey}":`, error);
}
}
}
export { StorageManager };

View File

@@ -18,7 +18,7 @@
}
.outline-box {
@apply outline-border relative cursor-pointer rounded-md p-1 outline outline-1;
@apply outline-border relative cursor-pointer rounded-md p-1 outline outline-1;
&::after {
@apply absolute left-1/2 top-1/2 z-20 h-0 w-[1px] rounded-sm opacity-0 outline outline-2 outline-transparent transition-all duration-300 content-[''];

View File

@@ -3,6 +3,7 @@ export * from './date';
export * from './diff';
export * from './hash';
export * from './inference';
export * from './letter';
export * from './merge';
export * from './namespace';
export * from './nprogress';

View File

@@ -0,0 +1,55 @@
import { describe, expect, it } from 'vitest';
import { capitalizeFirstLetter, toLowerCaseFirstLetter } from './letter';
// 编写测试用例
describe('capitalizeFirstLetter', () => {
it('should capitalize the first letter of a string', () => {
expect(capitalizeFirstLetter('hello')).toBe('Hello');
expect(capitalizeFirstLetter('world')).toBe('World');
});
it('should handle empty strings', () => {
expect(capitalizeFirstLetter('')).toBe('');
});
it('should handle single character strings', () => {
expect(capitalizeFirstLetter('a')).toBe('A');
expect(capitalizeFirstLetter('b')).toBe('B');
});
it('should not change the case of other characters', () => {
expect(capitalizeFirstLetter('hElLo')).toBe('HElLo');
});
});
describe('toLowerCaseFirstLetter', () => {
it('should convert the first letter to lowercase', () => {
expect(toLowerCaseFirstLetter('CommonAppName')).toBe('commonAppName');
expect(toLowerCaseFirstLetter('AnotherKeyExample')).toBe(
'anotherKeyExample',
);
});
it('should return the same string if the first letter is already lowercase', () => {
expect(toLowerCaseFirstLetter('alreadyLowerCase')).toBe('alreadyLowerCase');
});
it('should handle empty strings', () => {
expect(toLowerCaseFirstLetter('')).toBe('');
});
it('should handle single character strings', () => {
expect(toLowerCaseFirstLetter('A')).toBe('a');
expect(toLowerCaseFirstLetter('a')).toBe('a');
});
it('should handle strings with only one uppercase letter', () => {
expect(toLowerCaseFirstLetter('A')).toBe('a');
});
it('should handle strings with special characters', () => {
expect(toLowerCaseFirstLetter('!Special')).toBe('!Special');
expect(toLowerCaseFirstLetter('123Number')).toBe('123Number');
});
});

View File

@@ -0,0 +1,20 @@
/**
* 将字符串的首字母大写
* @param string
*/
function capitalizeFirstLetter(string: string): string {
return string.charAt(0).toUpperCase() + string.slice(1);
}
/**
* 将字符串的首字母转换为小写。
*
* @param str 要转换的字符串
* @returns 首字母小写的字符串
*/
function toLowerCaseFirstLetter(str: string): string {
if (!str) return str; // 如果字符串为空,直接返回
return str.charAt(0).toLowerCase() + str.slice(1);
}
export { capitalizeFirstLetter, toLowerCaseFirstLetter };

View File

@@ -0,0 +1,22 @@
type LocaleSupportType = 'en-US' | 'zh-CN';
type LayoutType =
| 'full-content'
| 'header-nav'
| 'mixed-nav'
| 'side-mixed-nav'
| 'side-nav';
type ThemeModeType = 'auto' | 'dark' | 'light';
type ContentCompactType = 'compact' | 'wide';
type LayoutHeaderModeType = 'auto' | 'auto-scroll' | 'fixed' | 'static';
export type {
ContentCompactType,
LayoutHeaderModeType,
LayoutType,
LocaleSupportType,
ThemeModeType,
};

View File

@@ -0,0 +1,40 @@
// `Prev` 类型用于表示递归深度的递减。它是一个元组,其索引代表了递归的层数,通过索引访问可以得到减少后的层数。
// 例如Prev[3] 等于 2表示递归深度从 3 减少到 2。
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...0[]];
// `FlattenDepth` 类型用于将一个嵌套的对象类型“展平”,同时考虑到了递归的深度。
// 它接受三个泛型参数T要处理的类型Prefix属性名前缀默认为空字符串Depth递归深度默认为3
// 如果当前深度Depth为 0则停止递归并返回 `never`。否则,如果属性值是对象类型,则递归调用 `FlattenDepth` 并递减深度。
// 对于非对象类型的属性,将其直接映射到结果类型中,并根据前缀构造属性名。
type FlattenDepth<T, Prefix extends string = '', Depth extends number = 4> = {
[K in keyof T]: T[K] extends object
? Depth extends 0
? never
: FlattenDepth<
T[K],
`${Prefix}${K extends string ? (Prefix extends '' ? K : Capitalize<K>) : ''}`,
Prev[Depth]
>
: {
[P in `${Prefix}${K extends string ? (Prefix extends '' ? K : Capitalize<K>) : ''}`]: T[K];
};
}[keyof T] extends infer O
? { [P in keyof O]: O[P] }
: never;
// `UnionToIntersection` 类型用于将一个联合类型转换为交叉类型。
// 这个类型通过条件类型和类型推断的方式来实现。它先尝试将输入类型U映射为一个函数类型
// 然后通过推断这个函数类型的返回类型infer I最终得到一个交叉类型。
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I,
) => void
? I
: never;
type Flatten<T> = UnionToIntersection<FlattenDepth<T>>;
type FlattenObject<T> = FlattenDepth<T>;
type FlattenObjectKeys<T> = keyof FlattenObject<T>;
export type { Flatten, FlattenObject, FlattenObjectKeys, UnionToIntersection };

View File

@@ -1,5 +1,6 @@
export type * from './access';
export type * from './app';
export type * from './flatten';
export type * from './menu-record';
export type * from './preference';
export type * from './tabs';
export type * from './tools';

View File

@@ -1,144 +0,0 @@
type LayoutType =
| 'full-content'
| 'header-nav'
| 'mixed-nav'
| 'side-mixed-nav'
| 'side-nav';
type BreadcrumbStyle = 'background' | 'normal';
type NavigationStyle = 'plain' | 'rounded';
type ThemeType = 'auto' | 'dark' | 'light';
type ContentCompactType = 'compact' | 'wide';
type LayoutHeaderMode = 'auto' | 'auto-scroll' | 'fixed' | 'static';
type PageTransitionType = 'fade-slide';
type AuthPageLayout = 'panel-center' | 'panel-left' | 'panel-right';
type SupportLocale = 'en-US' | 'zh-CN';
interface Language {
key: SupportLocale;
text: string;
}
interface Preference {
/** 应用名 */
appName: string;
/** 登录注册页面布局 */
authPageLayout: AuthPageLayout;
/** 面包屑是否只有一个时隐藏 */
breadcrumbHideOnlyOne: boolean;
/** 面包屑首页图标是否可见 */
breadcrumbHome: boolean;
/** 面包屑图标是否可见 */
breadcrumbIcon: boolean;
/** 面包屑类型 */
breadcrumbStyle: BreadcrumbStyle;
/** 面包屑是否可见 */
breadcrumbVisible: boolean;
/** 是否开启灰色模式 */
colorGrayMode: boolean;
/** 主题色 */
colorPrimary: string;
/** 是否开启色弱模式 */
colorWeakMode: boolean;
/** 是否开启紧凑模式 */
compact: boolean;
/** 是否开启内容紧凑模式 */
contentCompact: ContentCompactType;
/** 页脚Copyright */
copyright: string;
/** 应用默认头像 */
defaultAvatar: string;
/** 开启动态标题 */
dynamicTitle: boolean;
/** 页脚是否固定 */
footerFixed: boolean;
/** 页脚是否可见 */
footerVisible: boolean;
/** 顶栏是否隐藏 */
headerHidden: boolean;
/** header显示模式 */
headerMode: LayoutHeaderMode;
/** 顶栏是否可见 */
headerVisible: boolean;
/** 是否移动端 */
isMobile: boolean;
/** 开启标签页缓存功能 */
keepAlive: boolean;
/** 布局方式 */
layout: LayoutType;
/** 支持的语言 */
locale: SupportLocale;
/** 应用Logo */
logo: string;
/** logo是否可见 */
logoVisible: boolean;
/** 导航菜单手风琴模式 */
navigationAccordion: boolean;
/** 导航菜单是否切割,只在 layout=mixed-nav 生效 */
navigationSplit: boolean;
/** 导航菜单风格 */
navigationStyle: NavigationStyle;
/** 是否开启页面加载进度条 */
pageProgress: boolean;
/** 页面切换动画 */
pageTransition: PageTransitionType;
/** 页面切换动画是否启用 */
pageTransitionEnable: boolean;
/** 是否开启半深色菜单只在theme='light'时生效) */
semiDarkMenu: boolean;
/** 是否启用快捷键 */
shortcutKeys: boolean;
/** 是否显示偏好设置 */
showPreference: boolean;
/** 侧边栏是否折叠 */
sideCollapse: boolean;
/** 侧边栏折叠时是否显示title */
sideCollapseShowTitle: boolean;
/** 菜单自动展开状态 */
sideExpandOnHover: boolean;
/** 侧边栏扩展区域是否折叠 */
sideExtraCollapse: boolean;
/** 侧边栏是否隐藏 */
sideHidden: boolean;
/** 侧边栏是否可见 */
sideVisible: boolean;
/** 侧边栏宽度 */
sideWidth: number;
/** 是否开启多标签页图标 */
tabsIcon: boolean;
/** 是否开启多标签页 */
tabsVisible: boolean;
/** 当前主题 */
theme: ThemeType;
}
// 这些属性是静态的,不会随着用户的操作而改变
interface StaticPreference {
/** 主题色预设 */
colorPrimaryPresets: string[];
/** 支持的语言 */
supportLanguages: Language[];
}
type PreferenceKeys = keyof Preference;
export type {
AuthPageLayout,
BreadcrumbStyle,
ContentCompactType,
LayoutHeaderMode,
LayoutType,
PageTransitionType,
Preference,
PreferenceKeys,
StaticPreference,
SupportLocale,
ThemeType,
};