From f36184c378d73a5ecc835a2a2515c565f453e4cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Correnson?= <loic.correnson@cea.fr> Date: Mon, 11 Mar 2024 16:55:21 +0100 Subject: [PATCH] [ivette/lab] history stack --- ivette/src/renderer/Laboratory.tsx | 200 ++++++++++++++++++++--------- 1 file changed, 136 insertions(+), 64 deletions(-) diff --git a/ivette/src/renderer/Laboratory.tsx b/ivette/src/renderer/Laboratory.tsx index 6ef86087ccc..f4fbb327ab3 100644 --- a/ivette/src/renderer/Laboratory.tsx +++ b/ivette/src/renderer/Laboratory.tsx @@ -43,19 +43,19 @@ import * as Ext from './Extensions'; type viewId = string; -interface Scroll { H: number, V: number } +interface Split { H: number, V: number } interface Layout { A: compId, B: compId, C: compId, D: compId } interface TabViewState { viewId: viewId, custom: number, /* -1: transient, 0: favorite, n: custom */ - scroll: Scroll, - layout: Layout, + split: Split, + stack: Layout[], /* current at index 0 */ } interface LabViewState { - scroll: Scroll; - layout: Layout; + split: Split; + stack: Layout[]; panels: Set<compId>; docked: Map<compId, LayoutPosition>; tabs: TabViewState[]; @@ -64,12 +64,12 @@ interface LabViewState { sideComp: compId; // from Sidebar selection } -const defaultScroll: Scroll = { H: 0.5, V: 0.5 }; +const defaultSplit: Split = { H: 0.5, V: 0.5 }; const defaultLayout: Layout = { A: '', B: '', C: '', D: '' }; const LAB = new States.GlobalState<LabViewState>({ - scroll: defaultScroll, - layout: defaultLayout, + split: defaultSplit, + stack: [defaultLayout], panels: new Set(), docked: new Map(), tabs: [], @@ -82,7 +82,7 @@ const LAB = new States.GlobalState<LabViewState>({ /* --- Layout Utilities --- */ /* -------------------------------------------------------------------------- */ -function compareScroll(p: Scroll, q: Scroll): boolean { +function compareSplit(p: Split, q: Split): boolean { return p.H === q.H && p.V === q.V; } @@ -95,23 +95,32 @@ function compareLayout(u: Layout, v: Layout): boolean { ); } -function removeLayoutComponent(layout: Layout, compId: compId): Layout +function isDefined(m: Layout): boolean { - const { A, B, C, D } = layout; - return { - A: A !== compId ? A : '', - B: B !== compId ? B : '', - C: C !== compId ? C : '', - D: D !== compId ? D : '', - }; + return !!m.A || !!m.B || !!m.C || !!m.D; } -function addLayoutComponent( - layout: Layout, compId: compId, p: LayoutPosition +function isComplete(m: Layout): boolean +{ + return !m.A && !m.B && !m.C && !m.D; +} + +const removeLayout = (compId: compId) => (layout: Layout) : Layout => + { + const { A, B, C, D } = layout; + return { + A: A !== compId ? A : '', + B: B !== compId ? B : '', + C: C !== compId ? C : '', + D: D !== compId ? D : '', + }; + }; + +function addLayout( + layout: Layout, compId: compId, at: LayoutPosition ): Layout { - layout = removeLayoutComponent(layout, compId); - switch(p) { + switch(at) { case 'A': return { ...layout, A: compId }; case 'B': return { ...layout, B: compId }; case 'C': return { ...layout, C: compId }; @@ -140,6 +149,42 @@ function makeViewLayout(view: Ivette.Layout): Layout return { A, B, C, D }; } +function unstackLayout( + layout: Layout, + stack: Layout[], +): Layout[] +{ + let k = 1; + while( !isComplete(layout) && k < stack.length ) { + const layer = stack[k]; + layout = { + A: layout.A || layer.A, + B: layout.B || layer.B, + C: layout.C || layer.C, + D: layout.D || layer.D, + }; + k++; + } + return [layout, ... stack]; +} + +function addLayoutComponent( + stack: Layout[], + compId: compId, + at: LayoutPosition +): Layout[] +{ + stack = stack.map(removeLayout(compId)).filter(isDefined); + const layout = addLayout(stack[0], compId, at); + return unstackLayout(layout, stack.slice(1)); +} + +function removeLayoutComponent(stack: Layout[], compId: compId): Layout[] +{ + stack = stack.map(removeLayout(compId)).filter(isDefined); + return unstackLayout(stack[0], stack.slice(1)); +} + function completeLayout(m: Layout): Layout { const { A, B, C, D } = m; @@ -202,8 +247,8 @@ function newTab( { return tabs.concat({ viewId: view.id, custom, - scroll: defaultScroll, - layout: makeViewLayout(view.layout) + split: defaultSplit, + stack: [makeViewLayout(view.layout)], }); } @@ -214,8 +259,8 @@ function saveTab( const oldIndex = oldState.tabIndex; const toSave = newTabs[oldIndex]; if (toSave !== undefined) { - const { layout, scroll } = oldState; - newTabs[oldIndex] = { ...toSave, layout, scroll }; + const { stack, split } = oldState; + newTabs[oldIndex] = { ...toSave, stack, split }; } } @@ -259,18 +304,22 @@ function setCurrentComp(compId: compId = ''):void { function applyTab(index = -1): void { const state = LAB.getValue(); const old = state.tabIndex; - if (old === index) return; const tab = state.tabs[index]; if (tab === undefined) return; - const { viewId, layout, scroll } = tab; + const { viewId, stack, split } = tab; + if (old === index) { + LAB.setValue({ ...state, sideView: viewId }); + return; + } const tabs = [...state.tabs]; + const layout = stack[0]; saveTab(tabs, state); const panels = addPanels(state.panels, layout); LAB.setValue({ ...state, panels, - layout, - scroll, + stack, + split, tabs, tabIndex: index, sideView: viewId, @@ -291,8 +340,9 @@ function applyView(view: Ivette.ViewLayoutProps): void { const tabIndex = tabs.length - 1; saveTab(tabs, state); LAB.setValue({ - panels, layout, - scroll: state.scroll, + panels, + split: defaultSplit, + stack: [layout], docked: state.docked, tabs, tabIndex, @@ -302,6 +352,21 @@ function applyView(view: Ivette.ViewLayoutProps): void { } } +function restoreDefault(view: Ivette.ViewLayoutProps): void { + const state = LAB.getValue(); + const viewId = view.id; + const index = findTab(state.tabs, viewId); + const layout = makeViewLayout(view.layout); + const tab = state.tabs[index]; + const newTabs = [...state.tabs]; + newTabs[index] = { ...tab, stack: [layout] }; + if (index === state.tabIndex) { + LAB.setValue({ ...state, tabs: newTabs, stack: [layout] }); + } else { + LAB.setValue({ ...state, tabs: newTabs }); + } +} + function applyFavorite(view: Ivette.ViewLayoutProps, favorite: boolean): void { const state = LAB.getValue(); const index = findTab(state.tabs, view.id); @@ -324,31 +389,31 @@ function applyComponent( const state = LAB.getValue(); const { id, preferredPosition } = comp; const pos = at ?? preferredPosition ?? 'D'; - const layout = addLayoutComponent(state.layout, id, pos); + const stack = addLayoutComponent(state.stack, id, pos); const panels = copySet(state.panels).add(id); - LAB.setValue({ ...state, panels, layout, sideView: '', sideComp: id }); + LAB.setValue({ ...state, panels, stack, sideView: '', sideComp: id }); } function dockComponent(comp: Ivette.ComponentProps): void { const { id, preferredPosition } = comp; const state = LAB.getValue(); - const pos = getLayoutPosition(state.layout, id) ?? preferredPosition ?? 'D'; - const layout = removeLayoutComponent(state.layout, id); + const pos = getLayoutPosition(state.stack[0], id) ?? preferredPosition ?? 'D'; + const stack = removeLayoutComponent(state.stack, id); const docked = copyMap(state.docked).set(id, pos); - LAB.setValue({ ...state, docked, layout, sideView: '', sideComp: id }); + LAB.setValue({ ...state, docked, stack, sideView: '', sideComp: id }); } function closeComponent(compId: compId): void { const state = LAB.getValue(); - const layout = removeLayoutComponent(state.layout, compId); + const stack = removeLayoutComponent(state.stack, compId); const panels = copySet(state.panels); const docked = copyMap(state.docked); panels.delete(compId); docked.delete(compId); LAB.setValue({ - ...state, panels, docked, layout, sideView: '', sideComp: compId + ...state, panels, docked, stack, sideView: '', sideComp: compId }); } @@ -445,7 +510,7 @@ function LayoutMenu(): JSX.Element | null { const [state] = States.useGlobalState(LAB); const [panelWidth, setWidth] = React.useState(80); const [panelHeight, setHeight] = React.useState(80); - const layout = state.layout; + const layout = state.stack[0]; const { compId, dock, close } = menu; const display = compId !== ''; @@ -559,11 +624,11 @@ export function LabView(): JSX.Element { const [state] = States.useGlobalState(LAB); const setPosition = React.useCallback( - (H, V) => LAB.setValue({ ...state, scroll: { H, V } }), + (H, V) => LAB.setValue({ ...state, split: { H, V } }), [state] ); - const { A, B, C, D } = completeLayout(state.layout); - const { H, V } = state.scroll; + const { A, B, C, D } = completeLayout(state.stack[0]); + const { H, V } = state.split; const panels : JSX.Element[] = []; state.panels.forEach((id) => panels.push(<Pane key={id} compId={id}/>)); return ( @@ -588,11 +653,11 @@ interface ViewItemProps { selected: boolean; displayed: boolean; layout: Layout | undefined; - scroll: Scroll | undefined; + split: Split | undefined; } function ViewItem(props: ViewItemProps): JSX.Element { - const { view, favorite, displayed, selected, scroll, layout } = props; + const { view, favorite, displayed, selected, split, layout } = props; const { id, label: vname, title: vtitle } = view; const onSelection = (_evt:React.MouseEvent): void => { @@ -601,24 +666,26 @@ function ViewItem(props: ViewItemProps): JSX.Element { const icon = favorite ? 'FAVORITE' : 'DISPLAY'; const modified = - (scroll !== undefined && - !compareScroll(scroll, defaultScroll)) || + (split !== undefined && + !compareSplit(split, defaultSplit)) || (layout !== undefined && !compareLayout(layout, makeViewLayout(view.layout))); const label = modified ? vname + '*' : vname; - const title = modified ? (vtitle ?? vname) + ' (modified)' : vtitle; + const tname = vtitle || vname; + const title = modified ? tname + ' (modified)' : tname; const onContextMenu = (): void => { setCurrentView(id); const onDisplay = (): void => applyView(view); const onFavorite = (): void => applyFavorite(view, !favorite); + const onRestore = (): void => restoreDefault(view); const favAction = !favorite ? 'Add to Favorite' : 'Remove from Favorite'; Dome.popupMenu([ { label: 'Display View', enabled: !displayed, onClick: onDisplay }, { label: favAction, onClick: onFavorite }, - { label: 'Duplicate View' }, - { label: 'Restore Default', enabled: modified }, + // { label: 'Duplicate View' }, + { label: 'Restore Default', enabled: modified, onClick: onRestore }, ]); }; @@ -645,15 +712,16 @@ function ViewSection(): JSX.Element { const index = findTab(state.tabs, id); const favorite = 0 <= index && tabs[index].custom === 0; const displayed = 0 <= index && index === tabIndex; - const layout = displayed ? state.layout : undefined; - const scroll = displayed ? state.scroll : undefined; + const tab = 0 <= index ? tabs[index] : undefined; + const layout = displayed ? state.stack[0] : tab?.stack[0]; + const split = displayed ? state.split : tab?.split; return ( <ViewItem key={id} view={view} favorite={favorite} layout={layout} - scroll={scroll} + split={split} displayed={displayed} selected={id === state.sideView} /> ); @@ -686,7 +754,7 @@ function ComponentItem(props: ComponentItemProps): JSX.Element { const icon = position ? 'QSPLIT.' + position : docked ? 'QSPLIT.DOCK' : - active ? 'EXECUTE' : 'COMPONENT'; + 'COMPONENT'; const status = position ? 'Visible' : @@ -738,7 +806,8 @@ function GroupSection(props: GroupSectionProps): JSX.Element | null { const { id, label, title, filter } = props; const settings = 'ivette.sidebar.group.' + id; const components = Ext.useElements(COMPONENT).filter(filter) ?? []; - const [{ panels, docked, sideComp, layout }] = States.useGlobalState(LAB); + const [{ panels, docked, sideComp, stack }] = States.useGlobalState(LAB); + const layout = stack[0]; const items = components.map((comp) => { const { id } = comp; return ( @@ -846,10 +915,10 @@ function DockItem(props: DockItemProps): JSX.Element | null { } export function Dock(): JSX.Element { - const [{ docked, layout }] = States.useGlobalState(LAB); + const [{ docked, stack }] = States.useGlobalState(LAB); const items: JSX.Element[] = []; docked.forEach((pos, compId) => { - const curr = getLayoutPosition(layout, compId); + const curr = getLayoutPosition(stack[0], compId); items.push( <DockItem key={compId} @@ -870,19 +939,19 @@ interface TabViewProps { index: number; tabIndex: number; layout: Layout; - scroll: Scroll; + split: Split; } function TabView(props: TabViewProps): JSX.Element | null { const { tab, index, tabIndex } = props; const { viewId, custom } = tab; const view = Ext.useElement(VIEW, viewId); - if (!view || custom < 0) return null; + if (!view /* || custom < 0*/) return null; const selected = index === tabIndex; - const layout = selected ? props.layout : tab.layout; - const scroll = selected ? props.scroll : tab.scroll; + const layout = selected ? props.layout : tab.stack[0]; + const split = selected ? props.split : tab.split; const modified = - !compareScroll(scroll, defaultScroll) || + !compareSplit(split, defaultSplit) || !compareLayout(layout, makeViewLayout(view.layout)); const vname = view.label; const tname = custom > 0 ? `${vname} — ${custom}` : vname; @@ -890,7 +959,10 @@ function TabView(props: TabViewProps): JSX.Element | null { const tdup = custom > 0 ? 'Custom ' : ''; const tmod = modified ? ' (modified)': ''; const title = tdup + vname + tmod; - const icon = selected ? 'FAVORITE' : 'STAR'; + const icon = + (custom < 0) ? 'DISPLAY' : + selected ? 'FAVORITE' : + 'STAR'; return ( <Toolbar.Button className='labview-tab' @@ -912,8 +984,8 @@ export function Tabs(): JSX.Element { tab={tab} index={k} tabIndex={state.tabIndex} - layout={state.layout} - scroll={state.scroll} + layout={state.stack[0]} + split={state.split} /> )); return <>{items}</>; -- GitLab