desktop 앱 적용
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import { openWidgetWindow, closeWidgetWindow, focusWidgetWindow } from './widgetWindows';
|
||||
import { showNativeNotification } from './notifications';
|
||||
import { checkForUpdates, getAppVersion } from './updater';
|
||||
import type { DesktopBridge } from './types';
|
||||
|
||||
/** frontend App 로드 전 호출 — window.__goldenDesktopBridge 등록 */
|
||||
export function installDesktopBridge(): void {
|
||||
const bridge: DesktopBridge = {
|
||||
openWidgetWindow,
|
||||
closeWidgetWindow,
|
||||
focusWidgetWindow,
|
||||
showNativeNotification,
|
||||
checkForUpdates,
|
||||
getAppVersion,
|
||||
};
|
||||
window.__goldenDesktopBridge = bridge;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import {
|
||||
isPermissionGranted,
|
||||
requestPermission,
|
||||
sendNotification,
|
||||
} from '@tauri-apps/plugin-notification';
|
||||
|
||||
export async function showNativeNotification(title: string, body: string): Promise<void> {
|
||||
let granted = await isPermissionGranted();
|
||||
if (!granted) {
|
||||
const perm = await requestPermission();
|
||||
granted = perm === 'granted';
|
||||
}
|
||||
if (!granted) return;
|
||||
|
||||
await sendNotification({
|
||||
title,
|
||||
body,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 데스크톱(Tauri) 브릿지 타입 — frontend에서 window.__goldenDesktopBridge 로 호출
|
||||
*/
|
||||
import type { FloatingWidgetInstance } from '@frontend/types/floatingWidget';
|
||||
|
||||
export interface DesktopBridge {
|
||||
openWidgetWindow: (instance: FloatingWidgetInstance) => Promise<void>;
|
||||
closeWidgetWindow: (id: string) => Promise<void>;
|
||||
focusWidgetWindow: (id: string) => Promise<void>;
|
||||
showNativeNotification: (title: string, body: string) => Promise<void>;
|
||||
checkForUpdates: () => Promise<{ available: boolean; version?: string; message?: string }>;
|
||||
getAppVersion: () => Promise<string>;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__goldenDesktopBridge?: DesktopBridge;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { check } from '@tauri-apps/plugin-updater';
|
||||
import { relaunch } from '@tauri-apps/plugin-process';
|
||||
import { getVersion } from '@tauri-apps/api/app';
|
||||
|
||||
export async function getAppVersion(): Promise<string> {
|
||||
return getVersion();
|
||||
}
|
||||
|
||||
export async function checkForUpdates(): Promise<{
|
||||
available: boolean;
|
||||
version?: string;
|
||||
message?: string;
|
||||
}> {
|
||||
try {
|
||||
const update = await check();
|
||||
if (!update) {
|
||||
return { available: false, message: '최신 버전입니다.' };
|
||||
}
|
||||
await update.downloadAndInstall();
|
||||
await relaunch();
|
||||
return { available: true, version: update.version, message: '업데이트 설치 후 재시작합니다.' };
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : '업데이트 확인 실패';
|
||||
return { available: false, message: msg };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||
import type { FloatingWidgetInstance } from '@frontend/types/floatingWidget';
|
||||
|
||||
const widgetWindows = new Map<string, WebviewWindow>();
|
||||
|
||||
function widgetUrl(instance: FloatingWidgetInstance): string {
|
||||
const params = new URLSearchParams({
|
||||
id: instance.id,
|
||||
rows: String(instance.rows),
|
||||
cols: String(instance.cols),
|
||||
width: String(instance.width),
|
||||
height: String(instance.height),
|
||||
});
|
||||
if (instance.rowFr?.length) params.set('rowFr', instance.rowFr.join(','));
|
||||
if (instance.colFr?.length) params.set('colFr', instance.colFr.join(','));
|
||||
params.set('slots', JSON.stringify(instance.slots));
|
||||
return `widget.html?${params.toString()}`;
|
||||
}
|
||||
|
||||
export async function openWidgetWindow(instance: FloatingWidgetInstance): Promise<void> {
|
||||
const label = `widget-${instance.id}`;
|
||||
const existing = widgetWindows.get(instance.id) ?? (await WebviewWindow.getByLabel(label));
|
||||
if (existing) {
|
||||
await existing.setFocus();
|
||||
widgetWindows.set(instance.id, existing);
|
||||
return;
|
||||
}
|
||||
|
||||
const win = new WebviewWindow(label, {
|
||||
url: widgetUrl(instance),
|
||||
title: 'GoldenChart Widget',
|
||||
width: instance.width,
|
||||
height: instance.height,
|
||||
minWidth: 320,
|
||||
minHeight: 240,
|
||||
decorations: true,
|
||||
resizable: true,
|
||||
center: false,
|
||||
x: instance.position.x,
|
||||
y: instance.position.y,
|
||||
});
|
||||
|
||||
widgetWindows.set(instance.id, win);
|
||||
|
||||
void win.once('tauri://destroyed', () => {
|
||||
widgetWindows.delete(instance.id);
|
||||
});
|
||||
}
|
||||
|
||||
export async function closeWidgetWindow(id: string): Promise<void> {
|
||||
const label = `widget-${id}`;
|
||||
const win = widgetWindows.get(id) ?? (await WebviewWindow.getByLabel(label));
|
||||
if (win) {
|
||||
await win.close();
|
||||
widgetWindows.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
export async function focusWidgetWindow(id: string): Promise<void> {
|
||||
const label = `widget-${id}`;
|
||||
const win = widgetWindows.get(id) ?? (await WebviewWindow.getByLabel(label));
|
||||
if (win) await win.setFocus();
|
||||
}
|
||||
Reference in New Issue
Block a user