diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 00d299574a39..e4569523edc7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -47,3 +47,6 @@ Cargo.toml # Dashboard, Cloud & Authentication /app/ide-desktop/ @PabloBuchu @indiv0 @somebody1234 +# The data-link schema is owned by the libraries team +/app/ide-desktop/lib/dashboard/src/data/dataLinkSchema.json @radeusgd @jdunkerley @GregoryTravis @AdRiley +/app/ide-desktop/lib/dashboard/src/data/__tests__ @radeusgd @jdunkerley @GregoryTravis @AdRiley @PabloBuchu @indiv0 @somebody1234 diff --git a/CHANGELOG.md b/CHANGELOG.md index c754436e9e35..79f9a9930f6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -619,6 +619,8 @@ - [Separate `Group_By` from `columns` into new argument on `aggregate`.][9027] - [Allow `copy_to` and `move_to` to work between local and S3 files.][9054] - [Adjusted expression handling and new `Simple_Expression` type.][9128] +- [Allow reading Data Links configured locally or in the Cloud.][9215] +- [Update the XML methods and add more capabilities to document.][9233] [debug-shortcuts]: https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug @@ -894,6 +896,8 @@ [9027]: https://github.com/enso-org/enso/pull/9027 [9054]: https://github.com/enso-org/enso/pull/9054 [9128]: https://github.com/enso-org/enso/pull/9128 +[9215]: https://github.com/enso-org/enso/pull/9215 +[9233]: https://github.com/enso-org/enso/pull/9233 #### Enso Compiler diff --git a/app/gui2/src/bindings.ts b/app/gui2/src/bindings.ts index 4926981c114a..c7a21460aaa9 100644 --- a/app/gui2/src/bindings.ts +++ b/app/gui2/src/bindings.ts @@ -13,7 +13,6 @@ export const componentBrowserBindings = defineKeybinds('component-browser', { applySuggestion: ['Tab'], acceptSuggestion: ['Enter'], acceptInput: ['Mod+Enter'], - cancelEditing: ['Escape'], moveUp: ['ArrowUp'], moveDown: ['ArrowDown'], }) diff --git a/app/gui2/src/components/ComponentBrowser.vue b/app/gui2/src/components/ComponentBrowser.vue index 1d0fa30ec395..5977af926122 100644 --- a/app/gui2/src/components/ComponentBrowser.vue +++ b/app/gui2/src/components/ComponentBrowser.vue @@ -11,12 +11,14 @@ import ToggleIcon from '@/components/ToggleIcon.vue' import { useApproach } from '@/composables/animation' import { useEvent, useResizeObserver } from '@/composables/events' import type { useNavigator } from '@/composables/navigator' +import { injectInteractionHandler, type Interaction } from '@/providers/interactionHandler' import { useGraphStore } from '@/stores/graph' import type { RequiredImport } from '@/stores/graph/imports' import { useProjectStore } from '@/stores/project' import { groupColorStyle, useSuggestionDbStore } from '@/stores/suggestionDatabase' import { SuggestionKind, type SuggestionEntry } from '@/stores/suggestionDatabase/entry' import type { VisualizationDataSource } from '@/stores/visualization' +import { targetIsOutside } from '@/util/autoBlur' import { tryGetIndex } from '@/util/data/array' import type { Opt } from '@/util/data/opt' import { allRanges } from '@/util/data/range' @@ -35,6 +37,7 @@ const COMPONENT_BROWSER_TO_NODE_OFFSET = new Vec2(-4, -4) const projectStore = useProjectStore() const suggestionDbStore = useSuggestionDbStore() const graphStore = useGraphStore() +const interaction = injectInteractionHandler() const props = defineProps<{ nodePosition: Vec2 @@ -47,7 +50,24 @@ const emit = defineEmits<{ canceled: [] }>() +const cbOpen: Interaction = { + cancel: () => { + emit('canceled') + }, + click: (e: PointerEvent) => { + if (targetIsOutside(e, cbRoot)) { + if (input.anyChange.value) { + acceptInput() + } else { + interaction.cancel(cbOpen) + } + } + return false + }, +} + onMounted(() => { + interaction.setCurrent(cbOpen) input.reset(props.usage) if (inputField.value != null) { inputField.value.focus({ preventScroll: true }) @@ -159,23 +179,6 @@ function preventNonInputDefault(e: Event) { } } -useEvent( - window, - 'pointerdown', - (event) => { - if (event.button !== 0) return - if (!(event.target instanceof Element)) return - if (!cbRoot.value?.contains(event.target)) { - if (input.anyChange.value) { - emit('accepted', input.code.value, input.importsToAdd()) - } else { - emit('canceled') - } - } - }, - { capture: true }, -) - const inputElement = ref() const inputSize = useResizeObserver(inputElement, false) @@ -357,6 +360,7 @@ function acceptSuggestion(index: Opt = null) { function acceptInput() { emit('accepted', input.code.value.trim(), input.importsToAdd()) + interaction.end(cbOpen) } // === Key Events Handler === @@ -386,9 +390,6 @@ const handler = componentBrowserBindings.handler({ } scrolling.scrollWithTransition({ type: 'selected' }) }, - cancelEditing() { - emit('canceled') - }, }) diff --git a/app/gui2/src/components/GraphEditor.vue b/app/gui2/src/components/GraphEditor.vue index a5c55f2dce6e..d76f8ba8c094 100644 --- a/app/gui2/src/components/GraphEditor.vue +++ b/app/gui2/src/components/GraphEditor.vue @@ -21,7 +21,7 @@ import { keyboardBusy, keyboardBusyExceptIn, useEvent } from '@/composables/even import { useStackNavigator } from '@/composables/stackNavigator' import { provideGraphNavigator } from '@/providers/graphNavigator' import { provideGraphSelection } from '@/providers/graphSelection' -import { provideInteractionHandler, type Interaction } from '@/providers/interactionHandler' +import { provideInteractionHandler } from '@/providers/interactionHandler' import { provideWidgetRegistry } from '@/providers/widgetRegistry' import { useGraphStore, type NodeId } from '@/stores/graph' import type { RequiredImport } from '@/stores/graph/imports' @@ -128,7 +128,7 @@ const nodeSelection = provideGraphSelection(graphNavigator, graphStore.nodeRects const interactionBindingsHandler = interactionBindings.handler({ cancel: () => interaction.handleCancel(), - click: (e) => (e instanceof MouseEvent ? interaction.handleClick(e, graphNavigator) : false), + click: (e) => (e instanceof PointerEvent ? interaction.handleClick(e, graphNavigator) : false), }) // Return the environment for the placement of a new node. The passed nodes should be the nodes that are @@ -180,7 +180,8 @@ function sourcePortForSelection() { } useEvent(window, 'keydown', (event) => { - ;(!keyboardBusy() && (interactionBindingsHandler(event) || graphBindingsHandler(event))) || + interactionBindingsHandler(event) || + (!keyboardBusy() && graphBindingsHandler(event)) || (!keyboardBusyExceptIn(codeEditorArea.value) && codeEditorHandler(event)) }) useEvent(window, 'pointerdown', interactionBindingsHandler, { capture: true }) @@ -226,7 +227,7 @@ const graphBindingsHandler = graphBindings.handler({ openComponentBrowser() { if (keyboardBusy()) return false if (graphNavigator.sceneMousePos != null && !componentBrowserVisible.value) { - interaction.setCurrent(creatingNode) + showComponentBrowser() } }, newNode() { @@ -349,7 +350,6 @@ const { handleClick } = useDoubleClick( } }, () => { - if (keyboardBusy()) return false stackNavigator.exitNode() }, ) @@ -388,62 +388,25 @@ const groupColors = computed(() => { return styles }) -const editingNode: Interaction = { - init: () => { - // component browser usage is set in `graphStore.editedNodeInfo` watch - componentBrowserNodePosition.value = targetComponentBrowserNodePosition() - }, - cancel: () => { - hideComponentBrowser() - graphStore.editedNodeInfo = undefined - }, -} -const nodeIsBeingEdited = computed(() => graphStore.editedNodeInfo != null) -interaction.setWhen(nodeIsBeingEdited, editingNode) - -const creatingNode: Interaction = { - init: () => { - componentBrowserUsage.value = { type: 'newNode', sourcePort: sourcePortForSelection() } - componentBrowserNodePosition.value = targetComponentBrowserNodePosition() - componentBrowserVisible.value = true - }, - cancel: hideComponentBrowser, -} - -const creatingNodeFromButton: Interaction = { - init: () => { - componentBrowserUsage.value = { type: 'newNode', sourcePort: sourcePortForSelection() } - let targetPos = placementPositionForSelection() - if (targetPos == undefined) { - targetPos = nonDictatedPlacement(DEFAULT_NODE_SIZE, placementEnvironment.value).position - } - componentBrowserNodePosition.value = targetPos - componentBrowserVisible.value = true - }, - cancel: hideComponentBrowser, -} - -const creatingNodeFromPortDoubleClick: Interaction = { - init: () => { - // component browser usage is set in event handler - componentBrowserVisible.value = true - }, - cancel: hideComponentBrowser, +function showComponentBrowser(nodePosition?: Vec2, usage?: Usage) { + componentBrowserUsage.value = usage ?? { type: 'newNode', sourcePort: sourcePortForSelection() } + componentBrowserNodePosition.value = nodePosition ?? targetComponentBrowserNodePosition() + componentBrowserVisible.value = true } -const creatingNodeFromEdgeDrop: Interaction = { - init: () => { - // component browser usage is set in event handler - componentBrowserVisible.value = true - }, - cancel: hideComponentBrowser, +function startCreatingNodeFromButton() { + const targetPos = + placementPositionForSelection() ?? + nonDictatedPlacement(DEFAULT_NODE_SIZE, placementEnvironment.value).position + showComponentBrowser(targetPos) } function hideComponentBrowser() { + graphStore.editedNodeInfo = undefined componentBrowserVisible.value = false } -function onComponentBrowserCommit(content: string, requiredImports: RequiredImport[]) { +function commitComponentBrowser(content: string, requiredImports: RequiredImport[]) { if (content != null) { if (graphStore.editedNodeInfo) { // We finish editing a node. @@ -460,13 +423,7 @@ function onComponentBrowserCommit(content: string, requiredImports: RequiredImpo if (createdNode) nodeSelection.setSelection(new Set([createdNode])) } } - // Finish interaction. This should also hide component browser. - interaction.setCurrent(undefined) -} - -function onComponentBrowserCancel() { - // Finish interaction. This should also hide component browser. - interaction.setCurrent(undefined) + hideComponentBrowser() } // Watch the `editedNode` in the graph store @@ -474,15 +431,13 @@ watch( () => graphStore.editedNodeInfo, (editedInfo) => { if (editedInfo) { - componentBrowserNodePosition.value = targetComponentBrowserNodePosition() - componentBrowserUsage.value = { + showComponentBrowser(undefined, { type: 'editNode', node: editedInfo.id, cursorPos: editedInfo.initialCursorPos, - } - componentBrowserVisible.value = true + }) } else { - componentBrowserVisible.value = false + hideComponentBrowser() } }, ) @@ -627,30 +582,23 @@ async function readNodeFromExcelClipboard( } function handleNodeOutputPortDoubleClick(id: AstId) { - componentBrowserUsage.value = { type: 'newNode', sourcePort: id } const srcNode = graphStore.db.getPatternExpressionNodeId(id) if (srcNode == null) { console.error('Impossible happened: Double click on port not belonging to any node: ', id) return } const placementEnvironment = environmentForNodes([srcNode].values()) - componentBrowserNodePosition.value = previousNodeDictatedPlacement( - DEFAULT_NODE_SIZE, - placementEnvironment, - { - horizontalGap: gapBetweenNodes, - verticalGap: gapBetweenNodes, - }, - ).position - interaction.setCurrent(creatingNodeFromPortDoubleClick) + const position = previousNodeDictatedPlacement(DEFAULT_NODE_SIZE, placementEnvironment, { + horizontalGap: gapBetweenNodes, + verticalGap: gapBetweenNodes, + }).position + showComponentBrowser(position, { type: 'newNode', sourcePort: id }) } const stackNavigator = useStackNavigator() function handleEdgeDrop(source: AstId, position: Vec2) { - componentBrowserUsage.value = { type: 'newNode', sourcePort: source } - componentBrowserNodePosition.value = position - interaction.setCurrent(creatingNodeFromEdgeDrop) + showComponentBrowser(position, { type: 'newNode', sourcePort: source }) } @@ -680,8 +628,8 @@ function handleEdgeDrop(source: AstId, position: Vec2) { :navigator="graphNavigator" :nodePosition="componentBrowserNodePosition" :usage="componentBrowserUsage" - @accepted="onComponentBrowserCommit" - @canceled="onComponentBrowserCancel" + @accepted="commitComponentBrowser" + @canceled="hideComponentBrowser" /> - + @@ -716,6 +660,7 @@ function handleEdgeDrop(source: AstId, position: Vec2) { position: relative; contain: layout; overflow: clip; + user-select: none; --group-color-fallback: #006b8a; --node-color-no-type: #596b81; } diff --git a/app/gui2/src/components/GraphEditor/GraphEdge.vue b/app/gui2/src/components/GraphEditor/GraphEdge.vue index fb05b5da4da6..f7b41a559c8e 100644 --- a/app/gui2/src/components/GraphEditor/GraphEdge.vue +++ b/app/gui2/src/components/GraphEditor/GraphEdge.vue @@ -508,7 +508,7 @@ const connected = computed(() => isConnected(props.edge)) class="edge io" :data-source-node-id="sourceNode" :data-target-node-id="targetNode" - @pointerdown="click" + @pointerdown.stop="click" @pointerenter="hovered = true" @pointerleave="hovered = false" /> diff --git a/app/gui2/src/components/GraphEditor/GraphEdges.vue b/app/gui2/src/components/GraphEditor/GraphEdges.vue index 958503be431b..f4327c181b1f 100644 --- a/app/gui2/src/components/GraphEditor/GraphEdges.vue +++ b/app/gui2/src/components/GraphEditor/GraphEdges.vue @@ -26,7 +26,7 @@ const editingEdge: Interaction = { cancel() { graph.clearUnconnected() }, - click(_e: MouseEvent, graphNavigator: GraphNavigator): boolean { + click(_e: PointerEvent, graphNavigator: GraphNavigator): boolean { if (graph.unconnectedEdge == null) return false let source: AstId | undefined let sourceNode: NodeId | undefined diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetSelection.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetSelection.vue index 4c9e302511cd..278ab4d9f682 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetSelection.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetSelection.vue @@ -2,7 +2,8 @@ import NodeWidget from '@/components/GraphEditor/NodeWidget.vue' import SvgIcon from '@/components/SvgIcon.vue' import DropdownWidget from '@/components/widgets/DropdownWidget.vue' -import { Score, WidgetInput, defineWidget, widgetProps } from '@/providers/widgetRegistry' +import { injectInteractionHandler } from '@/providers/interactionHandler' +import { defineWidget, Score, WidgetInput, widgetProps } from '@/providers/widgetRegistry' import { singleChoiceConfiguration, type ArgumentWidgetConfiguration, @@ -15,6 +16,7 @@ import { type SuggestionEntryArgument, } from '@/stores/suggestionDatabase/entry.ts' import { Ast } from '@/util/ast' +import { targetIsOutside } from '@/util/autoBlur' import { ArgumentInfoKey } from '@/util/callTree' import { arrayEquals } from '@/util/data/array' import { @@ -27,6 +29,8 @@ import { computed, ref, watch } from 'vue' const props = defineProps(widgetProps(widgetDefinition)) const suggestions = useSuggestionDbStore() const graph = useGraphStore() +const interaction = injectInteractionHandler() +const widgetRoot = ref() interface Tag { /** If not set, the label is same as expression */ @@ -119,6 +123,15 @@ const innerWidgetInput = computed(() => { return { ...props.input, dynamicConfig: singleChoiceConfiguration(config) } }) const showDropdownWidget = ref(false) +interaction.setWhen(showDropdownWidget, { + cancel: () => { + showDropdownWidget.value = false + }, + click: (e: PointerEvent) => { + if (targetIsOutside(e, widgetRoot)) showDropdownWidget.value = false + return false + }, +}) function toggleDropdownWidget() { showDropdownWidget.value = !showDropdownWidget.value @@ -172,7 +185,13 @@ export const widgetDefinition = defineWidget(WidgetInput.isAstOrPlaceholder, {