Files
hot-api-center/src/apps/hotkeys/store.ts
abearxiong c8f643817c feat: Enhance hotkeys app with customizable settings and modal support
- Added a modal component for user input in settings.
- Implemented drag-and-drop functionality for customizing hotkeys.
- Integrated toast notifications for user feedback on actions.
- Updated store to manage customizable items and namespaces.
- Enhanced the refresh button to fetch items with a refresh option.
- Improved settings button to open settings in a new tab.
- Added caching mechanisms for hotkeys data.
- Created a settings page to manage hotkeys and namespaces.
- Updated query module to handle configuration retrieval.
2025-12-18 20:56:41 +08:00

237 lines
7.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { create } from 'zustand';
import { queryClient, config } from '../../modules/query'
import { NocoApi } from '@kevisual/noco'
import { toast } from 'react-toastify'
import { MyCache } from '@kevisual/cache'
const cache = new MyCache<CardItem[]>('hot_api')
type CacheCustom = Array<{
namespace: string;
items: CardItem[];
}>
const cacheCustom = new MyCache<CacheCustom>('hot_api_customize');
// --- Types ---
export interface CardItem {
id: string;
title: string;
iconUrl?: string;
description?: string;
data?: any;
sort?: number;
namespace?: string;
}
export interface StoreState {
items: CardItem[];
isLoading: boolean;
machineId?: string;
fetchItems: (refresh?: boolean) => Promise<void>;
sendEvent: (item: CardItem) => Promise<void>;
namespace?: string;
setNamespace?: (namespace: string) => void;
customizeItems?: CardItem[];
setCustomizeItems?: (items: CardItem[]) => void;
getCustomizeItems: (namespace?: string) => Promise<CardItem[]>;
initCustomizeItems?: () => Promise<void>;
getMachineId?: () => Promise<string>;
setCacheData?: (cacheCustom: CacheCustom) => void;
getCacheData?: () => Promise<CacheCustom | null>;
exportCacheData?: () => Promise<string | null>;
importCacheData?: (jsonData: string) => Promise<{
success: boolean;
importedNamespaces?: string[];
message: string;
}>;
nocoApi?: NocoApi;
}
// --- Store ---
export const useStore = create<StoreState>((set, get) => ({
items: [],
isLoading: false,
getMachineId: async () => {
let machineId = get().machineId;
if (machineId) {
return machineId;
}
const res = await queryClient.get({ path: 'config', key: 'getId' });
if (res.code === 200) {
machineId = res.data;
set({ machineId });
return machineId!;
}
return '';
},
fetchItems: async (refresh?: boolean) => {
set({ isLoading: true });
get().initCustomizeItems?.();
get().getMachineId?.();
const chacheData = await cache.getData().catch(() => null);
if (!refresh && chacheData && Array.isArray(chacheData) && chacheData.length > 0) {
set({ items: chacheData, isLoading: false });
return;
}
const life = await config.getConfig('life.json').catch(() => null);
if (!life) return;
const baseId = life?.baseId || '';
const nocoToken = life?.token || '';
const baseURL = life?.baseURL || '';
if (!baseId || !nocoToken || !baseURL) {
toast.error('未配置 nocodb 的 baseId 或 token请前往设置页面配置');
return
};
const nocoApi = new NocoApi({ baseURL, token: nocoToken });
const table = await nocoApi.getTableByName('控制中枢', baseId)
if (!table) {
toast.error('未找到 nocodb 中的 控制中枢 表,请检查配置');
return;
}
nocoApi.record.table = table.id
set({ nocoApi });
const res = await nocoApi.record.list({
page: 1,
limit: 10000,
where: "(类型,neq,文档)",
})
console.log('Fetched records:', res);
if (res.code === 200) {
let items: CardItem[] = res.data.list.map((record: any) => ({
id: record.Id,
title: record['标题'] || '未命名',
iconUrl: record['图标'] || `https://api.dicebear.com/9.x/bottts/svg?seed=${record.Id}`,
description: record['总结'] || '',
data: record['数据'] || {},
sort: 0,
}));
cache.setData(items, { expireTime: 30 * 24 * 60 * 60 * 1000 }); // Cache for 10 days
set({ items, isLoading: false });
} else {
toast.error('获取控制中枢数据失败,请检查配置');
set({ isLoading: false });
return;
}
},
sendEvent: async (item: CardItem) => {
console.log('Sending event for item:', item);
if (item.data?.type === 'hotkeys') {
const res = await queryClient.post({
path: 'key-sender',
keys: item?.data?.hotkeys
});
console.log('Event sent for item:', item, 'Response:', res);
if (res.code !== 200) {
toast.error('事件发送失败');
}
return;
}
toast.info('暂不支持该类型的控制中枢操作');
},
namespace: localStorage.getItem('hotapi-namespace') || 'default',
customizeItems: [],
setCustomizeItems: (items: CardItem[]) => {
set({ customizeItems: items });
// Save to cache asynchronously without blocking
const ns = useStore.getState().namespace || 'default';
(async () => {
const chacheData = await cacheCustom.getData().catch(() => null);
let allData: CacheCustom = chacheData || [];
// Update or add the namespace data
const existingIndex = allData.findIndex(c => c.namespace === ns);
if (existingIndex >= 0) {
allData[existingIndex].items = items;
} else {
allData.push({ namespace: ns, items });
}
await cacheCustom.setData(allData, { expireTime: 365 * 24 * 60 * 60 * 1000 }); // Cache for 1 year
})();
},
setNamespace: (namespace: string) => {
set({ namespace })
get().getCustomizeItems(namespace);
localStorage.setItem('hotapi-namespace', namespace);
},
getAllNamespaces: async () => {
const chacheData = await cacheCustom.getData().catch(() => null);
if (chacheData) {
return chacheData.map(c => c.namespace);
}
return ['default'];
},
getCustomizeItems: async (namespace?: string) => {
const ns = namespace || useStore.getState().namespace || 'default';
const chacheData = await cacheCustom.getData().catch(() => null);
if (chacheData) {
const nsData = chacheData.find(c => c.namespace === ns);
if (nsData) {
set({ customizeItems: nsData.items });
return nsData.items;
} else {
set({ customizeItems: [] });
return [];
}
}
return [];
},
initCustomizeItems: async () => {
const ns = useStore.getState().namespace || 'default';
await get().getCustomizeItems(ns);
},
getCacheData: async () => {
const chacheData = await cacheCustom.getData().catch(() => null);
return chacheData;
},
setCacheData: (cacheCustomData: CacheCustom) => {
cacheCustom.setData(cacheCustomData, { expireTime: 365 * 24 * 60 * 60 * 1000 }); // Cache for 1 year
},
exportCacheData: async () => {
const cacheData = await get().getCacheData?.();
if (cacheData) {
const exportData = {
version: '1.0.0',
exportTime: new Date().toISOString(),
data: cacheData
};
return JSON.stringify(exportData, null, 2);
}
return null;
},
importCacheData: async (jsonData: string) => {
try {
const importData = JSON.parse(jsonData);
// 验证导入的数据格式
if (!importData.data || !Array.isArray(importData.data)) {
throw new Error('导入数据格式不正确');
}
// 验证每个命名空间的数据格式
for (const nsData of importData.data) {
if (!nsData.namespace || !Array.isArray(nsData.items)) {
throw new Error('命名空间数据格式不正确');
}
}
// 导入数据
get().setCacheData?.(importData.data);
// 刷新当前命名空间的数据
const currentNs = get().namespace || 'default';
get().getCustomizeItems?.(currentNs);
return {
success: true,
importedNamespaces: importData.data.map((ns: any) => ns.namespace),
message: '数据导入成功'
};
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : '导入失败'
};
}
},
}));