diff --git a/packages/base/package.json b/packages/base/package.json index e81ba1df2..e4653805b 100644 --- a/packages/base/package.json +++ b/packages/base/package.json @@ -68,6 +68,7 @@ "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-toggle-group": "^1.1.10", "@rjsf/core": "^4.2.0", @@ -93,6 +94,7 @@ "proj4-list": "1.0.4", "react": "^18.0.1", "react-day-picker": "^9.7.0", + "react-markdown": "^10.1.0", "shpjs": "^6.1.0", "styled-components": "^5.3.6", "three": "^0.135.0", diff --git a/packages/base/src/commands/BaseCommandIDs.ts b/packages/base/src/commands/BaseCommandIDs.ts index 2a511d6d3..ef0c8ecbe 100644 --- a/packages/base/src/commands/BaseCommandIDs.ts +++ b/packages/base/src/commands/BaseCommandIDs.ts @@ -65,3 +65,6 @@ export const showFiltersTab = 'jupytergis:showFiltersTab'; export const showObjectPropertiesTab = 'jupytergis:showObjectPropertiesTab'; export const showAnnotationsTab = 'jupytergis:showAnnotationsTab'; export const showIdentifyPanelTab = 'jupytergis:showIdentifyPanelTab'; + +// Story maps +export const addLandmark = 'jupytergis:addLandmark'; diff --git a/packages/base/src/commands/index.ts b/packages/base/src/commands/index.ts index 807241d6b..b88543156 100644 --- a/packages/base/src/commands/index.ts +++ b/packages/base/src/commands/index.ts @@ -1,10 +1,13 @@ import { IDict, IJGISFormSchemaRegistry, + IJGISLayer, IJGISLayerBrowserRegistry, IJGISLayerGroup, IJGISLayerItem, + IJGISStoryMap, IJupyterGISModel, + ILandmarkLayer, JgisCoordinates, LayerType, SelectionType, @@ -16,7 +19,7 @@ import { ICompletionProviderManager } from '@jupyterlab/completer'; import { IStateDB } from '@jupyterlab/statedb'; import { ITranslator } from '@jupyterlab/translation'; import { CommandRegistry } from '@lumino/commands'; -import { ReadonlyPartialJSONObject } from '@lumino/coreutils'; +import { ReadonlyPartialJSONObject, UUID } from '@lumino/coreutils'; import { Coordinate } from 'ol/coordinate'; import { fromLonLat } from 'ol/proj'; @@ -1055,6 +1058,68 @@ export function addCommands( ...icons.get(CommandIDs.addMarker), }); + commands.addCommand(CommandIDs.addLandmark, { + label: trans.__('Add Landmark'), + isEnabled: () => { + return tracker.currentWidget + ? tracker.currentWidget.model.sharedModel.editable + : false; + }, + execute: args => { + const storyMapId = UUID.uuid4(); + const newLandmarkId = UUID.uuid4(); + const current = tracker.currentWidget; + if (!current) { + return; + } + const { zoom, extent } = current.model.getOptions(); + + if (!zoom || !extent) { + console.log('No extent or zoom found'); + return; + } + + const layerParams: ILandmarkLayer = { extent, zoom }; + const layerModel: IJGISLayer = { + type: 'LandmarkLayer', + visible: true, + name: 'Landmark', + parameters: layerParams, + }; + + current.model.addLayer(newLandmarkId, layerModel); + + // check for stories + const isStoriesExist = + Object.keys(current.model.sharedModel.storiesMap).length !== 0; + + // if not stories, then just add simple + if (!isStoriesExist) { + const title = 'New Story'; + const storyType = 'guided'; + const landmarks = [newLandmarkId]; + + const storyMap: IJGISStoryMap = { title, storyType, landmarks }; + + current.model.sharedModel.addStoryMap(storyMapId, storyMap); + } else { + // else need to update stories + const { story } = current.model.getSelectedStory(); + if (!story) { + console.log('No story found, something went wrong'); + return; + } + const newStory: IJGISStoryMap = { + ...story, + landmarks: [...(story.landmarks ?? []), newLandmarkId], + }; + + current.model.sharedModel.updateStoryMap(storyMapId, newStory); + } + }, + ...icons.get(CommandIDs.addLandmark), + }); + loadKeybindings(commands, keybindings); } diff --git a/packages/base/src/constants.ts b/packages/base/src/constants.ts index 215bf79d4..3821bb644 100644 --- a/packages/base/src/constants.ts +++ b/packages/base/src/constants.ts @@ -58,6 +58,7 @@ const iconObject = { [CommandIDs.identify]: { icon: infoIcon }, [CommandIDs.temporalController]: { icon: clockIcon }, [CommandIDs.addMarker]: { icon: markerIcon }, + [CommandIDs.addLandmark]: { iconClass: 'fa fa-landmark' }, }; /** diff --git a/packages/base/src/formbuilder/formselectors.ts b/packages/base/src/formbuilder/formselectors.ts index 1c00ff56e..b3ef7c518 100644 --- a/packages/base/src/formbuilder/formselectors.ts +++ b/packages/base/src/formbuilder/formselectors.ts @@ -3,6 +3,7 @@ import { LayerType, SourceType } from '@jupytergis/schema'; import { HeatmapLayerPropertiesForm, HillshadeLayerPropertiesForm, + LandmarkLayerPropertiesForm, LayerPropertiesForm, VectorLayerPropertiesForm, WebGlLayerPropertiesForm, @@ -33,6 +34,11 @@ export function getLayerTypeForm( break; case 'HeatmapLayer': LayerForm = HeatmapLayerPropertiesForm; + break; + case 'LandmarkLayer': + LayerForm = LandmarkLayerPropertiesForm; + break; + // ADD MORE FORM TYPES HERE } diff --git a/packages/base/src/formbuilder/objectform/layer/index.ts b/packages/base/src/formbuilder/objectform/layer/index.ts index 5e0faeaf4..d00dc9ee3 100644 --- a/packages/base/src/formbuilder/objectform/layer/index.ts +++ b/packages/base/src/formbuilder/objectform/layer/index.ts @@ -3,3 +3,4 @@ export * from './hillshadeLayerForm'; export * from './layerform'; export * from './vectorlayerform'; export * from './webGlLayerForm'; +export * from './landmarkLayerForm'; diff --git a/packages/base/src/formbuilder/objectform/layer/landmarkLayerForm.ts b/packages/base/src/formbuilder/objectform/layer/landmarkLayerForm.ts new file mode 100644 index 000000000..20105db29 --- /dev/null +++ b/packages/base/src/formbuilder/objectform/layer/landmarkLayerForm.ts @@ -0,0 +1,25 @@ +import { IDict } from '@jupytergis/schema'; + +import { LayerPropertiesForm } from './layerform'; + +/** + * The form to modify a hillshade layer. + */ +export class LandmarkLayerPropertiesForm extends LayerPropertiesForm { + protected processSchema( + data: IDict | undefined, + schema: IDict, + uiSchema: IDict, + ) { + super.processSchema(data, schema, uiSchema); + uiSchema['content'] = { + ...uiSchema['content'], + markdown: { + 'ui:widget': 'textarea', + 'ui:options': { + rows: 10, + }, + }, + }; + } +} diff --git a/packages/base/src/mainview/mainView.tsx b/packages/base/src/mainview/mainView.tsx index 6da13eb79..d62f4d1d2 100644 --- a/packages/base/src/mainview/mainView.tsx +++ b/packages/base/src/mainview/mainView.tsx @@ -33,6 +33,7 @@ import { JgisCoordinates, JupyterGISModel, IMarkerSource, + ILandmarkLayer, } from '@jupytergis/schema'; import { showErrorMessage } from '@jupyterlab/apputils'; import { IObservableMap, ObservableMap } from '@jupyterlab/observables'; @@ -53,6 +54,7 @@ import Feature, { FeatureLike } from 'ol/Feature'; import { FullScreen, ScaleLine } from 'ol/control'; import { Coordinate } from 'ol/coordinate'; import { singleClick } from 'ol/events/condition'; +import { getCenter } from 'ol/extent'; import { GeoJSON, MVT } from 'ol/format'; import { Geometry, Point } from 'ol/geom'; import { DragAndDrop, Select } from 'ol/interaction'; @@ -995,7 +997,8 @@ export class MainView extends React.Component { let sourceId: string | undefined; let source: IJGISSource | undefined; - if (layer.type !== 'StacLayer') { + // Sourceless layers + if (!['StacLayer', 'LandmarkLayer'].includes(layer.type)) { sourceId = layer.parameters?.source; if (!sourceId) { return; @@ -1119,6 +1122,11 @@ export class MainView extends React.Component { break; } + + case 'LandmarkLayer': { + // Special layer not for this + return; + } } // OpenLayers doesn't have name/id field so add it @@ -1200,7 +1208,6 @@ export class MainView extends React.Component { item => item.id === id && item.error === error.message, ) ) { - this._loadingLayers.delete(id); return; } @@ -1214,7 +1221,9 @@ export class MainView extends React.Component { error: error.message || 'invalid file path', index, }); + } finally { this._loadingLayers.delete(id); + this.setState(old => ({ ...old, loadingLayer: false })); } } @@ -1656,7 +1665,7 @@ export class MainView extends React.Component { const { x, y } = remoteViewport.value.coordinates; const zoom = remoteViewport.value.zoom; - this._moveToPosition({ x, y }, zoom, 0); + this._flyToPosition({ x, y }, zoom, 0); } } else { // If we are unfollowing a remote user, we reset our center and zoom to their previous values @@ -1668,7 +1677,7 @@ export class MainView extends React.Component { const viewportState = localState.viewportState?.value; if (viewportState) { - this._moveToPosition(viewportState.coordinates, viewportState.zoom); + this._flyToPosition(viewportState.coordinates, viewportState.zoom); } } } @@ -1790,7 +1799,7 @@ export class MainView extends React.Component { view.getProjection(), ); - this._moveToPosition({ x: centerCoord[0], y: centerCoord[1] }, zoom || 0); + this._flyToPosition({ x: centerCoord[0], y: centerCoord[1] }, zoom || 0); // Save the extent if it does not exists, to allow proper export to qgis. if (!options.extent) { @@ -2022,6 +2031,7 @@ export class MainView extends React.Component { }); } + // TODO this and flyToPosition need a rework private _onZoomToPosition(_: IJupyterGISModel, id: string) { // Check if the id is an annotation const annotation = this._model.annotationModel?.getAnnotation(id); @@ -2035,6 +2045,26 @@ export class MainView extends React.Component { const layer = this.getLayer(id); const source = layer?.getSource(); + // TODO: Landmark layers don't have an associated OL layer + // This could be better + if (!layer) { + const jgisLayer = this._model.getLayer(id); + const layerParams = jgisLayer?.parameters as ILandmarkLayer; + const coords = getCenter(layerParams.extent); + + // TODO: Should pass args through signal?? + const { story } = this._model.getSelectedStory(); + + this._flyToPosition( + { x: coords[0], y: coords[1] }, + layerParams.zoom, + (story?.transition?.time ?? 1) * 1000, // second -> ms + story?.transition?.type, + ); + + return; + } + if (source instanceof VectorSource) { extent = source.getExtent(); } @@ -2069,35 +2099,57 @@ export class MainView extends React.Component { }); } - private _moveToPosition( + private _flyToPosition( center: { x: number; y: number }, zoom: number, duration = 1000, + transitionType?: 'linear' | 'immediate' | 'smooth', ) { const view = this._Map.getView(); - view.setZoom(zoom); - view.setCenter([center.x, center.y]); - // Zoom needs to be set before changing center - if (!view.animate === undefined) { - view.animate({ zoom, duration }); + // Cancel any in-progress animations before starting new ones + view.cancelAnimations(); + + const currentZoom = view.getZoom() || 0; + const targetCenter: Coordinate = [center.x, center.y]; + + if (transitionType === 'immediate') { + view.setCenter(targetCenter); + view.setZoom(zoom); + return; + } + + if (transitionType === 'smooth') { + // Smooth: zoom out, center, and zoom in + // Centering takes full duration, zoom out completes halfway, zoom in starts halfway + const zoomOutLevel = Math.min(currentZoom, zoom) - 1; + + // Start centering (full duration) and zoom out (50% duration) simultaneously + view.animate({ + center: targetCenter, + duration: duration, + }); + // Chain zoom out -> zoom in (zoom in starts when zoom out completes) + view.animate( + { + zoom: zoomOutLevel, + duration: duration * 0.5, + }, + { + zoom: zoom, + duration: duration * 0.5, + }, + ); + } else { + // Linear: direct zoom view.animate({ - center: [center.x, center.y], + center: targetCenter, + zoom: zoom, duration, }); } } - private _flyToPosition( - center: { x: number; y: number }, - zoom: number, - duration = 1000, - ) { - const view = this._Map.getView(); - view.animate({ zoom, duration }); - view.animate({ center: [center.x, center.y], duration }); - } - private _onPointerMove(e: MouseEvent) { const pixel = this._Map.getEventPixel(e); const coordinates = this._Map.getCoordinateFromPixel(pixel); diff --git a/packages/base/src/panelview/components/layers.tsx b/packages/base/src/panelview/components/layers.tsx index cdb1873c6..30c7093e4 100644 --- a/packages/base/src/panelview/components/layers.tsx +++ b/packages/base/src/panelview/components/layers.tsx @@ -24,7 +24,11 @@ import React, { import { CommandIDs, icons } from '@/src/constants'; import { useGetSymbology } from '@/src/dialogs/symbology/hooks/useGetSymbology'; -import { nonVisibilityIcon, visibilityIcon } from '@/src/icons'; +import { + nonVisibilityIcon, + targetWithCenterIcon, + visibilityIcon, +} from '@/src/icons'; import { ILeftPanelClickHandlerParams } from '@/src/panelview/leftpanel'; import { LegendItem } from './legendItem'; @@ -41,13 +45,14 @@ interface IBodyProps { model: IJupyterGISModel; commands: CommandRegistry; state: IStateDB; + layerTree: IJGISLayerTree; } export const LayersBodyComponent: React.FC = props => { const model = props.model; const [layerTree, setLayerTree] = useState( - model?.getLayerTree() || [], + props.layerTree || [], ); const notifyCommands = () => { @@ -150,7 +155,7 @@ export const LayersBodyComponent: React.FC = props => { } else { // Check if new selection is the same type as previous selections const isSelectedSameType = Object.values(selectedValue).some( - selection => (selection as ISelection).type === type, + selection => selection.type === type, ); if (!isSelectedSameType) { @@ -181,46 +186,33 @@ export const LayersBodyComponent: React.FC = props => { onSelect({ type, item, event }); }; - /** - * Listen to the layers and layer tree changes. - */ + // Update layerTree when prop changes useEffect(() => { - const updateLayers = () => { - setLayerTree(model?.getLayerTree() || []); - }; - model?.sharedModel.layersChanged.connect(updateLayers); - model?.sharedModel.layerTreeChanged.connect(updateLayers); - - updateLayers(); - return () => { - model?.sharedModel.layersChanged.disconnect(updateLayers); - model?.sharedModel.layerTreeChanged.disconnect(updateLayers); - }; - }, [model]); + if (props.layerTree) { + setLayerTree(props.layerTree); + } + }, [props.layerTree]); return (
- {layerTree - .slice() - .reverse() - .map(layer => - typeof layer === 'string' ? ( - - ) : ( - - ), - )} + {layerTree.map(layer => + typeof layer === 'string' ? ( + + ) : ( + + ), + )}
); }; @@ -557,6 +549,26 @@ const LayerComponent: React.FC = props => { } }; + /** + * Set landmark layer to current map view. + */ + const handleSetLandmarkToCurrentView = () => { + if (!gisModel) { + return; + } + const { zoom, extent } = gisModel.getOptions(); + const updatedLayer = { + ...layer, + parameters: { + ...layer.parameters, + zoom, + extent, + }, + }; + + gisModel.sharedModel.updateLayer(layerId, updatedLayer); + }; + return (
= props => { {name} )} + + {layer.type === 'LandmarkLayer' && ( + + )}
{/* Show legend only if supported symbology */} diff --git a/packages/base/src/panelview/components/story-maps/PreviewModeSwitch.tsx b/packages/base/src/panelview/components/story-maps/PreviewModeSwitch.tsx new file mode 100644 index 000000000..375334272 --- /dev/null +++ b/packages/base/src/panelview/components/story-maps/PreviewModeSwitch.tsx @@ -0,0 +1,33 @@ +import * as React from 'react'; + +import { Switch } from '@/src/shared/components/Switch'; + +interface IPreviewModeSwitchProps { + checked: boolean; + onCheckedChange: () => void; +} + +export function PreviewModeSwitch({ + checked, + onCheckedChange, +}: IPreviewModeSwitchProps) { + return ( +
+ + +
+ ); +} diff --git a/packages/base/src/panelview/components/story-maps/StoryEditorPanel.tsx b/packages/base/src/panelview/components/story-maps/StoryEditorPanel.tsx new file mode 100644 index 000000000..d375963c7 --- /dev/null +++ b/packages/base/src/panelview/components/story-maps/StoryEditorPanel.tsx @@ -0,0 +1,46 @@ +import { IJGISStoryMap, IJupyterGISModel } from '@jupytergis/schema'; +import jgisSchema from '@jupytergis/schema/lib/schema/project/jgis.json'; +import React, { useMemo } from 'react'; + +import { BaseForm } from '@/src/formbuilder'; +import { deepCopy } from '@/src/tools'; +import { IDict } from '@/src/types'; + +interface IStoryPanelProps { + model: IJupyterGISModel; +} + +const storyMapSchema: IDict = deepCopy(jgisSchema.definitions.jGISStoryMap); + +export function StoryEditorPanel({ model }: IStoryPanelProps) { + const { landmarkId, story } = useMemo(() => { + return model.getSelectedStory(); + }, [model, model.sharedModel.storiesMap]); + + const syncStoryData = (properties: IDict) => { + model.sharedModel.updateStoryMap(landmarkId, properties as IJGISStoryMap); + }; + + if (!story) { + return ( +
+

No story map available. Create one by adding a landmark.

+
+ ); + } + + return ( +
+ +
+ ); +} + +export default StoryEditorPanel; diff --git a/packages/base/src/panelview/components/story-maps/StoryNavBar.tsx b/packages/base/src/panelview/components/story-maps/StoryNavBar.tsx new file mode 100644 index 000000000..492400682 --- /dev/null +++ b/packages/base/src/panelview/components/story-maps/StoryNavBar.tsx @@ -0,0 +1,36 @@ +import { ChevronLeft, ChevronRight } from 'lucide-react'; +import React from 'react'; + +import { Button } from '@/src/shared/components/Button'; + +interface IStoryNavBarProps { + onPrev: () => void; + onNext: () => void; + hasPrev: boolean; + hasNext: boolean; +} + +function StoryNavBar({ onPrev, onNext, hasPrev, hasNext }: IStoryNavBarProps) { + return ( +
+ + +
+ ); +} + +export default StoryNavBar; diff --git a/packages/base/src/panelview/components/story-maps/StoryViewerPanel.tsx b/packages/base/src/panelview/components/story-maps/StoryViewerPanel.tsx new file mode 100644 index 000000000..0d38bc732 --- /dev/null +++ b/packages/base/src/panelview/components/story-maps/StoryViewerPanel.tsx @@ -0,0 +1,225 @@ +import { + IJGISLayer, + IJGISStoryMap, + IJupyterGISModel, +} from '@jupytergis/schema'; +import React, { useEffect, useMemo, useState } from 'react'; +import Markdown from 'react-markdown'; + +import StoryNavBar from './StoryNavBar'; + +interface IStoryViewerPanelProps { + model: IJupyterGISModel; +} + +function StoryViewerPanel({ model }: IStoryViewerPanelProps) { + const [currentIndexDisplayed, setCurrentIndexDisplayed] = useState(0); + const [storyData, setStoryData] = useState(null); + + // Derive landmarks from story data + const landmarks = useMemo(() => { + if (!storyData?.landmarks) { + return []; + } + + return storyData.landmarks + .map(landmarkId => model.getLayer(landmarkId)) + .filter((layer): layer is IJGISLayer => layer !== undefined); + }, [storyData, model]); + + // Derive current landmark from landmarks and currentIndexDisplayed + const currentLandmark = useMemo(() => { + return landmarks[currentIndexDisplayed]; + }, [landmarks, currentIndexDisplayed]); + + // Derive active slide and layer name from current landmark + const activeSlide = useMemo(() => { + return currentLandmark?.parameters; + }, [currentLandmark]); + + const layerName = useMemo(() => { + return currentLandmark?.name ?? ''; + }, [currentLandmark]); + + // Derive landmark ID for zooming + const currentLandmarkId = useMemo(() => { + return storyData?.landmarks?.[currentIndexDisplayed]; + }, [storyData, currentIndexDisplayed]); + + useEffect(() => { + const updateStory = () => { + const { story } = model.getSelectedStory(); + setStoryData(story ?? null); + // Reset to first slide when story changes + setCurrentIndexDisplayed(0); + }; + + updateStory(); + + model.sharedModel.storyMapsChanged.connect(updateStory); + + return () => { + model.sharedModel.storyMapsChanged.disconnect(updateStory); + }; + }, [model]); + + // Auto-zoom when slide changes + useEffect(() => { + if (currentLandmarkId) { + zoomToCurrentLayer(); + } + }, [currentLandmarkId, model]); + + // Listen for layer selection changes in unguided mode + useEffect(() => { + // ! TODO this logic (getting a single selected layer) is also in the processing index.ts, move to tools + const handleSelectedLandmarkChange = (thig: any, more: any, extra: any) => { + // This is just to update the displayed content + // So bail early if we don't need to do that + if (!storyData || storyData.storyType !== 'unguided') { + return; + } + + const localState = model.sharedModel.awareness.getLocalState(); + if (!localState || !localState['selected']?.value) { + return; + } + + const selectedLayers = Object.keys(localState['selected'].value); + + // Ensure only one layer is selected + if (selectedLayers.length !== 1) { + return; + } + + const selectedLayerId = selectedLayers[0]; + const selectedLayer = model.getLayer(selectedLayerId); + if (!selectedLayer || selectedLayer.type !== 'LandmarkLayer') { + return; + } + + const index = storyData.landmarks?.indexOf(selectedLayerId); + if (index === undefined || index === -1) { + return; + } + + setCurrentIndexDisplayed(index); + }; + + model.sharedModel.awareness.on('change', handleSelectedLandmarkChange); + + return () => { + model.sharedModel.awareness.off('change', handleSelectedLandmarkChange); + }; + }, [model, storyData]); + + const zoomToCurrentLayer = () => { + if (currentLandmarkId) { + model.centerOnPosition(currentLandmarkId); + } + }; + + const handlePrev = () => { + if (currentIndexDisplayed > 0) { + setCurrentIndexDisplayed(currentIndexDisplayed - 1); + } + }; + + const handleNext = () => { + if (currentIndexDisplayed < landmarks.length - 1) { + setCurrentIndexDisplayed(currentIndexDisplayed + 1); + } + }; + + if (!storyData) { + return ( +
+

No story map available. Create one in the Story Editor panel.

+
+ ); + } + + return ( +
+ {/* Image container with title overlay */} + {activeSlide?.content?.image ? ( +
+ {activeSlide.content.title +

+ {`Slide ${currentIndexDisplayed + 1} - ${layerName ? layerName : 'Landmark Name'}`} +

+ {/* if guided -> nav buttons */} + {storyData.storyType === 'guided' && ( +
+ 0} + hasNext={currentIndexDisplayed < landmarks.length - 1} + /> +
+ )} +
+ ) : ( + <> +

{storyData.title}

+ {/* if guided -> nav buttons */} + {storyData.storyType === 'guided' && ( + 0} + hasNext={currentIndexDisplayed < landmarks.length - 1} + /> + )} + + )} +

