2026-10-17 12:55:39 +08:00
|
|
|
import type {
|
|
|
|
|
IslandLayoutSettings as ContractIslandLayoutSettings,
|
2026-10-25 16:41:29 +08:00
|
|
|
IslandTimePointDto as ContractIslandTimePointDto,
|
|
|
|
|
IslandWeather,
|
2026-10-17 12:55:39 +08:00
|
|
|
ScheduleDto as ContractScheduleDto,
|
|
|
|
|
} from '@classhelper/shared';
|
2026-09-12 08:41:08 +08:00
|
|
|
import { contextBridge, ipcRenderer } from 'electron';
|
2026-09-12 15:19:13 +08:00
|
|
|
import type { IslandAppearance, IslandState } from '@classhelper/shared';
|
2026-09-12 08:41:08 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 灵动岛窗口的预加载脚本。
|
2026-09-12 15:19:13 +08:00
|
|
|
* 只暴露「订阅状态/外观」与「发送用户操作」两件事,渲染进程不接触任何 Node 能力。
|
2026-09-12 08:41:08 +08:00
|
|
|
*/
|
|
|
|
|
export interface IslandBridge {
|
2026-10-25 16:41:29 +08:00
|
|
|
getIslandWeather(coordinates: { latitude: number; longitude: number }): Promise<IslandWeather>;
|
2026-10-17 12:55:39 +08:00
|
|
|
getLayout(): Promise<ContractIslandLayoutSettings>;
|
|
|
|
|
saveLayout(value: ContractIslandLayoutSettings): Promise<ContractIslandLayoutSettings>;
|
|
|
|
|
onLayout(handler: (value: ContractIslandLayoutSettings) => void): void;
|
|
|
|
|
getEditing(): Promise<boolean>;
|
2026-10-17 15:12:02 +08:00
|
|
|
getVisible(): Promise<boolean>;
|
|
|
|
|
onVisibility(handler: (visible: boolean) => void): void;
|
2026-10-17 12:55:39 +08:00
|
|
|
setEditing(value: boolean): Promise<void>;
|
|
|
|
|
onEditing(handler: (value: boolean) => void): void;
|
|
|
|
|
reportSize(value: { width: number; height: number }): void;
|
2026-10-17 14:29:25 +08:00
|
|
|
reportResidentControl(value: { x: number; y: number; width: number; height: number } | null): void;
|
|
|
|
|
noteResidentTouch(): void;
|
2026-10-17 15:56:44 +08:00
|
|
|
onViewport(handler: (value: { width: number; height: number }) => void): () => void;
|
|
|
|
|
getSchedules(): Promise<ContractScheduleDto[]>;
|
2026-10-17 12:55:39 +08:00
|
|
|
onSchedules(handler: (value: ContractScheduleDto[]) => void): void;
|
2026-10-25 16:41:29 +08:00
|
|
|
getTimePoints(): Promise<ContractIslandTimePointDto[]>;
|
|
|
|
|
onTimePoints(handler: (value: ContractIslandTimePointDto[]) => void): void;
|
2026-10-17 12:55:39 +08:00
|
|
|
|
2026-09-12 08:41:08 +08:00
|
|
|
onState(handler: (state: IslandState) => void): void;
|
2026-10-02 00:25:10 +08:00
|
|
|
sendAction(
|
|
|
|
|
action:
|
|
|
|
|
| 'expand'
|
|
|
|
|
| 'expand-list'
|
|
|
|
|
| 'collapse'
|
|
|
|
|
| 'dismiss'
|
|
|
|
|
| 'dismiss-all'
|
|
|
|
|
| 'mark-read'
|
|
|
|
|
| 'mark-all-read'
|
|
|
|
|
| 'open-app',
|
|
|
|
|
id?: string,
|
|
|
|
|
): void;
|
2026-09-12 08:41:08 +08:00
|
|
|
getState(): Promise<IslandState>;
|
2026-09-12 15:19:13 +08:00
|
|
|
/** 订阅个性化外观(设置页改动后实时生效) */
|
|
|
|
|
onAppearance(handler: (appearance: IslandAppearance) => void): void;
|
|
|
|
|
getAppearance(): Promise<IslandAppearance>;
|
2026-09-26 19:32:47 +08:00
|
|
|
/** 命中提示:指针在岛上 → 让窗口接收鼠标(只上报 true,关闭由主进程轮询决定) */
|
2026-09-12 18:46:20 +08:00
|
|
|
setInteractive(interactive: boolean): void;
|
2026-09-12 21:49:14 +08:00
|
|
|
/** 上报岛体矩形(窗口内 CSS px):主进程据此按光标位置兜底校正命中 */
|
|
|
|
|
setHitRect(rect: { x: number; y: number; width: number; height: number } | null): void;
|
2026-10-02 00:25:10 +08:00
|
|
|
/**
|
|
|
|
|
* 上报"本机是触摸屏":主进程据此把窗口改为贴合岛体并始终接收输入。
|
|
|
|
|
* 触摸屏上按光标判定命中的那套永远打不开(触摸不产生 mousemove、也不动系统光标),
|
|
|
|
|
* 不上报的话手指点不到岛(希沃白板等触摸设备)。
|
|
|
|
|
*/
|
|
|
|
|
setTouchMode(enabled: boolean): void;
|
2026-09-26 20:47:01 +08:00
|
|
|
/** 心跳:主进程据此发现"渲染进程卡死的幽灵窗口"(岛还在屏幕上但点不动)并重建窗口 */
|
|
|
|
|
alive(): void;
|
2026-09-12 08:41:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bridge: IslandBridge = {
|
2026-10-25 16:41:29 +08:00
|
|
|
getIslandWeather: (coordinates) => ipcRenderer.invoke('island:weather', coordinates),
|
2026-10-17 12:55:39 +08:00
|
|
|
getLayout: () => ipcRenderer.invoke('island:get-layout'),
|
|
|
|
|
saveLayout: (value) => ipcRenderer.invoke('island:save-layout', value),
|
|
|
|
|
onLayout: (handler) => {
|
|
|
|
|
ipcRenderer.on('island:layout', (_event, value) => handler(value));
|
|
|
|
|
},
|
|
|
|
|
getEditing: () => ipcRenderer.invoke('island:get-editing'),
|
2026-10-17 15:12:02 +08:00
|
|
|
getVisible: () => ipcRenderer.invoke('island:get-visible'),
|
|
|
|
|
onVisibility: (handler) => {
|
|
|
|
|
ipcRenderer.on('island:visibility', (_event, value) => handler(value));
|
|
|
|
|
},
|
2026-10-17 12:55:39 +08:00
|
|
|
setEditing: (value) => ipcRenderer.invoke('island:set-editing', value),
|
|
|
|
|
onEditing: (handler) => {
|
|
|
|
|
ipcRenderer.on('island:editing', (_event, value) => handler(value));
|
|
|
|
|
},
|
|
|
|
|
reportSize: (value) => ipcRenderer.send('island:content-size', value),
|
2026-10-17 14:29:25 +08:00
|
|
|
reportResidentControl: (value) => ipcRenderer.send('island:resident-control', value),
|
|
|
|
|
noteResidentTouch: () => ipcRenderer.send('island:resident-touch'),
|
2026-10-17 12:55:39 +08:00
|
|
|
onViewport: (handler) => {
|
2026-10-17 15:56:44 +08:00
|
|
|
const listener = (_event: Electron.IpcRendererEvent, value: { width: number; height: number }) =>
|
|
|
|
|
handler(value);
|
|
|
|
|
ipcRenderer.on('island:viewport', listener);
|
|
|
|
|
return () => ipcRenderer.removeListener('island:viewport', listener);
|
2026-10-17 12:55:39 +08:00
|
|
|
},
|
2026-10-17 15:56:44 +08:00
|
|
|
getSchedules: () => ipcRenderer.invoke('island:get-schedules'),
|
2026-10-17 12:55:39 +08:00
|
|
|
onSchedules: (handler) => {
|
|
|
|
|
ipcRenderer.on('island:schedules', (_event, value) => handler(value));
|
|
|
|
|
},
|
2026-10-25 16:41:29 +08:00
|
|
|
getTimePoints: () => ipcRenderer.invoke('island:get-time-points'),
|
|
|
|
|
onTimePoints: (handler) => {
|
|
|
|
|
ipcRenderer.on('island:time-points', (_event, value) => handler(value));
|
|
|
|
|
},
|
2026-10-17 12:55:39 +08:00
|
|
|
|
2026-09-12 08:41:08 +08:00
|
|
|
onState: (handler) => {
|
|
|
|
|
ipcRenderer.on('island:state', (_event, state: IslandState) => handler(state));
|
|
|
|
|
},
|
|
|
|
|
sendAction: (action, id) => {
|
|
|
|
|
ipcRenderer.send('island:action', { action, id });
|
|
|
|
|
},
|
|
|
|
|
getState: () => ipcRenderer.invoke('island:get-state'),
|
2026-09-12 15:19:13 +08:00
|
|
|
onAppearance: (handler) => {
|
|
|
|
|
ipcRenderer.on('island:appearance', (_event, appearance: IslandAppearance) => handler(appearance));
|
|
|
|
|
},
|
|
|
|
|
getAppearance: () => ipcRenderer.invoke('island:get-appearance'),
|
2026-09-12 18:46:20 +08:00
|
|
|
setInteractive: (interactive) => {
|
|
|
|
|
ipcRenderer.send('island:set-interactive', interactive);
|
|
|
|
|
},
|
2026-09-12 21:49:14 +08:00
|
|
|
setHitRect: (rect) => {
|
|
|
|
|
ipcRenderer.send('island:set-hit-rect', rect);
|
|
|
|
|
},
|
2026-10-02 00:25:10 +08:00
|
|
|
setTouchMode: (enabled) => {
|
|
|
|
|
ipcRenderer.send('island:set-touch-mode', enabled);
|
|
|
|
|
},
|
2026-09-26 20:47:01 +08:00
|
|
|
alive: () => {
|
|
|
|
|
ipcRenderer.send('island:alive');
|
|
|
|
|
},
|
2026-09-12 08:41:08 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
contextBridge.exposeInMainWorld('island', bridge);
|