diff --git a/deps/web-components b/deps/web-components index 3fff67600..c60bc8545 160000 --- a/deps/web-components +++ b/deps/web-components @@ -1 +1 @@ -Subproject commit 3fff67600244631d0fda5cb2829c7dffd0c1728d +Subproject commit c60bc8545d44bab919312efccab2f63999e4cb48 diff --git a/package.json b/package.json index 9dc968638..5e0491e9c 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,6 @@ "chalk": "^5.3.0", "http-proxy-middleware": "^3.0.3", "prettier": "^2.7.1", - "react-arborist": "^3.4.0", - "react-text-annotate-blend": "^1.2.0", "sass": "^1.49.0", "sass-embedded": "^1.79.1", "stylus": "^0.55.0", diff --git a/pages/dev/me/+Page.client.ts b/pages/dev/me/+Page.client.ts index 10818b210..053e67a21 100644 --- a/pages/dev/me/+Page.client.ts +++ b/pages/dev/me/+Page.client.ts @@ -1,7 +1,7 @@ import h from "@macrostrat/hyper"; import { DocumentationPage } from "~/layouts"; import { AuthStatus, useAuth } from "@macrostrat/auth-components"; -import { usePostgresQuery } from "#/integrations/xdd/extractions/lib/data-service"; +import { usePostgresQuery } from "#/integrations/xdd/extractions/data-service"; export function Page() { return h(DocumentationPage, { title: "Login" }, [ diff --git a/pages/integrations/xdd/extractions/@paperId/+Page.ts b/pages/integrations/xdd/extractions/@paperId/+Page.ts index 2c51156e2..158675f1f 100644 --- a/pages/integrations/xdd/extractions/@paperId/+Page.ts +++ b/pages/integrations/xdd/extractions/@paperId/+Page.ts @@ -3,12 +3,16 @@ import h from "@macrostrat/hyper"; import { ContentPage } from "~/layouts"; import { PageBreadcrumbs } from "~/components"; import { usePageContext } from "vike-react/usePageContext"; -import { ExtractionContext, enhanceData } from "../lib"; +import { + ExtractionContext, + enhanceData, +} from "@macrostrat/feedback-components"; import { useEntityTypeIndex, useModelIndex, usePostgresQuery, -} from "../lib/data-service"; +} from "../data-service"; +import { MatchedEntityLink } from "../match"; export function Page() { return h(ContentPage, [h(PageBreadcrumbs), h(PageMain)]); @@ -46,6 +50,7 @@ function ExtractionIndex() { h(ExtractionContext, { data: enhanceData(d, models, entityTypes), entityTypes, + matchComponent: MatchedEntityLink, }), ]); }), diff --git a/pages/integrations/xdd/extractions/lib/data-service.ts b/pages/integrations/xdd/extractions/data-service.ts similarity index 92% rename from pages/integrations/xdd/extractions/lib/data-service.ts rename to pages/integrations/xdd/extractions/data-service.ts index 982150068..455127892 100644 --- a/pages/integrations/xdd/extractions/lib/data-service.ts +++ b/pages/integrations/xdd/extractions/data-service.ts @@ -16,6 +16,10 @@ export function usePostgresQuery( ) { const [data, setData] = useState(null); + useEffect(() => { + console.warn("usePostgresQuery should be moved to a separate package"); + }, []); + let _filters: FilterDef[] = []; if (filters != null) { if (!Array.isArray(filters)) { diff --git a/pages/integrations/xdd/extractions/lib/index.ts b/pages/integrations/xdd/extractions/lib/index.ts deleted file mode 100644 index 07b445d0d..000000000 --- a/pages/integrations/xdd/extractions/lib/index.ts +++ /dev/null @@ -1,226 +0,0 @@ -import h from "./main.module.sass"; -import classNames from "classnames"; -import { Tag } from "@blueprintjs/core"; -import { Entity, EntityExt, Highlight, EntityType } from "./types"; -import { CSSProperties } from "react"; -import { asChromaColor } from "@macrostrat/color-utils"; - -export function buildHighlights( - entities: EntityExt[], - parent: EntityExt | null -): Highlight[] { - let highlights = []; - let parents = []; - if (parent != null) { - parents = [parent.id, ...(parent.parents ?? [])]; - } - - for (const entity of entities) { - highlights.push({ - start: entity.indices[0], - end: entity.indices[1], - text: entity.name, - backgroundColor: entity.type.color ?? "#ddd", - tag: entity.type.name, - id: entity.id, - parents, - }); - highlights.push(...buildHighlights(entity.children ?? [], entity)); - } - return highlights; -} - -export function enhanceData(extractionData, models, entityTypes) { - return { - ...extractionData, - model: models.get(extractionData.model_id), - entities: extractionData.entities?.map((d) => - enhanceEntity(d, entityTypes) - ), - }; -} - -export function getTagStyle( - baseColor: string, - options: { highlighted?: boolean; inDarkMode?: boolean; active?: boolean } -): CSSProperties { - const _baseColor = asChromaColor(baseColor ?? "#ddd"); - const { highlighted = true, inDarkMode = false, active = false } = options; - - let mixAmount = highlighted ? 0.8 : 0.5; - let backgroundAlpha = highlighted ? 0.8 : 0.2; - - if (active) { - mixAmount = 1; - backgroundAlpha = 1; - } - - const mixTarget = inDarkMode ? "white" : "black"; - - const color = _baseColor.mix(mixTarget, mixAmount).css(); - const borderColor = highlighted - ? _baseColor.mix(mixTarget, mixAmount / 2).css() - : "transparent"; - - return { - color, - backgroundColor: _baseColor.alpha(backgroundAlpha).css(), - boxSizing: "border-box", - borderStyle: "solid", - borderColor, - borderWidth: "1px", - fontWeight: active ? "bold" : "normal", - }; -} - -function enhanceEntity( - entity: Entity, - entityTypes: Map -): EntityExt { - return { - ...entity, - type: addColor(entityTypes.get(entity.type), entity.match != null), - children: entity.children?.map((d) => enhanceEntity(d, entityTypes)), - }; -} - -function addColor(entityType: EntityType, match = false) { - let color = entityType.color ?? "#ddd"; - - color = asChromaColor(color).brighten(match ? 1 : 2); - - return { ...entityType, color: color.css() }; -} - -export function ExtractionContext({ - data, - entityTypes, -}: { - data: any; - entityTypes: Map; -}) { - const highlights = buildHighlights(data.entities); - - return h("div", [ - h("p", h(HighlightedText, { text: data.paragraph_text, highlights })), - h(ModelInfo, { data: data.model }), - h( - "ul.entities", - data.entities.map((d) => h(ExtractionInfo, { data: d })) - ), - ]); -} - -export function ModelInfo({ data }) { - return h("p.model-name", ["Model: ", h("code.bp5-code", data.name)]); -} - -export function EntityTag({ - data, - highlighted = true, - active = false, - onClickType, -}) { - const { name, type, match } = data; - const className = classNames( - { - matched: match != null, - type: data.type.name, - }, - "entity" - ); - - const style = getTagStyle(type.color, { highlighted, active }); - - return h(Tag, { style, className }, [ - h("span.entity-name", name), - " ", - h( - "code.entity-type.bp5-code", - { - onClick(evt) { - if (active && onClickType != null) { - onClickType(type); - evt.stopPropagation(); - } - }, - }, - [type.name, h(Match, { data: match })] - ), - ]); -} - -function ExtractionInfo({ data }: { data: EntityExt }) { - const children = data.children ?? []; - - return h("li.entity-row", [ - h(EntityTag, { data }), - h.if(children.length > 0)([ - h( - "ul.children", - children.map((d) => h(ExtractionInfo, { data: d })) - ), - ]), - ]); -} - -function Match({ data }) { - if (data == null) return null; - const href = buildHref(data); - return h([" ", h("a.match", { href }, `#${matchID(data)}`)]); -} - -function buildHref(match) { - /** Build a URL for a matched term */ - if (match == null) return null; - - if (match.strat_name_id != null) { - return `/lex/strat-names/${match.strat_name_id}`; - } - - if (match.lith_id != null) { - return `/lex/lithologies`; - } - - if (match.lith_att_id != null) { - return `/lex/lithologies`; - } - - return null; -} - -function matchID(match) { - if (match == null) return null; - - for (const id of ["strat_name_id", "lith_id", "lith_att_id"]) { - if (match[id]) { - return match[id]; - } - } - return null; -} - -function HighlightedText(props: { text: string; highlights: Highlight[] }) { - const { text, highlights = [] } = props; - const parts = []; - let start = 0; - - const sortedHighlights = highlights.sort((a, b) => a.start - b.start); - const deconflictedHighlights = sortedHighlights.map((highlight, i) => { - if (i === 0) return highlight; - const prev = sortedHighlights[i - 1]; - if (highlight.start < prev.end) { - highlight.start = prev.end; - } - return highlight; - }); - - for (const highlight of deconflictedHighlights) { - const { start: s, end, ...rest } = highlight; - parts.push(text.slice(start, s)); - parts.push(h("span.highlight", { style: rest }, text.slice(s, end))); - start = end; - } - parts.push(text.slice(start)); - return h("span", parts); -} diff --git a/pages/integrations/xdd/extractions/lib/main.module.sass b/pages/integrations/xdd/extractions/lib/main.module.sass deleted file mode 100644 index d8630eef7..000000000 --- a/pages/integrations/xdd/extractions/lib/main.module.sass +++ /dev/null @@ -1,10 +0,0 @@ -.entities - list-style: none - padding-left: 0 - - .entities ul - list-style: none - -.entity - margin: 0.2em 0 0.5em - padding-right: 3px diff --git a/pages/integrations/xdd/extractions/lib/types.ts b/pages/integrations/xdd/extractions/lib/types.ts deleted file mode 100644 index 379a5b9ed..000000000 --- a/pages/integrations/xdd/extractions/lib/types.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { EntityType } from "../../extractions/lib/types"; - -type Match = any; - -export interface Entity { - id: number; - name: string; - type?: number; - indices: [number, number]; - children: Entity[]; - match?: Match; -} - -export { EntityType }; - -export type Highlight = { - start: number; - end: number; - tag?: string; - text?: string; - backgroundColor?: string; - borderColor?: string; - id: number; - parents?: number[]; -}; - -export interface EntityExt extends Omit { - type: EntityType; - children: EntityExt[]; -} diff --git a/pages/integrations/xdd/extractions/match.ts b/pages/integrations/xdd/extractions/match.ts new file mode 100644 index 000000000..04978e013 --- /dev/null +++ b/pages/integrations/xdd/extractions/match.ts @@ -0,0 +1,46 @@ +import h from "@macrostrat/hyper"; + +type MacrostratMatchData = { + /** Data for Macrostrat matches from the matching service */ + strat_name_id?: number; + lith_id?: number; + lith_att_id?: number; +}; + +export function MatchedEntityLink({ data }: { data: MacrostratMatchData }) { + if (data == null) return null; + const href = buildHref(data); + return h([" ", h("a.match", { href }, `#${matchID(data)}`)]); +} + +function buildHref(match) { + /** Build a URL for a matched term + * TODO: this is specific to Macrostrat's UI + * */ + if (match == null) return null; + + if (match.strat_name_id != null) { + return `/lex/strat-names/${match.strat_name_id}`; + } + + if (match.lith_id != null) { + return `/lex/lithologies`; + } + + if (match.lith_att_id != null) { + return `/lex/lithologies`; + } + + return null; +} + +function matchID(match: MacrostratMatchData) { + if (match == null) return null; + + for (const id of ["strat_name_id", "lith_id", "lith_att_id"]) { + if (match[id]) { + return match[id]; + } + } + return null; +} diff --git a/pages/integrations/xdd/feedback/+Page.client.ts b/pages/integrations/xdd/feedback/+Page.client.ts index 9a2d32699..d4690e6ae 100644 --- a/pages/integrations/xdd/feedback/+Page.client.ts +++ b/pages/integrations/xdd/feedback/+Page.client.ts @@ -1,9 +1,12 @@ import { FullscreenPage } from "~/layouts"; -import h from "./main.module.sass"; +import styles from "./main.module.sass"; +import hyper from "@macrostrat/hyper"; import { PageBreadcrumbs } from "~/components"; import { PostgRESTTableView } from "@macrostrat/data-sheet2"; import { postgrestPrefix } from "@macrostrat-web/settings"; +const h = hyper.styled(styles); + export function Page() { return h(FullscreenPage, { className: "main" }, [ h(PageBreadcrumbs), diff --git a/pages/integrations/xdd/feedback/@sourceTextID/+Page.client.module.sass b/pages/integrations/xdd/feedback/@sourceTextID/+Page.client.module.sass new file mode 100644 index 000000000..966c9547f --- /dev/null +++ b/pages/integrations/xdd/feedback/@sourceTextID/+Page.client.module.sass @@ -0,0 +1,13 @@ +.feedback-main + display: flex + flex-direction: column + max-width: 70em + margin: 0 auto + position: relative + max-height: 100% + +.feedback-interface + display: flex + flex-direction: column + flex: 1 + min-height: 100px diff --git a/pages/integrations/xdd/feedback/@sourceTextID/+Page.client.ts b/pages/integrations/xdd/feedback/@sourceTextID/+Page.client.ts index 9f4d91dc4..2a054f3a5 100644 --- a/pages/integrations/xdd/feedback/@sourceTextID/+Page.client.ts +++ b/pages/integrations/xdd/feedback/@sourceTextID/+Page.client.ts @@ -1,25 +1,31 @@ -import h from "./lib/feedback.module.sass"; -import { ContentPage, FullscreenPage } from "~/layouts"; +import h from "./+Page.client.module.sass"; +import { FullscreenPage } from "~/layouts"; import { PageBreadcrumbs } from "~/components"; import { usePageContext } from "vike-react/usePageContext"; -import { enhanceData } from "../../extractions/lib"; +import { + enhanceData, + FeedbackComponent, + GraphData, + treeToGraph, + TreeData, +} from "@macrostrat/feedback-components"; import { useEntityTypeIndex, useModelIndex, usePostgresQuery, -} from "../../extractions/lib/data-service"; -import { FeedbackComponent } from "./lib"; -import { Intent, NonIdealState, OverlaysProvider } from "@blueprintjs/core"; +} from "../../extractions/data-service"; +import { NonIdealState, OverlaysProvider, Spinner } from "@blueprintjs/core"; import { - Box, ErrorBoundary, - FlexCol, FlexRow, Pagination, Spacer, } from "@macrostrat/ui-components"; import { useState } from "react"; import { AuthStatus } from "@macrostrat/auth-components"; +import { MatchedEntityLink } from "#/integrations/xdd/extractions/match"; +import { knowledgeGraphAPIURL } from "@macrostrat-web/settings"; +import { Toaster } from "@blueprintjs/core"; /** * Get a single text window for feedback purposes @@ -55,7 +61,7 @@ function ExtractionIndex() { }); if (data == null || models == null || entityTypes == null) { - return h("div", "Loading..."); + return h(Spinner); } console.log(data); @@ -78,7 +84,7 @@ function MultiFeedbackInterface({ data, models, entityTypes }) { title: "Multiple model runs for feedback", description: `Showing entities from ${ ix + 1 - } of ${count} model runs. Merging several runs is not yet supported.`, + } of ${count} model runs. Merging runs is not yet supported.`, }), h(Pagination, { count, @@ -95,17 +101,118 @@ function MultiFeedbackInterface({ data, models, entityTypes }) { ]); } +const AppToaster = Toaster.create(); + function FeedbackInterface({ data, models, entityTypes }) { const window = enhanceData(data, models, entityTypes); const { entities = [], paragraph_text, model } = window; + + console.log(window); + console.log(Array.from(entityTypes.values())); + return h(FeedbackComponent, { entities, text: paragraph_text, model, entityTypes, - sourceTextID: window.source_text, - runID: window.model_run, + matchComponent: MatchedEntityLink, + onSave: wrapWithToaster( + async (tree) => { + const data = prepareDataForServer(tree, window.source_text, [ + window.model_run, + ]); + await postDataToServer(data); + }, + AppToaster, + { + success: "Model information saved", + error: "Failed to save model information", + } + ), + }); +} + +async function postDataToServer(data: ServerResults) { + const response = await fetch(knowledgeGraphAPIURL + "/record_run", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), }); + if (!response.ok) { + throw new Error(response.statusText); + } +} + +function wrapWithToaster( + fn: (...args: any[]) => Promise, + toaster: Toaster, + messages: { + success: string; + error: string; + } +) { + return async (...args: any[]) => { + try { + await fn(...args); + toaster.show({ + message: messages.success, + intent: "success", + }); + } catch (e) { + console.error(e); + toaster.show({ + message: messages.error + ": " + e.message, + intent: "danger", + }); + } + }; +} + +interface ServerResults extends GraphData { + sourceTextId: number; + supersedesRunIds: number[]; +} + +function prepareDataForServer( + tree: TreeData[], + sourceTextID: number, + supersedesRunIDs: number[] | null +): ServerResults { + /** This function should be used before sending the data to the server */ + const { nodes, edges } = treeToGraph(tree); + + // Prepare match for server + const normalizedNodes = nodes.map((d) => { + return { + ...d, + match: normalizeMatch(d.match), + }; + }); + + return { + nodes: normalizedNodes, + edges, + sourceTextId: sourceTextID, + supersedesRunIds: supersedesRunIDs ?? [], + }; +} + +// We will extend this in the future, probably, +// to handle ages and other things +type MatchInfo = { type: "lith" | "lith_att" | "strat_name"; id: number }; + +function normalizeMatch(match: any): MatchInfo | null { + if (match == null) return null; + if (match.lith_id) return { type: "lith", id: match.lith_id }; + if (match.lith_att_id) { + return { type: "lith_att", id: match.lith_att_id }; + } + if (match.strat_name_id) { + return { type: "strat_name", id: match.strat_name_id }; + } + return null; } diff --git a/pages/integrations/xdd/feedback/@sourceTextID/lib/edit-state.ts b/pages/integrations/xdd/feedback/@sourceTextID/lib/edit-state.ts deleted file mode 100644 index 1364cfba4..000000000 --- a/pages/integrations/xdd/feedback/@sourceTextID/lib/edit-state.ts +++ /dev/null @@ -1,409 +0,0 @@ -import { TreeData } from "./types"; -import { - createContext, - Dispatch, - useCallback, - useContext, - useReducer, -} from "react"; -import update, { Spec } from "immutability-helper"; -import { EntityType } from "#/integrations/xdd/extractions/lib/data-service"; -import { knowledgeGraphAPIURL } from "@macrostrat-web/settings"; -import { Toaster } from "@blueprintjs/core"; - -interface TreeState { - initialTree: TreeData[]; - tree: TreeData[]; - selectedNodes: number[]; - entityTypesMap: Map; - selectedEntityType: EntityType; - lastInternalId: number; - isSelectingEntityType: boolean; -} - -type TextRange = { - start: number; - end: number; - text: string; -}; - -type TreeAsyncAction = { - type: "save"; - tree: TreeData[]; - sourceTextID: number; - supersedesRunIDs: number[]; -}; - -type TreeAction = - | { - type: "move-node"; - payload: { dragIds: number[]; parentId: number; index: number }; - } - | { type: "delete-node"; payload: { ids: number[] } } - | { type: "select-node"; payload: { ids: number[] } } - | { type: "toggle-node-selected"; payload: { ids: number[] } } - | { type: "create-node"; payload: TextRange } - | { type: "select-entity-type"; payload: EntityType } - | { type: "toggle-entity-type-selector"; payload?: boolean | null } - | { type: "deselect" } - | { type: "reset" }; - -export type TreeDispatch = Dispatch; - -export function useUpdatableTree( - initialTree: TreeData[], - entityTypes: Map -): [TreeState, TreeDispatch] { - // Get the first entity type - const type = entityTypes.values().next().value; - - const [state, dispatch] = useReducer(treeReducer, { - initialTree, - tree: initialTree, - selectedNodes: [], - entityTypesMap: entityTypes, - selectedEntityType: type, - lastInternalId: 0, - isSelectingEntityType: false, - }); - - const handler = useCallback( - (action: TreeAsyncAction | TreeAction) => { - treeActionHandler(action).then((action) => { - if (action == null) return; - dispatch(action); - }); - }, - [dispatch] - ); - - return [state, handler]; -} - -const AppToaster = Toaster.create(); - -export const TreeDispatchContext = createContext(null); - -export function useTreeDispatch() { - const dispatch = useContext(TreeDispatchContext); - if (dispatch == null) { - throw new Error("No dispatch context available"); - } - return dispatch; -} - -async function treeActionHandler( - action: TreeAsyncAction | TreeAction -): Promise { - switch (action.type) { - case "save": - // Save the tree to the server - const data = prepareDataForServer( - action.tree, - action.sourceTextID, - action.supersedesRunIDs - ); - console.log(JSON.stringify(data, null, 2)); - - try { - const response = await fetch(knowledgeGraphAPIURL + "/record_run", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }); - if (!response.ok) { - throw new Error("Failed to save model information"); - } - AppToaster.show({ - message: "Model information saved", - intent: "success", - }); - } catch (e) { - // Show the error in the toaster - console.error(e); - AppToaster.show({ - message: "Failed to save model information", - intent: "danger", - }); - } - - return null; - default: - return action; - } -} - -function treeReducer(state: TreeState, action: TreeAction) { - console.log(action); - switch (action.type) { - case "move-node": - // For each node in the tree, if the node is in the dragIds, remove it from the tree and collect it - const [newTree, removedNodes] = removeNodes( - state.tree, - action.payload.dragIds - ); - - let keyPath: (number | "children")[] = []; - if (action.payload.parentId) { - keyPath = findNode(newTree, action.payload.parentId); - keyPath.push("children"); - } - - // Add removed nodes to the new tree at the correct location - let updateSpec = buildNestedSpec(keyPath, { - $splice: [[action.payload.index, 0, ...removedNodes]], - }); - - return { ...state, tree: update(newTree, updateSpec) }; - case "delete-node": - // For each node in the tree, if the node is in the ids, remove it from the tree - const [newTree2, _removedNodes] = removeNodes( - state.tree, - action.payload.ids - ); - // Get children of the removed nodes - // If children are not present elsewhere in the tree, insert them - - const children = _removedNodes - .flatMap((node) => node.children ?? []) - .filter((child) => !nodeIsInTree(newTree2, child.id)); - - // Reset the selection - - return { - ...state, - tree: [...newTree2, ...children], - selectedNodes: state.selectedNodes.filter( - (id) => !action.payload.ids.includes(id) - ), - }; - case "select-node": - const { ids } = action.payload; - return { ...state, selectedNodes: ids }; - // otherwise fall through to toggle-node-selected for a single ID - case "toggle-node-selected": - const nodesToAdd = action.payload.ids.filter( - (id) => !state.selectedNodes.includes(id) - ); - const nodesToKeep = state.selectedNodes.filter( - (id) => !action.payload.ids.includes(id) - ); - return { ...state, selectedNodes: [...nodesToKeep, ...nodesToAdd] }; - - case "create-node": - const newId = state.lastInternalId - 1; - const { text, start, end } = action.payload; - const node: TreeData = { - id: newId, - name: text, - children: [], - indices: [start, end], - type: state.selectedEntityType, - }; - - return { - ...state, - tree: [...state.tree, node], - selectedNodes: [newId], - lastInternalId: newId, - }; - - /** Entity type selection */ - case "toggle-entity-type-selector": - return { - ...state, - isSelectingEntityType: action.payload ?? !state.isSelectingEntityType, - }; - case "select-entity-type": { - // For each selected node, update the type - let newTree2 = state.tree; - for (let id of state.selectedNodes) { - const keyPath = findNode(state.tree, id); - const nestedSpec = buildNestedSpec(keyPath, { - type: { $set: action.payload }, - }); - newTree2 = update(newTree2, nestedSpec); - } - - return { - ...state, - tree: newTree2, - selectedEntityType: action.payload, - }; - } - case "deselect": - return { ...state, selectedNodes: [] }; - case "reset": - return { - ...state, - tree: state.initialTree, - selectedNodes: [], - }; - } -} - -function nodeIsInTree(tree: TreeData[], id: number): boolean { - for (let node of tree) { - if (node.id == id) { - return true; - } else if (node.children) { - if (nodeIsInTree(node.children, id)) { - return true; - } - } - } - return false; -} - -function buildNestedSpec( - keyPath: (number | "children")[], - innerSpec: Spec -): Spec { - // Build a nested object from a key path - - let spec = innerSpec; - for (let i = keyPath.length - 1; i >= 0; i--) { - spec = { [keyPath[i]]: spec }; - } - return spec; - // Since we don't have a "children" key at the root, we make the top-level spec an array -} - -function findNode( - tree: TreeData[], - id: number -): (number | "children")[] | null { - // Find the index of the node with the given id in the tree, returning the key path - for (let i = 0; i < tree.length; i++) { - if (tree[i].id == id) { - return [i]; - } else if (tree[i].children) { - let path = findNode(tree[i].children, id); - if (path != null) { - return [i, "children", ...path]; - } - } - } - return null; -} - -function removeNodes( - tree: TreeData[], - ids: number[] -): [TreeData[], TreeData[]] { - /** Remove nodes with the given ids from the tree and return the new tree and the removed nodes */ - let newTree: TreeData[] = []; - let removedNodes: TreeData[] = []; - - for (let node of tree) { - if (ids.includes(node.id)) { - removedNodes.push(node); - } else { - // Recurse into children - if (node.children) { - let [newChildren, removedChildren] = removeNodes(node.children, ids); - node = { ...node, children: newChildren }; - removedNodes.push(...removedChildren); - } - newTree.push(node); - } - } - - return [newTree, removedNodes]; -} - -export interface EntityOutput { - id: number; - type: number | null; - txt_range: number[][]; - name: string; - match: MatchInfo | null; - reasoning: string | null; -} - -// We will extend this in the future, probably, -// to handle ages and other things -type MatchInfo = { type: "lith" | "lith_att" | "strat_name"; id: number }; - -interface GraphData { - nodes: EntityOutput[]; - edges: { source: number; dest: number }[]; -} - -interface ServerResults extends GraphData { - sourceTextId: number; - supersedesRunIds: number[]; -} - -function normalizeMatch(match: any): MatchInfo | null { - if (match == null) return null; - if (match.lith_id) return { type: "lith", id: match.lith_id }; - if (match.lith_att_id) { - return { type: "lith_att", id: match.lith_att_id }; - } - if (match.strat_name_id) { - return { type: "strat_name", id: match.strat_name_id }; - } - return null; -} - -function prepareGraphForServer(tree: TreeData[]): GraphData { - // Convert the tree to a graph - let nodes: EntityOutput[] = []; - let edges: { source: number; dest: number }[] = []; - const nodeMap = new Map(); - - for (let node of tree) { - // If we've already found an instance of this node, we don't need to record - // it again - if (nodeMap.has(node.id)) { - continue; - } - - const { indices, id, name } = node; - - const nodeData: EntityOutput = { - id, - type: node.type.id, - name, - txt_range: [indices], - reasoning: null, - match: normalizeMatch(node.match), - }; - - nodeMap.set(node.id, node); - nodes.push(nodeData); - - if (node.children) { - for (let child of node.children) { - edges.push({ source: node.id, dest: child.id }); - } - - // Now process the children - const { nodes: childNodes, edges: childEdges } = prepareGraphForServer( - node.children - ); - nodes.push(...childNodes); - edges.push(...childEdges); - } - } - - return { nodes, edges }; -} - -function prepareDataForServer( - tree: TreeData[], - sourceTextID, - supersedesRunIDs -): ServerResults { - /** This function should be used before sending the data to the server */ - const { nodes, edges } = prepareGraphForServer(tree); - return { - nodes, - edges, - sourceTextId: sourceTextID, - supersedesRunIds: supersedesRunIDs ?? [], - }; -} diff --git a/pages/integrations/xdd/feedback/@sourceTextID/lib/feedback.module.sass b/pages/integrations/xdd/feedback/@sourceTextID/lib/feedback.module.sass deleted file mode 100644 index 61c6eac04..000000000 --- a/pages/integrations/xdd/feedback/@sourceTextID/lib/feedback.module.sass +++ /dev/null @@ -1,52 +0,0 @@ -.feedback-component - position: relative - width: 800px - - & > svg - width: 800px - -.node - cursor: pointer - - -.feedback-text - margin-bottom: 2em - -.entity-panel - position: relative - -.control-panel - max-width: 15em - position: absolute - top: 1em - right: 1em - padding: 0.2em 0.5em - - -.feedback-main - display: flex - flex-direction: column - max-width: 70em - margin: 0 auto - position: relative - max-height: 100% - -.feedback-interface - display: flex - flex-direction: column - flex: 1 - min-height: 100px - - -.entity-panel - flex: 1 - min-height: 100px - padding: 1em - background: var(--panel-secondary-background-color) - border-radius: 4px - // Inset box shadow - box-shadow: 0 0 0 1px var(--panel-border-color) inset - -.selection-tree - margin: -1em 0 - padding: 1em 0 diff --git a/pages/integrations/xdd/feedback/@sourceTextID/lib/index.ts b/pages/integrations/xdd/feedback/@sourceTextID/lib/index.ts deleted file mode 100644 index cae61521a..000000000 --- a/pages/integrations/xdd/feedback/@sourceTextID/lib/index.ts +++ /dev/null @@ -1,220 +0,0 @@ -import h from "./feedback.module.sass"; -import { Tree, TreeApi } from "react-arborist"; -import Node from "./node"; -import { FeedbackText } from "./text-visualizer"; -import { Entity, InternalEntity, TreeData } from "./types"; -import { ModelInfo } from "#/integrations/xdd/extractions/lib"; -import { TreeDispatchContext, useUpdatableTree } from "./edit-state"; -import { useEffect, useRef, useState } from "react"; -import { DataField } from "~/components/unit-details"; -import { ButtonGroup, Card } from "@blueprintjs/core"; -import { OmniboxSelector } from "./type-selector"; -import { CancelButton, SaveButton } from "@macrostrat/ui-components"; -import useElementDimensions from "use-element-dimensions"; - -function setsAreTheSame(a: Set, b: Set) { - if (a.size !== b.size) return false; - for (const item of a) { - if (!b.has(item)) return false; - } - return true; -} - -export function FeedbackComponent({ - entities = [], - text, - model, - entityTypes, - sourceTextID, - runID, -}) { - // Get the input arguments - - const [state, dispatch] = useUpdatableTree( - entities.map(processEntity), - entityTypes - ); - - const { selectedNodes, tree, selectedEntityType, isSelectingEntityType } = - state; - - const [{ width, height }, ref] = useElementDimensions(); - - return h(TreeDispatchContext.Provider, { value: dispatch }, [ - h(FeedbackText, { - text, - dispatch, - nodes: tree, - selectedNodes, - }), - h(ModelInfo, { data: model }), - h( - "div.entity-panel", - { - ref, - }, - [ - h(Card, { className: "control-panel" }, [ - h( - ButtonGroup, - { - vertical: true, - fill: true, - minimal: true, - alignText: "left", - }, - [ - h( - CancelButton, - { - icon: "trash", - disabled: state.initialTree == state.tree, - onClick() { - dispatch({ type: "reset" }); - }, - }, - "Reset" - ), - h( - SaveButton, - { - onClick() { - dispatch({ - type: "save", - tree, - sourceTextID: sourceTextID, - supersedesRunIDs: [runID], - }); - }, - disabled: state.initialTree == state.tree, - }, - "Save" - ), - ] - ), - h(EntityTypeSelector, { - entityTypes, - selected: selectedEntityType, - onChange(payload) { - dispatch({ type: "select-entity-type", payload }); - }, - isOpen: isSelectingEntityType, - setOpen: (isOpen: boolean) => - dispatch({ - type: "toggle-entity-type-selector", - payload: isOpen, - }), - }), - ]), - h(ManagedSelectionTree, { - selectedNodes, - dispatch, - tree, - width, - height, - }), - ] - ), - ]); -} - -function processEntity(entity: Entity): InternalEntity { - return { - ...entity, - term_type: entity.type.name, - txt_range: [entity.indices], - children: entity.children?.map(processEntity) ?? [], - }; -} - -function EntityTypeSelector({ - entityTypes, - selected, - isOpen, - setOpen, - onChange, -}) { - // Show all entity types when selected is null - const _selected = selected != null ? selected : undefined; - return h(DataField, { label: "Entity type", inline: true }, [ - h( - "code.bp5-code", - { - onClick() { - setOpen((d) => !d); - }, - }, - selected.name - ), - h(OmniboxSelector, { - isOpen, - items: Array.from(entityTypes.values()), - selectedItem: _selected, - onSelectItem(item) { - setOpen(false); - onChange(item); - }, - onClose() { - setOpen(false); - }, - }), - ]); -} - -function ManagedSelectionTree(props) { - const { selectedNodes, dispatch, tree, height, width, ...rest } = props; - - const ref = useRef>(); - - useEffect(() => { - if (ref.current == null) return; - // Check if selection matches current - const selection = new Set(selectedNodes.map((d) => d.toString())); - const currentSelection = ref.current.selectedIds; - if (setsAreTheSame(selection, currentSelection)) return; - // If the selection is the same, do nothing - - // Set selection - ref.current.setSelection({ - ids: selectedNodes.map((d) => d.toString()), - anchor: null, - mostRecent: null, - }); - }, [selectedNodes]); - - return h(Tree, { - className: "selection-tree", - height, - width, - ref, - data: tree, - onMove({ dragIds, parentId, index }) { - dispatch({ - type: "move-node", - payload: { - dragIds: dragIds.map((d) => parseInt(d)), - parentId: parentId ? parseInt(parentId) : null, - index, - }, - }); - }, - onDelete({ ids }) { - dispatch({ - type: "delete-node", - payload: { ids: ids.map((d) => parseInt(d)) }, - }); - }, - onSelect(nodes) { - let ids = nodes.map((d) => parseInt(d.id)); - if (ids.length == 1 && ids[0] == selectedNodes[0]) { - // Deselect - ids = []; - } - dispatch({ type: "select-node", payload: { ids } }); - }, - children: Node, - idAccessor(d: TreeData) { - return d.id.toString(); - }, - }); -} diff --git a/pages/integrations/xdd/feedback/@sourceTextID/lib/node.ts b/pages/integrations/xdd/feedback/@sourceTextID/lib/node.ts deleted file mode 100644 index 637a6b707..000000000 --- a/pages/integrations/xdd/feedback/@sourceTextID/lib/node.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { NodeApi, TreeApi } from "react-arborist"; -import { TreeData } from "./types"; -import h from "./feedback.module.sass"; -import { EntityTag } from "../../../extractions/lib"; -import { useTreeDispatch } from "./edit-state"; - -function isSelected(searchNode: TreeData, treeNode: TreeData) { - return searchNode.id == treeNode.id; - // We could also select children of the search node here if we wanted to -} - -function isNodeHighlighted(node: NodeApi, tree: TreeApi) { - // We treat no selection as all nodes being active. We may add some nuance later - if (tree.selectedNodes.length == 0) { - return true; - } - - for (const selectedNode of tree.selectedNodes) { - if (isSelected(node.data, selectedNode.data)) { - return true; - } - } - - // Check if the parent node is highlighted - if (node.parent != null && isNodeHighlighted(node.parent, tree)) { - return true; - } - - return false; -} - -function isNodeActive(node: NodeApi, tree: TreeApi) { - for (const selectedNode of tree.selectedNodes) { - if (isSelected(node.data, selectedNode.data)) { - return true; - } - } - return false; -} - -function Node({ node, style, dragHandle, tree }: any) { - let highlighted: boolean = isNodeHighlighted(node, tree); - let active: boolean = isNodeActive(node, tree); - - const dispatch = useTreeDispatch(); - - return h( - "div.node", - { style, ref: dragHandle }, - h(EntityTag, { - data: node.data, - active, - highlighted, - onClickType() { - dispatch({ type: "toggle-entity-type-selector" }); - }, - }) - ); -} - -export default Node; diff --git a/pages/integrations/xdd/feedback/@sourceTextID/lib/text-visualizer.ts b/pages/integrations/xdd/feedback/@sourceTextID/lib/text-visualizer.ts deleted file mode 100644 index 905f0eafd..000000000 --- a/pages/integrations/xdd/feedback/@sourceTextID/lib/text-visualizer.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { AnnotateBlendTag, TextAnnotateBlend } from "react-text-annotate-blend"; -import { InternalEntity } from "./types"; -import { TreeDispatch } from "./edit-state"; -import h from "./feedback.module.sass"; -import { buildHighlights } from "#/integrations/xdd/extractions/lib"; -import { Highlight } from "#/integrations/xdd/extractions/lib/types"; -import { useCallback } from "react"; -import { getTagStyle } from "#/integrations/xdd/extractions/lib"; - -export interface FeedbackTextProps { - text: string; - selectedNodes: number[]; - nodes: InternalEntity[]; - updateNodes: (nodes: string[]) => void; - dispatch: TreeDispatch; -} - -function buildTags( - highlights: Highlight[], - selectedNodes: number[] -): AnnotateBlendTag[] { - let tags: AnnotateBlendTag[] = []; - - // If entity ID has already been seen, don't add it again - const entities = new Set(); - - for (const highlight of highlights) { - // Don't add multiply-linked entities multiple times - if (entities.has(highlight.id)) continue; - - const highlighted = isHighlighted(highlight, selectedNodes); - const active = isActive(highlight, selectedNodes); - - tags.push({ - markStyle: { - ...getTagStyle(highlight.backgroundColor, { highlighted, active }), - borderRadius: "0.2em", - padding: "0.1em", - borderWidth: "1.5px", - cursor: "pointer", - }, - tagStyle: { - display: "none", - }, - ...highlight, - }); - - entities.add(highlight.id); - } - - return tags; -} - -function isActive(tag: Highlight, selectedNodes: number[]) { - return selectedNodes.includes(tag.id); -} - -function isHighlighted(tag: Highlight, selectedNodes: number[]) { - if (selectedNodes.length === 0) return true; - return ( - (selectedNodes.includes(tag.id) || - tag.parents?.some((d) => selectedNodes.includes(d))) ?? - false - ); -} - -export function FeedbackText(props: FeedbackTextProps) { - // Convert input to tags - const { text, selectedNodes, nodes, dispatch } = props; - let allTags: AnnotateBlendTag[] = buildTags( - buildHighlights(nodes, null), - selectedNodes - ); - - const onChange = useCallback( - (tags) => { - // New tags - console.log(tags); - const newTags = tags.filter((d) => !("id" in d)); - if (newTags.length > 0) { - const { start, end } = newTags[0]; - const payload = { start, end, text: text.slice(start, end) }; - dispatch({ type: "create-node", payload }); - return; - } - - const tagIDs = new Set(tags.map((d) => d.id)); - const removedIds = allTags.map((d) => d.id).filter((d) => !tagIDs.has(d)); - - /* Find the id that was removed: that is the one that will be selected - (we are hijacking the 'click to delete' functionality to select instead) */ - if (removedIds.length > 0) { - dispatch({ - type: "toggle-node-selected", - payload: { ids: removedIds }, - }); - } - }, - [allTags, text] - ); - - return h(TextAnnotateBlend, { - style: { - fontSize: "1.2em", - }, - className: "feedback-text", - content: text, - onChange, - value: allTags, - }); -} diff --git a/pages/integrations/xdd/feedback/@sourceTextID/lib/type-selector/index.ts b/pages/integrations/xdd/feedback/@sourceTextID/lib/type-selector/index.ts deleted file mode 100644 index 7ecc570b6..000000000 --- a/pages/integrations/xdd/feedback/@sourceTextID/lib/type-selector/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Entity type selector - */ - -import h from "./main.module.sass"; -import classNames from "classnames"; -import { Omnibar, OmnibarProps } from "@blueprintjs/select"; -import "@blueprintjs/select/lib/css/blueprint-select.css"; - -interface TagItemProps { - selected: boolean; - active: boolean; - className?: string; - item: T; - - onSelect(t: T): void; - - children?: React.ReactElement; -} - -const TagListItem: React.ComponentType> = (props) => { - /** Render a tag for the omnibox list */ - let { active, selected, className, onSelect, item, children } = props; - className = classNames({ active, selected }, className); - const onClick = () => onSelect(item); - - return h( - "div.item-container", - { - key: item.id, - className, - onClick, - }, - [ - h("div.swatch", { style: { backgroundColor: item.color } }), - h("div.item", {}, item.name), - ] - ); -}; - -type BoxLifecycleProps = Pick, "onClose" | "isOpen">; - -interface OmniboxProps extends BoxLifecycleProps { - items: T[]; - selectedItem: T; - onSelectItem: (t: T) => void; - listItemComponent?: React.ComponentType>; -} - -export function OmniboxSelector(props: OmniboxProps) { - /** A general omnibox for annotation types */ - const { onSelectItem, items, isOpen, onClose } = props; - - return h(Omnibar, { - onItemSelect: onSelectItem, - items, - resetOnSelect: false, - isOpen, - onClose, - itemRenderer(item, { handleClick, modifiers }) { - return h(TagListItem, { - key: item.id, - item, - onSelect: handleClick, - active: modifiers.active, - selected: modifiers.active, - }); - }, - }); -} diff --git a/pages/integrations/xdd/feedback/@sourceTextID/lib/type-selector/main.module.sass b/pages/integrations/xdd/feedback/@sourceTextID/lib/type-selector/main.module.sass deleted file mode 100644 index bbdb0e8b2..000000000 --- a/pages/integrations/xdd/feedback/@sourceTextID/lib/type-selector/main.module.sass +++ /dev/null @@ -1,13 +0,0 @@ -.item-container - min-height: 40px - display: flex - align-items: center - gap: 1em - - &.selected - background-color: var(--panel-background-color) - -.swatch - width: 30px - height: 30px - border-radius: 4px diff --git a/pages/integrations/xdd/feedback/@sourceTextID/lib/types.ts b/pages/integrations/xdd/feedback/@sourceTextID/lib/types.ts deleted file mode 100644 index 2116cfc37..000000000 --- a/pages/integrations/xdd/feedback/@sourceTextID/lib/types.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Entity, EntityExt } from "../../../extractions/lib/types"; - -export { Entity }; - -export interface InternalEntity extends EntityExt { - term_type: string; - txt_range: number[][]; - children: InternalEntity[]; - orig_id: number; - id: string; -} - -export interface TextData { - preprocessor_id: string; - extraction_pipeline_id: string; - model_id: string; - paper_id: string; - hashed_text: string; - weaviate_id: string; - paragraph_text: string; -} - -export interface Result { - text: TextData; - entities?: InternalEntity[]; -} - -export type TreeData = EntityExt; - -export interface ServerRelationship { - src_name: string; - dst_name: string; - relationship_type: string; -} - -export interface ServerEntity { - entity_name: string; - entity_type: string; -} - -export interface ServerResponse { - text: TextData; - relationships: ServerRelationship[]; - just_entities: ServerEntity[]; -} - -export interface RunText { - preprocessor_id: string; - paper_id: string; - hashed_text: string; - weaviate_id: string; - paragraph_text: string; -} - -export interface RunRelationship { - src: string; - dst: string; - relationship_type: string; -} - -export interface RunEntity { - entity: string; - entity_type: string; -} - -export interface RunSource { - text: RunText; - relationships: RunRelationship[]; - just_entities: RunEntity[]; -} - -export interface RunRecord { - run_id: string; - extraction_pipeline_id: string; - user_name: string; - model_id: string; - results: RunSource[]; -} diff --git a/src/components/unit-details/index.ts b/src/components/unit-details/index.ts index e55887cc3..2ae1f09f7 100644 --- a/src/components/unit-details/index.ts +++ b/src/components/unit-details/index.ts @@ -1,10 +1,12 @@ -import classNames from "classnames"; import { mergeAgeRanges } from "@macrostrat-web/utility-functions"; import { LithologyTag } from "~/components"; import h from "./main.module.sass"; import { Button, Popover } from "@blueprintjs/core"; import { DOMElement } from "react"; import { JSONView } from "@macrostrat/ui-components"; +import { DataField, ValueContainer } from "@macrostrat/ui-components"; + +export { DataField }; export function UnitDetailsPopover({ style, @@ -54,36 +56,6 @@ export function LegendJSONView({ data }) { return h(JSONView, { data, hideRoot: true, className: "legend-json-view" }); } -export function DataField({ - label, - value, - inline = true, - showIfEmpty = false, - className, - children, - unit, -}: { - label?: string; - value?: any; - inline?: boolean; - showIfEmpty?: boolean; - className?: string; - children?: any; - unit?: string; -}) { - if (!showIfEmpty && (value == null || value === "") && children == null) { - return null; - } - - return h("div.data-field", { className: classNames(className, { inline }) }, [ - h("div.label", label), - h("div.data-container", [ - h.if(value != null)(Value, { value, unit }), - children, - ]), - ]); -} - export type IntervalShort = { id: number; b_age: number; @@ -110,28 +82,15 @@ export function IntervalField({ intervals }: { intervals: IntervalShort[] }) { showAgeRange: true, }); }), - h(Value, { unit: "Ma", value: `${ageRange[0]} - ${ageRange[1]}` }), + h(ValueContainer, { + unit: "Ma", + value: `${ageRange[0]} - ${ageRange[1]}`, + }), ] ), ]); } -function Value({ - value, - unit, - children, -}: { - value?: any; - unit?: string; - children?: any; -}) { - const val = value ?? children; - return h("span.value-container", [ - h("span.value", val), - h.if(unit != null)([" ", h("span.unit", unit)]), - ]); -} - function Interval({ interval, showAgeRange = false, diff --git a/src/components/unit-details/main.module.sass b/src/components/unit-details/main.module.sass index 9d57cb057..b5c72b5da 100644 --- a/src/components/unit-details/main.module.sass +++ b/src/components/unit-details/main.module.sass @@ -30,12 +30,15 @@ background-color: var(--panel-secondary-background-color) margin: -1em -1em 0 padding: 0.5em 1em + code font-size: 0.9em color: var(--secondary-color) align-items: baseline + .spacer flex: 1 + button line-height: 1 @@ -43,24 +46,3 @@ margin: 0 0 0.5em font-size: 1.1em -.data-field - margin: 0.2em 0 0.4em - &.inline - display: flex - flex-direction: row - align-items: baseline - gap: 1em - justify-content: space-between - .data-container - text-align: right - .label - font-size: 0.9em - font-weight: bold - display: block - color: var(--secondary-color) - .value-container - font-size: 0.9em - display: inline-block - break-inside: avoid - .unit - color: var(--secondary-color) diff --git a/src/declaration.d.ts b/src/declaration.d.ts index bf7d239f9..1e70fc752 100644 --- a/src/declaration.d.ts +++ b/src/declaration.d.ts @@ -35,31 +35,22 @@ type Classes = { readonly [key: string]: string }; type StyledHyper = Classes & Hyper; +declare module "*.styl" { + const content: string; + export default content; +} + // Style modules declare module "*.module.styl" { - const classes: { readonly [key: string]: string }; - export default StyledHyper; + const classes: StyledHyper; } -// Override declarations for sass module +// Augment existing declarations for sass module declare module "*.module.sass" { - const classes: Classes; - export default StyledHyper; + const classes: StyledHyper; } -// Override declarations for sass module +// Augment existing declarations for sass module declare module "*.module.scss" { - import styles from "./main.module.sass"; - const classes: { [key: string]: string }; - export default StyledHyper; -} - -declare module "*.styl" { - const content: string; - export default content; -} - -declare module "*.sass" { - const content: string; - export default content; + const classes: StyledHyper; } diff --git a/tsconfig.json b/tsconfig.json index c077ff208..a2a638308 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,7 @@ "@macrostrat-web/*": ["packages/*/src"], "~/*": ["src/*"], "#/*": ["pages/*"] - } + }, }, "include": [ "deps/cesium-viewer/src/**/*", @@ -22,5 +22,5 @@ "packages/*/src/**/*", "src/**/*", "pages/**/*" - ] + ], } diff --git a/yarn.lock b/yarn.lock index d47e5b3ff..9fc50ae22 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5620,7 +5620,13 @@ __metadata: languageName: unknown linkType: soft -"@macrostrat/color-utils@npm:^1.0.0, @macrostrat/color-utils@workspace:*, @macrostrat/color-utils@workspace:deps/web-components/packages/color-utils": +"@macrostrat/chronostrat-utils@workspace:*, @macrostrat/chronostrat-utils@workspace:deps/web-components/packages/chronostrat-utils": + version: 0.0.0-use.local + resolution: "@macrostrat/chronostrat-utils@workspace:deps/web-components/packages/chronostrat-utils" + languageName: unknown + linkType: soft + +"@macrostrat/color-utils@npm:^1.0.0, @macrostrat/color-utils@workspace:*, @macrostrat/color-utils@workspace:^1.0.0, @macrostrat/color-utils@workspace:deps/web-components/packages/color-utils": version: 0.0.0-use.local resolution: "@macrostrat/color-utils@workspace:deps/web-components/packages/color-utils" dependencies: @@ -5673,6 +5679,7 @@ __metadata: "@macrostrat/timescale": "workspace:*" d3-array: "npm:^3.2.1" d3-geo-voronoi: "npm:^2.0.1" + zustand: "npm:^5.0.3" peerDependencies: "@blueprintjs/core": ^3.43.0 || ^4.3.0 || ^5.0.0 react: ^16.8.6||^17.0.0||^18.0.0 @@ -5711,6 +5718,7 @@ __metadata: "@babel/preset-react": "npm:^7.16.7" "@babel/preset-typescript": "npm:^7.16.7" "@blueprintjs/core": "npm:^5.10.2" + "@macrostrat/chronostrat-utils": "workspace:*" "@macrostrat/hyper": "npm:^2.0.1" "@rollup/plugin-commonjs": "npm:^21.0.1" "@rollup/plugin-json": "npm:^4.1.0" @@ -5780,6 +5788,12 @@ __metadata: dependencies: "@babel/preset-react": "npm:^7.18.6" "@blueprintjs/core": "npm:^5.10.2" + "@macrostrat/color-utils": "workspace:^1.0.0" + "@macrostrat/hyper": "npm:^3.0.0" + "@macrostrat/ui-components": "workspace:*" + "@types/d3-force": "npm:^3" + "@xyflow/react": "npm:^12.3.6" + d3-force: "npm:^3.0.0" react: "npm:^17.0.2||^18" react-arborist: "npm:^3.4.0" react-text-annotate-blend: "npm:^1.2.0" @@ -6169,7 +6183,6 @@ __metadata: part-regex: "npm:^0.1.2" prettier: "npm:^2.7.1" react: "npm:^18.3.0" - react-arborist: "npm:^3.4.0" react-cookie: "npm:^3.0.4" react-dom: "npm:^18.3.0" react-intersection-observer: "npm:^9.4.3" @@ -6178,7 +6191,6 @@ __metadata: react-router-dom: "npm:^6.8.2" react-router-hash-link: "npm:^2.4.3" react-spring: "npm:^9.7.3" - react-text-annotate-blend: "npm:^1.2.0" reduce-reducers: "npm:^1.0.4" redux: "npm:^4.0.5" sass: "npm:^1.49.0" @@ -7698,11 +7710,11 @@ __metadata: linkType: hard "@supabase/postgrest-js@npm:^1.17.7": - version: 1.17.7 - resolution: "@supabase/postgrest-js@npm:1.17.7" + version: 1.18.1 + resolution: "@supabase/postgrest-js@npm:1.18.1" dependencies: "@supabase/node-fetch": "npm:^2.6.14" - checksum: 10/c21b76226c68f33f3eb70814847c1b3f18e2f1962ec29f3fea15331fb2f86a93e68c038370931369ad207a656d599ece2f6d85d8285e6277c548d3528ff1358e + checksum: 10/c45ad2eac026d29b7d4ce00e5e68be02295eb6b7b8769541cd97c9030ac36ebbca21eb0729326365c002d97c66f77c1977d1cc86a71a5e7b29af74559a56bcdf languageName: node linkType: hard @@ -8726,6 +8738,13 @@ __metadata: languageName: node linkType: hard +"@types/d3-color@npm:*": + version: 3.1.3 + resolution: "@types/d3-color@npm:3.1.3" + checksum: 10/1cf0f512c09357b25d644ab01b54200be7c9b15c808333b0ccacf767fff36f17520b2fcde9dad45e1bd7ce84befad39b43da42b4fded57680fa2127006ca3ece + languageName: node + linkType: hard + "@types/d3-color@npm:^1": version: 1.4.3 resolution: "@types/d3-color@npm:1.4.3" @@ -8742,6 +8761,22 @@ __metadata: languageName: node linkType: hard +"@types/d3-drag@npm:^3.0.7": + version: 3.0.7 + resolution: "@types/d3-drag@npm:3.0.7" + dependencies: + "@types/d3-selection": "npm:*" + checksum: 10/93aba299c3a8d41ee326c5304ab694ceea135ed115c3b2ccab727a5d9bfc935f7f36d3fc416c013010eb755ac536c52adfcb15c195f241dc61f62650cc95088e + languageName: node + linkType: hard + +"@types/d3-force@npm:^3": + version: 3.0.10 + resolution: "@types/d3-force@npm:3.0.10" + checksum: 10/9c35abed2af91b94fc72d6b477188626e628ed89a01016437502c1deaf558da934b5d0cc808c2f2979ac853b6302b3d6ef763eddaff3a55552a55c0be710d5ca + languageName: node + linkType: hard + "@types/d3-geo@npm:^1.11.1": version: 1.12.7 resolution: "@types/d3-geo@npm:1.12.7" @@ -8767,6 +8802,15 @@ __metadata: languageName: node linkType: hard +"@types/d3-interpolate@npm:*": + version: 3.0.4 + resolution: "@types/d3-interpolate@npm:3.0.4" + dependencies: + "@types/d3-color": "npm:*" + checksum: 10/72a883afd52c91132598b02a8cdfced9e783c54ca7e4459f9e29d5f45d11fb33f2cabc844e42fd65ba6e28f2a931dcce1add8607d2f02ef6fb8ea5b83ae84127 + languageName: node + linkType: hard + "@types/d3-interpolate@npm:^1, @types/d3-interpolate@npm:^1.3.1": version: 1.4.3 resolution: "@types/d3-interpolate@npm:1.4.3" @@ -8801,6 +8845,13 @@ __metadata: languageName: node linkType: hard +"@types/d3-selection@npm:*, @types/d3-selection@npm:^3.0.10": + version: 3.0.11 + resolution: "@types/d3-selection@npm:3.0.11" + checksum: 10/2d2d993b9e9553d066566cb22916c632e5911090db99e247bd8c32855a344e6b7c25b674f3c27956c367a6b3b1214b09931ce854788c3be2072003e01f2c75d7 + languageName: node + linkType: hard + "@types/d3-selection@npm:^1, @types/d3-selection@npm:^1.4.2": version: 1.4.4 resolution: "@types/d3-selection@npm:1.4.4" @@ -8831,6 +8882,15 @@ __metadata: languageName: node linkType: hard +"@types/d3-transition@npm:^3.0.8": + version: 3.0.9 + resolution: "@types/d3-transition@npm:3.0.9" + dependencies: + "@types/d3-selection": "npm:*" + checksum: 10/dad647c485440f176117e8a45f31aee9427d8d4dfa174eaa2f01e702641db53ad0f752a144b20987c7189723c4f0afe0bf0f16d95b2a91aa28937eee4339c161 + languageName: node + linkType: hard + "@types/d3-zoom@npm:^1.7.4": version: 1.8.5 resolution: "@types/d3-zoom@npm:1.8.5" @@ -8841,6 +8901,16 @@ __metadata: languageName: node linkType: hard +"@types/d3-zoom@npm:^3.0.8": + version: 3.0.8 + resolution: "@types/d3-zoom@npm:3.0.8" + dependencies: + "@types/d3-interpolate": "npm:*" + "@types/d3-selection": "npm:*" + checksum: 10/cc6ba975cf4f55f94933413954d81b87feb1ee8b8cee8f2202cf526f218dcb3ba240cbeb04ed80522416201c4a7394b37de3eb695d840a36d190dfb2d3e62cb5 + languageName: node + linkType: hard + "@types/debug@npm:^4.0.0": version: 4.1.9 resolution: "@types/debug@npm:4.1.9" @@ -10474,6 +10544,35 @@ __metadata: languageName: node linkType: hard +"@xyflow/react@npm:^12.3.6": + version: 12.4.2 + resolution: "@xyflow/react@npm:12.4.2" + dependencies: + "@xyflow/system": "npm:0.0.50" + classcat: "npm:^5.0.3" + zustand: "npm:^4.4.0" + peerDependencies: + react: ">=17" + react-dom: ">=17" + checksum: 10/7137228d1fa29dc77dd9798c646638ff16c1c4f983c6d6b0f5898118f3c519eecbf1e6c6b2f6b4e4516409d921e6aed676b0ba7e7b000d029b0fca2a0193c712 + languageName: node + linkType: hard + +"@xyflow/system@npm:0.0.50": + version: 0.0.50 + resolution: "@xyflow/system@npm:0.0.50" + dependencies: + "@types/d3-drag": "npm:^3.0.7" + "@types/d3-selection": "npm:^3.0.10" + "@types/d3-transition": "npm:^3.0.8" + "@types/d3-zoom": "npm:^3.0.8" + d3-drag: "npm:^3.0.0" + d3-selection: "npm:^3.0.0" + d3-zoom: "npm:^3.0.0" + checksum: 10/ac81cdb5e40b0ba2871d084395e885438fa038ed99421a60a166e6323588adbd09f57a1fe86f3e06c63c4c2d244fc2cfe609e4cc0177f32ac1d8bb2c45970d29 + languageName: node + linkType: hard + "@yarnpkg/core@npm:^4.0.2": version: 4.0.3 resolution: "@yarnpkg/core@npm:4.0.3" @@ -12806,6 +12905,13 @@ __metadata: languageName: node linkType: hard +"classcat@npm:^5.0.3": + version: 5.0.5 + resolution: "classcat@npm:5.0.5" + checksum: 10/19bdeb99b8923b47f9df978b6ef2c5a4cc3bcaa8fb6be16244e31fad619b291b366429747331903ac2ea27560ffd6066d14089a99c95535ce0f1e897525fa63d + languageName: node + linkType: hard + "classnames@npm:*, classnames@npm:^2.2.5, classnames@npm:^2.2.6, classnames@npm:^2.3.0, classnames@npm:^2.3.1": version: 2.3.2 resolution: "classnames@npm:2.3.2" @@ -14274,6 +14380,13 @@ __metadata: languageName: node linkType: hard +"d3-dispatch@npm:1 - 3": + version: 3.0.1 + resolution: "d3-dispatch@npm:3.0.1" + checksum: 10/2b82f41bf4ef88c2f9033dfe32815b67e2ef1c5754a74137a74c7d44d6f0d6ecfa934ac56ed8afe358f6c1f06462e8aa42ca0a388397b5b77a42721570e80487 + languageName: node + linkType: hard + "d3-drag@npm:1, d3-drag@npm:^1.2.5": version: 1.2.5 resolution: "d3-drag@npm:1.2.5" @@ -14284,6 +14397,16 @@ __metadata: languageName: node linkType: hard +"d3-drag@npm:2 - 3, d3-drag@npm:^3.0.0": + version: 3.0.0 + resolution: "d3-drag@npm:3.0.0" + dependencies: + d3-dispatch: "npm:1 - 3" + d3-selection: "npm:3" + checksum: 10/80bc689935e5a46ee92b2d7f71e1c792279382affed9fbcf46034bff3ff7d3f50cf61a874da4bdf331037292b9e7dca5c6401a605d4bb699fdcb4e0c87e176ec + languageName: node + linkType: hard + "d3-dsv@npm:1": version: 1.2.0 resolution: "d3-dsv@npm:1.2.0" @@ -14312,6 +14435,13 @@ __metadata: languageName: node linkType: hard +"d3-ease@npm:1 - 3": + version: 3.0.1 + resolution: "d3-ease@npm:3.0.1" + checksum: 10/985d46e868494e9e6806fedd20bad712a50dcf98f357bf604a843a9f6bc17714a657c83dd762f183173dcde983a3570fa679b2bc40017d40b24163cdc4167796 + languageName: node + linkType: hard + "d3-fetch@npm:1": version: 1.2.0 resolution: "d3-fetch@npm:1.2.0" @@ -14333,6 +14463,17 @@ __metadata: languageName: node linkType: hard +"d3-force@npm:^3.0.0": + version: 3.0.0 + resolution: "d3-force@npm:3.0.0" + dependencies: + d3-dispatch: "npm:1 - 3" + d3-quadtree: "npm:1 - 3" + d3-timer: "npm:1 - 3" + checksum: 10/85945f8d444d78567009518f0ab54c0f0c8873eb8eb9a2ff0ab667b0f81b419e101a411415d4a2c752547ec7143f89675e8c33b8f111e55e5579a04cb7f4591c + languageName: node + linkType: hard + "d3-format@npm:1 - 2, d3-format@npm:^2.0.0": version: 2.0.0 resolution: "d3-format@npm:2.0.0" @@ -14409,6 +14550,15 @@ __metadata: languageName: node linkType: hard +"d3-interpolate@npm:1 - 3, d3-interpolate@npm:1.2.0 - 3": + version: 3.0.1 + resolution: "d3-interpolate@npm:3.0.1" + dependencies: + d3-color: "npm:1 - 3" + checksum: 10/988d66497ef5c190cf64f8c80cd66e1e9a58c4d1f8932d776a8e3ae59330291795d5a342f5a97602782ccbef21a5df73bc7faf1f0dc46a5145ba6243a82a0f0e + languageName: node + linkType: hard + "d3-interpolate@npm:1, d3-interpolate@npm:^1.4.0": version: 1.4.0 resolution: "d3-interpolate@npm:1.4.0" @@ -14427,15 +14577,6 @@ __metadata: languageName: node linkType: hard -"d3-interpolate@npm:1.2.0 - 3": - version: 3.0.1 - resolution: "d3-interpolate@npm:3.0.1" - dependencies: - d3-color: "npm:1 - 3" - checksum: 10/988d66497ef5c190cf64f8c80cd66e1e9a58c4d1f8932d776a8e3ae59330291795d5a342f5a97602782ccbef21a5df73bc7faf1f0dc46a5145ba6243a82a0f0e - languageName: node - linkType: hard - "d3-path@npm:1, d3-path@npm:^1.0.5, d3-path@npm:^1.0.9": version: 1.0.9 resolution: "d3-path@npm:1.0.9" @@ -14464,6 +14605,13 @@ __metadata: languageName: node linkType: hard +"d3-quadtree@npm:1 - 3": + version: 3.0.1 + resolution: "d3-quadtree@npm:3.0.1" + checksum: 10/1915b6a7b031fc312f9af61947072db9468c5a2b03837f6a90b38fdaebcd0ea17a883bffd94d16b8a6848e81711a06222f7d39f129386ef1850297219b8d32ba + languageName: node + linkType: hard + "d3-queue@npm:1": version: 1.2.3 resolution: "d3-queue@npm:1.2.3" @@ -14535,6 +14683,13 @@ __metadata: languageName: node linkType: hard +"d3-selection@npm:2 - 3, d3-selection@npm:3, d3-selection@npm:^3.0.0": + version: 3.0.0 + resolution: "d3-selection@npm:3.0.0" + checksum: 10/0e5acfd305b31628b7be5009ba7303d84bb34817a88ed4dde9c8bd9c23528573fc5272f89fc04e5be03d2cbf5441a248d7274aaf55a8ef3dad46e16333d72298 + languageName: node + linkType: hard + "d3-shape@npm:1, d3-shape@npm:^1.0.6, d3-shape@npm:^1.2.0": version: 1.3.7 resolution: "d3-shape@npm:1.3.7" @@ -14612,6 +14767,13 @@ __metadata: languageName: node linkType: hard +"d3-timer@npm:1 - 3": + version: 3.0.1 + resolution: "d3-timer@npm:3.0.1" + checksum: 10/004128602bb187948d72c7dc153f0f063f38ac7a584171de0b45e3a841ad2e17f1e40ad396a4af9cce5551b6ab4a838d5246d23492553843d9da4a4050a911e2 + languageName: node + linkType: hard + "d3-transition@npm:1": version: 1.3.2 resolution: "d3-transition@npm:1.3.2" @@ -14626,6 +14788,21 @@ __metadata: languageName: node linkType: hard +"d3-transition@npm:2 - 3": + version: 3.0.1 + resolution: "d3-transition@npm:3.0.1" + dependencies: + d3-color: "npm:1 - 3" + d3-dispatch: "npm:1 - 3" + d3-ease: "npm:1 - 3" + d3-interpolate: "npm:1 - 3" + d3-timer: "npm:1 - 3" + peerDependencies: + d3-selection: 2 - 3 + checksum: 10/02571636acb82f5532117928a87fe25de68f088c38ab4a8b16e495f0f2d08a3fd2937eaebdefdfcf7f1461545524927d2632d795839b88d2e4c71e387aaaffac + languageName: node + linkType: hard + "d3-tricontour@npm:1": version: 1.0.2 resolution: "d3-tricontour@npm:1.0.2" @@ -14656,6 +14833,19 @@ __metadata: languageName: node linkType: hard +"d3-zoom@npm:^3.0.0": + version: 3.0.0 + resolution: "d3-zoom@npm:3.0.0" + dependencies: + d3-dispatch: "npm:1 - 3" + d3-drag: "npm:2 - 3" + d3-interpolate: "npm:1 - 3" + d3-selection: "npm:2 - 3" + d3-transition: "npm:2 - 3" + checksum: 10/0e6e5c14e33c4ecdff311a900dd037dea407734f2dd2818988ed6eae342c1799e8605824523678bd404f81e37824cc588f62dbde46912444c89acc7888036c6b + languageName: node + linkType: hard + "d3@npm:3": version: 3.5.17 resolution: "d3@npm:3.5.17" @@ -33186,6 +33376,15 @@ __metadata: languageName: node linkType: hard +"use-sync-external-store@npm:^1.2.2": + version: 1.4.0 + resolution: "use-sync-external-store@npm:1.4.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10/08bf581a8a2effaefc355e9d18ed025d436230f4cc973db2f593166df357cf63e47b9097b6e5089b594758bde322e1737754ad64905e030d70f8ff7ee671fd01 + languageName: node + linkType: hard + "util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" @@ -34629,6 +34828,26 @@ __metadata: languageName: node linkType: hard +"zustand@npm:^4.4.0": + version: 4.5.6 + resolution: "zustand@npm:4.5.6" + dependencies: + use-sync-external-store: "npm:^1.2.2" + peerDependencies: + "@types/react": ">=16.8" + immer: ">=9.0.6" + react: ">=16.8" + peerDependenciesMeta: + "@types/react": + optional: true + immer: + optional: true + react: + optional: true + checksum: 10/9efd6faca813d9e6e35289b606b5f3c744e806c5a0e5e22ecf73b08af4a23037bdc13ac97902505c664a51aed806384c0652e259a0f199fa31a9bc672e1d7d91 + languageName: node + linkType: hard + "zustand@npm:^4.5.1": version: 4.5.1 resolution: "zustand@npm:4.5.1" @@ -34670,9 +34889,9 @@ __metadata: languageName: node linkType: hard -"zustand@npm:^5.0.2": - version: 5.0.2 - resolution: "zustand@npm:5.0.2" +"zustand@npm:^5.0.2, zustand@npm:^5.0.3": + version: 5.0.3 + resolution: "zustand@npm:5.0.3" peerDependencies: "@types/react": ">=18.0.0" immer: ">=9.0.6" @@ -34687,7 +34906,7 @@ __metadata: optional: true use-sync-external-store: optional: true - checksum: 10/9fb60796b9770dcc3f78dd794e7f424ff735e5676784cbc9726761037613942b62470b24a9ca9e98534ee4369a3b5429be570ff34281cb3c9d6d4e8df559ec3f + checksum: 10/35728fdaa68291ea3e469524316dda4fe1d8cc22d8be3df309ca99bda0dbc7e66a1c502f66c26f76abfb4bd49a6e1368160353eb3cb173c24042a5f252075462 languageName: node linkType: hard