+ {activeSlide?.content?.title + ? activeSlide.content.title + : 'Slide Title'} +

+ {activeSlide?.content?.markdown && ( +
+ {activeSlide.content.markdown} +
+ )} +
+ ); +} + +export default StoryViewerPanel; diff --git a/packages/base/src/panelview/leftpanel.tsx b/packages/base/src/panelview/leftpanel.tsx index 4eead1a39..652fc9e78 100644 --- a/packages/base/src/panelview/leftpanel.tsx +++ b/packages/base/src/panelview/leftpanel.tsx @@ -1,4 +1,9 @@ -import { IJupyterGISModel, SelectionType } from '@jupytergis/schema'; +import { + IJupyterGISModel, + IJGISLayerItem, + IJGISLayerTree, + SelectionType, +} from '@jupytergis/schema'; import { IStateDB } from '@jupyterlab/statedb'; import { CommandRegistry } from '@lumino/commands'; import { MouseEvent as ReactMouseEvent } from 'react'; @@ -30,18 +35,106 @@ export const LeftPanel: React.FC = ( props: ILeftPanelProps, ) => { const [settings, setSettings] = React.useState(props.model.jgisSettings); + const [layerTree, setLayerTree] = React.useState( + props.model.getLayerTree(), + ); React.useEffect(() => { const onSettingsChanged = () => { setSettings({ ...props.model.jgisSettings }); }; + const updateLayerTree = () => { + setLayerTree(props.model.getLayerTree() || []); + }; props.model.settingsChanged.connect(onSettingsChanged); + props.model.sharedModel.layersChanged.connect(updateLayerTree); + props.model.sharedModel.layerTreeChanged.connect(updateLayerTree); + + updateLayerTree(); return () => { props.model.settingsChanged.disconnect(onSettingsChanged); + props.model.sharedModel.layersChanged.disconnect(updateLayerTree); + props.model.sharedModel.layerTreeChanged.disconnect(updateLayerTree); }; }, [props.model]); + // Since landmarks are technically layers they are stored in the layer tree, so we separate them + // from regular layers. Process the tree once to build both filtered and landmark trees. + const { filteredLayerTree, landmarkLayerTree } = React.useMemo(() => { + const filtered: IJGISLayerTree = []; + const landmarks: IJGISLayerTree = []; + + const processLayer = ( + layer: IJGISLayerItem, + ): { filtered: IJGISLayerItem | null; landmark: IJGISLayerItem | null } => { + if (typeof layer === 'string') { + const layerData = props.model.getLayer(layer); + const isLandmark = layerData?.type === 'LandmarkLayer'; + return { + filtered: isLandmark ? null : layer, + landmark: isLandmark ? layer : null, + }; + } + + // For layer groups, recursively process their layers + const filteredGroupLayers: IJGISLayerItem[] = []; + const landmarkGroupLayers: IJGISLayerItem[] = []; + + for (const groupLayer of layer.layers) { + const result = processLayer(groupLayer); + if (result.filtered !== null) { + filteredGroupLayers.push(result.filtered); + } + if (result.landmark !== null) { + landmarkGroupLayers.push(result.landmark); + } + } + + return { + filtered: + filteredGroupLayers.length > 0 + ? { ...layer, layers: filteredGroupLayers } + : null, + landmark: + landmarkGroupLayers.length > 0 + ? { ...layer, layers: landmarkGroupLayers } + : null, + }; + }; + + for (const layer of layerTree) { + const result = processLayer(layer); + if (result.filtered !== null) { + filtered.push(result.filtered); + } + if (result.landmark !== null) { + landmarks.push(result.landmark); + } + } + + // Reverse filteredLayerTree before returning + filtered.reverse(); + + return { + filteredLayerTree: filtered, + landmarkLayerTree: landmarks, + }; + }, [layerTree]); + + // Updates landmarks array based on layer tree array + React.useEffect(() => { + const { landmarkId, story } = props.model.getSelectedStory(); + + if (!story) { + return; + } + props.model.sharedModel.updateStoryMap(landmarkId, { + ...story, + landmarks: landmarkLayerTree as string[], + }); + }, [landmarkLayerTree]); + const allLeftTabsDisabled = settings.layersDisabled && settings.stacBrowserDisabled && @@ -51,10 +144,13 @@ export const LeftPanel: React.FC = ( const tabInfo = [ !settings.layersDisabled ? { name: 'layers', title: 'Layers' } : false, - !settings.stacBrowserDisabled + !settings.stacBrowserDisabled && !settings.storyMapPresentation ? { name: 'stac', title: 'Stac Browser' } : false, - !settings.filtersDisabled ? { name: 'filters', title: 'Filters' } : false, + !settings.filtersDisabled && !settings.storyMapPresentation + ? { name: 'filters', title: 'Filters' } + : false, + { name: 'landmarks', title: 'Landmarks' }, ].filter(Boolean) as { name: string; title: string }[]; const [curTab, setCurTab] = React.useState( @@ -95,6 +191,7 @@ export const LeftPanel: React.FC = ( model={props.model} commands={props.commands} state={props.state} + layerTree={filteredLayerTree} > )} @@ -110,6 +207,15 @@ export const LeftPanel: React.FC = ( )} + + + + ); diff --git a/packages/base/src/panelview/rightpanel.tsx b/packages/base/src/panelview/rightpanel.tsx index 7874eaeda..9452b705e 100644 --- a/packages/base/src/panelview/rightpanel.tsx +++ b/packages/base/src/panelview/rightpanel.tsx @@ -8,6 +8,9 @@ import * as React from 'react'; import { AnnotationsPanel } from './annotationPanel'; import { IdentifyPanelComponent } from './components/identify-panel/IdentifyPanel'; +import { PreviewModeSwitch } from './components/story-maps/PreviewModeSwitch'; +import StoryEditorPanel from './components/story-maps/StoryEditorPanel'; +import StoryViewerPanel from './components/story-maps/StoryViewerPanel'; import { ObjectPropertiesReact } from './objectproperties'; import { PanelTabs, @@ -23,11 +26,16 @@ interface IRightPanelProps { } export const RightPanel: React.FC = props => { + const [displayEditor, setDisplayEditor] = React.useState(true); const [settings, setSettings] = React.useState(props.model.jgisSettings); const tabInfo = [ !settings.objectPropertiesDisabled ? { name: 'objectProperties', title: 'Object Properties' } : false, + { + name: 'storyPanel', + title: displayEditor ? 'Story Editor' : 'Story Map', + }, !settings.annotationsDisabled ? { name: 'annotations', title: 'Annotations' } : false, @@ -36,9 +44,12 @@ export const RightPanel: React.FC = props => { : false, ].filter(Boolean) as { name: string; title: string }[]; - const [curTab, setCurTab] = React.useState( - tabInfo.length > 0 ? tabInfo[0].name : undefined, - ); + const [curTab, setCurTab] = React.useState(() => { + if (settings.storyMapPresentation) { + return 'storyPanel'; + } + return tabInfo.length > 0 ? tabInfo[0].name : ''; + }); React.useEffect(() => { const onSettingsChanged = () => { @@ -82,6 +93,10 @@ export const RightPanel: React.FC = props => { const [selectedObjectProperties, setSelectedObjectProperties] = React.useState(undefined); + const toggleEditor = () => { + setDisplayEditor(!displayEditor); + }; + return (
= props => { ))} - {!settings.objectPropertiesDisabled && ( - - + + + )} + + + {!settings.storyMapPresentation && ( + - - )} + )} + {settings.storyMapPresentation || !displayEditor ? ( + + ) : ( + + )} + {!settings.annotationsDisabled && ( diff --git a/packages/base/src/shared/components/Switch.tsx b/packages/base/src/shared/components/Switch.tsx new file mode 100644 index 000000000..735fed1a5 --- /dev/null +++ b/packages/base/src/shared/components/Switch.tsx @@ -0,0 +1,24 @@ +import * as SwitchPrimitive from '@radix-ui/react-switch'; +import * as React from 'react'; + +import { cn } from './utils'; + +function Switch({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ); +} + +export { Switch }; diff --git a/packages/base/src/toolbar/widget.tsx b/packages/base/src/toolbar/widget.tsx index b258411d2..646347cf6 100644 --- a/packages/base/src/toolbar/widget.tsx +++ b/packages/base/src/toolbar/widget.tsx @@ -92,6 +92,9 @@ export class ToolbarWidget extends ReactiveToolbar { type: 'submenu', submenu: vectorSubMenu(options.commands), }); + NewSubMenu.addItem({ + command: CommandIDs.addLandmark, + }); const NewEntryButton = new ToolbarButton({ icon: addIcon, diff --git a/packages/base/style/base.css b/packages/base/style/base.css index ba07d8062..9b665e9d6 100644 --- a/packages/base/style/base.css +++ b/packages/base/style/base.css @@ -11,6 +11,7 @@ @import url('./statusBar.css'); @import url('./temporalSlider.css'); @import url('./tabPanel.css'); +@import url('./storyPanel.css'); @import url('ol/ol.css'); @import url('./shared/button.css'); @import url('./shared/toggle.css'); @@ -20,6 +21,7 @@ @import url('./shared/dropdownMenu.css'); @import url('./shared/badge.css'); @import url('./shared/checkbox.css'); +@import url('./shared/switch.css'); .errors { color: var(--jp-warn-color0); diff --git a/packages/base/style/leftPanel.css b/packages/base/style/leftPanel.css index 6765a7ec6..771192104 100644 --- a/packages/base/style/leftPanel.css +++ b/packages/base/style/leftPanel.css @@ -138,6 +138,7 @@ .jp-gis-sourceText { padding: 3px 0; cursor: pointer; + flex-grow: 1; } li .lm-Menu-itemLabel { diff --git a/packages/base/style/shared/switch.css b/packages/base/style/shared/switch.css new file mode 100644 index 000000000..70a10811f --- /dev/null +++ b/packages/base/style/shared/switch.css @@ -0,0 +1,63 @@ +.jgis-switch { + display: inline-flex; + height: 1.15rem; + width: 2rem; + flex-shrink: 0; + align-items: center; + border-radius: 9999px !important; + border: 1px solid transparent; + /* border: 1px solid var(--jp-brand-color3); */ + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1); + outline: none; + cursor: pointer; +} + +.jgis-switch:focus-visible { + border-color: var(--jp-border-color0); + box-shadow: 0 0 0 3px + color-mix(in srgb, var(--jp-ui-font-color0), transparent 50%); +} + +.jgis-switch:disabled { + cursor: not-allowed; + opacity: 0.5; +} + +.jgis-switch[data-state='checked'] { + background-color: var(--jp-accent-color0); +} + +.jgis-switch[data-state='unchecked'] { + background-color: var(--jp-layout-color2); +} + +.jgis-switch-thumb { + background-color: var(--jp-layout-color1); + pointer-events: none; + display: block; + width: 1rem; + height: 1rem; + border-radius: 9999px; + box-shadow: none; + transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1); + transform: translateX(0); +} + +.jgis-switch-thumb[data-state='checked'] { + transform: translateX(calc(100% - 6px)); +} + +.jgis-switch-thumb[data-state='unchecked'] { + transform: translateX(-2px); +} + +@media (prefers-color-scheme: dark) { + .jgis-switch-thumb[data-state='unchecked'] { + background-color: var(--jp-ui-font-color0); + } + + .jgis-switch-thumb[data-state='checked'] { + background-color: var(--jp-layout-color1); + } +} diff --git a/packages/base/style/shared/tabs.css b/packages/base/style/shared/tabs.css index e69ae6416..65e35b42d 100644 --- a/packages/base/style/shared/tabs.css +++ b/packages/base/style/shared/tabs.css @@ -1,7 +1,6 @@ .jgis-panel-tabs { display: flex; flex-direction: column; - gap: 1rem; justify-content: center; align-items: center; background-color: var(--jp-layout-color0); @@ -20,7 +19,8 @@ gap: 1rem; width: 100%; font-size: 9px; - border-radius: 5px; + border-top-left-radius: 5px; + border-top-right-radius: 5px; overflow-x: scroll; } @@ -62,6 +62,7 @@ width: 100%; overflow-y: scroll; max-height: 480px; + padding-top: 1rem; } .jgis-tabs-content:focus-visible { diff --git a/packages/base/style/storyPanel.css b/packages/base/style/storyPanel.css new file mode 100644 index 000000000..fd19817da --- /dev/null +++ b/packages/base/style/storyPanel.css @@ -0,0 +1,13 @@ +.jgis-story-viewer-panel * { + box-sizing: border-box; +} + +.jgis-story-viewer-content p, +.jgis-story-viewer-content span, +.jgis-story-viewer-content li { + font-size: var(--jp-ui-font-size2); +} + +.jgis-story-viewer-content > ul { + padding-left: 1rem; +} diff --git a/packages/base/style/tabPanel.css b/packages/base/style/tabPanel.css index 5be5249f0..f7626f64d 100644 --- a/packages/base/style/tabPanel.css +++ b/packages/base/style/tabPanel.css @@ -1,6 +1,5 @@ .jgis-panel-tab-content { - border-radius: 5px; - font-size: 10px; + font-size: var(--jp-ui-font-size0); padding-bottom: 50px; } diff --git a/packages/schema/src/doc.ts b/packages/schema/src/doc.ts index 169b33f21..62c47fad2 100644 --- a/packages/schema/src/doc.ts +++ b/packages/schema/src/doc.ts @@ -11,6 +11,7 @@ import { IJGISOptions, IJGISSource, IJGISSources, + IJGISStoryMap, } from './_interface/project/jgis'; import { SCHEMA_VERSION } from './_interface/version'; import { @@ -18,6 +19,8 @@ import { IJGISLayerDocChange, IJGISLayerTreeDocChange, IJGISSourceDocChange, + IJGISStoryMapDocChange, + IJGISStoryMaps, IJupyterGISDoc, IJupyterGISDocChange, } from './interfaces'; @@ -33,15 +36,18 @@ export class JupyterGISDoc this._layers = this.ydoc.getMap>('layers'); this._layerTree = this.ydoc.getArray('layerTree'); this._sources = this.ydoc.getMap>('sources'); + this._storiesMap = this.ydoc.getMap>('storiesMap'); this._metadata = this.ydoc.getMap('metadata'); this.undoManager.addToScope(this._layers); this.undoManager.addToScope(this._sources); + this.undoManager.addToScope(this._storiesMap); this.undoManager.addToScope(this._layerTree); this._layers.observeDeep(this._layersObserver.bind(this)); this._layerTree.observe(this._layerTreeObserver.bind(this)); this._sources.observeDeep(this._sourcesObserver.bind(this)); + this._storiesMap.observeDeep(this._storyMapsObserver.bind(this)); this._options.observe(this._optionsObserver.bind(this)); this._metadata.observe(this._metaObserver.bind(this)); } @@ -51,9 +57,10 @@ export class JupyterGISDoc const layerTree = this._layerTree.toJSON(); const options = this._options.toJSON(); const sources = this._sources.toJSON(); + const storiesMap = this._storiesMap.toJSON(); const metadata = this._metadata.toJSON(); - return { layers, layerTree, sources, options, metadata }; + return { layers, layerTree, sources, storiesMap, options, metadata }; } setSource(value: JSONObject | string): void { @@ -86,6 +93,11 @@ export class JupyterGISDoc this._sources.set(key, val), ); + const storiesMap = value['storiesMap'] ?? {}; + Object.entries(storiesMap).forEach(([key, val]) => + this._storiesMap.set(key, val), + ); + const metadata = value['metadata'] ?? {}; Object.entries(metadata).forEach(([key, val]) => this._metadata.set(key, val as string), @@ -125,6 +137,18 @@ export class JupyterGISDoc return JSONExt.deepCopy(this._sources.toJSON()); } + set storiesMap(storiesMap: IJGISStoryMaps) { + this.transact(() => { + for (const [key, value] of Object.entries(storiesMap)) { + this._storiesMap.set(key, value); + } + }); + } + + get storiesMap(): IJGISStoryMaps { + return JSONExt.deepCopy(this._storiesMap.toJSON()); + } + get layerTree(): IJGISLayerTree { return JSONExt.deepCopy(this._layerTree.toJSON()); } @@ -174,6 +198,10 @@ export class JupyterGISDoc return this._sourcesChanged; } + get storyMapsChanged(): ISignal { + return this._storyMapsChanged; + } + get optionsChanged(): ISignal { return this._optionsChanged; } @@ -272,6 +300,29 @@ export class JupyterGISDoc this.transact(() => this._sources.set(id, value)); } + removeStoryMap(id: string): void { + this.transact(() => { + this._storiesMap.delete(id); + }); + } + + addStoryMap(id: string, value: IJGISStoryMap): void { + this.transact(() => { + this._storiesMap.set(id, value); + }); + } + + updateStoryMap(id: string, value: any): void { + this.transact(() => this._storiesMap.set(id, value)); + } + + getStoryMap(id: string): IJGISStoryMap | undefined { + if (!this._storiesMap.has(id)) { + return undefined; + } + return JSONExt.deepCopy(this._storiesMap.get(id)); + } + getOption(key: keyof IJGISOptions): IDict | undefined { const content = this._options.get(key); if (!content) { @@ -387,6 +438,29 @@ export class JupyterGISDoc } } + private _storyMapsObserver(events: Y.YEvent[]): void { + const changes: Array<{ + id: string; + newValue: IJGISStoryMap; + }> = []; + let needEmit = false; + events.forEach(event => { + event.keys.forEach((change, key) => { + if (!needEmit) { + needEmit = true; + } + changes.push({ + id: key, + newValue: JSONExt.deepCopy(event.target.toJSON()[key]), + }); + }); + }); + needEmit = changes.length === 0 ? true : needEmit; + if (needEmit) { + this._storyMapsChanged.emit({ storyMapChange: changes }); + } + } + private _optionsObserver = (event: Y.YMapEvent>): void => { this._optionsChanged.emit(event.keys); }; @@ -398,6 +472,7 @@ export class JupyterGISDoc private _layers: Y.Map; private _layerTree: Y.Array; private _sources: Y.Map; + private _storiesMap: Y.Map; private _options: Y.Map; private _metadata: Y.Map; @@ -412,5 +487,9 @@ export class JupyterGISDoc private _sourcesChanged = new Signal( this, ); + private _storyMapsChanged = new Signal< + IJupyterGISDoc, + IJGISStoryMapDocChange + >(this); private _metadataChanged = new Signal(this); } diff --git a/packages/schema/src/interfaces.ts b/packages/schema/src/interfaces.ts index ac918dbf7..cf8a5c884 100644 --- a/packages/schema/src/interfaces.ts +++ b/packages/schema/src/interfaces.ts @@ -26,12 +26,17 @@ import { IJGISOptions, IJGISSource, IJGISSources, + IJGISStoryMap, SourceType, } from './_interface/project/jgis'; import { IRasterSource } from './_interface/project/sources/rasterSource'; import { Modes } from './types'; export { IGeoJSONSource } from './_interface/project/sources/geoJsonSource'; +export interface IJGISStoryMaps { + [k: string]: IJGISStoryMap; +} + export type JgisCoordinates = { x: number; y: number }; export interface IViewPortState { @@ -58,6 +63,13 @@ export interface IJGISLayerTreeDocChange { layerTreeChange?: Delta; } +export interface IJGISStoryMapDocChange { + storyMapChange?: Array<{ + id: string; + newValue: IJGISStoryMap | undefined; + }>; +} + export interface IJGISSourceDocChange { sourceChange?: Array<{ id: string; @@ -92,6 +104,7 @@ export interface IJupyterGISDoc extends YDocument { options: IJGISOptions; layers: IJGISLayers; sources: IJGISSources; + storiesMap: IJGISStoryMaps; layerTree: IJGISLayerTree; metadata: any; @@ -119,6 +132,11 @@ export interface IJupyterGISDoc extends YDocument { addSource(id: string, value: IJGISSource): void; updateSource(id: string, value: IJGISSource): void; + getStoryMap(id: string): IJGISStoryMap | undefined; + removeStoryMap(id: string): void; + addStoryMap(id: string, value: IJGISStoryMap): void; + updateStoryMap(id: string, value: IJGISStoryMap): void; + addLayerTreeItem(index: number, item: IJGISLayerItem): void; updateLayerTreeItem(index: number, item: IJGISLayerItem): void; @@ -138,6 +156,7 @@ export interface IJupyterGISDoc extends YDocument { optionsChanged: ISignal; layersChanged: ISignal; sourcesChanged: ISignal; + storyMapsChanged: ISignal; layerTreeChanged: ISignal; metadataChanged: ISignal; } @@ -188,6 +207,8 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel { pathChanged: ISignal; + storiesMap: Map; + getFeaturesForCurrentTile: ({ sourceId, }: { @@ -259,6 +280,7 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel { triggerLayerUpdate(layerId: string, layer: IJGISLayer): void; disposed: ISignal; + getSelectedStory(): { landmarkId: string; story: IJGISStoryMap | undefined }; } export interface IUserData { @@ -394,4 +416,7 @@ export interface IJupyterGISSettings { objectPropertiesDisabled?: boolean; annotationsDisabled?: boolean; identifyDisabled?: boolean; + + // Story maps + storyMapPresentation: boolean; } diff --git a/packages/schema/src/model.ts b/packages/schema/src/model.ts index e5a5c53f6..ed58f9f6f 100644 --- a/packages/schema/src/model.ts +++ b/packages/schema/src/model.ts @@ -18,6 +18,7 @@ import { IJGISOptions, IJGISSource, IJGISSources, + IJGISStoryMap, } from './_interface/project/jgis'; import { JupyterGISDoc } from './doc'; import { @@ -52,6 +53,7 @@ const DEFAULT_SETTINGS: IJupyterGISSettings = { objectPropertiesDisabled: false, annotationsDisabled: false, identifyDisabled: false, + storyMapPresentation: false, }; export class JupyterGISModel implements IJupyterGISModel { @@ -504,8 +506,28 @@ export class JupyterGISModel implements IJupyterGISModel { const source_id = layer?.parameters?.source; this._removeLayerTreeLayer(this.getLayerTree(), layer_id); - this.sharedModel.removeLayer(layer_id); - this.sharedModel.removeSource(source_id); + + if (layer?.type === 'LandmarkLayer') { + this.sharedModel.removeLayer(layer_id); + + // remove this layer id from story maps + Object.entries(this.sharedModel.storiesMap).forEach( + ([storyMapId, storyMap]) => { + if (storyMap.landmarks?.includes(layer_id)) { + const updatedLandmarks = storyMap.landmarks.filter( + id => id !== layer_id, + ); + this.sharedModel.updateStoryMap(storyMapId, { + ...storyMap, + landmarks: updatedLandmarks, + }); + } + }, + ); + } else { + this.sharedModel.removeLayer(layer_id); + this.sharedModel.removeSource(source_id); + } } setOptions(value: IJGISOptions) { @@ -583,6 +605,20 @@ export class JupyterGISModel implements IJupyterGISModel { return this.sharedModel.awareness.clientID; } + /** + * Placeholder in case we eventually want to support multiple stories + * @returns First/only story + */ + getSelectedStory(): { landmarkId: string; story: IJGISStoryMap | undefined } { + const stories = this.sharedModel.storiesMap; + const storyId = Object.keys(stories)[0]; + + return { + landmarkId: storyId, + story: this.sharedModel.getStoryMap(storyId), + }; + } + /** * Add an item in the layer tree. * @@ -924,6 +960,7 @@ export class JupyterGISModel implements IJupyterGISModel { private _geolocation: JgisCoordinates; private _geolocationChanged = new Signal(this); private _tileFeatureCache: Map> = new Map(); + storiesMap: Map = new Map(); } export namespace JupyterGISModel { diff --git a/packages/schema/src/schema/project/jgis.json b/packages/schema/src/schema/project/jgis.json index 9a013d5d6..80908bb71 100644 --- a/packages/schema/src/schema/project/jgis.json +++ b/packages/schema/src/schema/project/jgis.json @@ -41,7 +41,8 @@ "WebGlLayer", "ImageLayer", "HeatmapLayer", - "StacLayer" + "StacLayer", + "LandmarkLayer" ] }, "sourceType": { @@ -136,6 +137,46 @@ } ] }, + "jGISStoryMap": { + "title": "IJGISStoryMap", + "type": "object", + "additionalProperties": false, + "properties": { + "title": { + "type": "string", + "description": "The title of the story map" + }, + "storyType": { + "type": "string", + "enum": ["guided", "unguided"], + "description": "The type of story map" + }, + "transition": { + "type": "object", + "description": "Transition configuration between landmarks", + "properties": { + "type": { + "type": "string", + "enum": ["linear", "immediate", "smooth"], + "description": "Transition animation style" + }, + "time": { + "type": "number", + "description": "The time in seconds for the transition" + } + }, + "required": ["type", "time"] + }, + "landmarks": { + "type": "array", + "default": [], + "description": "Array of landmarks for the story map", + "items": { + "type": "string" + } + } + } + }, "jGISLayers": { "title": "IJGISLayers", "type": "object", diff --git a/packages/schema/src/schema/project/layers/landmarkLayer.json b/packages/schema/src/schema/project/layers/landmarkLayer.json new file mode 100644 index 000000000..15fff8103 --- /dev/null +++ b/packages/schema/src/schema/project/layers/landmarkLayer.json @@ -0,0 +1,37 @@ +{ + "type": "object", + "description": "LandmarkLayer", + "title": "ILandmarkLayer", + "additionalProperties": false, + "required": ["zoom", "extent"], + "properties": { + "content": { + "type": "object", + "additionalProperties": false, + "properties": { + "title": { + "type": "string" + }, + "image": { + "type": "string", + "description": "Link to image for the story" + }, + "markdown": { + "type": "string", + "description": "Markdown string representing the content of the story stop" + } + } + }, + "zoom": { + "type": "number", + "default": 0 + }, + "extent": { + "type": "array", + "default": null, + "items": { + "type": "number" + } + } + } +} diff --git a/packages/schema/src/types.ts b/packages/schema/src/types.ts index 97d58074c..48a163438 100644 --- a/packages/schema/src/types.ts +++ b/packages/schema/src/types.ts @@ -15,6 +15,7 @@ export * from './_interface/project/sources/markerSource'; // Layers export * from './_interface/project/layers/heatmapLayer'; export * from './_interface/project/layers/hillshadeLayer'; +export * from './_interface/project/layers/landmarkLayer'; export * from './_interface/project/layers/rasterLayer'; export * from './_interface/project/layers/vectorLayer'; export * from './_interface/project/layers/imageLayer'; diff --git a/python/jupytergis_core/jupytergis_core/jgis_ydoc.py b/python/jupytergis_core/jupytergis_core/jgis_ydoc.py index 10ca54912..cfebd87f9 100644 --- a/python/jupytergis_core/jupytergis_core/jgis_ydoc.py +++ b/python/jupytergis_core/jupytergis_core/jgis_ydoc.py @@ -13,6 +13,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._ydoc["layers"] = self._ylayers = Map() self._ydoc["sources"] = self._ysources = Map() + self._ydoc["storiesMap"] = self._ystoriesMap = Map() self._ydoc["options"] = self._yoptions = Map() self._ydoc["layerTree"] = self._ylayerTree = Array() self._ydoc["metadata"] = self._ymetadata = Map() @@ -28,6 +29,7 @@ def get(self) -> str: """ layers = self._ylayers.to_py() sources = self._ysources.to_py() + stories_map = self._ystoriesMap.to_py() options = self._yoptions.to_py() meta = self._ymetadata.to_py() layers_tree = self._ylayerTree.to_py() @@ -36,6 +38,7 @@ def get(self) -> str: schemaVersion=SCHEMA_VERSION, layers=layers, sources=sources, + storiesMap=stories_map, options=options, layerTree=layers_tree, metadata=meta, @@ -68,6 +71,9 @@ def set(self, value: str) -> None: self._ysources.clear() self._ysources.update(valueDict.get("sources", {})) + self._ystoriesMap.clear() + self._ystoriesMap.update(valueDict.get("storiesMap", {})) + self._yoptions.clear() self._yoptions.update(valueDict.get("options", {})) @@ -88,6 +94,9 @@ def observe(self, callback: Callable[[str, Any], None]): self._subscriptions[self._ysources] = self._ysources.observe_deep( partial(callback, "sources") ) + self._subscriptions[self._ystoriesMap] = self._ystoriesMap.observe_deep( + partial(callback, "storiesMap") + ) self._subscriptions[self._yoptions] = self._yoptions.observe_deep( partial(callback, "options") ) diff --git a/python/jupytergis_core/jupytergis_core/schema/__init__.py b/python/jupytergis_core/jupytergis_core/schema/__init__.py index 8e51c202e..e14630efb 100644 --- a/python/jupytergis_core/jupytergis_core/schema/__init__.py +++ b/python/jupytergis_core/jupytergis_core/schema/__init__.py @@ -7,6 +7,7 @@ from .interfaces.project.layers.imageLayer import IImageLayer # noqa from .interfaces.project.layers.webGlLayer import IWebGlLayer # noqa from .interfaces.project.layers.heatmapLayer import IHeatmapLayer # noqa +from .interfaces.project.layers.landmarkLayer import ILandmarkLayer # noqa from .interfaces.project.sources.vectorTileSource import IVectorTileSource # noqa from .interfaces.project.sources.markerSource import IMarkerSource # noqa diff --git a/python/jupytergis_core/schema/jupytergis-settings.json b/python/jupytergis_core/schema/jupytergis-settings.json index 4d82e2cdb..5148a68e5 100644 --- a/python/jupytergis_core/schema/jupytergis-settings.json +++ b/python/jupytergis_core/schema/jupytergis-settings.json @@ -43,6 +43,11 @@ "type": "boolean", "title": "Disable Right Panel", "default": false + }, + "storyMapPresentation": { + "type": "boolean", + "title": "Enable Story Map Presentations", + "default": false } } } diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py index 48947ed64..092451400 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py @@ -29,6 +29,7 @@ IMarkerSource, IVideoSource, IWebGlLayer, + ILandmarkLayer, LayerType, SourceType, ) @@ -863,6 +864,7 @@ class Config: | IImageLayer | IWebGlLayer | IHeatmapLayer + | ILandmarkLayer ) _parent = Optional[GISDocument] @@ -967,6 +969,7 @@ def create_source( OBJECT_FACTORY.register_factory(LayerType.WebGlLayer, IWebGlLayer) OBJECT_FACTORY.register_factory(LayerType.ImageLayer, IImageLayer) OBJECT_FACTORY.register_factory(LayerType.HeatmapLayer, IHeatmapLayer) +OBJECT_FACTORY.register_factory(LayerType.LandmarkLayer, ILandmarkLayer) OBJECT_FACTORY.register_factory(SourceType.VectorTileSource, IVectorTileSource) OBJECT_FACTORY.register_factory(SourceType.MarkerSource, IMarkerSource) diff --git a/yarn.lock b/yarn.lock index 0fdc87116..9f0d1f2f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -899,6 +899,7 @@ __metadata: "@radix-ui/react-dropdown-menu": ^2.1.15 "@radix-ui/react-popover": ^1.1.14 "@radix-ui/react-slot": ^1.2.3 + "@radix-ui/react-switch": ^1.2.6 "@radix-ui/react-tabs": ^1.1.12 "@radix-ui/react-toggle-group": ^1.1.10 "@rjsf/core": ^4.2.0 @@ -930,6 +931,7 @@ __metadata: proj4-list: 1.0.4 react: ^18.0.1 react-day-picker: ^9.7.0 + react-markdown: ^10.1.0 rimraf: ^3.0.2 shpjs: ^6.1.0 styled-components: ^5.3.6 @@ -3137,6 +3139,31 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-switch@npm:^1.2.6": + version: 1.2.6 + resolution: "@radix-ui/react-switch@npm:1.2.6" + dependencies: + "@radix-ui/primitive": 1.1.3 + "@radix-ui/react-compose-refs": 1.1.2 + "@radix-ui/react-context": 1.1.2 + "@radix-ui/react-primitive": 2.1.3 + "@radix-ui/react-use-controllable-state": 1.2.2 + "@radix-ui/react-use-previous": 1.1.1 + "@radix-ui/react-use-size": 1.1.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 737ebe7cd5544455411e8a606980e4491281fb38a829eb08a4505251f51c32dcae0b9f13b9ab0b574980d4e228e352b1613971f0b2a516d0fe2eefe2bb318231 + languageName: node + linkType: hard + "@radix-ui/react-tabs@npm:^1.1.12": version: 1.1.13 resolution: "@radix-ui/react-tabs@npm:1.1.13" @@ -3541,6 +3568,15 @@ __metadata: languageName: node linkType: hard +"@types/debug@npm:^4.0.0": + version: 4.1.12 + resolution: "@types/debug@npm:4.1.12" + dependencies: + "@types/ms": "*" + checksum: 47876a852de8240bfdaf7481357af2b88cb660d30c72e73789abf00c499d6bc7cd5e52f41c915d1b9cd8ec9fef5b05688d7b7aef17f7f272c2d04679508d1053 + languageName: node + linkType: hard + "@types/eslint-scope@npm:^3.7.7": version: 3.7.7 resolution: "@types/eslint-scope@npm:3.7.7" @@ -3561,7 +3597,16 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:^1.0.8": +"@types/estree-jsx@npm:^1.0.0": + version: 1.0.5 + resolution: "@types/estree-jsx@npm:1.0.5" + dependencies: + "@types/estree": "*" + checksum: a028ab0cd7b2950168a05c6a86026eb3a36a54a4adfae57f13911d7b49dffe573d9c2b28421b2d029b49b3d02fcd686611be2622dc3dad6d9791166c083f6008 + languageName: node + linkType: hard + +"@types/estree@npm:*, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.8": version: 1.0.8 resolution: "@types/estree@npm:1.0.8" checksum: bd93e2e415b6f182ec4da1074e1f36c480f1d26add3e696d54fb30c09bc470897e41361c8fd957bf0985024f8fbf1e6e2aff977d79352ef7eb93a5c6dcff6c11 @@ -3584,6 +3629,15 @@ __metadata: languageName: node linkType: hard +"@types/hast@npm:^3.0.0": + version: 3.0.4 + resolution: "@types/hast@npm:3.0.4" + dependencies: + "@types/unist": "*" + checksum: 7a973e8d16fcdf3936090fa2280f408fb2b6a4f13b42edeb5fbd614efe042b82eac68e298e556d50f6b4ad585a3a93c353e9c826feccdc77af59de8dd400d044 + languageName: node + linkType: hard + "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0": version: 2.0.6 resolution: "@types/istanbul-lib-coverage@npm:2.0.6" @@ -3650,6 +3704,15 @@ __metadata: languageName: node linkType: hard +"@types/mdast@npm:^4.0.0": + version: 4.0.4 + resolution: "@types/mdast@npm:4.0.4" + dependencies: + "@types/unist": "*" + checksum: 20c4e9574cc409db662a35cba52b068b91eb696b3049e94321219d47d34c8ccc99a142be5c76c80a538b612457b03586bc2f6b727a3e9e7530f4c8568f6282ee + languageName: node + linkType: hard + "@types/minimatch@npm:^3.0.3": version: 3.0.5 resolution: "@types/minimatch@npm:3.0.5" @@ -3664,6 +3727,13 @@ __metadata: languageName: node linkType: hard +"@types/ms@npm:*": + version: 2.1.0 + resolution: "@types/ms@npm:2.1.0" + checksum: 532d2ebb91937ccc4a89389715e5b47d4c66e708d15942fe6cc25add6dc37b2be058230a327dd50f43f89b8b6d5d52b74685a9e8f70516edfc9bdd6be910eff4 + languageName: node + linkType: hard + "@types/node@npm:*": version: 24.3.1 resolution: "@types/node@npm:24.3.1" @@ -3786,6 +3856,20 @@ __metadata: languageName: node linkType: hard +"@types/unist@npm:*, @types/unist@npm:^3.0.0": + version: 3.0.3 + resolution: "@types/unist@npm:3.0.3" + checksum: 96e6453da9e075aaef1dc22482463898198acdc1eeb99b465e65e34303e2ec1e3b1ed4469a9118275ec284dc98019f63c3f5d49422f0e4ac707e5ab90fb3b71a + languageName: node + linkType: hard + +"@types/unist@npm:^2.0.0": + version: 2.0.11 + resolution: "@types/unist@npm:2.0.11" + checksum: 6d436e832bc35c6dde9f056ac515ebf2b3384a1d7f63679d12358766f9b313368077402e9c1126a14d827f10370a5485e628bf61aa91117cf4fc882423191a4e + languageName: node + linkType: hard + "@types/uuid@npm:^10.0.0": version: 10.0.0 resolution: "@types/uuid@npm:10.0.0" @@ -3948,7 +4032,7 @@ __metadata: languageName: node linkType: hard -"@ungap/structured-clone@npm:^1.2.0": +"@ungap/structured-clone@npm:^1.0.0, @ungap/structured-clone@npm:^1.2.0": version: 1.3.0 resolution: "@ungap/structured-clone@npm:1.3.0" checksum: 64ed518f49c2b31f5b50f8570a1e37bde3b62f2460042c50f132430b2d869c4a6586f13aa33a58a4722715b8158c68cae2827389d6752ac54da2893c83e480fc @@ -4642,6 +4726,13 @@ __metadata: languageName: node linkType: hard +"bail@npm:^2.0.0": + version: 2.0.2 + resolution: "bail@npm:2.0.2" + checksum: aab4e8ccdc8d762bf3fdfce8e706601695620c0c2eda256dd85088dc0be3cfd7ff126f6e99c2bee1f24f5d418414aacf09d7f9702f16d6963df2fa488cda8824 + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -4864,6 +4955,13 @@ __metadata: languageName: node linkType: hard +"ccount@npm:^2.0.0": + version: 2.0.1 + resolution: "ccount@npm:2.0.1" + checksum: 48193dada54c9e260e0acf57fc16171a225305548f9ad20d5471e0f7a8c026aedd8747091dccb0d900cde7df4e4ddbd235df0d8de4a64c71b12f0d3303eeafd4 + languageName: node + linkType: hard + "chalk@npm:4.1.0": version: 4.1.0 resolution: "chalk@npm:4.1.0" @@ -4895,6 +4993,34 @@ __metadata: languageName: node linkType: hard +"character-entities-html4@npm:^2.0.0": + version: 2.1.0 + resolution: "character-entities-html4@npm:2.1.0" + checksum: 7034aa7c7fa90309667f6dd50499c8a760c3d3a6fb159adb4e0bada0107d194551cdbad0714302f62d06ce4ed68565c8c2e15fdef2e8f8764eb63fa92b34b11d + languageName: node + linkType: hard + +"character-entities-legacy@npm:^3.0.0": + version: 3.0.0 + resolution: "character-entities-legacy@npm:3.0.0" + checksum: 7582af055cb488b626d364b7d7a4e46b06abd526fb63c0e4eb35bcb9c9799cc4f76b39f34fdccef2d1174ac95e53e9ab355aae83227c1a2505877893fce77731 + languageName: node + linkType: hard + +"character-entities@npm:^2.0.0": + version: 2.0.2 + resolution: "character-entities@npm:2.0.2" + checksum: cf1643814023697f725e47328fcec17923b8f1799102a8a79c1514e894815651794a2bffd84bb1b3a4b124b050154e4529ed6e81f7c8068a734aecf07a6d3def + languageName: node + linkType: hard + +"character-reference-invalid@npm:^2.0.0": + version: 2.0.1 + resolution: "character-reference-invalid@npm:2.0.1" + checksum: 98d3b1a52ae510b7329e6ee7f6210df14f1e318c5415975d4c9e7ee0ef4c07875d47c6e74230c64551f12f556b4a8ccc24d9f3691a2aa197019e72a95e9297ee + languageName: node + linkType: hard + "chardet@npm:^2.1.0": version: 2.1.0 resolution: "chardet@npm:2.1.0" @@ -5119,6 +5245,13 @@ __metadata: languageName: node linkType: hard +"comma-separated-tokens@npm:^2.0.0": + version: 2.0.3 + resolution: "comma-separated-tokens@npm:2.0.3" + checksum: e3bf9e0332a5c45f49b90e79bcdb4a7a85f28d6a6f0876a94f1bb9b2bfbdbbb9292aac50e1e742d8c0db1e62a0229a106f57917e2d067fca951d81737651700d + languageName: node + linkType: hard + "commander@npm:^10.0.1": version: 10.0.1 resolution: "commander@npm:10.0.1" @@ -5593,6 +5726,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:^4.0.0": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: ^2.1.3 + peerDependenciesMeta: + supports-color: + optional: true + checksum: 4805abd570e601acdca85b6aa3757186084a45cff9b2fa6eee1f3b173caa776b45f478b2a71a572d616d2010cea9211d0ac4a02a610e4c18ac4324bde3760834 + languageName: node + linkType: hard + "decamelize-keys@npm:^1.1.0": version: 1.1.1 resolution: "decamelize-keys@npm:1.1.1" @@ -5617,6 +5762,15 @@ __metadata: languageName: node linkType: hard +"decode-named-character-reference@npm:^1.0.0": + version: 1.2.0 + resolution: "decode-named-character-reference@npm:1.2.0" + dependencies: + character-entities: ^2.0.0 + checksum: f26b23046c1a137c0b41fa51e3ce07ba8364640322c742a31570999784abc8572fc24cb108a76b14ff72ddb75d35aad3d14b10d7743639112145a2664b9d1864 + languageName: node + linkType: hard + "dedent@npm:1.5.3": version: 1.5.3 resolution: "dedent@npm:1.5.3" @@ -5695,6 +5849,13 @@ __metadata: languageName: node linkType: hard +"dequal@npm:^2.0.0": + version: 2.0.3 + resolution: "dequal@npm:2.0.3" + checksum: 8679b850e1a3d0ebbc46ee780d5df7b478c23f335887464023a631d1b9af051ad4a6595a44220f9ff8ff95a8ddccf019b5ad778a976fd7bbf77383d36f412f90 + languageName: node + linkType: hard + "detect-indent@npm:^5.0.0": version: 5.0.0 resolution: "detect-indent@npm:5.0.0" @@ -5716,6 +5877,15 @@ __metadata: languageName: node linkType: hard +"devlop@npm:^1.0.0, devlop@npm:^1.1.0": + version: 1.1.0 + resolution: "devlop@npm:1.1.0" + dependencies: + dequal: ^2.0.0 + checksum: d2ff650bac0bb6ef08c48f3ba98640bb5fec5cce81e9957eb620408d1bab1204d382a45b785c6b3314dc867bb0684936b84c6867820da6db97cbb5d3c15dd185 + languageName: node + linkType: hard + "diff-sequences@npm:^29.6.3": version: 29.6.3 resolution: "diff-sequences@npm:29.6.3" @@ -6435,6 +6605,13 @@ __metadata: languageName: node linkType: hard +"estree-util-is-identifier-name@npm:^3.0.0": + version: 3.0.0 + resolution: "estree-util-is-identifier-name@npm:3.0.0" + checksum: ea3909f0188ea164af0aadeca87c087e3e5da78d76da5ae9c7954ff1340ea3e4679c4653bbf4299ffb70caa9b322218cc1128db2541f3d2976eb9704f9857787 + languageName: node + linkType: hard + "esutils@npm:^2.0.2": version: 2.0.3 resolution: "esutils@npm:2.0.3" @@ -6506,6 +6683,13 @@ __metadata: languageName: node linkType: hard +"extend@npm:^3.0.0": + version: 3.0.2 + resolution: "extend@npm:3.0.2" + checksum: a50a8309ca65ea5d426382ff09f33586527882cf532931cb08ca786ea3146c0553310bda688710ff61d7668eba9f96b923fe1420cdf56a2c3eaf30fcab87b515 + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -7325,6 +7509,38 @@ __metadata: languageName: node linkType: hard +"hast-util-to-jsx-runtime@npm:^2.0.0": + version: 2.3.6 + resolution: "hast-util-to-jsx-runtime@npm:2.3.6" + dependencies: + "@types/estree": ^1.0.0 + "@types/hast": ^3.0.0 + "@types/unist": ^3.0.0 + comma-separated-tokens: ^2.0.0 + devlop: ^1.0.0 + estree-util-is-identifier-name: ^3.0.0 + hast-util-whitespace: ^3.0.0 + mdast-util-mdx-expression: ^2.0.0 + mdast-util-mdx-jsx: ^3.0.0 + mdast-util-mdxjs-esm: ^2.0.0 + property-information: ^7.0.0 + space-separated-tokens: ^2.0.0 + style-to-js: ^1.0.0 + unist-util-position: ^5.0.0 + vfile-message: ^4.0.0 + checksum: 78c25465cf010f1004b22f0bbb3bd47793f458ead3561c779ea2b9204ceb1adc9c048592b0a15025df0c683a12ebe16a8bef008c06d9c0369f51116f64b35a2d + languageName: node + linkType: hard + +"hast-util-whitespace@npm:^3.0.0": + version: 3.0.0 + resolution: "hast-util-whitespace@npm:3.0.0" + dependencies: + "@types/hast": ^3.0.0 + checksum: 41d93ccce218ba935dc3c12acdf586193c35069489c8c8f50c2aa824c00dec94a3c78b03d1db40fa75381942a189161922e4b7bca700b3a2cc779634c351a1e4 + languageName: node + linkType: hard + "hoist-non-react-statics@npm:^3.0.0": version: 3.3.2 resolution: "hoist-non-react-statics@npm:3.3.2" @@ -7368,6 +7584,13 @@ __metadata: languageName: node linkType: hard +"html-url-attributes@npm:^3.0.0": + version: 3.0.1 + resolution: "html-url-attributes@npm:3.0.1" + checksum: 1ecbf9cae0c438d2802386710177b7bbf7e30cc61327e9f125eb32fca7302cd1e3ab45c441859cb1e7646109be322fc1163592ad4dfde9b14d09416d101a6573 + languageName: node + linkType: hard + "htmlparser2@npm:^8.0.0": version: 8.0.2 resolution: "htmlparser2@npm:8.0.2" @@ -7594,6 +7817,13 @@ __metadata: languageName: node linkType: hard +"inline-style-parser@npm:0.2.4": + version: 0.2.4 + resolution: "inline-style-parser@npm:0.2.4" + checksum: 5df20a21dd8d67104faaae29774bb50dc9690c75bc5c45dac107559670a5530104ead72c4cf54f390026e617e7014c65b3d68fb0bb573a37c4d1f94e9c36e1ca + languageName: node + linkType: hard + "inquirer@npm:^8.2.4": version: 8.2.7 resolution: "inquirer@npm:8.2.7" @@ -7642,6 +7872,23 @@ __metadata: languageName: node linkType: hard +"is-alphabetical@npm:^2.0.0": + version: 2.0.1 + resolution: "is-alphabetical@npm:2.0.1" + checksum: 56207db8d9de0850f0cd30f4966bf731eb82cedfe496cbc2e97e7c3bacaf66fc54a972d2d08c0d93bb679cb84976a05d24c5ad63de56fabbfc60aadae312edaa + languageName: node + linkType: hard + +"is-alphanumerical@npm:^2.0.0": + version: 2.0.1 + resolution: "is-alphanumerical@npm:2.0.1" + dependencies: + is-alphabetical: ^2.0.0 + is-decimal: ^2.0.0 + checksum: 87acc068008d4c9c4e9f5bd5e251041d42e7a50995c77b1499cf6ed248f971aadeddb11f239cabf09f7975ee58cac7a48ffc170b7890076d8d227b24a68663c9 + languageName: node + linkType: hard + "is-arguments@npm:^1.0.4": version: 1.2.0 resolution: "is-arguments@npm:1.2.0" @@ -7750,6 +7997,13 @@ __metadata: languageName: node linkType: hard +"is-decimal@npm:^2.0.0": + version: 2.0.1 + resolution: "is-decimal@npm:2.0.1" + checksum: 97132de7acdce77caa7b797632970a2ecd649a88e715db0e4dbc00ab0708b5e7574ba5903962c860cd4894a14fd12b100c0c4ac8aed445cf6f55c6cf747a4158 + languageName: node + linkType: hard + "is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": version: 2.2.1 resolution: "is-docker@npm:2.2.1" @@ -7803,6 +8057,13 @@ __metadata: languageName: node linkType: hard +"is-hexadecimal@npm:^2.0.0": + version: 2.0.1 + resolution: "is-hexadecimal@npm:2.0.1" + checksum: 66a2ea85994c622858f063f23eda506db29d92b52580709eb6f4c19550552d4dcf3fb81952e52f7cf972097237959e00adc7bb8c9400cd12886e15bf06145321 + languageName: node + linkType: hard + "is-interactive@npm:^1.0.0": version: 1.0.0 resolution: "is-interactive@npm:1.0.0" @@ -7869,6 +8130,13 @@ __metadata: languageName: node linkType: hard +"is-plain-obj@npm:^4.0.0": + version: 4.1.0 + resolution: "is-plain-obj@npm:4.1.0" + checksum: 6dc45da70d04a81f35c9310971e78a6a3c7a63547ef782e3a07ee3674695081b6ca4e977fbb8efc48dae3375e0b34558d2bcd722aec9bddfa2d7db5b041be8ce + languageName: node + linkType: hard + "is-plain-object@npm:^2.0.4": version: 2.0.4 resolution: "is-plain-object@npm:2.0.4" @@ -8793,6 +9061,13 @@ __metadata: languageName: node linkType: hard +"longest-streak@npm:^3.0.0": + version: 3.1.0 + resolution: "longest-streak@npm:3.1.0" + checksum: d7f952ed004cbdb5c8bcfc4f7f5c3d65449e6c5a9e9be4505a656e3df5a57ee125f284286b4bf8ecea0c21a7b3bf2b8f9001ad506c319b9815ad6a63a47d0fd0 + languageName: node + linkType: hard + "loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" @@ -8914,6 +9189,127 @@ __metadata: languageName: node linkType: hard +"mdast-util-from-markdown@npm:^2.0.0": + version: 2.0.2 + resolution: "mdast-util-from-markdown@npm:2.0.2" + dependencies: + "@types/mdast": ^4.0.0 + "@types/unist": ^3.0.0 + decode-named-character-reference: ^1.0.0 + devlop: ^1.0.0 + mdast-util-to-string: ^4.0.0 + micromark: ^4.0.0 + micromark-util-decode-numeric-character-reference: ^2.0.0 + micromark-util-decode-string: ^2.0.0 + micromark-util-normalize-identifier: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + unist-util-stringify-position: ^4.0.0 + checksum: 1ad19f48b30ac6e0cb756070c210c78ad93c26876edfb3f75127783bc6df8b9402016d8f3e9964f3d1d5430503138ec65c145e869438727e1aa7f3cebf228fba + languageName: node + linkType: hard + +"mdast-util-mdx-expression@npm:^2.0.0": + version: 2.0.1 + resolution: "mdast-util-mdx-expression@npm:2.0.1" + dependencies: + "@types/estree-jsx": ^1.0.0 + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + devlop: ^1.0.0 + mdast-util-from-markdown: ^2.0.0 + mdast-util-to-markdown: ^2.0.0 + checksum: 6af56b06bde3ab971129db9855dcf0d31806c70b3b052d7a90a5499a366b57ffd0c2efca67d281c448c557298ba7e3e61bd07133733b735440840dd339b28e19 + languageName: node + linkType: hard + +"mdast-util-mdx-jsx@npm:^3.0.0": + version: 3.2.0 + resolution: "mdast-util-mdx-jsx@npm:3.2.0" + dependencies: + "@types/estree-jsx": ^1.0.0 + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + "@types/unist": ^3.0.0 + ccount: ^2.0.0 + devlop: ^1.1.0 + mdast-util-from-markdown: ^2.0.0 + mdast-util-to-markdown: ^2.0.0 + parse-entities: ^4.0.0 + stringify-entities: ^4.0.0 + unist-util-stringify-position: ^4.0.0 + vfile-message: ^4.0.0 + checksum: 224f5f6ad247f0f2622ee36c82ac7a4c6a60c31850de4056bf95f531bd2f7ec8943ef34dfe8a8375851f65c07e4913c4f33045d703df4ff4d11b2de5a088f7f9 + languageName: node + linkType: hard + +"mdast-util-mdxjs-esm@npm:^2.0.0": + version: 2.0.1 + resolution: "mdast-util-mdxjs-esm@npm:2.0.1" + dependencies: + "@types/estree-jsx": ^1.0.0 + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + devlop: ^1.0.0 + mdast-util-from-markdown: ^2.0.0 + mdast-util-to-markdown: ^2.0.0 + checksum: 1f9dad04d31d59005332e9157ea9510dc1d03092aadbc607a10475c7eec1c158b475aa0601a3a4f74e13097ca735deb8c2d9d37928ddef25d3029fd7c9e14dc3 + languageName: node + linkType: hard + +"mdast-util-phrasing@npm:^4.0.0": + version: 4.1.0 + resolution: "mdast-util-phrasing@npm:4.1.0" + dependencies: + "@types/mdast": ^4.0.0 + unist-util-is: ^6.0.0 + checksum: 3a97533e8ad104a422f8bebb34b3dde4f17167b8ed3a721cf9263c7416bd3447d2364e6d012a594aada40cac9e949db28a060bb71a982231693609034ed5324e + languageName: node + linkType: hard + +"mdast-util-to-hast@npm:^13.0.0": + version: 13.2.0 + resolution: "mdast-util-to-hast@npm:13.2.0" + dependencies: + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + "@ungap/structured-clone": ^1.0.0 + devlop: ^1.0.0 + micromark-util-sanitize-uri: ^2.0.0 + trim-lines: ^3.0.0 + unist-util-position: ^5.0.0 + unist-util-visit: ^5.0.0 + vfile: ^6.0.0 + checksum: 7e5231ff3d4e35e1421908437577fd5098141f64918ff5cc8a0f7a8a76c5407f7a3ee88d75f7a1f7afb763989c9f357475fa0ba8296c00aaff1e940098fe86a6 + languageName: node + linkType: hard + +"mdast-util-to-markdown@npm:^2.0.0": + version: 2.1.2 + resolution: "mdast-util-to-markdown@npm:2.1.2" + dependencies: + "@types/mdast": ^4.0.0 + "@types/unist": ^3.0.0 + longest-streak: ^3.0.0 + mdast-util-phrasing: ^4.0.0 + mdast-util-to-string: ^4.0.0 + micromark-util-classify-character: ^2.0.0 + micromark-util-decode-string: ^2.0.0 + unist-util-visit: ^5.0.0 + zwitch: ^2.0.0 + checksum: 288d152bd50c00632e6e01c610bb904a220d1e226c8086c40627877959746f83ab0b872f4150cb7d910198953b1bf756e384ac3fee3e7b0ddb4517f9084c5803 + languageName: node + linkType: hard + +"mdast-util-to-string@npm:^4.0.0": + version: 4.0.0 + resolution: "mdast-util-to-string@npm:4.0.0" + dependencies: + "@types/mdast": ^4.0.0 + checksum: 35489fb5710d58cbc2d6c8b6547df161a3f81e0f28f320dfb3548a9393555daf07c310c0c497708e67ed4dfea4a06e5655799e7d631ca91420c288b4525d6c29 + languageName: node + linkType: hard + "memoizee@npm:^0.4.15": version: 0.4.17 resolution: "memoizee@npm:0.4.17" @@ -8977,6 +9373,242 @@ __metadata: languageName: node linkType: hard +"micromark-core-commonmark@npm:^2.0.0": + version: 2.0.3 + resolution: "micromark-core-commonmark@npm:2.0.3" + dependencies: + decode-named-character-reference: ^1.0.0 + devlop: ^1.0.0 + micromark-factory-destination: ^2.0.0 + micromark-factory-label: ^2.0.0 + micromark-factory-space: ^2.0.0 + micromark-factory-title: ^2.0.0 + micromark-factory-whitespace: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-chunked: ^2.0.0 + micromark-util-classify-character: ^2.0.0 + micromark-util-html-tag-name: ^2.0.0 + micromark-util-normalize-identifier: ^2.0.0 + micromark-util-resolve-all: ^2.0.0 + micromark-util-subtokenize: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: cfb0fd9c895f86a4e9344f7f0344fe6bd1018945798222835248146a42430b8c7bc0b2857af574cf4e1b4ce4e5c1a35a1479942421492e37baddde8de85814dc + languageName: node + linkType: hard + +"micromark-factory-destination@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-destination@npm:2.0.1" + dependencies: + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 9c4baa9ca2ed43c061bbf40ddd3d85154c2a0f1f485de9dea41d7dd2ad994ebb02034a003b2c1dbe228ba83a0576d591f0e90e0bf978713f84ee7d7f3aa98320 + languageName: node + linkType: hard + +"micromark-factory-label@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-label@npm:2.0.1" + dependencies: + devlop: ^1.0.0 + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: bd03f5a75f27cdbf03b894ddc5c4480fc0763061fecf9eb927d6429233c930394f223969a99472df142d570c831236134de3dc23245d23d9f046f9d0b623b5c2 + languageName: node + linkType: hard + +"micromark-factory-space@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-space@npm:2.0.1" + dependencies: + micromark-util-character: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 1bd68a017c1a66f4787506660c1e1c5019169aac3b1cb075d49ac5e360e0b2065e984d4e1d6e9e52a9d44000f2fa1c98e66a743d7aae78b4b05616bf3242ed71 + languageName: node + linkType: hard + +"micromark-factory-title@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-title@npm:2.0.1" + dependencies: + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: b4d2e4850a8ba0dff25ce54e55a3eb0d43dda88a16293f53953153288f9d84bcdfa8ca4606b2cfbb4f132ea79587bbb478a73092a349f893f5264fbcdbce2ee1 + languageName: node + linkType: hard + +"micromark-factory-whitespace@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-whitespace@npm:2.0.1" + dependencies: + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 67b3944d012a42fee9e10e99178254a04d48af762b54c10a50fcab988688799993efb038daf9f5dbc04001a97b9c1b673fc6f00e6a56997877ab25449f0c8650 + languageName: node + linkType: hard + +"micromark-util-character@npm:^2.0.0": + version: 2.1.1 + resolution: "micromark-util-character@npm:2.1.1" + dependencies: + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: e9e409efe4f2596acd44587e8591b722bfc041c1577e8fe0d9c007a4776fb800f9b3637a22862ad2ba9489f4bdf72bb547fce5767dbbfe0a5e6760e2a21c6495 + languageName: node + linkType: hard + +"micromark-util-chunked@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-chunked@npm:2.0.1" + dependencies: + micromark-util-symbol: ^2.0.0 + checksum: f8cb2a67bcefe4bd2846d838c97b777101f0043b9f1de4f69baf3e26bb1f9885948444e3c3aec66db7595cad8173bd4567a000eb933576c233d54631f6323fe4 + languageName: node + linkType: hard + +"micromark-util-classify-character@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-classify-character@npm:2.0.1" + dependencies: + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 4d8bbe3a6dbf69ac0fc43516866b5bab019fe3f4568edc525d4feaaaf78423fa54e6b6732b5bccbeed924455279a3758ffc9556954aafb903982598a95a02704 + languageName: node + linkType: hard + +"micromark-util-combine-extensions@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-combine-extensions@npm:2.0.1" + dependencies: + micromark-util-chunked: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 5d22fb9ee37e8143adfe128a72b50fa09568c2cc553b3c76160486c96dbbb298c5802a177a10a215144a604b381796071b5d35be1f2c2b2ee17995eda92f0c8e + languageName: node + linkType: hard + +"micromark-util-decode-numeric-character-reference@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-util-decode-numeric-character-reference@npm:2.0.2" + dependencies: + micromark-util-symbol: ^2.0.0 + checksum: ee11c8bde51e250e302050474c4a2adca094bca05c69f6cdd241af12df285c48c88d19ee6e022b9728281c280be16328904adca994605680c43af56019f4b0b6 + languageName: node + linkType: hard + +"micromark-util-decode-string@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-decode-string@npm:2.0.1" + dependencies: + decode-named-character-reference: ^1.0.0 + micromark-util-character: ^2.0.0 + micromark-util-decode-numeric-character-reference: ^2.0.0 + micromark-util-symbol: ^2.0.0 + checksum: e9546ae53f9b5a4f9aa6aaf3e750087100d3429485ca80dbacec99ff2bb15a406fa7d93784a0fc2fe05ad7296b9295e75160ef71faec9e90110b7be2ae66241a + languageName: node + linkType: hard + +"micromark-util-encode@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-encode@npm:2.0.1" + checksum: be890b98e78dd0cdd953a313f4148c4692cc2fb05533e56fef5f421287d3c08feee38ca679f318e740530791fc251bfe8c80efa926fcceb4419b269c9343d226 + languageName: node + linkType: hard + +"micromark-util-html-tag-name@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-html-tag-name@npm:2.0.1" + checksum: dea365f5ad28ad74ff29fcb581f7b74fc1f80271c5141b3b2bc91c454cbb6dfca753f28ae03730d657874fcbd89d0494d0e3965dfdca06d9855f467c576afa9d + languageName: node + linkType: hard + +"micromark-util-normalize-identifier@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-normalize-identifier@npm:2.0.1" + dependencies: + micromark-util-symbol: ^2.0.0 + checksum: 1eb9a289d7da067323df9fdc78bfa90ca3207ad8fd893ca02f3133e973adcb3743b233393d23d95c84ccaf5d220ae7f5a28402a644f135dcd4b8cfa60a7b5f84 + languageName: node + linkType: hard + +"micromark-util-resolve-all@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-resolve-all@npm:2.0.1" + dependencies: + micromark-util-types: ^2.0.0 + checksum: 9275f3ddb6c26f254dd2158e66215d050454b279707a7d9ce5a3cd0eba23201021cedcb78ae1a746c1b23227dcc418ee40dd074ade195359506797a5493550cc + languageName: node + linkType: hard + +"micromark-util-sanitize-uri@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-sanitize-uri@npm:2.0.1" + dependencies: + micromark-util-character: ^2.0.0 + micromark-util-encode: ^2.0.0 + micromark-util-symbol: ^2.0.0 + checksum: d01517840c17de67aaa0b0f03bfe05fac8a41d99723cd8ce16c62f6810e99cd3695364a34c335485018e5e2c00e69031744630a1b85c6868aa2f2ca1b36daa2f + languageName: node + linkType: hard + +"micromark-util-subtokenize@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-util-subtokenize@npm:2.1.0" + dependencies: + devlop: ^1.0.0 + micromark-util-chunked: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 2e194bc8a5279d256582020500e5072a95c1094571be49043704343032e1fffbe09c862ef9c131cf5c762e296ddb54ff8bc767b3786a798524a68d1db6942934 + languageName: node + linkType: hard + +"micromark-util-symbol@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-symbol@npm:2.0.1" + checksum: fb7346950550bc85a55793dda94a8b3cb3abc068dbd7570d1162db7aee803411d06c0a5de4ae59cd945f46143bdeadd4bba02a02248fa0d18cc577babaa00044 + languageName: node + linkType: hard + +"micromark-util-types@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-util-types@npm:2.0.2" + checksum: 884f7974839e4bc6d2bd662e57c973a9164fd5c0d8fe16cddf07472b86a7e6726747c00674952c0321d17685d700cd3295e9f58a842a53acdf6c6d55ab051aab + languageName: node + linkType: hard + +"micromark@npm:^4.0.0": + version: 4.0.2 + resolution: "micromark@npm:4.0.2" + dependencies: + "@types/debug": ^4.0.0 + debug: ^4.0.0 + decode-named-character-reference: ^1.0.0 + devlop: ^1.0.0 + micromark-core-commonmark: ^2.0.0 + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-chunked: ^2.0.0 + micromark-util-combine-extensions: ^2.0.0 + micromark-util-decode-numeric-character-reference: ^2.0.0 + micromark-util-encode: ^2.0.0 + micromark-util-normalize-identifier: ^2.0.0 + micromark-util-resolve-all: ^2.0.0 + micromark-util-sanitize-uri: ^2.0.0 + micromark-util-subtokenize: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 5306c15dd12f543755bc627fc361d4255dfc430e7af6069a07ac0eacc338fbd761fe8e93f02a8bfab6097bab12ee903192fe31389222459d5029242a5aaba3b8 + languageName: node + linkType: hard + "micromatch@npm:^4.0.0, micromatch@npm:^4.0.4, micromatch@npm:^4.0.8": version: 4.0.8 resolution: "micromatch@npm:4.0.8" @@ -10065,6 +10697,21 @@ __metadata: languageName: node linkType: hard +"parse-entities@npm:^4.0.0": + version: 4.0.2 + resolution: "parse-entities@npm:4.0.2" + dependencies: + "@types/unist": ^2.0.0 + character-entities-legacy: ^3.0.0 + character-reference-invalid: ^2.0.0 + decode-named-character-reference: ^1.0.0 + is-alphanumerical: ^2.0.0 + is-decimal: ^2.0.0 + is-hexadecimal: ^2.0.0 + checksum: db22b46da1a62af00409c929ac49fbd306b5ebf0dbacf4646d2ae2b58616ef90a40eedc282568a3cf740fac2a7928bc97146973a628f6977ca274dedc2ad6edc + languageName: node + linkType: hard + "parse-headers@npm:^2.0.2": version: 2.0.6 resolution: "parse-headers@npm:2.0.6" @@ -10559,6 +11206,13 @@ __metadata: languageName: node linkType: hard +"property-information@npm:^7.0.0": + version: 7.1.0 + resolution: "property-information@npm:7.1.0" + checksum: 3875161d204bac89d75181f6d3ebc3ecaeb2699b4e2ecfcf5452201d7cdd275168c6742d7ff8cec5ab0c342fae72369ac705e1f8e9680a9acd911692e80dfb88 + languageName: node + linkType: hard + "protocol-buffers-schema@npm:^3.3.1": version: 3.6.0 resolution: "protocol-buffers-schema@npm:3.6.0" @@ -10708,6 +11362,28 @@ __metadata: languageName: node linkType: hard +"react-markdown@npm:^10.1.0": + version: 10.1.0 + resolution: "react-markdown@npm:10.1.0" + dependencies: + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + devlop: ^1.0.0 + hast-util-to-jsx-runtime: ^2.0.0 + html-url-attributes: ^3.0.0 + mdast-util-to-hast: ^13.0.0 + remark-parse: ^11.0.0 + remark-rehype: ^11.0.0 + unified: ^11.0.0 + unist-util-visit: ^5.0.0 + vfile: ^6.0.0 + peerDependencies: + "@types/react": ">=18" + react: ">=18" + checksum: fa7ef860e32a18206c5b301de8672be609b108f46f0f091e9779d50ff8145bd63d0f6e82ffb18fc1b7aee2264cbdac1100205596ff10d2c3d2de6627abb3868f + languageName: node + linkType: hard + "react-remove-scroll-bar@npm:^2.3.7": version: 2.3.8 resolution: "react-remove-scroll-bar@npm:2.3.8" @@ -10913,6 +11589,31 @@ __metadata: languageName: node linkType: hard +"remark-parse@npm:^11.0.0": + version: 11.0.0 + resolution: "remark-parse@npm:11.0.0" + dependencies: + "@types/mdast": ^4.0.0 + mdast-util-from-markdown: ^2.0.0 + micromark-util-types: ^2.0.0 + unified: ^11.0.0 + checksum: d83d245290fa84bb04fb3e78111f09c74f7417e7c012a64dd8dc04fccc3699036d828fbd8eeec8944f774b6c30cc1d925c98f8c46495ebcee7c595496342ab7f + languageName: node + linkType: hard + +"remark-rehype@npm:^11.0.0": + version: 11.1.2 + resolution: "remark-rehype@npm:11.1.2" + dependencies: + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + mdast-util-to-hast: ^13.0.0 + unified: ^11.0.0 + vfile: ^6.0.0 + checksum: 6eab55cb3464ec01d8e002cc9fe02ae57f48162899693fd53b5ba553ac8699dae7b55fce9df7131a5981313b19b495d6fbfa98a9d6bd243e7485591364d9b5b3 + languageName: node + linkType: hard + "require-directory@npm:^2.1.1": version: 2.1.1 resolution: "require-directory@npm:2.1.1" @@ -11537,6 +12238,13 @@ __metadata: languageName: node linkType: hard +"space-separated-tokens@npm:^2.0.0": + version: 2.0.2 + resolution: "space-separated-tokens@npm:2.0.2" + checksum: 202e97d7ca1ba0758a0aa4fe226ff98142073bcceeff2da3aad037968878552c3bbce3b3231970025375bbba5aee00c5b8206eda408da837ab2dc9c0f26be990 + languageName: node + linkType: hard + "spdx-correct@npm:^3.0.0": version: 3.2.0 resolution: "spdx-correct@npm:3.2.0" @@ -11724,6 +12432,16 @@ __metadata: languageName: node linkType: hard +"stringify-entities@npm:^4.0.0": + version: 4.0.4 + resolution: "stringify-entities@npm:4.0.4" + dependencies: + character-entities-html4: ^2.0.0 + character-entities-legacy: ^3.0.0 + checksum: ac1344ef211eacf6cf0a0a8feaf96f9c36083835b406560d2c6ff5a87406a41b13f2f0b4c570a3b391f465121c4fd6822b863ffb197e8c0601a64097862cc5b5 + languageName: node + linkType: hard + "strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" @@ -11795,6 +12513,24 @@ __metadata: languageName: node linkType: hard +"style-to-js@npm:^1.0.0": + version: 1.1.18 + resolution: "style-to-js@npm:1.1.18" + dependencies: + style-to-object: 1.0.11 + checksum: df544489fac603c2800685e27ea4efca0d4c3973f108b384ff98dc4093a5da00b7f89888178d7d25fb306c9efe62b2502c58fde2a00daddb14f9b72b6ae452ae + languageName: node + linkType: hard + +"style-to-object@npm:1.0.11": + version: 1.0.11 + resolution: "style-to-object@npm:1.0.11" + dependencies: + inline-style-parser: 0.2.4 + checksum: 4a00ada833b6f23dda3948e1d603f4ab4257c7c4a197dc6b74a085cfe05b07a2167a44883c45569907eec2630116f5008180cbbf62bc301c131f138e90c575fc + languageName: node + linkType: hard + "styled-components@npm:^5.3.6": version: 5.3.11 resolution: "styled-components@npm:5.3.11" @@ -12096,6 +12832,13 @@ __metadata: languageName: node linkType: hard +"trim-lines@npm:^3.0.0": + version: 3.0.1 + resolution: "trim-lines@npm:3.0.1" + checksum: e241da104682a0e0d807222cc1496b92e716af4db7a002f4aeff33ae6a0024fef93165d49eab11aa07c71e1347c42d46563f91dfaa4d3fb945aa535cdead53ed + languageName: node + linkType: hard + "trim-newlines@npm:^3.0.0": version: 3.0.1 resolution: "trim-newlines@npm:3.0.1" @@ -12103,6 +12846,13 @@ __metadata: languageName: node linkType: hard +"trough@npm:^2.0.0": + version: 2.2.0 + resolution: "trough@npm:2.2.0" + checksum: 6097df63169aca1f9b08c263b1b501a9b878387f46e161dde93f6d0bba7febba93c95f876a293c5ea370f6cb03bcb687b2488c8955c3cfb66c2c0161ea8c00f6 + languageName: node + linkType: hard + "ts-loader@npm:^9.2.6": version: 9.5.4 resolution: "ts-loader@npm:9.5.4" @@ -12405,6 +13155,21 @@ __metadata: languageName: node linkType: hard +"unified@npm:^11.0.0": + version: 11.0.5 + resolution: "unified@npm:11.0.5" + dependencies: + "@types/unist": ^3.0.0 + bail: ^2.0.0 + devlop: ^1.0.0 + extend: ^3.0.0 + is-plain-obj: ^4.0.0 + trough: ^2.0.0 + vfile: ^6.0.0 + checksum: b3bf7fd6f568cc261e074dae21188483b0f2a8ab858d62e6e85b75b96cc655f59532906ae3c64d56a9b257408722d71f1d4135292b3d7ee02907c8b592fb3cf0 + languageName: node + linkType: hard + "unique-filename@npm:^3.0.0": version: 3.0.0 resolution: "unique-filename@npm:3.0.0" @@ -12423,6 +13188,54 @@ __metadata: languageName: node linkType: hard +"unist-util-is@npm:^6.0.0": + version: 6.0.1 + resolution: "unist-util-is@npm:6.0.1" + dependencies: + "@types/unist": ^3.0.0 + checksum: e57733e1766b55c9a873a42d2f34daa211580788b1bba26af2fc22e48e147bdcff0f9a752ed2a19238864823735fbbe27a1804d6a5a22b182c23aa0191e41c12 + languageName: node + linkType: hard + +"unist-util-position@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-position@npm:5.0.0" + dependencies: + "@types/unist": ^3.0.0 + checksum: f89b27989b19f07878de9579cd8db2aa0194c8360db69e2c99bd2124a480d79c08f04b73a64daf01a8fb3af7cba65ff4b45a0b978ca243226084ad5f5d441dde + languageName: node + linkType: hard + +"unist-util-stringify-position@npm:^4.0.0": + version: 4.0.0 + resolution: "unist-util-stringify-position@npm:4.0.0" + dependencies: + "@types/unist": ^3.0.0 + checksum: e2e7aee4b92ddb64d314b4ac89eef7a46e4c829cbd3ee4aee516d100772b490eb6b4974f653ba0717a0071ca6ea0770bf22b0a2ea62c65fcba1d071285e96324 + languageName: node + linkType: hard + +"unist-util-visit-parents@npm:^6.0.0": + version: 6.0.2 + resolution: "unist-util-visit-parents@npm:6.0.2" + dependencies: + "@types/unist": ^3.0.0 + unist-util-is: ^6.0.0 + checksum: cf28578a6f0b81877965e261fe82460f83b8c3a9cab3b2080c046b215f3223c6195b01064256619ca3411a1930face93a1a2a72d34d8716e684d6cd59f53cd9a + languageName: node + linkType: hard + +"unist-util-visit@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-visit@npm:5.0.0" + dependencies: + "@types/unist": ^3.0.0 + unist-util-is: ^6.0.0 + unist-util-visit-parents: ^6.0.0 + checksum: 9ec42e618e7e5d0202f3c191cd30791b51641285732767ee2e6bcd035931032e3c1b29093f4d7fd0c79175bbc1f26f24f26ee49770d32be76f8730a652a857e6 + languageName: node + linkType: hard + "universal-user-agent@npm:^6.0.0": version: 6.0.1 resolution: "universal-user-agent@npm:6.0.1" @@ -12633,6 +13446,26 @@ __metadata: languageName: node linkType: hard +"vfile-message@npm:^4.0.0": + version: 4.0.3 + resolution: "vfile-message@npm:4.0.3" + dependencies: + "@types/unist": ^3.0.0 + unist-util-stringify-position: ^4.0.0 + checksum: f5e8516f2aa0feb4c866d507543d4e90f9ab309e2c988577dbf4ebd268d495f72f2b48149849d14300164d5d60b5f74b5641cd285bb4408a3942b758683d9276 + languageName: node + linkType: hard + +"vfile@npm:^6.0.0": + version: 6.0.3 + resolution: "vfile@npm:6.0.3" + dependencies: + "@types/unist": ^3.0.0 + vfile-message: ^4.0.0 + checksum: 152b6729be1af70df723efb65c1a1170fd483d41086557da3651eea69a1dd1f0c22ea4344834d56d30734b9185bcab63e22edc81d3f0e9bed8aa4660d61080af + languageName: node + linkType: hard + "vscode-jsonrpc@npm:8.2.0": version: 8.2.0 resolution: "vscode-jsonrpc@npm:8.2.0" @@ -13320,3 +14153,10 @@ __metadata: checksum: 76113b43ec4031f9a120285c830e43bafae4c25c0a72f7a9e228af815c3e52432a8414c5d4eb6cfd3282443a4a3d2bc18b445dd702e770b1e330b6e39081750f languageName: node linkType: hard + +"zwitch@npm:^2.0.0": + version: 2.0.4 + resolution: "zwitch@npm:2.0.4" + checksum: f22ec5fc2d5f02c423c93d35cdfa83573a3a3bd98c66b927c368ea4d0e7252a500df2a90a6b45522be536a96a73404393c958e945fdba95e6832c200791702b6 + languageName: node + linkType: hard