Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

collapsible side panel #88

Merged
merged 5 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion gui/src/app/SPAnalysis/SPAnalysisContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ const SPAnalysisContextProvider: FunctionComponent<PropsWithChildren<SPAnalysisC
const parsedData = deserializeAnalysisFromLocalStorage(savedState)
update({ type: 'loadInitialData', state: parsedData })
}

}, [data, queries])

return (
Expand Down
41 changes: 36 additions & 5 deletions gui/src/app/pages/HomePage/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Splitter } from "@fi-sci/splitter";
import { FunctionComponent, useCallback, useContext, useEffect, useMemo, useState } from "react";
import { FunctionComponent, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import DataFileEditor from "../../FileEditor/DataFileEditor";
import StanFileEditor from "../../FileEditor/StanFileEditor";
import RunPanel from "../../RunPanel/RunPanel";
Expand Down Expand Up @@ -30,23 +30,39 @@ const HomePage: FunctionComponent<Props> = ({ width, height }) => {
}

const HomePageChild: FunctionComponent<Props> = ({ width, height }) => {


const { data, update } = useContext(SPAnalysisContext)
const setSamplingOpts = useCallback((opts: SamplingOpts) => {
update({ type: 'setSamplingOpts', opts })
}, [update])

const [compiledMainJsUrl, setCompiledMainJsUrl] = useState<string>('')

const leftPanelWidth = Math.max(250, Math.min(340, width * 0.2))
const [leftPanelCollapsed, setLeftPanelCollapsed] = useState(determineShouldBeInitiallyCollapsed(width))
const expandedLeftPanelWidth = determineLeftPanelWidth(width) // what the width would be if expanded
const leftPanelWidth = leftPanelCollapsed ? 20 : expandedLeftPanelWidth // the actual width

// We automatically collapse the panel if user has resized the window to be
// too small but we only want to do this right when we cross the threshold,
// not every time we resize by a pixel. Similar for expanding the panel when
// we cross the threshold in the other direction.
const lastWidth = useRef(width)
useEffect(() => {
if (!determineShouldBeInitiallyCollapsed(lastWidth.current) && determineShouldBeInitiallyCollapsed(width)) {
lastWidth.current = width
setLeftPanelCollapsed(true)
}
else if (determineShouldBeInitiallyCollapsed(lastWidth.current) && !determineShouldBeInitiallyCollapsed(width)) {
lastWidth.current = width
setLeftPanelCollapsed(false)
}
}, [width])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since all we care about for lastWidth is whether it implies a collapsed state, this could probably be simplified (reducing function calls) as something along the lines of:

const isCollapsed = useRef(determineShouldBeInitiallyCollapsed(width));
useEffect(() => {
  const shouldBeCollapsed = determineShouldBeInitiallyCollapsed(width);
  if (isCollapsed.current !== shouldBeCollapsed) {
    isCollapsed.current = shouldBeCollapsed;
    setLeftPanelCollapsed(shouldBeCollapsed);
  } 
}, [width])

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think your isCollapsed variable should really be named something like lastShouldBeCollapsed. Either way it's somewhat confusing. I think I'd prefer to stick with what I have.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking more about it, I think your simplification is clearer. I essentially used your code but I used the name lastShouldBeCollapsed


const topBarHeight = 25

useEffect(() => {
document.title = "Stan Playground - " + data.meta.title;
}, [data.meta.title])


return (
<div style={{ position: 'absolute', width, height, overflow: 'hidden' }}>
<div className="top-bar" style={{ position: 'absolute', left: 0, top: 0, width, height: topBarHeight, overflow: 'hidden' }}>
Expand All @@ -58,6 +74,8 @@ const HomePageChild: FunctionComponent<Props> = ({ width, height }) => {
</div>
<div className="left-panel" style={{ position: 'absolute', left: 0, top: topBarHeight + 2, width: leftPanelWidth, height: height - topBarHeight - 2, overflow: 'auto' }}>
<LeftPanel
collapsed={leftPanelCollapsed}
onSetCollapsed={setLeftPanelCollapsed}
width={leftPanelWidth}
height={height - topBarHeight - 2}
hasUnsavedChanges={modelHasUnsavedChanges(data)}
Expand Down Expand Up @@ -100,6 +118,19 @@ const HomePageChild: FunctionComponent<Props> = ({ width, height }) => {
)
}

// the width of the left panel when it is expanded based on the overall width
const determineLeftPanelWidth = (width: number) => {
const minWidth = 250
const maxWidth = 400
return Math.min(maxWidth, Math.max(minWidth, width / 4))
}

// whether the left panel should be collapsed initially based on the overall
// width
const determineShouldBeInitiallyCollapsed = (width: number) => {
return width < 800
}

type RightViewProps = {
width: number
height: number
Expand Down
39 changes: 37 additions & 2 deletions gui/src/app/pages/HomePage/LeftPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { Hyperlink } from "@fi-sci/misc"
import { Hyperlink, SmallIconButton } from "@fi-sci/misc"
import ModalWindow, { useModalWindow } from "@fi-sci/modal-window"
import { FunctionComponent, useCallback, useContext } from "react"
import examplesStanies, { Stanie } from "../../exampleStanies/exampleStanies"
import { SPAnalysisContext } from "../../SPAnalysis/SPAnalysisContextProvider"
import ExportWindow from "./ExportWindow"
import ImportWindow from "./ImportWindow"
import { ChevronLeft, ChevronRight } from "@mui/icons-material"

type LeftPanelProps = {
width: number
height: number
hasUnsavedChanges: boolean
collapsed: boolean
onSetCollapsed: (collapsed: boolean) => void
}

const LeftPanel: FunctionComponent<LeftPanelProps> = ({ width, height, hasUnsavedChanges }) => {
const LeftPanel: FunctionComponent<LeftPanelProps> = ({ width, height, hasUnsavedChanges, collapsed, onSetCollapsed }) => {
// note: this is close enough to pass in directly if we wish
const { update } = useContext(SPAnalysisContext)

Expand All @@ -22,9 +25,21 @@ const LeftPanel: FunctionComponent<LeftPanelProps> = ({ width, height, hasUnsave

const { visible: exportVisible, handleOpen: exportOpen, handleClose: exportClose } = useModalWindow()
const { visible: importVisible, handleOpen: importOpen, handleClose: importClose } = useModalWindow()

if (collapsed) {
return (
<div style={{position: 'absolute', width, height, backgroundColor: 'lightgray', overflowY: 'auto'}}>
<div style={{margin: 5}}>
<ExpandButton onClick={() => onSetCollapsed(false)} />
</div>
</div>
)
}

return (
<div style={{position: 'absolute', width, height, backgroundColor: 'lightgray', overflowY: 'auto'}}>
<div style={{margin: 5}}>
<CollapseButton onClick={() => onSetCollapsed(true)} />
<h3>Examples</h3>
{
examplesStanies.map((stanie, i) => (
Expand Down Expand Up @@ -85,4 +100,24 @@ const LeftPanel: FunctionComponent<LeftPanelProps> = ({ width, height, hasUnsave
)
}

const ExpandButton: FunctionComponent<{ onClick: () => void }> = ({ onClick }) => {
return (
<SmallIconButton
icon={<ChevronRight />}
onClick={onClick}
title="Expand"
/>
)
}

const CollapseButton: FunctionComponent<{ onClick: () => void }> = ({ onClick }) => {
return (
<SmallIconButton
icon={<ChevronLeft />}
onClick={onClick}
title="Collapse"
/>
)
}

export default LeftPanel