위젯 추가팝업 수정
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import './forceServerApi';
|
import './forceServerApi';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { getCurrentWindow } from '@tauri-apps/api/window';
|
import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||||
import { LogicalSize } from '@tauri-apps/api/dpi';
|
import { LogicalSize } from '@tauri-apps/api/dpi';
|
||||||
@@ -8,6 +8,7 @@ import { ensureDesktopApiBase } from './ensureDesktopApiBase';
|
|||||||
import FloatingWidgetWindow from '@frontend/components/floatingWidgets/FloatingWidgetWindow';
|
import FloatingWidgetWindow from '@frontend/components/floatingWidgets/FloatingWidgetWindow';
|
||||||
import {
|
import {
|
||||||
defaultGridFr,
|
defaultGridFr,
|
||||||
|
NATIVE_WIDGET_PICKER_WINDOW,
|
||||||
type FloatingWidgetInstance,
|
type FloatingWidgetInstance,
|
||||||
} from '@frontend/types/floatingWidget';
|
} from '@frontend/types/floatingWidget';
|
||||||
import type { Theme } from '@frontend/types';
|
import type { Theme } from '@frontend/types';
|
||||||
@@ -134,6 +135,27 @@ const WidgetApp: React.FC = () => {
|
|||||||
void getCurrentWindow().close();
|
void getCurrentWindow().close();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const sizeBeforePickerRef = useRef<{ width: number; height: number } | null>(null);
|
||||||
|
|
||||||
|
const handlePickerOpenChange = useCallback(async (open: boolean) => {
|
||||||
|
const win = getCurrentWindow();
|
||||||
|
if (open) {
|
||||||
|
const size = await win.innerSize();
|
||||||
|
sizeBeforePickerRef.current = { width: size.width, height: size.height };
|
||||||
|
const maxW = Math.max(480, window.screen.availWidth - 48);
|
||||||
|
const maxH = Math.max(520, window.screen.availHeight - 48);
|
||||||
|
const w = Math.min(NATIVE_WIDGET_PICKER_WINDOW.width, maxW);
|
||||||
|
const h = Math.min(NATIVE_WIDGET_PICKER_WINDOW.height, maxH);
|
||||||
|
await win.setSize(new LogicalSize(w, h));
|
||||||
|
await win.center();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const prev = sizeBeforePickerRef.current;
|
||||||
|
if (!prev) return;
|
||||||
|
sizeBeforePickerRef.current = null;
|
||||||
|
await win.setSize(new LogicalSize(prev.width, prev.height));
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
return <p className="wd-widget-empty">위젯 정보를 불러올 수 없습니다.</p>;
|
return <p className="wd-widget-empty">위젯 정보를 불러올 수 없습니다.</p>;
|
||||||
}
|
}
|
||||||
@@ -161,6 +183,7 @@ const WidgetApp: React.FC = () => {
|
|||||||
nativeShell
|
nativeShell
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
onFocus={() => {}}
|
onFocus={() => {}}
|
||||||
|
onPickerOpenChange={open => { void handlePickerOpenChange(open); }}
|
||||||
onUpdateSlots={slots => setInstance(prev => (prev ? { ...prev, slots } : prev))}
|
onUpdateSlots={slots => setInstance(prev => (prev ? { ...prev, slots } : prev))}
|
||||||
onUpdateSize={(width, height) => {
|
onUpdateSize={(width, height) => {
|
||||||
setInstance(prev => (prev ? { ...prev, width, height } : prev));
|
setInstance(prev => (prev ? { ...prev, width, height } : prev));
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ interface Props {
|
|||||||
onUpdateGridFr: (rowFr: number[], colFr: number[]) => void;
|
onUpdateGridFr: (rowFr: number[], colFr: number[]) => void;
|
||||||
/** Tauri OS 창 — 네이티브 타이틀·리사이즈 사용, 내부 플로팅 크롬 숨김 */
|
/** Tauri OS 창 — 네이티브 타이틀·리사이즈 사용, 내부 플로팅 크롬 숨김 */
|
||||||
nativeShell?: boolean;
|
nativeShell?: boolean;
|
||||||
|
/** 위젯 선택 팝업 열림/닫힘 (네이티브 창 크기 조절용) */
|
||||||
|
onPickerOpenChange?: (open: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FloatingWidgetWindow: React.FC<Props> = ({
|
const FloatingWidgetWindow: React.FC<Props> = ({
|
||||||
@@ -35,8 +37,19 @@ const FloatingWidgetWindow: React.FC<Props> = ({
|
|||||||
onUpdateSize,
|
onUpdateSize,
|
||||||
onUpdateGridFr,
|
onUpdateGridFr,
|
||||||
nativeShell = false,
|
nativeShell = false,
|
||||||
|
onPickerOpenChange,
|
||||||
}) => {
|
}) => {
|
||||||
const [pickSlotId, setPickSlotId] = useState<string | null>(null);
|
const [pickSlotId, setPickSlotId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const openPicker = useCallback((slotId: string) => {
|
||||||
|
setPickSlotId(slotId);
|
||||||
|
onPickerOpenChange?.(true);
|
||||||
|
}, [onPickerOpenChange]);
|
||||||
|
|
||||||
|
const closePicker = useCallback(() => {
|
||||||
|
setPickSlotId(null);
|
||||||
|
onPickerOpenChange?.(false);
|
||||||
|
}, [onPickerOpenChange]);
|
||||||
const bodyWrapRef = useRef<HTMLDivElement>(null);
|
const bodyWrapRef = useRef<HTMLDivElement>(null);
|
||||||
const bodyRef = useRef<HTMLDivElement>(null);
|
const bodyRef = useRef<HTMLDivElement>(null);
|
||||||
const [bodySize, setBodySize] = useState({ width: 0, height: 0 });
|
const [bodySize, setBodySize] = useState({ width: 0, height: 0 });
|
||||||
@@ -130,8 +143,8 @@ const FloatingWidgetWindow: React.FC<Props> = ({
|
|||||||
const applyWidgetType = useCallback((widgetType: string) => {
|
const applyWidgetType = useCallback((widgetType: string) => {
|
||||||
if (!pickSlotId) return;
|
if (!pickSlotId) return;
|
||||||
updateSlot(pickSlotId, s => ({ ...s, widgetType, config: {} }));
|
updateSlot(pickSlotId, s => ({ ...s, widgetType, config: {} }));
|
||||||
setPickSlotId(null);
|
closePicker();
|
||||||
}, [pickSlotId, updateSlot]);
|
}, [pickSlotId, updateSlot, closePicker]);
|
||||||
|
|
||||||
const clearWidget = useCallback((slotId: string) => {
|
const clearWidget = useCallback((slotId: string) => {
|
||||||
updateSlot(slotId, s => ({ ...s, widgetType: null, config: {} }));
|
updateSlot(slotId, s => ({ ...s, widgetType: null, config: {} }));
|
||||||
@@ -233,7 +246,7 @@ const FloatingWidgetWindow: React.FC<Props> = ({
|
|||||||
<WidgetSlotView
|
<WidgetSlotView
|
||||||
slot={slot}
|
slot={slot}
|
||||||
zoneId="center"
|
zoneId="center"
|
||||||
onPickWidget={id => setPickSlotId(id)}
|
onPickWidget={openPicker}
|
||||||
onClearWidget={id => clearWidget(id)}
|
onClearWidget={id => clearWidget(id)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -291,7 +304,8 @@ const FloatingWidgetWindow: React.FC<Props> = ({
|
|||||||
<WidgetPickerModal
|
<WidgetPickerModal
|
||||||
open={pickSlotId != null}
|
open={pickSlotId != null}
|
||||||
category="all"
|
category="all"
|
||||||
onClose={() => setPickSlotId(null)}
|
nativeCompact={nativeShell}
|
||||||
|
onClose={closePicker}
|
||||||
onSelect={applyWidgetType}
|
onSelect={applyWidgetType}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ interface Props {
|
|||||||
category: WidgetPickerCategory;
|
category: WidgetPickerCategory;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSelect: (widgetType: string) => void;
|
onSelect: (widgetType: string) => void;
|
||||||
|
/** Tauri 네이티브 위젯 창 — 작은 viewport에 맞춘 전체 화면형 팝업 */
|
||||||
|
nativeCompact?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WidgetPickerModal: React.FC<Props> = ({
|
const WidgetPickerModal: React.FC<Props> = ({
|
||||||
@@ -17,6 +19,7 @@ const WidgetPickerModal: React.FC<Props> = ({
|
|||||||
category,
|
category,
|
||||||
onClose,
|
onClose,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
nativeCompact = false,
|
||||||
}) => {
|
}) => {
|
||||||
if (!open) return null;
|
if (!open) return null;
|
||||||
|
|
||||||
@@ -37,11 +40,12 @@ const WidgetPickerModal: React.FC<Props> = ({
|
|||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={title}
|
title={title}
|
||||||
titleKo="Widget"
|
titleKo="Widget"
|
||||||
width={620}
|
width={720}
|
||||||
maxWidth="96vw"
|
maxWidth="96vw"
|
||||||
backdrop
|
backdrop
|
||||||
closeOnBackdrop
|
closeOnBackdrop
|
||||||
zIndex={13200}
|
zIndex={13200}
|
||||||
|
shellClassName={nativeCompact ? 'app-popup-shell--widget-picker-native' : 'app-popup-shell--widget-picker'}
|
||||||
bodyClassName="wd-picker-popup-body"
|
bodyClassName="wd-picker-popup-body"
|
||||||
>
|
>
|
||||||
<p className="wd-layout-intro">{intro}</p>
|
<p className="wd-layout-intro">{intro}</p>
|
||||||
|
|||||||
@@ -1231,6 +1231,29 @@
|
|||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 위젯 선택 팝업 — 웹·대시보드 (서버와 동일한 넓은 목록) */
|
||||||
|
.app-popup-shell--widget-picker {
|
||||||
|
width: min(720px, 96vw) !important;
|
||||||
|
max-height: min(860px, 92vh);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-popup-shell--widget-picker .wd-picker-popup-body {
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tauri 네이티브 위젯 창 — OS 창 확대 후 팝업이 거의 전체를 채움 */
|
||||||
|
html.fw-native-widget .app-popup-shell--widget-picker-native {
|
||||||
|
width: calc(100vw - 12px) !important;
|
||||||
|
max-width: calc(100vw - 12px) !important;
|
||||||
|
max-height: calc(100vh - 12px);
|
||||||
|
min-height: calc(100vh - 16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
html.fw-native-widget .app-popup-shell--widget-picker-native .wd-picker-popup-body {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.bps-page--wd-no-left .bps-side-wrap--left,
|
.bps-page--wd-no-left .bps-side-wrap--left,
|
||||||
.bps-page--wd-no-left .bps-side-wrap--left + .bps-splitter {
|
.bps-page--wd-no-left .bps-side-wrap--left + .bps-splitter {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ export const FLOATING_WIDGET_CELL_MIN_W = 140;
|
|||||||
export const FLOATING_WIDGET_CELL_MIN_H = 100;
|
export const FLOATING_WIDGET_CELL_MIN_H = 100;
|
||||||
export const FLOATING_WIDGET_GRID_GAP = 1;
|
export const FLOATING_WIDGET_GRID_GAP = 1;
|
||||||
|
|
||||||
|
/** Tauri 네이티브 위젯 창 — 위젯 선택 팝업 표시용 OS 창 크기 */
|
||||||
|
export const NATIVE_WIDGET_PICKER_WINDOW = { width: 740, height: 860 } as const;
|
||||||
|
|
||||||
export interface FloatingWidgetInstance {
|
export interface FloatingWidgetInstance {
|
||||||
id: string;
|
id: string;
|
||||||
rows: number;
|
rows: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user