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 [expandedProgramIds, setExpandedProgramIds] = useState(() => new Set(programs.map((program) => program.id)));
|
||||||
const programMap = Object.fromEntries(programs.map((program) => [program.id, program]));
|
const programMap = Object.fromEntries(programs.map((program) => [program.id, program]));
|
||||||
const relations = programs.flatMap((program) =>
|
const relations = programs.flatMap((program) =>
|
||||||
@@ -1536,14 +1544,14 @@ function RelationTreePanel({ programs, onProgramClick, onOpenRelationPopup, side
|
|||||||
const miniSideLaneWidth = 96;
|
const miniSideLaneWidth = 96;
|
||||||
const sidebarSizes = [280, 420, 560];
|
const sidebarSizes = [280, 420, 560];
|
||||||
const resolvedSidebarWidth = sidebarWidth ?? sidebarSizes[0];
|
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 miniMaxLevelCount = Math.max(1, ...graphLevels.map((level) => level?.length ?? 0));
|
||||||
const miniGraphWidth = Math.max(
|
const miniGraphWidth = Math.max(
|
||||||
560,
|
fullPage ? 1120 : 560,
|
||||||
miniPadding * 2 + miniSideLaneWidth + miniMaxLevelCount * miniNodeWidth + Math.max(0, miniMaxLevelCount - 1) * miniColumnGap
|
miniPadding * 2 + miniSideLaneWidth + miniMaxLevelCount * miniNodeWidth + Math.max(0, miniMaxLevelCount - 1) * miniColumnGap
|
||||||
);
|
);
|
||||||
const miniGraphHeight = Math.max(
|
const miniGraphHeight = Math.max(
|
||||||
660,
|
fullPage ? 720 : 660,
|
||||||
miniPadding * 2 + graphLevels.length * miniNodeHeight + Math.max(0, graphLevels.length - 1) * miniRowGap
|
miniPadding * 2 + graphLevels.length * miniNodeHeight + Math.max(0, graphLevels.length - 1) * miniRowGap
|
||||||
);
|
);
|
||||||
const miniNodePositions = Object.fromEntries(
|
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 canShrinkSidebar = resolvedSidebarWidth > sidebarSizes[0];
|
||||||
const canGrowSidebar = resolvedSidebarWidth < sidebarSizes[sidebarSizes.length - 1];
|
|
||||||
const setSidebarSize = (size) => {
|
const setSidebarSize = (size) => {
|
||||||
onSidebarWidthChange?.(size);
|
onSidebarWidthChange?.(size);
|
||||||
};
|
};
|
||||||
@@ -1651,24 +1658,26 @@ function RelationTreePanel({ programs, onProgramClick, onOpenRelationPopup, side
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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 className="mb-4 flex items-start justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-[11px] font-black uppercase tracking-wide text-blue-700">Program Map</p>
|
<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">
|
<p className="mt-1 text-xs font-semibold leading-5 text-slate-500">
|
||||||
필요할 때 연결도 영역을 크게 펼칩니다.
|
{fullPage ? '연결 관계를 큰 화면에서 확인합니다.' : '필요할 때 연결도 영역을 새창으로 크게 펼칩니다.'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
{!fullPage && (
|
||||||
type="button"
|
<button
|
||||||
onClick={onOpenRelationPopup}
|
type="button"
|
||||||
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"
|
onClick={onOpenRelationPopup}
|
||||||
title="프로그램 연결도 및 관계 수정"
|
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"
|
||||||
aria-label="프로그램 연결도 및 관계 수정"
|
title="프로그램 연결도 및 관계 수정"
|
||||||
>
|
aria-label="프로그램 연결도 및 관계 수정"
|
||||||
!
|
>
|
||||||
</button>
|
!
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-3xl bg-white/70 p-2 shadow-inner ring-1 ring-slate-100">
|
<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">
|
<div className="mb-2 flex items-center justify-between gap-2 px-1">
|
||||||
@@ -1682,6 +1691,7 @@ function RelationTreePanel({ programs, onProgramClick, onOpenRelationPopup, side
|
|||||||
상용
|
상용
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{!fullPage && (
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<div className="flex shrink-0 items-center gap-1">
|
<div className="flex shrink-0 items-center gap-1">
|
||||||
<button
|
<button
|
||||||
@@ -1696,21 +1706,21 @@ function RelationTreePanel({ programs, onProgramClick, onOpenRelationPopup, side
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={!canGrowSidebar}
|
onClick={onOpenMapWindow}
|
||||||
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"
|
||||||
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="연결도 새창으로 보기"
|
||||||
aria-label="연결도 영역 최대"
|
title="연결도 새창으로 보기"
|
||||||
title="연결도 영역 최대"
|
>
|
||||||
>
|
<Maximize2 className="h-3.5 w-3.5" />
|
||||||
<Maximize2 className="h-3.5 w-3.5" />
|
</button>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{relations.length > 0 ? (
|
{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
|
<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={{
|
style={{
|
||||||
width: miniGraphWidth,
|
width: miniGraphWidth,
|
||||||
height: miniGraphHeight,
|
height: miniGraphHeight,
|
||||||
@@ -1832,6 +1842,7 @@ function RelationTreePanel({ programs, onProgramClick, onOpenRelationPopup, side
|
|||||||
export default function App() {
|
export default function App() {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const isDetailWindow = urlParams.get('view') === 'program-detail' || urlParams.get('view') === 'cheonjiin-detail';
|
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 detailProgramId = urlParams.get('program') ?? 'cheonjiin';
|
||||||
const [programStates, setProgramStates] = useState(readStoredProgramStates);
|
const [programStates, setProgramStates] = useState(readStoredProgramStates);
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
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) {
|
if (isDetailWindow) {
|
||||||
const detailProgram = programs.find((program) => program.id === detailProgramId) ?? programs[0];
|
const detailProgram = programs.find((program) => program.id === detailProgramId) ?? programs[0];
|
||||||
const storedProgram = getStoredProgram(detailProgram.id);
|
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 (
|
return (
|
||||||
<main className="min-h-screen bg-[radial-gradient(circle_at_top_left,#dffcf4_0,#f7f8fa_34%,#eef2ff_100%)] text-slate-900">
|
<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">
|
<div className="mx-auto max-w-[1760px] space-y-4 px-3 py-4">
|
||||||
@@ -2540,6 +2574,7 @@ export default function App() {
|
|||||||
programs={programs}
|
programs={programs}
|
||||||
onProgramClick={openProgramWindow}
|
onProgramClick={openProgramWindow}
|
||||||
onOpenRelationPopup={() => setIsRelationPopupOpen(true)}
|
onOpenRelationPopup={() => setIsRelationPopupOpen(true)}
|
||||||
|
onOpenMapWindow={openRelationMapWindow}
|
||||||
sidebarWidth={sidebarWidth}
|
sidebarWidth={sidebarWidth}
|
||||||
onSidebarWidthChange={setSidebarWidth}
|
onSidebarWidthChange={setSidebarWidth}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user