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

Add zen mode #594

Merged
merged 2 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 0 additions & 11 deletions src/web/topic/components/Edge/ScoreEdge.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,7 @@ const divOptions = {
};

export const StyledDiv = styled("div", divOptions)<DivProps>`
pointer-events: all;
cursor: default;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: white;
border: 1px solid ${edgeColor};
border-radius: 15%;
padding: 4px;
${({ labelX, labelY }) => css`
position: absolute;
Expand Down
9 changes: 9 additions & 0 deletions src/web/topic/components/Edge/ScoreEdge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { useUserCanEditTopicData } from "@/web/topic/store/userHooks";
import { Edge } from "@/web/topic/utils/graph";
import { useUnrestrictedEditing } from "@/web/view/actionConfigStore";
import { setSelected, useDrawSimpleEdgePaths } from "@/web/view/currentViewStore/store";
import { useZenMode } from "@/web/view/userConfigStore";

const flowMarkerId = "flowMarker";
const nonFlowMarkerId = "nonFlowMarker";
Expand Down Expand Up @@ -95,7 +96,9 @@ interface Props {
export const ScoreEdge = ({ inReactFlow, ...flowEdge }: EdgeProps & Props) => {
const { sessionUser } = useSessionUser();
const userCanEditTopicData = useUserCanEditTopicData(sessionUser?.username);

const unrestrictedEditing = useUnrestrictedEditing();
const zenMode = useZenMode();
const drawSimpleEdgePaths = useDrawSimpleEdgePaths();

const edge = convertToEdge(flowEdge);
Expand Down Expand Up @@ -149,6 +152,12 @@ export const ScoreEdge = ({ inReactFlow, ...flowEdge }: EdgeProps & Props) => {
onClick={() => setSelected(edge.id)}
onContextMenu={(event) => openContextMenu(event, { edge })}
spotlight={spotlight}
className={
// pointer-events and cursor are set because this div is within an SVG and doesn't handle pointer-events properly by default
"[pointer-events:all] cursor-default flex flex-col items-center justify-center bg-white p-1 rounded-xl" +
// during zenMode, div only contains the label text (no indicators), so border doesn't seem necessary (if this looks awkward, we can always show border instead)
(zenMode && spotlight === "normal" ? " border-none" : "")
}
>
<CommonIndicators graphPart={edge} notes={edge.data.notes} />
<Typography
Expand Down
25 changes: 18 additions & 7 deletions src/web/topic/components/Indicator/CommonIndicators.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
import { Stack } from "@mui/material";
import { memo } from "react";
import { memo, useContext } from "react";

import { ContextIndicator } from "@/web/topic/components/Indicator/ContextIndicator";
import { CriteriaTableIndicator } from "@/web/topic/components/Indicator/CriteriaTableIndicator";
import { DetailsIndicator } from "@/web/topic/components/Indicator/DetailsIndicator";
import { Score } from "@/web/topic/components/Score/Score";
import { WorkspaceContext } from "@/web/topic/components/TopicWorkspace/WorkspaceContext";
import { GraphPart } from "@/web/topic/utils/graph";
import { useZenMode } from "@/web/view/userConfigStore";

interface Props {
graphPart: GraphPart;
notes: string;
}

const CommonIndicatorsBase = ({ graphPart, notes }: Props) => {
const zenMode = useZenMode();
const workspaceContext = useContext(WorkspaceContext);

return (
<Stack direction="row" margin="2px" spacing="2px">
{/* TODO: should this be moved because it's not used for all graph parts? */}
<ContextIndicator graphPart={graphPart} />
{/* TODO: should this be moved because it's only used for problem? */}
<CriteriaTableIndicator nodeId={graphPart.id} />
<DetailsIndicator graphPartId={graphPart.id} notes={notes} />
<Score graphPartId={graphPart.id} />
{!zenMode && (
<>
{/* TODO: should this be moved because it's not used for all graph parts? */}
<ContextIndicator graphPart={graphPart} />
{/* TODO: should this be moved because it's only used for problem? */}
<CriteriaTableIndicator nodeId={graphPart.id} />
<DetailsIndicator graphPartId={graphPart.id} notes={notes} />
</>
)}

{/* table's purpose is mainly for scores, so show scores there even if in zen mode */}
{(!zenMode || workspaceContext === "table") && <Score graphPartId={graphPart.id} />}
</Stack>
);
};
Expand Down
4 changes: 4 additions & 0 deletions src/web/topic/components/Indicator/ContentIndicators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { FoundResearchIndicator } from "@/web/topic/components/Indicator/FoundRe
import { JustificationIndicator } from "@/web/topic/components/Indicator/JustificationIndicator";
import { QuestionIndicator } from "@/web/topic/components/Indicator/QuestionIndicator";
import { GraphPartType } from "@/web/topic/utils/graph";
import { useZenMode } from "@/web/view/userConfigStore";

interface Props {
graphPartId: string;
Expand All @@ -15,6 +16,9 @@ interface Props {
}

const ContentIndicatorsBase = ({ graphPartId, graphPartType, color, className }: Props) => {
const zenMode = useZenMode();
if (zenMode) return <></>;

return (
<Stack direction="row" margin="2px" spacing="2px" className={className}>
<QuestionIndicator graphPartId={graphPartId} partColor={color} />
Expand Down
5 changes: 4 additions & 1 deletion src/web/topic/components/Indicator/StatusIndicators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { type ButtonProps, Stack } from "@mui/material";
import { memo } from "react";

import { ForceShownIndicator } from "@/web/topic/components/Indicator/ForceShownIndicator";
import { useIndicateWhenNodeForcedToShow } from "@/web/view/userConfigStore";
import { useIndicateWhenNodeForcedToShow, useZenMode } from "@/web/view/userConfigStore";

interface Props {
graphPartId: string;
Expand All @@ -15,6 +15,9 @@ interface Props {
*/
const StatusIndicatorsBase = ({ graphPartId, color, className }: Props) => {
const indicateWhenNodeForcedToShow = useIndicateWhenNodeForcedToShow();
const zenMode = useZenMode();

if (zenMode) return <></>;

return (
<Stack direction="row" margin="2px" spacing="2px" className={className}>
Expand Down
4 changes: 3 additions & 1 deletion src/web/topic/components/Node/NodeHandle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Node, RelationDirection } from "@/web/topic/utils/graph";
import { Orientation } from "@/web/topic/utils/layout";
import { nodeDecorations } from "@/web/topic/utils/node";
import { showNode } from "@/web/view/currentViewStore/filter";
import { useZenMode } from "@/web/view/userConfigStore";

const NodeSummary = ({ node, beforeSlot }: { node: Node; beforeSlot?: ReactNode }) => {
const { NodeIcon, title } = nodeDecorations[node.type];
Expand All @@ -37,6 +38,7 @@ const NodeHandleBase = ({ node, direction, orientation }: Props) => {
const { sessionUser } = useSessionUser();
const userCanEditTopicData = useUserCanEditTopicData(sessionUser?.username);

const zenMode = useZenMode();
const neighborsInDirection = useNeighborsInDirection(node.id, direction);
const hiddenNeighbors = useHiddenNodes(neighborsInDirection);

Expand All @@ -47,7 +49,7 @@ const NodeHandleBase = ({ node, direction, orientation }: Props) => {
});

const hasHiddenNeighbors = sortedHiddenNeighbors.length > 0;
const showHandle = userCanEditTopicData || hasHiddenNeighbors;
const showHandle = !zenMode && (userCanEditTopicData || hasHiddenNeighbors);

const type = direction === "parent" ? "target" : "source";

Expand Down
2 changes: 2 additions & 0 deletions src/web/topic/components/TopicWorkspace/TopicWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import { userCanEditScores } from "@/web/topic/utils/score";
import { getReadonlyMode, toggleReadonlyMode } from "@/web/view/actionConfigStore";
import { getSelectedGraphPart, setSelected, useFormat } from "@/web/view/currentViewStore/store";
import { getPerspectives } from "@/web/view/perspectiveStore";
import { toggleZenMode } from "@/web/view/userConfigStore";

const useWorkspaceHotkeys = (user: { username: string } | null | undefined) => {
useHotkeys([hotkeys.deselectPart], () => setSelected(null));
useHotkeys([hotkeys.zenMode], () => toggleZenMode());
useHotkeys([hotkeys.readonlyMode], () => toggleReadonlyMode());

useHotkeys([hotkeys.score], (_, hotkeysEvent) => {
Expand Down
41 changes: 28 additions & 13 deletions src/web/topic/components/TopicWorkspace/WorkspaceToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Highlight,
QuestionMark,
Redo,
SelfImprovement,
Undo,
} from "@mui/icons-material";
import { AppBar, Divider, IconButton, ToggleButton, Toolbar, Tooltip } from "@mui/material";
Expand All @@ -24,6 +25,7 @@ import { useOnPlayground } from "@/web/topic/store/topicHooks";
import { useUserCanEditTopicData } from "@/web/topic/store/userHooks";
import { redo, undo } from "@/web/topic/store/utilActions";
import { useTemporalHooks } from "@/web/topic/store/utilHooks";
import { hotkeys } from "@/web/topic/utils/hotkeys";
import {
toggleFlashlightMode,
toggleReadonlyMode,
Expand All @@ -41,6 +43,7 @@ import {
resetPerspectives,
useIsComparingPerspectives,
} from "@/web/view/perspectiveStore";
import { toggleZenMode, useZenMode } from "@/web/view/userConfigStore";

export const WorkspaceToolbar = () => {
const { sessionUser } = useSessionUser();
Expand All @@ -50,6 +53,7 @@ export const WorkspaceToolbar = () => {
const [canUndo, canRedo] = useTemporalHooks();
const [canGoBack, canGoForward] = useCanGoBackForward();

const zenMode = useZenMode();
const isComparingPerspectives = useIsComparingPerspectives();
const flashlightMode = useFlashlightMode();
const readonlyMode = useReadonlyMode();
Expand Down Expand Up @@ -137,10 +141,23 @@ export const WorkspaceToolbar = () => {
</>
)}

<Divider orientation="vertical" flexItem />

<ToggleButton
value={zenMode}
title={`Zen mode (${hotkeys.zenMode})`}
aria-label={`Zen mode (${hotkeys.zenMode})`}
color="primary"
size="small"
selected={zenMode}
onClick={() => toggleZenMode()}
className="rounded-full border-none"
>
<SelfImprovement />
</ToggleButton>

{!onPlayground && (
<>
<Divider orientation="vertical" flexItem />

<ToggleButton
value={isComparingPerspectives}
title="Compare perspectives"
Expand All @@ -151,7 +168,7 @@ export const WorkspaceToolbar = () => {
onClick={() =>
isComparingPerspectives ? resetPerspectives() : comparePerspectives()
}
sx={{ borderRadius: "50%", border: "0" }}
className="rounded-full border-none"
>
<Group />
</ToggleButton>
Expand All @@ -171,7 +188,7 @@ export const WorkspaceToolbar = () => {
size="small"
selected={flashlightMode}
onClick={() => toggleFlashlightMode(!flashlightMode)}
sx={{ borderRadius: "50%", border: "0" }}
className="hidden rounded-full border-none sm:flex" // hide on mobile because there's not enough space
>
<Highlight />
</ToggleButton>
Expand All @@ -180,13 +197,13 @@ export const WorkspaceToolbar = () => {
{readonlyMode && (
<ToggleButton
value={readonlyMode}
title="Read-only mode"
aria-label="Read-only mode"
title={`Read-only mode (${hotkeys.readonlyMode})`}
aria-label={`Read-only mode (${hotkeys.readonlyMode})`}
color="primary"
size="small"
selected={readonlyMode}
onClick={() => toggleReadonlyMode()}
sx={{ borderRadius: "50%", border: "0" }}
className="rounded-full border-none"
>
<EditOff />
</ToggleButton>
Expand Down Expand Up @@ -234,12 +251,10 @@ export const WorkspaceToolbar = () => {
<IconButton
color="error"
aria-label="Error info"
sx={{
// Don't make it look like clicking will do something, since it won't.
// Using a button here is an attempt to make it accessible, since the tooltip will show
// on focus.
cursor: "default",
}}
// Don't make it look like clicking will do something, since it won't.
// Using a button here is an attempt to make it accessible, since the tooltip will show
// on focus.
className="cursor-default"
>
<Error />
</IconButton>
Expand Down
1 change: 1 addition & 0 deletions src/web/topic/utils/hotkeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const hotkeys = {
readonlyMode: "Alt + R",
pan: "Up, Down, Left, Right",
score: "1, 2, 3, 4, 5, 6, 7, 8, 9, -",
zenMode: "Alt + Z",
zoomIn: "Ctrl + =",
zoomOut: "Ctrl + -",
};
10 changes: 10 additions & 0 deletions src/web/view/userConfigStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { create } from "zustand";
import { persist } from "zustand/middleware";

interface UserConfigStoreState {
zenMode: boolean;
fillNodesWithColor: boolean;
indicateWhenNodeForcedToShow: boolean;
}

const initialState: UserConfigStoreState = {
zenMode: false,
fillNodesWithColor: false,
indicateWhenNodeForcedToShow: false,
};
Expand All @@ -18,6 +20,10 @@ const useUserConfigStore = create<UserConfigStoreState>()(
);

// hooks
export const useZenMode = () => {
return useUserConfigStore((state) => state.zenMode);
};

export const useFillNodesWithColor = () => {
return useUserConfigStore((state) => state.fillNodesWithColor);
};
Expand All @@ -27,6 +33,10 @@ export const useIndicateWhenNodeForcedToShow = () => {
};

// actions
export const toggleZenMode = () => {
useUserConfigStore.setState((state) => ({ zenMode: !state.zenMode }));
};

export const toggleFillNodesWithColor = (fill: boolean) => {
useUserConfigStore.setState({ fillNodesWithColor: fill });
};
Expand Down
Loading