chore: exclude local resource artifacts from main sync
This commit is contained in:
0
src/features/layout/README.md
Executable file → Normal file
0
src/features/layout/README.md
Executable file → Normal file
0
src/features/layout/component-sample-gallery/ComponentSamplesLayout.tsx
Executable file → Normal file
0
src/features/layout/component-sample-gallery/ComponentSamplesLayout.tsx
Executable file → Normal file
0
src/features/layout/component-sample-gallery/index.ts
Executable file → Normal file
0
src/features/layout/component-sample-gallery/index.ts
Executable file → Normal file
0
src/features/layout/dashboard-feature-gallery/DashboardFeatureGalleryLayout.tsx
Executable file → Normal file
0
src/features/layout/dashboard-feature-gallery/DashboardFeatureGalleryLayout.tsx
Executable file → Normal file
0
src/features/layout/dashboard-feature-gallery/index.ts
Executable file → Normal file
0
src/features/layout/dashboard-feature-gallery/index.ts
Executable file → Normal file
0
src/features/layout/dashboard-report-gallery/DashboardReportGalleryLayout.tsx
Executable file → Normal file
0
src/features/layout/dashboard-report-gallery/DashboardReportGalleryLayout.tsx
Executable file → Normal file
0
src/features/layout/dashboard-report-gallery/index.ts
Executable file → Normal file
0
src/features/layout/dashboard-report-gallery/index.ts
Executable file → Normal file
0
src/features/layout/docs-markdown-preview/DocsMarkdownPreviewLayout.tsx
Executable file → Normal file
0
src/features/layout/docs-markdown-preview/DocsMarkdownPreviewLayout.tsx
Executable file → Normal file
0
src/features/layout/docs-markdown-preview/index.ts
Executable file → Normal file
0
src/features/layout/docs-markdown-preview/index.ts
Executable file → Normal file
0
src/features/layout/feature-markdown-preview/FeatureMarkdownPreviewListLayout.tsx
Executable file → Normal file
0
src/features/layout/feature-markdown-preview/FeatureMarkdownPreviewListLayout.tsx
Executable file → Normal file
0
src/features/layout/feature-markdown-preview/index.ts
Executable file → Normal file
0
src/features/layout/feature-markdown-preview/index.ts
Executable file → Normal file
@@ -4,15 +4,9 @@ import { useEffect, useMemo, useState, type ReactNode } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { canUseChatType, resolveCurrentChatPermissionRoles, useChatTypeRegistry } from '../../../app/main/chatTypeAccess';
|
||||
import { createChatConversationRoom, fetchChatConversations } from '../../../app/main/mainChatPanel';
|
||||
import {
|
||||
LAYOUT_EDITOR_GUIDED_CHAT_TYPE_DESCRIPTION,
|
||||
LAYOUT_EDITOR_GUIDED_CHAT_TYPE_ID,
|
||||
LAYOUT_EDITOR_GUIDED_CHAT_TYPE_NAME,
|
||||
} from '../../../app/main/chatTypeDefaults';
|
||||
import { buildChatPath } from '../../../app/main/routes';
|
||||
import { useTokenAccess } from '../../../app/main/tokenAccess';
|
||||
import { SelectUI, type SelectOptionItem } from '../../../components/inputs/select';
|
||||
import { resolvePreferredLayoutCodexChatType } from '../../../views/play/layoutCodexChatType';
|
||||
import { listSavedLayouts, saveLayout, type SavedLayoutRecord } from '../../../views/play/layoutStorage';
|
||||
import { isReusableLayoutConversation, resolveLayoutCodexRequestSocketUrl, resolveLayoutConversationTitle } from './featureMenu.chat';
|
||||
import type { FeatureMenuTabKey, LayoutInteractionRule } from './featureMenu.types';
|
||||
@@ -40,21 +34,17 @@ export function FeatureMenuLayoutPage({ layoutId, savedLayouts, onSavedLayoutsCh
|
||||
const [draftBody, setDraftBody] = useState('');
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
const [selectedChatTypeId, setSelectedChatTypeId] = useState<string | null>(null);
|
||||
|
||||
const chatPermissionRoles = useMemo(() => resolveCurrentChatPermissionRoles(hasAccess), [hasAccess]);
|
||||
const availableChatTypes = useMemo(
|
||||
() => chatTypes.filter((item) => canUseChatType(item, chatPermissionRoles)),
|
||||
[chatPermissionRoles, chatTypes],
|
||||
);
|
||||
const preferredCodexChatType = useMemo(
|
||||
() => resolvePreferredLayoutCodexChatType(availableChatTypes),
|
||||
[availableChatTypes],
|
||||
const selectedChatType = useMemo(
|
||||
() => availableChatTypes.find((item) => item.id === selectedChatTypeId) ?? availableChatTypes[0] ?? null,
|
||||
[availableChatTypes, selectedChatTypeId],
|
||||
);
|
||||
const targetChatType = preferredCodexChatType ?? {
|
||||
id: LAYOUT_EDITOR_GUIDED_CHAT_TYPE_ID,
|
||||
name: LAYOUT_EDITOR_GUIDED_CHAT_TYPE_NAME,
|
||||
description: LAYOUT_EDITOR_GUIDED_CHAT_TYPE_DESCRIPTION,
|
||||
};
|
||||
|
||||
const layoutOptions = useMemo<SelectOptionItem[]>(
|
||||
() =>
|
||||
@@ -91,6 +81,19 @@ export function FeatureMenuLayoutPage({ layoutId, savedLayouts, onSavedLayoutsCh
|
||||
setSelectedLayoutId(fallback?.id ?? null);
|
||||
}, [layoutId, savedLayouts, selectedLayoutId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (availableChatTypes.length === 0) {
|
||||
setSelectedChatTypeId(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedChatTypeId && availableChatTypes.some((item) => item.id === selectedChatTypeId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedChatTypeId(availableChatTypes[0]?.id ?? null);
|
||||
}, [availableChatTypes, selectedChatTypeId]);
|
||||
|
||||
const selectedLayout = selectedLayoutId ? savedLayouts.find((item) => item.id === selectedLayoutId) ?? null : null;
|
||||
const selectedLayoutInteractions = useMemo<LayoutInteractionRule[]>(
|
||||
() => normalizeLayoutInteractions(selectedLayout?.tree ?? null),
|
||||
@@ -276,7 +279,10 @@ export function FeatureMenuLayoutPage({ layoutId, savedLayouts, onSavedLayoutsCh
|
||||
};
|
||||
|
||||
const handleCodexExecute = async () => {
|
||||
if (!selectedLayout) {
|
||||
if (!selectedLayout || !selectedChatType) {
|
||||
if (!selectedChatType) {
|
||||
void messageApi.error('사용 가능한 채팅유형이 없어 Codex 실행을 보낼 수 없습니다.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -289,7 +295,7 @@ export function FeatureMenuLayoutPage({ layoutId, savedLayouts, onSavedLayoutsCh
|
||||
try {
|
||||
const conversations = await fetchChatConversations();
|
||||
const matchedConversation = conversations.find((item) =>
|
||||
isReusableLayoutConversation(item, conversationTitle, targetChatType.id),
|
||||
isReusableLayoutConversation(item, conversationTitle, selectedChatType.id),
|
||||
);
|
||||
const shouldCreateConversation = !matchedConversation;
|
||||
const targetSessionId =
|
||||
@@ -302,10 +308,10 @@ export function FeatureMenuLayoutPage({ layoutId, savedLayouts, onSavedLayoutsCh
|
||||
await createChatConversationRoom({
|
||||
sessionId: targetSessionId,
|
||||
title: conversationTitle,
|
||||
chatTypeId: targetChatType.id,
|
||||
lastChatTypeId: targetChatType.id,
|
||||
contextLabel: targetChatType.name,
|
||||
contextDescription: targetChatType.description,
|
||||
chatTypeId: selectedChatType.id,
|
||||
lastChatTypeId: selectedChatType.id,
|
||||
contextLabel: selectedChatType.name,
|
||||
contextDescription: selectedChatType.description,
|
||||
notifyOffline: true,
|
||||
});
|
||||
}
|
||||
@@ -326,9 +332,9 @@ export function FeatureMenuLayoutPage({ layoutId, savedLayouts, onSavedLayoutsCh
|
||||
type: 'message:send',
|
||||
payload: {
|
||||
text: prompt,
|
||||
chatTypeId: targetChatType.id,
|
||||
chatTypeLabel: targetChatType.name,
|
||||
chatTypeDescription: targetChatType.description,
|
||||
chatTypeId: selectedChatType.id,
|
||||
chatTypeLabel: selectedChatType.name,
|
||||
chatTypeDescription: selectedChatType.description,
|
||||
requestId,
|
||||
mode: 'queue',
|
||||
},
|
||||
@@ -434,13 +440,29 @@ export function FeatureMenuLayoutPage({ layoutId, savedLayouts, onSavedLayoutsCh
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="feature-menu-layout-page__field">
|
||||
<Text className="feature-menu-layout-page__field-label">Codex 채팅유형</Text>
|
||||
<SelectUI
|
||||
data={availableChatTypes.map((item) => ({
|
||||
code: item.id,
|
||||
value: item.name,
|
||||
}))}
|
||||
value={selectedChatType?.id ?? undefined}
|
||||
allowClear={false}
|
||||
disabled={availableChatTypes.length === 0}
|
||||
className="feature-menu-layout-page__select"
|
||||
onChange={(nextCode) => {
|
||||
setSelectedChatTypeId(nextCode ?? null);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Tooltip title="Codex 실행">
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
icon={<PlayCircleOutlined />}
|
||||
className="feature-menu-layout-page__run-button"
|
||||
disabled={!selectedLayout}
|
||||
disabled={!selectedLayout || !selectedChatType}
|
||||
loading={isSending}
|
||||
aria-label="Codex 실행"
|
||||
onClick={() => {
|
||||
|
||||
@@ -14,7 +14,7 @@ export function resolveLayoutCodexRequestSocketUrl(sessionId: string) {
|
||||
|
||||
if (['127.0.0.1', 'localhost', '0.0.0.0'].includes(window.location.hostname)) {
|
||||
resolvedUrl.protocol = 'wss:';
|
||||
resolvedUrl.hostname = 'test.sm-home.cloud';
|
||||
resolvedUrl.hostname = 'preview.sm-home.cloud';
|
||||
resolvedUrl.port = '';
|
||||
resolvedUrl.pathname = '/ws/chat';
|
||||
}
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
## 작업 대상
|
||||
- 패키지 루트: `src/features/layout/feature-menu/`
|
||||
- 관련 저장 레이아웃 ID: `layout-1777643627048`
|
||||
- 검증 기준 도메인: `https://test.sm-home.cloud/`
|
||||
- 최종 확인 기준: `4173 preview`
|
||||
- 운영 비교 도메인: `https://test.sm-home.cloud/`
|
||||
- 소스 변경 검증 도메인: `https://preview.sm-home.cloud/`
|
||||
- 로컬 preview 컨테이너: `http://127.0.0.1:4173`
|
||||
|
||||
## 확인된 기존 문제
|
||||
- 상단 description 요약이 모바일 세로 공간을 과도하게 차지했다.
|
||||
|
||||
@@ -61,7 +61,8 @@
|
||||
|
||||
## 검증 기준
|
||||
- 실제 수정본이 있으면 문서 설명보다 화면 결과와 preview 검증을 우선한다.
|
||||
- 검증 대상 기본 도메인은 `https://test.sm-home.cloud/`다.
|
||||
- 운영 비교 도메인은 `https://test.sm-home.cloud/`다.
|
||||
- 소스 변경 검증과 최종 확인 도메인은 `https://preview.sm-home.cloud/`다.
|
||||
- 최종 검증 산출물은 `resources/verification/` 아래에 패키지 기준으로 함께 보관한다.
|
||||
|
||||
## 패키지 내부 산출물
|
||||
@@ -69,4 +70,4 @@
|
||||
- 개발 완료 문서: `resources/feature-menu-implementation.md`
|
||||
- 최종 preview 검증 이미지: `resources/verification/feature-menu-preview-mobile-final.png`
|
||||
- 최종 preview 검증 이미지: `resources/verification/feature-menu-preview-desktop-final.png`
|
||||
- `test.sm-home.cloud` 비교 검증 이미지: `resources/verification/feature-menu-test-sm-home-mobile.png`
|
||||
- `test.sm-home.cloud` 운영 비교 이미지: `resources/verification/feature-menu-test-sm-home-mobile.png`
|
||||
|
||||
@@ -33,26 +33,26 @@
|
||||
- 최종 분석 문서: `resources/feature-menu-analysis.md`
|
||||
- 최종 preview 모바일: `resources/verification/feature-menu-preview-mobile-final.png`
|
||||
- 최종 preview 데스크톱: `resources/verification/feature-menu-preview-desktop-final.png`
|
||||
- `test.sm-home.cloud` 재현 확인 이미지: `resources/verification/feature-menu-test-sm-home-mobile.png`
|
||||
- `test.sm-home.cloud` 운영 비교 이미지: `resources/verification/feature-menu-test-sm-home-mobile.png`
|
||||
|
||||
## 검증 결과
|
||||
- `test.sm-home.cloud` 모바일 재현에서는 textarea 하단이 편집 쉘 아래로 약 `91px` 넘치는 기존 상태를 다시 확인했다.
|
||||
- `2026-05-03` `4173 preview` 모바일 재검증에서는 `tabs.bottom = 651`, `textarea.bottom = 651`로 맞춰졌고, 마지막 줄까지 내부 스크롤로 확인됐다.
|
||||
- 최종 반영 결과는 `4173 preview` 기준 모바일/데스크톱 캡처로 보관했다.
|
||||
- `2026-05-03` 로컬 preview 컨테이너(`http://127.0.0.1:4173`) 모바일 재검증에서는 `tabs.bottom = 651`, `textarea.bottom = 651`로 맞춰졌고, 마지막 줄까지 내부 스크롤로 확인됐다.
|
||||
- 최종 반영 결과는 `preview.sm-home.cloud` 기준 모바일/데스크톱 캡처로 보관했다.
|
||||
- 같은 날짜 후속 개선으로 루트/편집 셸/탭 본문을 다시 `auto + minmax(0, 1fr)` 구조로 복구하고, textarea를 `autoSize` 대신 탭 본문 남는 높이 전체를 채우는 방식으로 조정했다.
|
||||
- `2026-05-03` `4173 preview` 최종 재검증에서는 모바일 기준 `bodyScrollHeight = 844`, `root.bottom = 844`, `textarea.bottom = 831`, `textarea.height = 487.17`로 남는 공간을 채우면서도 페이지 바깥 overflow는 발생하지 않았다.
|
||||
- 같은 날짜 추가 미세조정 뒤 `4173 preview` 모바일 재검증에서는 `bodyScrollHeight = 664`, `root.bottom = 664`, `textarea.bottom = 599`, `textarea.height = 255.17`로 textarea 하단 여유를 더 확보했고, 페이지 바깥 overflow는 없었다.
|
||||
- 같은 날짜 최신 재검증에서는 `test.sm-home.cloud`가 여전히 기존 번들로 `textarea.bottom = 747.25`인 반면, `4173 preview` 수정본은 `shell.bottom = 660`, `tabs.bottom = 655`, `textarea.bottom = 595`, `textarea.height = 272.17`로 wrapper 외곽과 하단 입력 영역이 더 안쪽에 들어오도록 정리됐다.
|
||||
- `2026-05-03` 로컬 preview 컨테이너(`http://127.0.0.1:4173`) 최종 재검증에서는 모바일 기준 `bodyScrollHeight = 844`, `root.bottom = 844`, `textarea.bottom = 831`, `textarea.height = 487.17`로 남는 공간을 채우면서도 페이지 바깥 overflow는 발생하지 않았다.
|
||||
- 같은 날짜 추가 미세조정 뒤 로컬 preview 컨테이너(`http://127.0.0.1:4173`) 모바일 재검증에서는 `bodyScrollHeight = 664`, `root.bottom = 664`, `textarea.bottom = 599`, `textarea.height = 255.17`로 textarea 하단 여유를 더 확보했고, 페이지 바깥 overflow는 없었다.
|
||||
- 같은 날짜 최신 재검증에서는 `test.sm-home.cloud`가 여전히 기존 번들로 `textarea.bottom = 747.25`인 반면, `preview.sm-home.cloud` 수정본은 `shell.bottom = 660`, `tabs.bottom = 655`, `textarea.bottom = 595`, `textarea.height = 272.17`로 wrapper 외곽과 하단 입력 영역이 더 안쪽에 들어오도록 정리됐다.
|
||||
- 이번 후속 수정 검증은 동일한 모바일 문제 이미지 기준으로 부모 wrapper 하단의 과한 빈 영역이 사라졌는지 확인하는 것이 목적이다.
|
||||
- `2026-05-03` `4173 preview` 모바일 재검증에서는 `shell.bottom = 547.83`, `tabs.bottom = 542.83`, `textarea.bottom = 542.83`, `textarea.height = 220`으로 wrapper 자체가 이전보다 약 `292px` 짧아졌다.
|
||||
- 같은 날짜 최신 재검증에서는 `4173 preview` 모바일 기준 `shell.bottom = 840`, `tabs.bottom = 836`, `textarea.bottom = 836`, `textarea.height = 521.17`로 다시 하단까지 거의 채우면서도 카드 외곽 하단 선이 캡처 안에서 유지됐다.
|
||||
- 같은 날짜 최신 재검증에서는 `4173 preview` 모바일 기준 `shell.bottom = 806`, `tabs.bottom = 801`, `textarea.bottom = 771`, `textarea.height = 455.17`로 부모 카드 하단이 다시 화면 안에 들어오면서 textarea 높이도 이전 `v30`보다 `187px` 커졌다.
|
||||
- 같은 날짜 최신 재검증에서는 `4173 preview` 모바일 기준 `shell.bottom = 794`, `tabs.bottom = 789`, `textarea.bottom = 765`, `textarea.height = 449.17`로 부모 카드 하단선을 `v31`보다 `12px` 더 위로 올리면서도 textarea 높이는 `6px`만 줄였다.
|
||||
- 같은 날짜 최신 재검증에서는 `4173 preview` 모바일 기준 `shell.bottom = 778`, `tabs.bottom = 774`, `textarea.bottom = 754`, `textarea.height = 447.17`로 wrapper 하단선이 캡처 안에서 분명히 보이면서도 textarea 높이는 직전 대비 `2px`만 줄었다.
|
||||
- 같은 날짜 원복 기준은 아이폰 12 Pro viewport에서 `test.sm-home.cloud`가 `shell.bottom = 598`, `notes.bottom = 574`인 반면, `4173 preview` 수정본은 `shell.bottom = 616`, `notes.bottom = 600`으로 두번째 카드가 실제로 더 아래까지 내려오던 시점의 값으로 맞춘다.
|
||||
- 이번 최신 조정 검증은 모바일 wrapper와 textarea를 동시에 더 늘리는 것이 목적이며, `4173 preview` 기준으로 다시 확인한다.
|
||||
- 같은 날짜 최신 `v36` 검증에서는 아이폰 12 Pro viewport 기준 `4173 preview`가 `shell.bottom = 586`, `tabs.bottom = 582`, `textarea.bottom = 578`, `textarea.height = 323.17`로 확인됐고, 같은 조건 `test.sm-home.cloud`는 `shell.bottom = 564`, `tabs.bottom = 560`, `textarea.bottom = 548`, `textarea.height = 293.17`이었다.
|
||||
- 같은 날짜 데스크톱 `4173 preview` 재검증에서는 `shell.bottom = 1088`, `tabs.bottom = 1077`, `textarea.bottom = 1077`로 기존 데스크톱 채움 구조는 유지됐다.
|
||||
- 같은 날짜 데스크톱 `4173 preview` 재검증에서는 `shell.bottom = 1188`, `tabs.bottom = 1177`, `textarea.bottom = 1177`로 데스크톱 채움 구조가 그대로 유지됐다.
|
||||
- 같은 날짜 후속 수정으로 `기능설명 입력` 탭은 `title input` 없이 textarea 하나만 남았고, `4173 preview` 기준 `titleInputCount = 0`, `textareaCount = 1`로 확인했다.
|
||||
- `2026-05-03` 로컬 preview 컨테이너(`http://127.0.0.1:4173`) 모바일 재검증에서는 `shell.bottom = 547.83`, `tabs.bottom = 542.83`, `textarea.bottom = 542.83`, `textarea.height = 220`으로 wrapper 자체가 이전보다 약 `292px` 짧아졌다.
|
||||
- 같은 날짜 최신 재검증에서는 `preview.sm-home.cloud` 모바일 기준 `shell.bottom = 840`, `tabs.bottom = 836`, `textarea.bottom = 836`, `textarea.height = 521.17`로 다시 하단까지 거의 채우면서도 카드 외곽 하단 선이 캡처 안에서 유지됐다.
|
||||
- 같은 날짜 최신 재검증에서는 `preview.sm-home.cloud` 모바일 기준 `shell.bottom = 806`, `tabs.bottom = 801`, `textarea.bottom = 771`, `textarea.height = 455.17`로 부모 카드 하단이 다시 화면 안에 들어오면서 textarea 높이도 이전 `v30`보다 `187px` 커졌다.
|
||||
- 같은 날짜 최신 재검증에서는 `preview.sm-home.cloud` 모바일 기준 `shell.bottom = 794`, `tabs.bottom = 789`, `textarea.bottom = 765`, `textarea.height = 449.17`로 부모 카드 하단선을 `v31`보다 `12px` 더 위로 올리면서도 textarea 높이는 `6px`만 줄였다.
|
||||
- 같은 날짜 최신 재검증에서는 `preview.sm-home.cloud` 모바일 기준 `shell.bottom = 778`, `tabs.bottom = 774`, `textarea.bottom = 754`, `textarea.height = 447.17`로 wrapper 하단선이 캡처 안에서 분명히 보이면서도 textarea 높이는 직전 대비 `2px`만 줄었다.
|
||||
- 같은 날짜 원복 기준은 아이폰 12 Pro viewport에서 `test.sm-home.cloud`가 `shell.bottom = 598`, `notes.bottom = 574`인 반면, `preview.sm-home.cloud` 수정본은 `shell.bottom = 616`, `notes.bottom = 600`으로 두번째 카드가 실제로 더 아래까지 내려오던 시점의 값으로 맞춘다.
|
||||
- 이번 최신 조정 검증은 모바일 wrapper와 textarea를 동시에 더 늘리는 것이 목적이며, `preview.sm-home.cloud` 기준으로 다시 확인한다.
|
||||
- 같은 날짜 최신 `v36` 검증에서는 아이폰 12 Pro viewport 기준 `preview.sm-home.cloud`가 `shell.bottom = 586`, `tabs.bottom = 582`, `textarea.bottom = 578`, `textarea.height = 323.17`로 확인됐고, 같은 조건 `test.sm-home.cloud`는 `shell.bottom = 564`, `tabs.bottom = 560`, `textarea.bottom = 548`, `textarea.height = 293.17`이었다.
|
||||
- 같은 날짜 데스크톱 `preview.sm-home.cloud` 재검증에서는 `shell.bottom = 1088`, `tabs.bottom = 1077`, `textarea.bottom = 1077`로 기존 데스크톱 채움 구조는 유지됐다.
|
||||
- 같은 날짜 데스크톱 `preview.sm-home.cloud` 재검증에서는 `shell.bottom = 1188`, `tabs.bottom = 1177`, `textarea.bottom = 1177`로 데스크톱 채움 구조가 그대로 유지됐다.
|
||||
- 같은 날짜 후속 수정으로 `기능설명 입력` 탭은 `title input` 없이 textarea 하나만 남았고, `preview.sm-home.cloud` 기준 `titleInputCount = 0`, `textareaCount = 1`로 확인했다.
|
||||
- 이번 이관 작업은 패키지 내부 문서/리소스 구조 정리이며 동작 로직 추가 변경은 포함하지 않는다.
|
||||
|
||||
46
src/features/layout/renderSavedLayoutContent.tsx
Normal file
46
src/features/layout/renderSavedLayoutContent.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { FeatureMenuLayoutPage } from './feature-menu';
|
||||
import { MemoLayoutPage } from './memo';
|
||||
import { LayoutPlaygroundView } from '../../views/play/LayoutPlaygroundView';
|
||||
import type { SavedLayoutRecord } from '../../views/play/layoutStorage';
|
||||
|
||||
type RenderSavedLayoutContentParams = {
|
||||
layoutId: string;
|
||||
layout: SavedLayoutRecord;
|
||||
savedLayouts: SavedLayoutRecord[];
|
||||
onSavedLayoutsChange?: (layouts: SavedLayoutRecord[]) => void;
|
||||
};
|
||||
|
||||
function normalizeLayoutName(name: string) {
|
||||
return name.replace(/\s+/g, '').trim();
|
||||
}
|
||||
|
||||
export function isMemoSavedLayout(layout: Pick<SavedLayoutRecord, 'name'>) {
|
||||
return normalizeLayoutName(layout.name).startsWith('메모');
|
||||
}
|
||||
|
||||
export function isFeatureMenuSavedLayout(layout: Pick<SavedLayoutRecord, 'name'>) {
|
||||
return normalizeLayoutName(layout.name).startsWith('기능설명관리');
|
||||
}
|
||||
|
||||
export function renderSavedLayoutContent({
|
||||
layoutId,
|
||||
layout,
|
||||
savedLayouts,
|
||||
onSavedLayoutsChange,
|
||||
}: RenderSavedLayoutContentParams) {
|
||||
if (isMemoSavedLayout(layout)) {
|
||||
return <MemoLayoutPage layoutId={layoutId} />;
|
||||
}
|
||||
|
||||
if (isFeatureMenuSavedLayout(layout)) {
|
||||
return (
|
||||
<FeatureMenuLayoutPage
|
||||
layoutId={layoutId}
|
||||
savedLayouts={savedLayouts}
|
||||
onSavedLayoutsChange={onSavedLayoutsChange ?? (() => undefined)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <LayoutPlaygroundView savedLayoutViewId={layoutId} onSavedLayoutsChange={onSavedLayoutsChange} />;
|
||||
}
|
||||
0
src/features/layout/widget-registry-gallery/WidgetRegistryLayout.tsx
Executable file → Normal file
0
src/features/layout/widget-registry-gallery/WidgetRegistryLayout.tsx
Executable file → Normal file
0
src/features/layout/widget-registry-gallery/index.ts
Executable file → Normal file
0
src/features/layout/widget-registry-gallery/index.ts
Executable file → Normal file
33
src/features/layout/widget-sample-gallery/SampleWidgetsLayout.tsx
Executable file → Normal file
33
src/features/layout/widget-sample-gallery/SampleWidgetsLayout.tsx
Executable file → Normal file
@@ -7,12 +7,18 @@ export type SampleWidgetsLayoutProps = {
|
||||
entries: SampleEntry[];
|
||||
pathFilter?: string;
|
||||
includeComponentIds?: string[];
|
||||
includeSampleIds?: string[];
|
||||
disableWidgetCardWrapper?: boolean;
|
||||
singlePreviewMode?: boolean;
|
||||
};
|
||||
|
||||
export function SampleWidgetsLayout({
|
||||
entries,
|
||||
pathFilter = '/widgets/',
|
||||
includeComponentIds = [],
|
||||
includeSampleIds = [],
|
||||
disableWidgetCardWrapper = false,
|
||||
singlePreviewMode = false,
|
||||
}: SampleWidgetsLayoutProps) {
|
||||
const [sampleEntries, setSampleEntries] = useState<LoadedSampleEntry[]>([]);
|
||||
|
||||
@@ -30,25 +36,38 @@ export function SampleWidgetsLayout({
|
||||
};
|
||||
}, [entries, pathFilter]);
|
||||
|
||||
const visibleEntries =
|
||||
includeComponentIds.length > 0
|
||||
? sampleEntries.filter((entry) => includeComponentIds.includes(entry.sampleMeta.componentId))
|
||||
: sampleEntries;
|
||||
const visibleEntries = sampleEntries.filter((entry) => {
|
||||
if (includeComponentIds.length > 0 && !includeComponentIds.includes(entry.sampleMeta.componentId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (includeSampleIds.length > 0 && !includeSampleIds.includes(entry.sampleMeta.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (visibleEntries.length === 0) {
|
||||
return <Empty description="표시할 위젯 샘플이 없습니다." />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex gap={20} wrap className="sample-widgets-layout">
|
||||
<Flex
|
||||
gap={singlePreviewMode ? 0 : 20}
|
||||
wrap={!singlePreviewMode}
|
||||
className={`sample-widgets-layout${singlePreviewMode ? ' sample-widgets-layout--single-preview' : ''}`}
|
||||
>
|
||||
{visibleEntries.map(({ modulePath, Sample, sampleMeta }) => (
|
||||
<div
|
||||
key={modulePath}
|
||||
id={`widget-sample-${sampleMeta.componentId}`}
|
||||
className="sample-widgets-layout__item"
|
||||
className={`sample-widgets-layout__item${singlePreviewMode ? ' sample-widgets-layout__item--single-preview' : ''}`}
|
||||
data-focus-id={`widget:${sampleMeta.componentId}`}
|
||||
>
|
||||
<div className="sample-widgets-layout__item">{createElement(Sample)}</div>
|
||||
<div className={`sample-widgets-layout__item${singlePreviewMode ? ' sample-widgets-layout__item--single-preview' : ''}`}>
|
||||
{createElement(Sample, { disableWidgetCardWrapper })}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Flex>
|
||||
|
||||
0
src/features/layout/widget-sample-gallery/index.ts
Executable file → Normal file
0
src/features/layout/widget-sample-gallery/index.ts
Executable file → Normal file
Reference in New Issue
Block a user