diff --git a/.github/PULL_REQUEST_TEMPLATE/rc.md b/.github/PULL_REQUEST_TEMPLATE/rc.md index 0e7121b014..2be7db96f1 100644 --- a/.github/PULL_REQUEST_TEMPLATE/rc.md +++ b/.github/PULL_REQUEST_TEMPLATE/rc.md @@ -109,35 +109,57 @@ I can successfully: I can successfully: -- [ ] Open create range dialog from command search bar. -- [ ] Open create range dialog from range toolbar. -- [ ] Open create range dialog from context menu in range toolbar. -- [ ] Create a new local range. -- [ ] Create a new persisted range. -- [ ] Set a parent range for a range. -- [ ] Attach labels to a range. -- [ ] Open the range overview dialog from the resources view. -- [ ] Edit range properties from the overview dialog. -- [ ] Edit meta-data properties on the range. -- [ ] Add child ranges to a range. -- [ ] Navigate to and from child ranges on a range. -- [ ] Make a change to the range in the edit dialog and see the changes propagate to the - overview dialog. -- [ ] Save a local range to Synnax in the range toolbar. -- [ ] Switch the active range in the range toolbar. -- [ ] Load a local range from the search bar. -- [ ] Load a persisted range from the search bar. -- [ ] Rename a range from the range toolbar. -- [ ] Edit a range from the range toolbar. -- [ ] Remove a range from the range toolbar. -- [ ] Delete a persisted range from the range toolbar. -- [ ] Delete a range in the resources view. -- [ ] Delete multiple ranges in the resources view. -- [ ] Set a range as an active range from the resources view. -- [ ] Edit a range from the resources view. -- [ ] Add a range to a plot from the resources view. -- [ ] Copy a link to a range and open it from the resources view. -- [ ] Rename a range from the range toolbar. +- [ ] Create Range Modal + - [ ] Create a new local range. + - [ ] Create a new persisted range. + - [ ] Set parent range + - [ ] Add labels + - [ ] Rename existing range + - [ ] Change times on existing range +- [ ] Range Layout + - [ ] Rename range. + - [ ] Rename range from tab. + - [ ] Change start and end times. + - [ ] Add labels. + - [ ] Set metadata. + - [ ] Delete metadata. + - [ ] Add child ranges. + - [ ] Open snapshots. + - [ ] Navigate to and from child ranges +- [ ] Search and Command Palette + - [ ] Open an existing range window + - [ ] Open create range dialog +- [ ] Range Toolbar + - [ ] Open create range modal from toolbar link when no range exists + - [ ] Switch the active range by clicking + - [ ] Context Menu + - [ ] Open create range modal + - [ ] Rename range + - [ ] Set active range + - [ ] Open create range modal with child range + - [ ] Add to active line plot + - [ ] Add to new line plot + - [ ] Remove from range toolbar + - [ ] Delete persisted range + - [ ] Copy link of persisted range + - [ ] Save local range to Synnax +- [ ] Resources Toolbar + - [ ] Open the range overview dialog by clicking on a range + - [ ] Context Menu + - [ ] Set active range + - [ ] Rename range + - [ ] Open create range modal with child range + - [ ] Group ranges + - [ ] Add to active line plot + - [ ] Add multiple ranges to active line plot + - [ ] Add to new line plot + - [ ] Add multiple ranges to new line plot + - [ ] Delete range + - [ ] Delete multiple ranges + - [ ] Copy link to range +- [ ] Open a range from its url +- [ ] Make changes to a range with resources toolbar, overview, and ranges toolbar open + and see changes propagate to all three. ### Channels diff --git a/console/src/lineplot/LinePlot.tsx b/console/src/lineplot/LinePlot.tsx index dd2753e934..2146416f9f 100644 --- a/console/src/lineplot/LinePlot.tsx +++ b/console/src/lineplot/LinePlot.tsx @@ -328,7 +328,7 @@ const Loaded = ({ layoutKey }: { layoutKey: string }): ReactElement => { break; case "range": placer( - Range.createEditLayout({ + Range.createLayout({ initial: { timeRange: { start: Number(timeRange.start.valueOf()), diff --git a/console/src/range/EditLayout.css b/console/src/range/CreateLayout.css similarity index 87% rename from console/src/range/EditLayout.css rename to console/src/range/CreateLayout.css index 95970e620e..8e5898e781 100644 --- a/console/src/range/EditLayout.css +++ b/console/src/range/CreateLayout.css @@ -9,7 +9,7 @@ * included in the file licenses/APL.txt. */ -.console-range-edit-layout { - padding-top: 2rem; +.console-range-create-layout { + padding-top: 2rem; height: 100%; -} \ No newline at end of file +} diff --git a/console/src/range/EditLayout.tsx b/console/src/range/CreateLayout.tsx similarity index 66% rename from console/src/range/EditLayout.tsx rename to console/src/range/CreateLayout.tsx index 7d576bd5ba..4bb5639f04 100644 --- a/console/src/range/EditLayout.tsx +++ b/console/src/range/CreateLayout.tsx @@ -7,21 +7,16 @@ // License, use of this software will be governed by the Apache License, Version 2.0, // included in the file licenses/APL.txt. -import "@/range/EditLayout.css"; +import "@/range/CreateLayout.css"; -import { - ontology, - ranger, - TimeRange, - TimeStamp, - UnexpectedError, -} from "@synnaxlabs/client"; -import { Icon, Logo } from "@synnaxlabs/media"; +import { ontology, type ranger, TimeRange, TimeStamp } from "@synnaxlabs/client"; +import { Icon } from "@synnaxlabs/media"; import { Align, Button, Form, Icon as PIcon, + Input, Nav, Ranger, Status, @@ -29,9 +24,8 @@ import { Text, Triggers, } from "@synnaxlabs/pluto"; -import { Input } from "@synnaxlabs/pluto/input"; import { deep, primitiveIsZero } from "@synnaxlabs/x"; -import { useMutation, useQuery } from "@tanstack/react-query"; +import { useMutation } from "@tanstack/react-query"; import { type ReactElement, useCallback, useRef } from "react"; import { useDispatch } from "react-redux"; import { v4 as uuidv4 } from "uuid"; @@ -40,7 +34,6 @@ import { z } from "zod"; import { CSS } from "@/css"; import { Label } from "@/label"; import { Layout } from "@/layout"; -import { useSelect } from "@/range/selectors"; import { add } from "@/range/slice"; const formSchema = z.object({ @@ -51,29 +44,31 @@ const formSchema = z.object({ parent: z.string().optional(), }); -type Args = Partial>; +type FormProps = z.infer; + +type Args = Partial; -export const EDIT_LAYOUT_TYPE = "editRange"; +export const CREATE_LAYOUT_TYPE = "editRange"; const SAVE_TRIGGER: Triggers.Trigger = ["Control", "Enter"]; -interface CreateEditLayoutProps extends Partial { +interface CreateLayoutProps extends Partial { initial?: Partial; } -export const createEditLayout = ({ +export const createLayout = ({ name, initial = {}, window, ...rest -}: CreateEditLayoutProps): Layout.State => ({ +}: CreateLayoutProps): Layout.State => ({ ...rest, - key: EDIT_LAYOUT_TYPE, - type: EDIT_LAYOUT_TYPE, - windowKey: EDIT_LAYOUT_TYPE, + key: CREATE_LAYOUT_TYPE, + type: CREATE_LAYOUT_TYPE, + windowKey: CREATE_LAYOUT_TYPE, icon: "Range", location: "modal", - name: name ?? (initial.key != null ? "Range.Edit" : "Range.Create"), + name: name ?? "Range.Create", window: { resizable: false, size: { height: 370, width: 700 }, @@ -83,99 +78,55 @@ export const createEditLayout = ({ args: initial, }); -type DefineRangeFormProps = z.infer; - const parentRangeIcon = ( }> ); -export const Edit = (props: Layout.RendererProps): ReactElement => { +export const Create = (props: Layout.RendererProps): ReactElement => { const { layoutKey } = props; const now = useRef(Number(TimeStamp.now().valueOf())).current; const args = Layout.useSelectArgs(layoutKey); - const range = useSelect(args.key); - const client = Synnax.use(); - const isCreate = args.key == null; - const isEdit = !isCreate && (range == null || range.persisted); - const initialValues = useQuery({ - queryKey: ["range-edit", args], - queryFn: async () => { - if (isCreate) - return { - name: "", - labels: [], - timeRange: { start: now, end: now }, - parent: "", - ...args, - }; - if (range == null || range.persisted) { - const key = args.key as string; - if (client == null) throw new UnexpectedError("Client is not available"); - const rng = await client.ranges.retrieve(key); - const parent = await rng.retrieveParent(); - const labels = await rng.labels(); - return { - key: rng.key, - name: rng.name, - timeRange: rng.timeRange.numeric, - labels: labels.map((l) => l.key), - parent: parent?.key ?? "", - }; - } - if (range.variant !== "static") throw new UnexpectedError("Range is not static"); - return { - key: range.key, - name: range.name, - timeRange: range.timeRange, - labels: [], - parent: "", - }; - }, - }); - if (initialValues.isPending) return ; - if (initialValues.isError) throw initialValues.error; - return ( - - ); + const initialValues: FormProps = { + name: "", + labels: [], + timeRange: { start: now, end: now }, + parent: "", + ...args, + }; + + return ; }; -interface EditLayoutFormProps extends Layout.RendererProps { - initialValues: DefineRangeFormProps; - isRemoteEdit: boolean; +interface CreateLayoutFormProps extends Layout.RendererProps { + initialValues: FormProps; onClose: () => void; } -const EditLayoutForm = ({ +const CreateLayoutForm = ({ initialValues, - isRemoteEdit, onClose, -}: EditLayoutFormProps): ReactElement => { +}: CreateLayoutFormProps): ReactElement => { const methods = Form.use({ values: deep.copy(initialValues), schema: formSchema }); const dispatch = useDispatch(); const client = Synnax.use(); + const clientExists = client != null; const addStatus = Status.useAggregator(); - const isCreate = initialValues.key == null; const { mutate, isPending } = useMutation({ - mutationFn: async (persist: boolean) => { + mutationFn: async (persisted: boolean) => { if (!methods.validate()) return; const values = methods.value(); - const { timeRange: tr, parent } = methods.value(); + const { timeRange: tr, parent } = values; const timeRange = new TimeRange(tr); const name = values.name.trim(); - const key = isCreate ? uuidv4() : (initialValues.key as string); - const persisted = persist || isRemoteEdit; + const key = initialValues.key ?? uuidv4(); const parentID = primitiveIsZero(parent) ? undefined : new ontology.ID({ key: parent as string, type: "range" }); const otgID = new ontology.ID({ key, type: "range" }); - if (persisted && client != null) { + if (persisted && clientExists) { await client.ranges.create({ key, name, timeRange }, { parent: parentID }); await client.labels.label(otgID, values.labels, { replace: true }); } @@ -198,7 +149,7 @@ const EditLayoutForm = ({ ); return ( - + - - path="parent" - visible={isCreate || isRemoteEdit} - padHelpText={false} - > + path="parent" visible padHelpText={false}> {({ onChange, ...p }) => ( - To Save + To Save to Synnax mutate(true)} + variant={"outlined"} + onClick={() => mutate(false)} disabled={isPending} + > + Save Locally + + mutate(true)} + disabled={!clientExists || isPending} + tooltip={clientExists ? "Save to Cluster" : "No Cluster Connected"} + tooltipLocation="bottom" + loading={isPending} triggers={[SAVE_TRIGGER]} > - Save + Save to Synnax diff --git a/console/src/range/Select.tsx b/console/src/range/Select.tsx index 6e6e8ab093..82283be28f 100644 --- a/console/src/range/Select.tsx +++ b/console/src/range/Select.tsx @@ -24,17 +24,16 @@ import { import { type ReactElement } from "react"; import { Layout } from "@/layout"; -import { createEditLayout } from "@/range/EditLayout"; +import { createLayout } from "@/range/CreateLayout"; import { type Range } from "@/range/slice"; -export interface SelectMultipleRangesProps - extends Select.MultipleProps {} +interface SelectMultipleRangesProps extends Select.MultipleProps {} const dynamicIcon = ( ); -export const listColumns: Array> = [ +const listColumns: Array> = [ { key: "name", name: "Name", @@ -72,9 +71,7 @@ const RenderTag = ({ const renderTag = componentRenderProp(RenderTag); -export const SelectMultipleRanges = ( - props: SelectMultipleRangesProps, -): ReactElement => ( +const SelectMultipleRanges = (props: SelectMultipleRangesProps): ReactElement => ( ); -export interface SelectSingleRangeProps extends Select.SingleProps {} +interface SelectSingleRangeProps extends Select.SingleProps {} -export const SelectRange = (props: SelectSingleRangeProps): ReactElement => ( +const SelectRange = (props: SelectSingleRangeProps): ReactElement => ( ); -export interface SelectMultipleInputItemProps +interface SelectMultipleInputItemProps extends Omit, Pick { value: string[]; @@ -97,18 +94,13 @@ export interface SelectMultipleInputItemProps } const SelectEmptyContent = (): ReactElement => { - const newLayout = Layout.usePlacer(); + const placer = Layout.usePlacer(); return ( No Ranges: - { - newLayout(createEditLayout({})); - }} - > + placer(createLayout({}))}> Define a Range @@ -131,7 +123,7 @@ export const SelectMultipleInputItem = ({ ); -export interface SelectInputItemProps +interface SelectInputItemProps extends Omit, Input.Control, Pick {} diff --git a/console/src/range/Toolbar.tsx b/console/src/range/Toolbar.tsx index 6912271e10..1c86313b60 100644 --- a/console/src/range/Toolbar.tsx +++ b/console/src/range/Toolbar.tsx @@ -10,7 +10,7 @@ import "@/range/Toolbar.css"; import { Store } from "@reduxjs/toolkit"; -import { type label, ranger, Synnax as Client, TimeRange } from "@synnaxlabs/client"; +import { type label, ranger, Synnax as Client } from "@synnaxlabs/client"; import { Icon } from "@synnaxlabs/media"; import { Align, @@ -40,7 +40,7 @@ import { Layout } from "@/layout"; import { create as createLinePlot } from "@/lineplot/LinePlot"; import { setRanges as setLinePlotRanges } from "@/lineplot/slice"; import { Link } from "@/link"; -import { createEditLayout } from "@/range/EditLayout"; +import { createLayout } from "@/range/CreateLayout"; import { overviewLayout } from "@/range/external"; import { select, useSelect, useSelectMultiple } from "@/range/selectors"; import { @@ -89,7 +89,13 @@ export const addChildRangeMenuItem = ( } > - Add Child Range + Create Child Range + +); + +export const deleteMenuItem = ( + } itemKey="delete"> + Delete ); @@ -99,19 +105,22 @@ export const setAsActiveMenuItem = ( ); +export const viewDetailsMenuItem = ( + } itemKey="details"> + View Details + +); + export const fromClientRange = (ranges: ranger.Range | ranger.Range[]): Range[] => toArray(ranges).map((range) => ({ variant: "static", key: range.key, name: range.name, - timeRange: { - start: Number(range.timeRange.start.valueOf()), - end: Number(range.timeRange.end.valueOf()), - }, + timeRange: range.timeRange.numeric, persisted: true, })); -export const fetchIfNotInState = async ( +const fetchIfNotInState = async ( store: Store, client: Client, key: string, @@ -125,7 +134,7 @@ export const fetchIfNotInState = async ( return existing; }; -export const useAddToActivePlot = (): ((key: string) => void) => { +const useAddToActivePlot = (): ((key: string) => void) => { const store = useStore(); const client = Synnax.use(); const addStatus = Status.useAggregator(); @@ -144,13 +153,12 @@ export const useAddToActivePlot = (): ((key: string) => void) => { }), ); }, - onError: (e) => { + onError: (e) => addStatus({ variant: "error", message: `Failed to add range to plot`, description: e.message, - }); - }, + }), }).mutate; }; @@ -178,49 +186,65 @@ const useViewDetails = (): ((key: string) => void) => { const useAddToNewPlot = (): ((key: string) => void) => { const store = useStore(); const client = Synnax.use(); - const placeLayout = Layout.usePlacer(); + const placer = Layout.usePlacer(); const addStatus = Status.useAggregator(); return useMutation({ mutationFn: async (key: string) => { if (client == null) return; const res = await fetchIfNotInState(store, client, key); - placeLayout( + placer( createLinePlot({ name: `Plot for ${res.name}`, ranges: { x1: [key], x2: [] }, }), ); }, - onError: (e) => { + onError: (e) => addStatus({ variant: "error", message: `Failed to add range to plot`, description: e.message, - }); - }, + }), }).mutate; }; -export const List = (): ReactElement => { +interface NoRangesProps { + onLinkClick: (key?: string) => void; +} + +const NoRanges = ({ onLinkClick }: NoRangesProps): ReactElement => { + const handleLinkClick: React.MouseEventHandler = (e) => { + e.stopPropagation(); + onLinkClick(); + }; + + return ( + + + No ranges added. + + Add a range + + + + ); +}; + +const List = (): ReactElement => { const menuProps = PMenu.useContextMenu(); const client = Synnax.use(); - const placeLayout = Layout.usePlacer(); + const placer = Layout.usePlacer(); const removeLayout = Layout.useRemover(); const dispatch = useDispatch(); const ranges = useSelectMultiple(); const activeRange = useSelect(); - const handleAddOrEdit = (key?: string): void => { - placeLayout(createEditLayout({ initial: { key } })); - }; + const handleCreate = (key?: string): void => + void placer(createLayout({ initial: { key } })); - const handleRemove = (keys: string[]): void => { - dispatch(remove({ keys })); - }; + const handleRemove = (keys: string[]): void => void dispatch(remove({ keys })); - const handleSelect = (key: string): void => { - dispatch(setActive(key)); - }; + const handleSelect = (key: string): void => void dispatch(setActive(key)); const addStatus = Status.useAggregator(); @@ -254,46 +278,27 @@ export const List = (): ReactElement => { }); const save = useMutation({ - mutationFn: async (key: string) => { + onMutate: async (key: string) => { const range = ranges.find((r) => r.key === key); if (range == null || range.variant === "dynamic") return; - await client?.ranges.create({ - key: range.key, - timeRange: new TimeRange(range.timeRange.start, range.timeRange.end), - name: range.name, - }); dispatch(add({ ranges: [{ ...range, persisted: true }] })); + return range; }, - onError: (e, _, range) => { + mutationFn: async (key: string) => { + const range = ranges.find((r) => r.key === key); + if (range == null || range.variant === "dynamic") return; + await client?.ranges.create({ ...range }); + }, + onError: (e) => addStatus({ variant: "error", message: "Failed to save range", description: e.message, - }); - dispatch(add({ ranges: [range as Range] })); - }, + }), }); const handleLink = Link.useCopyToClipboard(); - const NoRanges = (): ReactElement => { - const handleLinkClick: React.MouseEventHandler = (e) => { - e.stopPropagation(); - handleAddOrEdit(); - }; - - return ( - - - No ranges. - - Create a range - - - - ); - }; - const ContextMenu = ({ keys: [key], }: PMenu.ContextMenuMenuProps): ReactElement | null => { @@ -301,31 +306,28 @@ export const List = (): ReactElement => { const activeLayout = Layout.useSelectActiveMosaicLayout(); const addToActivePlot = useAddToActivePlot(); const addToNewPlot = useAddToNewPlot(); - const placeLayout = Layout.usePlacer(); - const handleSetActive = () => { - dispatch(setActive(key)); - }; + const placer = Layout.usePlacer(); + const handleSetActive = () => void dispatch(setActive(key)); const handleViewDetails = useViewDetails(); + const handleAddChildRange = () => + void placer(createLayout({ initial: { parent: key } })); - const handleAddChildRange = () => { - placeLayout(createEditLayout({ initial: { parent: key } })); - }; + const rangeExists = rng != null; - const handleSelect = { - rename: (): void => Text.edit(`text-${key}`), - create: () => handleAddOrEdit(), - edit: () => handleAddOrEdit(rng?.key), - remove: () => rng != null && handleRemove([rng.key]), - delete: () => rng != null && del.mutate(rng.key), - details: () => rng != null && handleViewDetails(rng.key), - save: () => rng != null && save.mutate(rng.key), + const handleSelect: Record void> = { + rename: () => Text.edit(`text-${key}`), + create: () => handleCreate(), + remove: () => rangeExists && handleRemove([rng.key]), + delete: () => rangeExists && del.mutate(rng.key), + details: () => rangeExists && handleViewDetails(rng.key), + save: () => rangeExists && save.mutate(rng.key), link: () => - rng != null && + rangeExists && handleLink({ name: rng.name, ontologyID: { key: rng.key, - type: "range", + type: ranger.RangeOntologyType, }, }), addToActivePlot: () => addToActivePlot(key), @@ -336,22 +338,18 @@ export const List = (): ReactElement => { return ( - } itemKey="details"> - View Details - } itemKey="create"> Create New - - {rng != null && ( + {rangeExists && ( <> + + {rng.key !== activeRange?.key && setAsActiveMenuItem} + {viewDetailsMenuItem} + - } itemKey="edit"> - Edit - {addChildRangeMenuItem} - {rng.key !== activeRange?.key && setAsActiveMenuItem} {activeLayout?.type === "lineplot" && addToActivePlotMenuItem} {addToNewPlotMenuItem} @@ -359,9 +357,11 @@ export const List = (): ReactElement => { Remove from List {rng.persisted ? ( - } itemKey="delete"> - Delete - + <> + {deleteMenuItem} + + + ) : ( client != null && ( <> @@ -372,15 +372,9 @@ export const List = (): ReactElement => { ) )} - {rng.persisted && ( - <> - - - - )} - )} + ); @@ -390,7 +384,7 @@ export const List = (): ReactElement => { } {...menuProps}> data={ranges.filter((r) => r.variant === "static") as StaticRange[]} - emptyContent={} + emptyContent={} > { }; const Content = (): ReactElement => { - const p = Layout.usePlacer(); + const placer = Layout.usePlacer(); return ( @@ -483,7 +477,7 @@ const Content = (): ReactElement => { {[ { children: , - onClick: () => p(createEditLayout({})), + onClick: () => placer(createLayout({})), }, ]} diff --git a/console/src/range/external.ts b/console/src/range/external.ts index c660c3620d..164bf15fce 100644 --- a/console/src/range/external.ts +++ b/console/src/range/external.ts @@ -8,11 +8,11 @@ // included in the file licenses/APL.txt. import { Layout } from "@/layout"; -import { Edit, EDIT_LAYOUT_TYPE } from "@/range/EditLayout"; +import { Create, CREATE_LAYOUT_TYPE } from "@/range/CreateLayout"; import { Overview, overviewLayout } from "@/range/overview/Overview"; export * from "@/range/ContextMenu"; -export * from "@/range/EditLayout"; +export * from "@/range/CreateLayout"; export * from "@/range/overview/Overview"; export * from "@/range/Select"; export * from "@/range/selectors"; @@ -21,6 +21,6 @@ export * from "@/range/slice"; export * from "@/range/Toolbar"; export const LAYOUTS: Record = { - [EDIT_LAYOUT_TYPE]: Edit, + [CREATE_LAYOUT_TYPE]: Create, [overviewLayout.type]: Overview, }; diff --git a/console/src/range/overview/ChildRanges.tsx b/console/src/range/overview/ChildRanges.tsx index be826c5a59..2cafd9723f 100644 --- a/console/src/range/overview/ChildRanges.tsx +++ b/console/src/range/overview/ChildRanges.tsx @@ -23,7 +23,7 @@ import { import { FC, useState } from "react"; import { Layout } from "@/layout"; -import { createEditLayout, overviewLayout } from "@/range/external"; +import { createLayout, overviewLayout } from "@/range/external"; export const ChildRangeListItem = (props: List.ItemProps) => { const { entry } = props; @@ -99,7 +99,7 @@ export const ChildRanges: FC = ({ rangeKey }) => { style={{ width: "fit-content" }} iconSpacing="small" variant="text" - onClick={() => placer(createEditLayout({ initial: { parent: rangeKey } }))} + onClick={() => placer(createLayout({ initial: { parent: rangeKey } }))} > Add Child Range diff --git a/console/src/range/services/ontology.tsx b/console/src/range/services/ontology.tsx index 1573785cd5..ec6aa4702b 100644 --- a/console/src/range/services/ontology.tsx +++ b/console/src/range/services/ontology.tsx @@ -8,7 +8,7 @@ // included in the file licenses/APL.txt. import { type Store } from "@reduxjs/toolkit"; -import { ontology, type ranger, type Synnax } from "@synnaxlabs/client"; +import { ontology, ranger, type Synnax } from "@synnaxlabs/client"; import { Icon } from "@synnaxlabs/media"; import { type Haul, List, Menu as PMenu, Ranger, Text, Tree } from "@synnaxlabs/pluto"; import { CrudeTimeRange, errors } from "@synnaxlabs/x"; @@ -21,7 +21,7 @@ import { LinePlot } from "@/lineplot"; import { Link } from "@/link"; import { Ontology } from "@/ontology"; import { useConfirmDelete } from "@/ontology/hooks"; -import { createEditLayout } from "@/range/EditLayout"; +import { createLayout } from "@/range/CreateLayout"; import { overviewLayout } from "@/range/overview/Overview"; import { select, useSelect } from "@/range/selectors"; import { add, remove, rename, setActive, type StoreState } from "@/range/slice"; @@ -29,8 +29,10 @@ import { addChildRangeMenuItem, addToActivePlotMenuItem, addToNewPlotMenuItem, + deleteMenuItem, fromClientRange, setAsActiveMenuItem, + viewDetailsMenuItem, } from "@/range/Toolbar"; const handleSelect: Ontology.HandleSelect = async ({ @@ -57,7 +59,7 @@ const handleRename: Ontology.HandleTreeRename = { }, }; -export const fetchIfNotInState = async ( +const fetchIfNotInState = async ( store: Store, client: Synnax, key: string, @@ -76,13 +78,12 @@ const useActivate = (): ((props: Ontology.TreeContextMenuProps) => void) => await fetchIfNotInState(store, client, res.id.key); store.dispatch(setActive(res.id.key)); }, - onError: (e, { addStatus }) => { + onError: (e, { addStatus }) => addStatus({ variant: "error", message: `Failed to activate range`, description: e.message, - }); - }, + }), }).mutate; const useAddToActivePlot = (): ((props: Ontology.TreeContextMenuProps) => void) => @@ -101,13 +102,12 @@ const useAddToActivePlot = (): ((props: Ontology.TreeContextMenuProps) => void) }), ); }, - onError: (e, { addStatus }) => { + onError: (e, { addStatus }) => addStatus({ variant: "error", message: `Failed to add range to plot`, description: e.message, - }); - }, + }), }).mutate; const useAddToNewPlot = (): ((props: Ontology.TreeContextMenuProps) => void) => @@ -125,24 +125,22 @@ const useAddToNewPlot = (): ((props: Ontology.TreeContextMenuProps) => void) => }), ); }, - onError: (e, { addStatus }) => { + onError: (e, { addStatus }) => addStatus({ variant: "error", message: `Failed to add range to plot`, description: e.message, - }); - }, + }), }).mutate; const useViewDetails = (): ((props: Ontology.TreeContextMenuProps) => void) => { - const placeLayout = Layout.usePlacer(); - return ({ selection: { resources } }) => { - placeLayout({ + const placer = Layout.usePlacer(); + return ({ selection: { resources } }) => + placer({ ...overviewLayout, name: resources[0].name, key: resources[0].id.key, }); - }; }; const useDelete = (): ((props: Ontology.TreeContextMenuProps) => void) => { @@ -193,8 +191,7 @@ const useDelete = (): ((props: Ontology.TreeContextMenuProps) => void) => { store.dispatch(add({ ranges })); } let message = "Failed to delete ranges"; - if (resources.length === 1) - message = `Failed to delete range ${resources[0].name}`; + if (resources.length === 1) message = `Failed to delete ${resources[0].name}`; addStatus({ variant: "error", message, @@ -204,13 +201,6 @@ const useDelete = (): ((props: Ontology.TreeContextMenuProps) => void) => { }).mutate; }; -const handleEdit = ({ - selection: { resources }, - placeLayout, -}: Ontology.TreeContextMenuProps): void => { - placeLayout(createEditLayout({ initial: { key: resources[0].id.key } })); -}; - const TreeContextMenu: Ontology.TreeContextMenu = (props) => { const { selection, @@ -218,24 +208,22 @@ const TreeContextMenu: Ontology.TreeContextMenu = (props) => { } = props; const activeRange = useSelect(); const layout = Layout.useSelectActiveMosaicLayout(); - const del = useDelete(); + const handleDelete = useDelete(); const addToActivePlot = useAddToActivePlot(); const addToNewPlot = useAddToNewPlot(); const activate = useActivate(); const groupFromSelection = Group.useCreateFromSelection(); const handleLink = Link.useCopyToClipboard(); - const placeLayout = Layout.usePlacer(); - const handleAddChildRange = () => { - placeLayout(createEditLayout({ initial: { parent: resources[0].id.key } })); - }; + const placer = Layout.usePlacer(); + const handleAddChildRange = () => + void placer(createLayout({ initial: { parent: resources[0].id.key } })); const viewDetails = useViewDetails(); const handleSelect = { - delete: () => del(props), + delete: () => handleDelete(props), rename: () => Tree.startRenaming(nodes[0].key), setAsActive: () => activate(props), addToActivePlot: () => addToActivePlot(props), addToNewPlot: () => addToNewPlot(props), - edit: () => handleEdit(props), group: () => groupFromSelection(props), viewDetails: () => viewDetails(props), link: () => @@ -251,27 +239,25 @@ const TreeContextMenu: Ontology.TreeContextMenu = (props) => { {isSingle && ( <> {resources[0].id.key !== activeRange?.key && setAsActiveMenuItem} - }> - View Details - + {viewDetailsMenuItem} - }> - Edit - {addChildRangeMenuItem} + )} - {layout?.type === "lineplot" && addToActivePlotMenuItem} {addToNewPlotMenuItem} - }> - Delete - - {isSingle && } + {deleteMenuItem} + {isSingle && ( + <> + + + + )} ); @@ -279,7 +265,7 @@ const TreeContextMenu: Ontology.TreeContextMenu = (props) => { const haulItems = ({ id }: ontology.Resource): Haul.Item[] => [ { - type: "range", + type: ranger.RangeOntologyType, key: id.key, }, ]; diff --git a/console/src/range/services/palette.tsx b/console/src/range/services/palette.tsx index c8979e550b..e0cfffeb58 100644 --- a/console/src/range/services/palette.tsx +++ b/console/src/range/services/palette.tsx @@ -10,13 +10,13 @@ import { Icon } from "@synnaxlabs/media"; import { type Command } from "@/palette/Palette"; -import { createEditLayout } from "@/range/EditLayout"; +import { createLayout } from "@/range/CreateLayout"; export const defineCommand: Command = { key: "define-range", name: "Create a Range", icon: , - onSelect: ({ placeLayout }) => placeLayout(createEditLayout({})), + onSelect: ({ placeLayout }) => placeLayout(createLayout({})), }; export const COMMANDS = [defineCommand];