Open relation map in separate window
This commit is contained in:
91
src/App.jsx
91
src/App.jsx
@@ -1499,7 +1499,15 @@ function ProgramComparePopup({ programs, comparisons, onComparisonChange, onClos
|
||||
);
|
||||
}
|
||||
|
||||
function RelationTreePanel({ programs, onProgramClick, onOpenRelationPopup, sidebarWidth, onSidebarWidthChange }) {
|
||||
function RelationTreePanel({
|
||||
programs,
|
||||
onProgramClick,
|
||||
onOpenRelationPopup,
|
||||
onOpenMapWindow,
|
||||
sidebarWidth,
|
||||
onSidebarWidthChange,
|
||||
fullPage = false
|
||||
}) {
|
||||
const [expandedProgramIds, setExpandedProgramIds] = useState(() => new Set(programs.map((program) => program.id)));
|
||||
const programMap = Object.fromEntries(programs.map((program) => [program.id, program]));
|
||||
const relations = programs.flatMap((program) =>
|
||||
@@ -1536,14 +1544,14 @@ function RelationTreePanel({ programs, onProgramClick, onOpenRelationPopup, side
|
||||
const miniSideLaneWidth = 96;
|
||||
const sidebarSizes = [280, 420, 560];
|
||||
const resolvedSidebarWidth = sidebarWidth ?? sidebarSizes[0];
|
||||
const miniViewportWidth = Math.max(230, resolvedSidebarWidth - 32);
|
||||
const miniViewportWidth = Math.max(230, fullPage ? 0 : resolvedSidebarWidth - 32);
|
||||
const miniMaxLevelCount = Math.max(1, ...graphLevels.map((level) => level?.length ?? 0));
|
||||
const miniGraphWidth = Math.max(
|
||||
560,
|
||||
fullPage ? 1120 : 560,
|
||||
miniPadding * 2 + miniSideLaneWidth + miniMaxLevelCount * miniNodeWidth + Math.max(0, miniMaxLevelCount - 1) * miniColumnGap
|
||||
);
|
||||
const miniGraphHeight = Math.max(
|
||||
660,
|
||||
fullPage ? 720 : 660,
|
||||
miniPadding * 2 + graphLevels.length * miniNodeHeight + Math.max(0, graphLevels.length - 1) * miniRowGap
|
||||
);
|
||||
const miniNodePositions = Object.fromEntries(
|
||||
@@ -1560,9 +1568,8 @@ function RelationTreePanel({ programs, onProgramClick, onOpenRelationPopup, side
|
||||
]);
|
||||
})
|
||||
);
|
||||
const mapOffset = Math.max(0, Math.round((miniGraphWidth - miniViewportWidth) / 2));
|
||||
const mapOffset = fullPage ? 0 : Math.max(0, Math.round((miniGraphWidth - miniViewportWidth) / 2));
|
||||
const canShrinkSidebar = resolvedSidebarWidth > sidebarSizes[0];
|
||||
const canGrowSidebar = resolvedSidebarWidth < sidebarSizes[sidebarSizes.length - 1];
|
||||
const setSidebarSize = (size) => {
|
||||
onSidebarWidthChange?.(size);
|
||||
};
|
||||
@@ -1651,24 +1658,26 @@ function RelationTreePanel({ programs, onProgramClick, onOpenRelationPopup, side
|
||||
};
|
||||
|
||||
return (
|
||||
<aside className="sticky top-5 max-h-[calc(100vh-40px)] overflow-y-auto overflow-x-hidden rounded-[26px] border border-white/75 bg-white/75 p-3.5 shadow-sm backdrop-blur transition-[width] duration-300">
|
||||
<aside className={`${fullPage ? 'min-h-screen overflow-auto rounded-none border-0 bg-white/80 p-5' : 'sticky top-5 max-h-[calc(100vh-40px)] overflow-y-auto overflow-x-hidden rounded-[26px] border border-white/75 bg-white/75 p-3.5 shadow-sm'} backdrop-blur transition-[width] duration-300`}>
|
||||
<div className="mb-4 flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-[11px] font-black uppercase tracking-wide text-blue-700">Program Map</p>
|
||||
<h2 className="mt-1 text-lg font-black text-slate-950">프로그램 연결</h2>
|
||||
<h2 className={`${fullPage ? 'text-2xl' : 'text-lg'} mt-1 font-black text-slate-950`}>프로그램 연결</h2>
|
||||
<p className="mt-1 text-xs font-semibold leading-5 text-slate-500">
|
||||
필요할 때 연결도 영역을 크게 펼칩니다.
|
||||
{fullPage ? '연결 관계를 큰 화면에서 확인합니다.' : '필요할 때 연결도 영역을 새창으로 크게 펼칩니다.'}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOpenRelationPopup}
|
||||
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-amber-100 text-base font-black text-amber-700 ring-1 ring-amber-200 hover:bg-amber-200"
|
||||
title="프로그램 연결도 및 관계 수정"
|
||||
aria-label="프로그램 연결도 및 관계 수정"
|
||||
>
|
||||
!
|
||||
</button>
|
||||
{!fullPage && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOpenRelationPopup}
|
||||
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-amber-100 text-base font-black text-amber-700 ring-1 ring-amber-200 hover:bg-amber-200"
|
||||
title="프로그램 연결도 및 관계 수정"
|
||||
aria-label="프로그램 연결도 및 관계 수정"
|
||||
>
|
||||
!
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="rounded-3xl bg-white/70 p-2 shadow-inner ring-1 ring-slate-100">
|
||||
<div className="mb-2 flex items-center justify-between gap-2 px-1">
|
||||
@@ -1682,6 +1691,7 @@ function RelationTreePanel({ programs, onProgramClick, onOpenRelationPopup, side
|
||||
상용
|
||||
</span>
|
||||
</div>
|
||||
{!fullPage && (
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex shrink-0 items-center gap-1">
|
||||
<button
|
||||
@@ -1696,21 +1706,21 @@ function RelationTreePanel({ programs, onProgramClick, onOpenRelationPopup, side
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!canGrowSidebar}
|
||||
onClick={() => setSidebarSize(sidebarSizes[sidebarSizes.length - 1])}
|
||||
className="flex h-7 w-7 items-center justify-center rounded-full bg-white text-slate-500 shadow-sm ring-1 ring-slate-200 hover:bg-slate-50 disabled:opacity-25"
|
||||
aria-label="연결도 영역 최대"
|
||||
title="연결도 영역 최대"
|
||||
>
|
||||
<Maximize2 className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
onClick={onOpenMapWindow}
|
||||
className="flex h-7 w-7 items-center justify-center rounded-full bg-white text-slate-500 shadow-sm ring-1 ring-slate-200 hover:bg-slate-50 disabled:opacity-25"
|
||||
aria-label="연결도 새창으로 보기"
|
||||
title="연결도 새창으로 보기"
|
||||
>
|
||||
<Maximize2 className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{relations.length > 0 ? (
|
||||
<div className="relative h-[660px] overflow-hidden rounded-2xl bg-gradient-to-br from-slate-50 to-white ring-1 ring-slate-100">
|
||||
<div className={`${fullPage ? 'h-[calc(100vh-150px)] overflow-auto' : 'h-[660px] overflow-hidden'} relative rounded-2xl bg-gradient-to-br from-slate-50 to-white ring-1 ring-slate-100`}>
|
||||
<div
|
||||
className="absolute left-0 top-0 transition-transform duration-300 ease-out"
|
||||
className={`${fullPage ? 'relative' : 'absolute left-0 top-0 transition-transform duration-300 ease-out'}`}
|
||||
style={{
|
||||
width: miniGraphWidth,
|
||||
height: miniGraphHeight,
|
||||
@@ -1832,6 +1842,7 @@ function RelationTreePanel({ programs, onProgramClick, onOpenRelationPopup, side
|
||||
export default function App() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const isDetailWindow = urlParams.get('view') === 'program-detail' || urlParams.get('view') === 'cheonjiin-detail';
|
||||
const isRelationMapWindow = urlParams.get('view') === 'relation-map';
|
||||
const detailProgramId = urlParams.get('program') ?? 'cheonjiin';
|
||||
const [programStates, setProgramStates] = useState(readStoredProgramStates);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
@@ -2479,6 +2490,14 @@ export default function App() {
|
||||
);
|
||||
};
|
||||
|
||||
const openRelationMapWindow = () => {
|
||||
window.open(
|
||||
'/?view=relation-map',
|
||||
'program-relation-map',
|
||||
'popup=yes,width=1280,height=920,left=120,top=40,resizable=yes,scrollbars=yes'
|
||||
);
|
||||
};
|
||||
|
||||
if (isDetailWindow) {
|
||||
const detailProgram = programs.find((program) => program.id === detailProgramId) ?? programs[0];
|
||||
const storedProgram = getStoredProgram(detailProgram.id);
|
||||
@@ -2506,6 +2525,21 @@ export default function App() {
|
||||
);
|
||||
}
|
||||
|
||||
if (isRelationMapWindow) {
|
||||
return (
|
||||
<main className="min-h-screen bg-[radial-gradient(circle_at_top_left,#dffcf4_0,#f7f8fa_34%,#eef2ff_100%)] text-slate-900">
|
||||
<RelationTreePanel
|
||||
programs={programs}
|
||||
onProgramClick={openProgramWindow}
|
||||
onOpenRelationPopup={() => setIsRelationPopupOpen(true)}
|
||||
sidebarWidth={1280}
|
||||
onSidebarWidthChange={() => {}}
|
||||
fullPage
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-[radial-gradient(circle_at_top_left,#dffcf4_0,#f7f8fa_34%,#eef2ff_100%)] text-slate-900">
|
||||
<div className="mx-auto max-w-[1760px] space-y-4 px-3 py-4">
|
||||
@@ -2540,6 +2574,7 @@ export default function App() {
|
||||
programs={programs}
|
||||
onProgramClick={openProgramWindow}
|
||||
onOpenRelationPopup={() => setIsRelationPopupOpen(true)}
|
||||
onOpenMapWindow={openRelationMapWindow}
|
||||
sidebarWidth={sidebarWidth}
|
||||
onSidebarWidthChange={setSidebarWidth}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user