앱 위젯추가 팝업 수정
This commit is contained in:
@@ -24,8 +24,8 @@ interface Props {
|
||||
onUpdateGridFr: (rowFr: number[], colFr: number[]) => void;
|
||||
/** Tauri OS 창 — 네이티브 타이틀·리사이즈 사용, 내부 플로팅 크롬 숨김 */
|
||||
nativeShell?: boolean;
|
||||
/** 위젯 선택 팝업 열림/닫힘 (네이티브 창 크기 조절용) */
|
||||
onPickerOpenChange?: (open: boolean) => void;
|
||||
/** 위젯 선택 팝업 열림/닫힘 (네이티브 창 크기 조절 — 완료 후 picker 표시) */
|
||||
onPickerOpenChange?: (open: boolean) => void | Promise<void>;
|
||||
}
|
||||
|
||||
const FloatingWidgetWindow: React.FC<Props> = ({
|
||||
@@ -40,16 +40,29 @@ const FloatingWidgetWindow: React.FC<Props> = ({
|
||||
onPickerOpenChange,
|
||||
}) => {
|
||||
const [pickSlotId, setPickSlotId] = useState<string | null>(null);
|
||||
const [pickerOpening, setPickerOpening] = useState(false);
|
||||
|
||||
const openPicker = useCallback((slotId: string) => {
|
||||
const openPicker = useCallback(async (slotId: string) => {
|
||||
if (nativeShell && onPickerOpenChange) {
|
||||
setPickerOpening(true);
|
||||
try {
|
||||
await onPickerOpenChange(true);
|
||||
await new Promise<void>(resolve => {
|
||||
requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
|
||||
});
|
||||
} finally {
|
||||
setPickerOpening(false);
|
||||
}
|
||||
}
|
||||
setPickSlotId(slotId);
|
||||
onPickerOpenChange?.(true);
|
||||
}, [onPickerOpenChange]);
|
||||
}, [nativeShell, onPickerOpenChange]);
|
||||
|
||||
const closePicker = useCallback(() => {
|
||||
const closePicker = useCallback(async () => {
|
||||
setPickSlotId(null);
|
||||
onPickerOpenChange?.(false);
|
||||
}, [onPickerOpenChange]);
|
||||
if (nativeShell && onPickerOpenChange) {
|
||||
await onPickerOpenChange(false);
|
||||
}
|
||||
}, [nativeShell, onPickerOpenChange]);
|
||||
const bodyWrapRef = useRef<HTMLDivElement>(null);
|
||||
const bodyRef = useRef<HTMLDivElement>(null);
|
||||
const [bodySize, setBodySize] = useState({ width: 0, height: 0 });
|
||||
@@ -143,8 +156,7 @@ const FloatingWidgetWindow: React.FC<Props> = ({
|
||||
const applyWidgetType = useCallback((widgetType: string) => {
|
||||
if (!pickSlotId) return;
|
||||
updateSlot(pickSlotId, s => ({ ...s, widgetType, config: {} }));
|
||||
closePicker();
|
||||
}, [pickSlotId, updateSlot, closePicker]);
|
||||
}, [pickSlotId, updateSlot]);
|
||||
|
||||
const clearWidget = useCallback((slotId: string) => {
|
||||
updateSlot(slotId, s => ({ ...s, widgetType: null, config: {} }));
|
||||
@@ -246,7 +258,7 @@ const FloatingWidgetWindow: React.FC<Props> = ({
|
||||
<WidgetSlotView
|
||||
slot={slot}
|
||||
zoneId="center"
|
||||
onPickWidget={openPicker}
|
||||
onPickWidget={id => { void openPicker(id); }}
|
||||
onClearWidget={id => clearWidget(id)}
|
||||
/>
|
||||
</div>
|
||||
@@ -301,13 +313,18 @@ const FloatingWidgetWindow: React.FC<Props> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<WidgetPickerModal
|
||||
open={pickSlotId != null}
|
||||
category="all"
|
||||
nativeCompact={nativeShell}
|
||||
onClose={closePicker}
|
||||
onSelect={applyWidgetType}
|
||||
/>
|
||||
{(pickerOpening || pickSlotId != null) && (
|
||||
<WidgetPickerModal
|
||||
open={pickSlotId != null}
|
||||
category="all"
|
||||
nativeCompact={nativeShell}
|
||||
onClose={() => { void closePicker(); }}
|
||||
onSelect={applyWidgetType}
|
||||
/>
|
||||
)}
|
||||
{pickerOpening && pickSlotId == null && nativeShell && (
|
||||
<div className="wd-picker-native-loading" aria-live="polite">위젯 목록 준비 중…</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
@@ -10,20 +10,11 @@ interface Props {
|
||||
category: WidgetPickerCategory;
|
||||
onClose: () => void;
|
||||
onSelect: (widgetType: string) => void;
|
||||
/** Tauri 네이티브 위젯 창 — 작은 viewport에 맞춘 전체 화면형 팝업 */
|
||||
/** Tauri 네이티브 위젯 창 — OS 창 전체를 쓰는 고정형 picker */
|
||||
nativeCompact?: boolean;
|
||||
}
|
||||
|
||||
const WidgetPickerModal: React.FC<Props> = ({
|
||||
open,
|
||||
category,
|
||||
onClose,
|
||||
onSelect,
|
||||
nativeCompact = false,
|
||||
}) => {
|
||||
if (!open) return null;
|
||||
|
||||
const groups = category === 'all' ? widgetsByGroupAll() : widgetsByGroup(category);
|
||||
function pickerMeta(category: WidgetPickerCategory) {
|
||||
const title = category === 'center'
|
||||
? '메인 위젯'
|
||||
: category === 'side'
|
||||
@@ -34,20 +25,19 @@ const WidgetPickerModal: React.FC<Props> = ({
|
||||
: category === 'side'
|
||||
? '좌·우·하단 패널에 추가할 위젯을 선택하세요. 주식 HTS/위젯 프로그램에서 흔히 쓰는 구성을 지원합니다.'
|
||||
: '추가할 위젯 유형을 선택하세요. 차트·시세·매매·포트폴리오 등 모든 위젯을 사용할 수 있습니다.';
|
||||
return { title, intro };
|
||||
}
|
||||
|
||||
const WidgetPickerContent: React.FC<{
|
||||
category: WidgetPickerCategory;
|
||||
onSelect: (widgetType: string) => void;
|
||||
onClose: () => void;
|
||||
}> = ({ category, onSelect, onClose }) => {
|
||||
const groups = category === 'all' ? widgetsByGroupAll() : widgetsByGroup(category);
|
||||
const { intro } = pickerMeta(category);
|
||||
|
||||
return (
|
||||
<AppPopup
|
||||
onClose={onClose}
|
||||
title={title}
|
||||
titleKo="Widget"
|
||||
width={720}
|
||||
maxWidth="96vw"
|
||||
backdrop
|
||||
closeOnBackdrop
|
||||
zIndex={13200}
|
||||
shellClassName={nativeCompact ? 'app-popup-shell--widget-picker-native' : 'app-popup-shell--widget-picker'}
|
||||
bodyClassName="wd-picker-popup-body"
|
||||
>
|
||||
<>
|
||||
<p className="wd-layout-intro">{intro}</p>
|
||||
{groups.map(g => (
|
||||
<section key={g.group} className="wd-picker-section">
|
||||
@@ -70,6 +60,85 @@ const WidgetPickerModal: React.FC<Props> = ({
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const NativeWidgetPicker: React.FC<Props> = ({
|
||||
open,
|
||||
category,
|
||||
onClose,
|
||||
onSelect,
|
||||
}) => {
|
||||
if (!open) return null;
|
||||
const { title } = pickerMeta(category);
|
||||
|
||||
return (
|
||||
<div className="wd-picker-native-overlay" role="presentation">
|
||||
<div
|
||||
className="wd-picker-native-shell"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="wd-picker-native-title"
|
||||
>
|
||||
<div className="wd-picker-native-header">
|
||||
<h2 id="wd-picker-native-title" className="wd-picker-native-title">
|
||||
{title}
|
||||
<span className="wd-picker-native-title-ko"> (Widget)</span>
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
className="wd-picker-native-close"
|
||||
onClick={onClose}
|
||||
aria-label="닫기"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="wd-picker-native-body wd-picker-popup-body">
|
||||
<WidgetPickerContent category={category} onSelect={onSelect} onClose={onClose} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const WidgetPickerModal: React.FC<Props> = ({
|
||||
open,
|
||||
category,
|
||||
onClose,
|
||||
onSelect,
|
||||
nativeCompact = false,
|
||||
}) => {
|
||||
if (!open) return null;
|
||||
|
||||
if (nativeCompact) {
|
||||
return (
|
||||
<NativeWidgetPicker
|
||||
open={open}
|
||||
category={category}
|
||||
onClose={onClose}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const { title } = pickerMeta(category);
|
||||
|
||||
return (
|
||||
<AppPopup
|
||||
onClose={onClose}
|
||||
title={title}
|
||||
titleKo="Widget"
|
||||
width={720}
|
||||
maxWidth="96vw"
|
||||
backdrop
|
||||
closeOnBackdrop
|
||||
zIndex={13200}
|
||||
shellClassName="app-popup-shell--widget-picker"
|
||||
bodyClassName="wd-picker-popup-body"
|
||||
>
|
||||
<WidgetPickerContent category={category} onSelect={onSelect} onClose={onClose} />
|
||||
</AppPopup>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user