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

feat: toggle node images and data fields on editor graph #3789

Merged
merged 5 commits into from
Oct 16, 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
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import { Link } from "react-navi";

import { useStore } from "../../../lib/store";
import { getParentId } from "../lib/utils";
import { DataField } from "./DataField";
import Hanger from "./Hanger";
import Node from "./Node";
import { Tag } from "./Tag";
import { Thumbnail } from "./Thumbnail";

type Props = {
type: TYPES;
Expand Down Expand Up @@ -92,9 +94,18 @@ const Checklist: React.FC<Props> = React.memo((props) => {
onContextMenu={handleContext}
ref={drag}
>
{props.data?.img && (
<Thumbnail
imageSource={props.data?.img}
imageAltText={props.data?.text}
/>
)}
{Icon && <Icon />}
<span>{props.text}</span>
</Link>
{props.data?.fn && (
<DataField value={props.data.fn} variant="parent" />
)}
jessicamcinchak marked this conversation as resolved.
Show resolved Hide resolved
{props.tags?.map((tag) => <Tag tag={tag} key={tag} />)}
</Box>
{groupedOptions ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Box from "@mui/material/Box";
import { useStore } from "pages/FlowEditor/lib/store";
import React from "react";

export const DataField: React.FC<{
value: string;
variant: "parent" | "child";
}> = ({ value, variant }) => {
const showDataFields = useStore((state) => state.showDataFields);
if (!showDataFields) return null;

return (
<Box
sx={(theme) => ({
fontFamily: theme.typography.data.fontFamily,
fontSize: theme.typography.data,
backgroundColor: "#f0f0f0",
borderColor:
variant === "parent"
? theme.palette.common.black
: theme.palette.grey[400],
borderWidth:
variant === "parent" ? "0 1px 1px 1px" : "1px 0 0 0",
borderStyle: "solid",
width: "100%",
p: 0.5,
textAlign: "center",
})}
>
{value}
</Box>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import React from "react";
import { Link } from "react-navi";

import { useStore } from "../../../lib/store";
import { DataField } from "./DataField";
import Hanger from "./Hanger";
import Node from "./Node";
import { Thumbnail } from "./Thumbnail";

const Option: React.FC<any> = (props) => {
const childNodes = useStore((state) => state.childNodesOf(props.id));
Expand All @@ -14,6 +16,7 @@ const Option: React.FC<any> = (props) => {

let background = "#666"; // no flag color
let color = "#000";

try {
const flag = flatFlags.find(({ value }) =>
[props.data?.flag, props.data?.val].filter(Boolean).includes(value),
Expand All @@ -27,8 +30,15 @@ const Option: React.FC<any> = (props) => {
className={classNames("card", "option", { wasVisited: props.wasVisited })}
>
<Link href={href} prefetch={false} onClick={(e) => e.preventDefault()}>
{props.data?.img && (
<Thumbnail
imageSource={props.data?.img}
imageAltText={props.data.text}
/>
)}
<div className="band" style={{ background, color }}></div>
<div className="text">{props.data.text}</div>
{props.data?.val && <DataField value={props.data.val} variant="child" />}
</Link>
<ol className="decisions">
{childNodes.map((child: any) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import { Link } from "react-navi";

import { useStore } from "../../../lib/store";
import { getParentId } from "../lib/utils";
import { DataField } from "./DataField";
import Hanger from "./Hanger";
import Node from "./Node";
import { Tag } from "./Tag";
import { Thumbnail } from "./Thumbnail";

type Props = {
type: TYPES | "Error";
Expand Down Expand Up @@ -80,9 +82,18 @@ const Question: React.FC<Props> = React.memo((props) => {
onContextMenu={handleContext}
ref={drag}
>
{props.data?.img && (
<Thumbnail
imageSource={props.data?.img}
imageAltText={props.data?.text}
/>
)}
{Icon && <Icon titleAccess={iconTitleAccess} />}
<span>{props.text}</span>
</Link>
{props.type !== TYPES.SetValue && props.data?.fn && (
<DataField value={props.data.fn} variant="parent" />
)}
Copy link
Member Author

@jessicamcinchak jessicamcinchak Oct 16, 2024

Choose a reason for hiding this comment

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

Two points about this:

  • SetValue as a whole already has a "data" style so we don't need to additionally show the data field band
  • props.data.fn will not catch all data fields on nodes (eg especially individual file types in FileUpload&Label, List component, etc) but it should catch all the major "top-level" data fields - which feels "good enough" for this experiment!
    • Would be a nice future exercise to apply some of Daf's thorough search mappings here for more accurate coverage, but doesn't feel like a priority right now

{props.tags?.map((tag) => <Tag tag={tag} key={tag} />)}
<ol className="options">
{childNodes.map((child: any) => (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Box from "@mui/material/Box";
import { useStore } from "pages/FlowEditor/lib/store";
import React from "react";

export const Thumbnail: React.FC<{
imageSource: string;
imageAltText?: string;
}> = ({ imageSource, imageAltText }) => {
const showImages = useStore((state) => state.showImages);
if (!showImages) return null;

return (
<Box
sx={{
maxWidth: "220px",
maxHeight: "220px",
margin: "auto",
}}
component="img"
src={imageSource}
alt={imageAltText}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import DataFieldIcon from "@mui/icons-material/Code";
import DataFieldOffIcon from "@mui/icons-material/CodeOff";
import Box from "@mui/material/Box";
import IconButton from "@mui/material/IconButton";
import Tooltip from "@mui/material/Tooltip";
import { useStore } from "pages/FlowEditor/lib/store";
import React from "react";

export const ToggleDataFieldsButton: React.FC = () => {
const [showDataFields, toggleShowDataFields] = useStore((state) => [
state.showDataFields,
state.toggleShowDataFields,
]);

return (
<Box
sx={(theme) => ({
position: "fixed",
bottom: theme.spacing(6),
left: theme.spacing(7),
zIndex: theme.zIndex.appBar,
border: `1px solid ${theme.palette.border.main}`,
borderRadius: "3px",
background: theme.palette.background.paper,
})}
>
<Tooltip title="Toggle data fields" placement="right">
<IconButton
aria-label="Toggle data fields"
onClick={toggleShowDataFields}
size="large"
sx={(theme) => ({
padding: theme.spacing(1),
color: showDataFields
? theme.palette.text.primary
: theme.palette.text.disabled,
})}
>
{showDataFields ? <DataFieldIcon /> : <DataFieldOffIcon />}
Copy link
Member Author

Choose a reason for hiding this comment

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

Open to MUI icon suggestions for this one especially - there's only so many that have "on"/"off" pairings !

But I think there's something sweet and hopefully not too far of a stretch about "auto" wand → "auto-answering" → data fields ?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd say we need to be more literal as the magic element is an implied function of the data itself, rather than describing the function of the button.

Not perfect from a technical sense (code vs data), but CodeIcon and CodeOffIcon is a really simple icon combo that gives a stronger literal link to the 'thing' being toggled.

Copy link
Member Author

@jessicamcinchak jessicamcinchak Oct 16, 2024

Choose a reason for hiding this comment

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

Good shout - that's definitely a clearer option - updated!

Screenshot from 2024-10-16 12-38-59

</IconButton>
</Tooltip>
</Box>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import ImageOffIcon from "@mui/icons-material/HideImage";
import ImageIcon from "@mui/icons-material/Image";
import Box from "@mui/material/Box";
import IconButton from "@mui/material/IconButton";
import Tooltip from "@mui/material/Tooltip";
import { useStore } from "pages/FlowEditor/lib/store";
import React from "react";

export const ToggleImagesButton: React.FC = () => {
const [showImages, toggleShowImages] = useStore((state) => [
state.showImages,
state.toggleShowImages,
]);

return (
<Box
sx={(theme) => ({
position: "fixed",
bottom: theme.spacing(10),
left: theme.spacing(7),
zIndex: theme.zIndex.appBar,
border: `1px solid ${theme.palette.border.main}`,
borderRadius: "3px",
background: theme.palette.background.paper,
})}
>
<Tooltip title="Toggle images" placement="right">
<IconButton
aria-label="Toggle images"
onClick={toggleShowImages}
size="large"
sx={(theme) => ({
padding: theme.spacing(1),
color: showImages
? theme.palette.text.primary
: theme.palette.text.disabled,
})}
>
{showImages ? <ImageIcon /> : <ImageOffIcon />}
</IconButton>
</Tooltip>
</Box>
);
};
2 changes: 1 addition & 1 deletion editor.planx.uk/src/pages/FlowEditor/floweditor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ $fontMonospace: "Source Code Pro", monospace;
flex-direction: column;
padding: 0;
min-width: 60px;
max-width: 200px;
max-width: fit-content;
}
.band {
width: 100%;
Expand Down
12 changes: 11 additions & 1 deletion editor.planx.uk/src/pages/FlowEditor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import "./floweditor.scss";

import Box from "@mui/material/Box";
import ButtonGroup from "@mui/material/ButtonGroup";
import { styled } from "@mui/material/styles";
import { HEADER_HEIGHT_EDITOR } from "components/Header";
import React, { useRef } from "react";
import { useCurrentRoute } from "react-navi";

import Flow from "./components/Flow";
import { ToggleDataFieldsButton } from "./components/FlowEditor/ToggleDataFieldsButton";
import { ToggleImagesButton } from "./components/FlowEditor/ToggleImagesButton";
import { ToggleTagsButton } from "./components/FlowEditor/ToggleTagsButton";
import Sidebar from "./components/Sidebar";
import { useStore } from "./lib/store";
Expand Down Expand Up @@ -50,7 +53,14 @@ const FlowEditor = () => {
>
<Box id="editor" ref={scrollContainerRef} sx={{ position: "relative" }}>
<Flow flow={flow} breadcrumbs={breadcrumbs} />
<ToggleTagsButton />
<ButtonGroup
orientation="vertical"
aria-label="Toggle node attributes"
>
<ToggleImagesButton />
<ToggleDataFieldsButton />
<ToggleTagsButton />
</ButtonGroup>
</Box>
</Box>
<Sidebar />
Expand Down
15 changes: 14 additions & 1 deletion editor.planx.uk/src/pages/FlowEditor/lib/store/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { getPathForNode, sortFlow } from "@opensystemslab/planx-core";
import {
ComponentType,
FlowGraph,
IndexedNode,
NodeId,
OrderedFlow,
} from "@opensystemslab/planx-core/types";
Expand Down Expand Up @@ -52,6 +51,10 @@ export interface EditorUIStore {
hideTestEnvBanner: () => void;
showTags: boolean;
toggleShowTags: () => void;
showImages: boolean;
toggleShowImages: () => void;
showDataFields: boolean;
toggleShowDataFields: () => void;
}

export const editorUIStore: StateCreator<
Expand All @@ -76,12 +79,22 @@ export const editorUIStore: StateCreator<
showTags: false,

toggleShowTags: () => set({ showTags: !get().showTags }),

showImages: false,

toggleShowImages: () => set({ showImages: !get().showImages }),

showDataFields: false,

toggleShowDataFields: () => set({ showDataFields: !get().showDataFields }),
}),
{
name: "editorUIStore",
partialize: (state) => ({
showSidebar: state.showSidebar,
showTags: state.showTags,
showImages: state.showImages,
showDataFields: state.showDataFields,
}),
},
);
Expand Down
Loading