From 7aeafc21e4e0534d47c0d7e8390f2e756dedb19b Mon Sep 17 00:00:00 2001 From: Andy Fitzgerald Date: Fri, 29 Sep 2023 12:00:13 -0700 Subject: [PATCH 1/7] Initial input component elements --- src/components/ChildConcepts.tsx | 17 +- src/components/Children.tsx | 25 +- src/components/ConceptSelectLink.tsx | 46 +++ src/components/Hierarchy.tsx | 152 ++++++---- src/components/Orphans.tsx | 9 +- src/components/TopConcepts.tsx | 151 +++++----- src/components/TreeStructure.tsx | 25 +- src/components/TreeView.tsx | 68 +++-- src/helpers/HierarchyInput.tsx | 406 +++++++++++++++++++++++++++ src/helpers/index.ts | 1 + src/index.ts | 9 +- src/queries.ts | 22 ++ 12 files changed, 765 insertions(+), 166 deletions(-) create mode 100644 src/components/ConceptSelectLink.tsx create mode 100644 src/helpers/HierarchyInput.tsx diff --git a/src/components/ChildConcepts.tsx b/src/components/ChildConcepts.tsx index 2d3b5bb..2cab726 100644 --- a/src/components/ChildConcepts.tsx +++ b/src/components/ChildConcepts.tsx @@ -8,11 +8,24 @@ import {ChildConceptTerm} from '../types' import {StyledChildConcepts} from '../styles' import {Children} from './Children' -export const ChildConcepts = ({concepts}: {concepts: ChildConceptTerm[]}) => { +export const ChildConcepts = ({ + concepts, + inputComponent = false, + selectConcept, +}: { + concepts: ChildConceptTerm[] + selectConcept: any + inputComponent: Boolean +}) => { return ( {concepts.map((concept: any) => ( - + ))} ) diff --git a/src/components/Children.tsx b/src/components/Children.tsx index e015ef5..6d07c76 100644 --- a/src/components/Children.tsx +++ b/src/components/Children.tsx @@ -24,9 +24,18 @@ import {SchemeContext} from './TreeView' import {TreeContext} from './Hierarchy' import {ChildConcepts} from './ChildConcepts' import {ConceptDetailLink} from './ConceptDetailLink' +import {ConceptSelectLink} from './ConceptSelectLink' import {ConceptDetailDialogue} from './ConceptDetailDialogue' -export const Children = ({concept}: {concept: ChildConceptTerm}) => { +export const Children = ({ + concept, + selectConcept, + inputComponent = false, +}: { + concept: ChildConceptTerm + selectConcept: any + inputComponent: Boolean +}) => { const document: any = useContext(SchemeContext) || {} //@ts-expect-error — This is part of the same complaint as in Hierarchy.tsx const {treeVisibility} = useContext(TreeContext) || {} @@ -69,7 +78,11 @@ export const Children = ({concept}: {concept: ChildConceptTerm}) => { )} {!concept?.prefLabel && [new concept]} - + {inputComponent ? ( + + ) : ( + + )} {!document.displayed?.controls && } @@ -160,7 +173,13 @@ export const Children = ({concept}: {concept: ChildConceptTerm}) => { {concept?.childConcepts && concept.childConcepts.length > 0 && concept?.level && - concept.level < 5 && } + concept.level < 5 && ( + + )} ) } diff --git a/src/components/ConceptSelectLink.tsx b/src/components/ConceptSelectLink.tsx new file mode 100644 index 0000000..5a00ca6 --- /dev/null +++ b/src/components/ConceptSelectLink.tsx @@ -0,0 +1,46 @@ +/** + * Concept Detail Link + * Renders a link to a concept in the hierarchy tree that opens in a new pane. + */ + +// import {useCallback, useContext} from 'react' +// import {usePaneRouter} from 'sanity/desk' +// import {RouterContext} from 'sanity/router' +import {ChildConceptTerm} from '../types' +import {StyledConceptLink} from '../styles' + +export function ConceptSelectLink({ + concept, + selectConcept, +}: { + concept: ChildConceptTerm + selectConcept: any +}) { + // const routerContext = useContext(RouterContext) + // const {routerPanesState, groupIndex} = usePaneRouter() + + const {prefLabel} = concept ?? {} + + // const openInNewPane = useCallback(() => { + // if (!routerContext || !id) { + // return + // } + + // const panes = [...routerPanesState] + // panes.splice(groupIndex + 1, groupIndex + 1, [ + // { + // id: id, + // params: {type: 'skosConcept'}, + // }, + // ]) + + // const href = routerContext.resolvePathFromState({panes}) + // routerContext.navigateUrl({path: href}) + // }, [id, routerContext, routerPanesState, groupIndex]) + + return ( + selectConcept(prefLabel)}> + {prefLabel} + + ) +} diff --git a/src/components/Hierarchy.tsx b/src/components/Hierarchy.tsx index c5a4be8..1ada1b5 100644 --- a/src/components/Hierarchy.tsx +++ b/src/components/Hierarchy.tsx @@ -12,7 +12,7 @@ import {AddCircleIcon} from '@sanity/icons' import {randomKey} from '@sanity/util/content' import {useListeningQuery} from 'sanity-plugin-utils' import {useCreateConcept} from '../hooks' -import {trunkBuilder} from '../queries' +import {trunkBuilder, inputBuilder} from '../queries' import {DocumentConcepts} from '../types' import {HierarchyButton} from '../styles' import {SchemeContext} from './TreeView' @@ -26,10 +26,22 @@ type GlobalVisibility = { treeVisibility: string } -export const Hierarchy = () => { +export const Hierarchy = ({ + inputComponent = false, + branchId = '', + selectConcept, +}: { + // eslint-disable-next-line react/require-default-props + inputComponent?: Boolean + branchId: string + // eslint-disable-next-line react/require-default-props + selectConcept?: any +}) => { const document: any = useContext(SchemeContext) || {} const documentId = document.displayed?._id + // console.log('Input Compontent? ', inputComponent) + const createConcept = useCreateConcept(document) const createTopConcept = useCallback(() => { @@ -58,28 +70,32 @@ export const Hierarchy = () => { const {data, loading, error} = useListeningQuery( { - fetch: trunkBuilder(), + // add `inputcomponent` to context? + fetch: inputComponent ? inputBuilder() : trunkBuilder(), + // fetch: trunkBuilder(), listen: `*[_type == "skosConcept" || _id == $id]`, }, { - params: {id: documentId}, + params: {id: documentId, branchId}, } ) if (loading) { return ( - - - - Loading hierarchy… - - + + + + + Loading hierarchy… + + + ) } else if (error) { return
error: {error}
@@ -91,53 +107,67 @@ export const Hierarchy = () => { // I suspect this is an error. - - - - Hierarchy Tree - - - Hierarchy is determined by the 'Broader' relationships assigned to each concept. - - - - {(data.topConcepts?.filter((concept) => (concept?.childConcepts?.length ?? 0) > 0) - .length > 0 || - data.orphans?.filter((concept) => (concept?.childConcepts?.length ?? 0) > 0).length > - 0) && ( - - - - Collapse - - - - | + {inputComponent ? ( + + ) : ( + <> + + + + Hierarchy Tree - - - Expand - - - - )} - {document.displayed?.controls && ( + + Hierarchy is determined by the 'Broader' relationships assigned to each concept. + + - - - Add Top Concept - - - - - Add Concept - - + {(data.topConcepts?.filter((concept) => (concept?.childConcepts?.length ?? 0) > 0) + .length > 0 || + data.orphans?.filter((concept) => (concept?.childConcepts?.length ?? 0) > 0) + .length > 0) && ( + + + + Collapse + + + + | + + + + Expand + + + + )} + {document.displayed?.controls && ( + + + + Add Top Concept + + + + + Add Concept + + + + )} - )} - - - + + + + )} ) diff --git a/src/components/Orphans.tsx b/src/components/Orphans.tsx index af298ea..cb325d7 100644 --- a/src/components/Orphans.tsx +++ b/src/components/Orphans.tsx @@ -18,9 +18,10 @@ import {ConceptDetailDialogue} from './ConceptDetailDialogue' type OrphanProps = { concept: ChildConceptTerm treeVisibility: string + inputComponent: Boolean } -export const Orphans = ({concept, treeVisibility}: OrphanProps) => { +export const Orphans = ({concept, treeVisibility, inputComponent}: OrphanProps) => { const document: any = useContext(SchemeContext) || {} const createConcept = useCreateConcept(document) const removeConcept = useRemoveConcept(document) @@ -116,7 +117,11 @@ export const Orphans = ({concept, treeVisibility}: OrphanProps) => { )} {concept?.childConcepts && concept.childConcepts.length > 0 && ( - + )} ) diff --git a/src/components/TopConcepts.tsx b/src/components/TopConcepts.tsx index d57585e..94a4be8 100644 --- a/src/components/TopConcepts.tsx +++ b/src/components/TopConcepts.tsx @@ -18,9 +18,16 @@ import {ConceptDetailDialogue} from './ConceptDetailDialogue' type TopConceptsProps = { concept: TopConceptTerm treeVisibility: string + inputComponent: Boolean + selectConcept: any } -export const TopConcepts = ({concept, treeVisibility}: TopConceptsProps) => { +export const TopConcepts = ({ + concept, + treeVisibility, + inputComponent, + selectConcept, +}: TopConceptsProps) => { const document: any = useContext(SchemeContext) || {} const createConcept = useCreateConcept(document) const removeConcept = useRemoveConcept(document) @@ -45,78 +52,84 @@ export const TopConcepts = ({concept, treeVisibility}: TopConceptsProps) => { return ( - - - {concept?.childConcepts && concept.childConcepts.length > 0 && ( - - - - )} - {concept?.childConcepts && concept.childConcepts.length == 0 && ( - - )} - {!concept?.prefLabel && [new concept]} - - - - top concept - - {!document.displayed?.controls && } - {document.displayed?.controls && ( - - - - - Add a child concept - - - - } - fallbackPlacements={['right', 'left']} - placement="top" - > - - - - - - - - Remove concept from scheme - - - - } - fallbackPlacements={['right', 'left']} - placement="top" - > - + + {concept?.childConcepts && concept.childConcepts.length > 0 && ( + - - - + + + )} + {concept?.childConcepts && concept.childConcepts.length == 0 && ( + + )} + {!concept?.prefLabel && [new concept]} + - )} - + + top concept + + {!document.displayed?.controls && } + {document.displayed?.controls && ( + + + + + Add a child concept + + + + } + fallbackPlacements={['right', 'left']} + placement="top" + > + + + + + + + + Remove concept from scheme + + + + } + fallbackPlacements={['right', 'left']} + placement="top" + > + + + + + + )} + + )} {concept?.childConcepts && concept.childConcepts.length > 0 && ( - + )} ) diff --git a/src/components/TreeStructure.tsx b/src/components/TreeStructure.tsx index 2ce6da6..1d130af 100644 --- a/src/components/TreeStructure.tsx +++ b/src/components/TreeStructure.tsx @@ -13,11 +13,25 @@ import {TopConcepts} from './TopConcepts' import {Orphans} from './Orphans' import {NoConcepts} from './guides' -export const TreeStructure = ({concepts}: {concepts: DocumentConcepts}) => { +export const TreeStructure = ({ + concepts, + inputComponent, + selectConcept, +}: { + concepts: DocumentConcepts + inputComponent: Boolean + selectConcept: any +}) => { // @ts-expect-error — I think this is the same complier issue as Hierarchy.tsx // To investigate. const {treeId, treeVisibility} = useContext(TreeContext) + // const treeId = 'd8d' + // const treeVisibility = 'open' + + // console.log("treeId: ", treeId) // d8d + // console.log("treeVisibility: ", treeVisibility) // open + if (concepts.topConcepts === null && concepts.orphans.length === 0) return return ( @@ -29,12 +43,19 @@ export const TreeStructure = ({concepts}: {concepts: DocumentConcepts}) => { key={concept?.id + treeId} concept={concept} treeVisibility={treeVisibility} + inputComponent={inputComponent} + selectConcept={selectConcept} /> ) })} {concepts.orphans.map((concept: ChildConceptTerm) => { return ( - + ) })} diff --git a/src/components/TreeView.tsx b/src/components/TreeView.tsx index 8d5e5ea..1451651 100644 --- a/src/components/TreeView.tsx +++ b/src/components/TreeView.tsx @@ -1,3 +1,9 @@ +import {createContext, CSSProperties} from 'react' +import {Box, Container, Stack, Text} from '@sanity/ui' +import Hierarchy from './Hierarchy' + +export const SchemeContext = createContext(null) + /** * Tree View Component Wrapper * This is the view component for the hierarchy tree. It is the @@ -7,35 +13,51 @@ * What is the type of the document object returned by the Desk * structure? */ +export const TreeView = ({ + document, + branchId, + inputComponent = false, + selectConcept, +}: { + document: any + branchId: string + // eslint-disable-next-line react/require-default-props + inputComponent?: boolean + selectConcept: any +}) => { + // console.log('document: ', document) + // console.log('input component: ', inputComponent) -import {createContext, CSSProperties} from 'react' -import {Box, Container, Stack, Text} from '@sanity/ui' -import Hierarchy from './Hierarchy' - -export const SchemeContext = createContext(null) - -export const TreeView = ({document}: {document: any}) => { const containerStyle: CSSProperties = {paddingTop: '1.25rem'} const descriptionStyle: CSSProperties = {whiteSpace: 'pre-wrap'} + return ( - - {document.displayed?.description && ( - - - - - Description - - - {document.displayed.description} - + {inputComponent ? ( + + ) : ( + + {document.displayed?.description && ( + + + + + Description + + + {document.displayed.description} + + - - - )} - - + + )} + + + )} ) } diff --git a/src/helpers/HierarchyInput.tsx b/src/helpers/HierarchyInput.tsx new file mode 100644 index 0000000..c929e68 --- /dev/null +++ b/src/helpers/HierarchyInput.tsx @@ -0,0 +1,406 @@ +import {Grid, Stack, Button, Dialog, Box} from '@sanity/ui' +import {useState, useEffect} from 'react' +import {useClient} from 'sanity' +// import {TreeStructure} from '../components/TreeStructure' +import {TreeView} from '../components/TreeView' + +export function HierarchyInput(props: any) { + // const {onChange}: {onChange: any} = props.inputProps + + // console.log(onChange) + + // need to ensure things don't crash with an empty document here + const fillerDocument = { + displayed: { + baseIri: 'https://example.com/', + concepts: [ + { + related: [], + _createdAt: '2023-08-16T13:39:16Z', + _rev: 'L4M5BwH2sViysRDOPFUFJf', + _type: 'skosConcept', + conceptId: '8833c1', + baseIri: 'https://example.com/', + prefLabel: 'Birds', + _id: '1a4435a2b87a42e3e81b98a4baa7d5ff', + broader: [ + { + _ref: 'a0955815c4770261d41bac8aa4d3e748', + _type: 'reference', + _key: '830e7c', + }, + ], + _updatedAt: '2023-08-16T13:39:22Z', + }, + { + _createdAt: '2023-08-16T13:39:45Z', + _rev: 'L4M5BwH2sViysRDOPFUJDy', + conceptId: '35e673', + related: [], + prefLabel: 'Butterflies', + _type: 'skosConcept', + _id: '98026195a69129c5c6a2e82d41145255', + broader: [ + { + _ref: 'a0955815c4770261d41bac8aa4d3e748', + _type: 'reference', + _key: 'b429b5', + }, + ], + _updatedAt: '2023-08-16T13:39:51Z', + baseIri: 'https://example.com/', + }, + { + _createdAt: '2023-08-16T13:39:37Z', + prefLabel: 'Bees', + _type: 'skosConcept', + broader: [ + { + _ref: 'a0955815c4770261d41bac8aa4d3e748', + _type: 'reference', + _key: '6ee448', + }, + ], + _updatedAt: '2023-08-16T13:39:42Z', + baseIri: 'https://example.com/', + related: [], + _rev: 'L4M5BwH2sViysRDOPFUI5u', + conceptId: '7f424c', + _id: 'bfd22e25ee28d94cf76e87acd9201312', + }, + { + _id: 'ce33d0fc9213ca036d7cecb958a8b476', + _createdAt: '2023-08-16T13:39:25Z', + prefLabel: 'Hummingbirds', + _type: 'skosConcept', + conceptId: '1e668d', + broader: [ + { + _ref: '1a4435a2b87a42e3e81b98a4baa7d5ff', + _type: 'reference', + _key: '096095', + }, + ], + _updatedAt: '2023-08-16T13:39:32Z', + baseIri: 'https://example.com/', + related: [], + _rev: 'bzZ5SGBSb1WLZp9hRgt70C', + }, + ], + _type: 'skosConceptScheme', + _rev: 'bzZ5SGBSb1WLZp9hRjb2ls', + schemeId: 'cf76c1', + _updatedAt: '2023-08-16T19:23:42Z', + controls: false, + _id: 'bf23320c-decf-41b4-b0ba-a8bdd7ce3d2b', + title: 'Plant Finder Facets', + topConcepts: [ + { + _createdAt: '2023-08-16T13:38:57Z', + _rev: 'L4M5BwH2sViysRDOPFUEBb', + conceptId: '1e5e6c', + _id: 'a0955815c4770261d41bac8aa4d3e748', + _updatedAt: '2023-08-16T13:39:05Z', + baseIri: 'https://example.com/', + related: [], + prefLabel: 'Habitat', + _type: 'skosConcept', + broader: [], + }, + ], + _createdAt: '2023-08-16T13:33:34Z', + description: 'Used identify distinct attributes of plants for the Plant Finder tool\n', + }, + } + + const [open, setOpen] = useState(false) + // eslint-disable-next-line no-unused-vars + const [document, setDocument] = useState(fillerDocument) + const client = useClient({apiVersion: '2021-10-21'}) + + // console.log('props: ', props) + + const filter = props.schemaType.options.filter // returns the filter function + const filterExec = filter() + const branchId = filterExec.params.branchId + const schemeId = filterExec.params.schemeId + // console.log('branchId: ', branchId) + // console.log('schemeId: ', schemeId) + + // returns => + // filter: "!(_id in path("drafts.**")) && _id in *[_type=="skosConceptScheme" && schemeId == $schemeId].concepts[]._ref + // && $branchId in broader[]->conceptId + // || $branchId in broader[]->broader[]->conceptId + // || $branchId in broader[]->broader[]->broader[]->conceptId + // || $branchId in broader[]->broader[]->broader[]->broader[]->conceptId + // || $branchId in broader[]->broader[]->broader[]->broader[]->broader[]->conceptId" + // params: { + // branchId: "1e5e6c" + // schemeId: "cf76c1" + // } + + useEffect(() => { + client + .fetch(`{"displayed": *[schemeId == "${schemeId}"][0]}`) + .then((res) => { + // setDocument(res) + return res + }) + // eslint-disable-next-line no-console + .then((res) => console.log('fetch result: ', res)) + }, []) + + function BrowseHierarchy() { + setOpen(true) + } + + // const concepts = { + // _updatedAt: '2023-08-16T19:23:42Z', + // topConcepts: [ + // { + // id: '36832ba2b1eca768553fd0f2c6befd3e', + // level: 0, + // prefLabel: 'Foliage Color', + // definition: null, + // example: null, + // scopeNote: null, + // childConcepts: [ + // { + // id: 'b184088ffe172dbb7650e378fda269d2', + // level: 1, + // prefLabel: 'Black', + // definition: null, + // example: null, + // scopeNote: null, + // childConcepts: [], + // }, + // { + // example: null, + // scopeNote: null, + // childConcepts: [], + // id: '7de989b3a2347cf6fe97bb62e8f0f9d7', + // level: 1, + // prefLabel: 'Blue', + // definition: null, + // }, + // { + // example: null, + // scopeNote: null, + // childConcepts: [], + // id: 'e47f59f33f9ef4f4c45eb22330e6080d', + // level: 1, + // prefLabel: 'Bronze', + // definition: null, + // }, + // { + // level: 1, + // prefLabel: 'Green', + // definition: null, + // example: null, + // scopeNote: null, + // childConcepts: [], + // id: '18f598d89c54eec56ce5e447d05ab6b9', + // }, + // { + // id: '2432d591f792f9ef506858d67f6d9790', + // level: 1, + // prefLabel: 'Purple', + // definition: null, + // example: null, + // scopeNote: null, + // childConcepts: [], + // }, + // { + // scopeNote: null, + // childConcepts: [], + // id: '70c88ff6550b0bfa58aad6f023e2cab6', + // level: 1, + // prefLabel: 'Red', + // definition: null, + // example: null, + // }, + // { + // example: null, + // scopeNote: null, + // childConcepts: [], + // id: 'b0f9032805575200aa2a3d8ba61fb8f4', + // level: 1, + // prefLabel: 'Silver', + // definition: null, + // }, + // ], + // }, + // { + // level: 0, + // prefLabel: 'Foliage Type', + // definition: null, + // example: null, + // scopeNote: null, + // childConcepts: [ + // { + // prefLabel: 'Deciduous', + // definition: null, + // example: null, + // scopeNote: null, + // childConcepts: [], + // id: '2e1000bc7f7a5af2dafc69eb02ca1ff5', + // level: 1, + // }, + // { + // definition: null, + // example: null, + // scopeNote: null, + // childConcepts: [], + // id: '45be6f64fb92d563fbc8db60b80f9b31', + // level: 1, + // prefLabel: 'Evergreen', + // }, + // { + // level: 1, + // prefLabel: 'Semi-Evergreen', + // definition: null, + // example: null, + // scopeNote: null, + // childConcepts: [], + // id: '043a7e50488886b266d3669c6bb0bb2f', + // }, + // ], + // id: '23366a6ad173eb7c68a2391d69159b07', + // }, + // { + // level: 0, + // prefLabel: 'Habitat', + // definition: null, + // example: null, + // scopeNote: null, + // childConcepts: [ + // { + // id: 'bfd22e25ee28d94cf76e87acd9201312', + // level: 1, + // prefLabel: 'Bees', + // definition: null, + // example: null, + // scopeNote: null, + // childConcepts: [], + // }, + // { + // level: 1, + // prefLabel: 'Birds', + // definition: null, + // example: null, + // scopeNote: null, + // childConcepts: [ + // { + // id: 'ce33d0fc9213ca036d7cecb958a8b476', + // level: 2, + // prefLabel: 'Hummingbirds', + // definition: null, + // example: null, + // scopeNote: null, + // childConcepts: [], + // }, + // ], + // id: '1a4435a2b87a42e3e81b98a4baa7d5ff', + // }, + // { + // id: '98026195a69129c5c6a2e82d41145255', + // level: 1, + // prefLabel: 'Butterflies', + // definition: null, + // example: null, + // scopeNote: null, + // childConcepts: [], + // }, + // ], + // id: 'a0955815c4770261d41bac8aa4d3e748', + // }, + // { + // example: null, + // scopeNote: null, + // childConcepts: [ + // { + // definition: null, + // example: null, + // scopeNote: null, + // childConcepts: [], + // id: 'f2dac4274c77ab817af75f5beb06036e', + // level: 1, + // prefLabel: 'zone 6 (-10°F to 0°F)', + // }, + // { + // level: 1, + // prefLabel: 'zone 7 (0°F to 10°F)', + // definition: 'USDA Zones 7 and 8 generally cover the maritime Pacific Northwest.\n', + // example: null, + // scopeNote: null, + // childConcepts: [], + // id: '7fc655f010742f24b3c075069fb1ead4', + // }, + // { + // id: '93f3f473012f3bad2600d91fca7c3844', + // level: 1, + // prefLabel: 'zone 8 (10°F to 20°F)', + // definition: 'USDA Zones 7 and 8 generally cover the maritime Pacific Northwest.\n', + // example: null, + // scopeNote: null, + // childConcepts: [], + // }, + // { + // id: '6663c7a56060c2ea2ace1f1f89257b63', + // level: 1, + // prefLabel: 'zone 9 (20°F to 30°F)', + // definition: null, + // example: null, + // scopeNote: null, + // childConcepts: [], + // }, + // ], + // id: '0bdb7beddb6f6eb69f29f476298a1a01', + // level: 0, + // prefLabel: 'Hardiness', + // definition: 'USDA climate zones ', + // }, + // ], + // orphans: [], + // } + + // so: I need to get the document from the schemeId, filtered to the branch ID, if present + // _not_ just the filtered terms + // also need to figure out which document values are actually needed + // and rewrite trunkBuilder to filter by branchId ✅ + + const handleAction = (label: any) => { + // eslint-disable-next-line no-alert + alert(`Action here, y'all — ${label}`) + // console.log(e) + } + + return ( + + {props.renderDefault(props)} + + +