From 496258a0b2c7796892bbe1096d9f0013be4e7b45 Mon Sep 17 00:00:00 2001 From: Adam Obuchowicz Date: Tue, 4 Feb 2025 10:08:23 +0100 Subject: [PATCH 1/8] WIP --- .../widgets/WidgetCloudBrowser.vue | 24 ++++++++++++------- .../GraphEditor/widgets/WidgetSelection.vue | 9 +++---- .../components/widgets/FileBrowserWidget.vue | 14 +++++++++++ 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetCloudBrowser.vue b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetCloudBrowser.vue index f9c31d25378a..cdc637d97313 100644 --- a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetCloudBrowser.vue +++ b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetCloudBrowser.vue @@ -12,19 +12,25 @@ import { computed, h } from 'vue' const props = defineProps(widgetProps(widgetDefinition)) +const writeMode = computed( + () => props.input[ArgumentInfoKey]?.info?.reprType.includes(WRITABLE_FILE_TYPE) ?? false, +) const item: CustomDropdownItem = { label: 'Choose file from cloud...', onClick: ({ setActivity, close }) => { setActivity( - h(FileBrowserWidget, { - onPathSelected: (path: string) => { - props.onUpdate({ - portUpdate: { value: Ast.TextLiteral.new(path), origin: props.input.portId }, - directInteraction: true, - }) - close() - }, - }), + computed(() => + h(FileBrowserWidget, { + writeMode: writeMode.value, + onPathSelected: (path: string) => { + props.onUpdate({ + portUpdate: { value: Ast.TextLiteral.new(path), origin: props.input.portId }, + directInteraction: true, + }) + close() + }, + }), + ), ) }, } diff --git a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelection.vue b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelection.vue index 50b88ed39446..55fd7b77fc4f 100644 --- a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelection.vue +++ b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelection.vue @@ -32,9 +32,10 @@ import { arrayEquals } from '@/util/data/array' import type { Opt } from '@/util/data/opt' import { ProjectPath } from '@/util/projectPath' import { qnLastSegment, tryQualifiedName } from '@/util/qualifiedName' +import { ToValue } from '@/util/reactivity' import { autoUpdate, offset, shift, size, useFloating } from '@floating-ui/vue' import type { Ref, RendererNode, VNode } from 'vue' -import { computed, proxyRefs, ref, shallowRef, watch } from 'vue' +import { computed, proxyRefs, ref, shallowRef, toValue, watch } from 'vue' const props = defineProps(widgetProps(widgetDefinition)) const suggestions = useSuggestionDbStore() @@ -51,7 +52,7 @@ const editedWidget = ref() const editedValue = ref | string | undefined>() const isHovered = ref(false) /** See @{link Actions.setActivity} */ -const activity = shallowRef() +const activity = shallowRef>() // How much wider a dropdown can be than a port it is attached to, when a long text is present. // Any text beyond that limit will receive an ellipsis and sliding animation on hover. @@ -466,7 +467,7 @@ export interface Actions { * For example, the {@link WidgetCloudBrowser} installs a custom entry that, when clicked, * opens a file browser where the dropdown was. */ - setActivity: (activity: VNode) => void + setActivity: (activity: ToValue) => void close: () => void } @@ -514,7 +515,7 @@ declare module '@/providers/widgetRegistry' { >
- +
diff --git a/app/gui/src/project-view/components/widgets/FileBrowserWidget.vue b/app/gui/src/project-view/components/widgets/FileBrowserWidget.vue index 0e1639714101..53f4c989980f 100644 --- a/app/gui/src/project-view/components/widgets/FileBrowserWidget.vue +++ b/app/gui/src/project-view/components/widgets/FileBrowserWidget.vue @@ -4,6 +4,7 @@ import SvgButton from '@/components/SvgButton.vue' import SvgIcon from '@/components/SvgIcon.vue' import { useBackend } from '@/composables/backend' import { injectBackend } from '@/providers/backend' +import { entryDisplayPath } from '@/stores/suggestionDatabase/entry' import type { ToValue } from '@/util/reactivity' import { useToast } from '@/util/toast' import type { @@ -22,6 +23,8 @@ import Backend, { import { computed, ref, toValue, watch } from 'vue' import { Err, Ok, Result } from 'ydoc-shared/util/data/result' +const { writeMode = false } = defineProps<{ writeMode?: boolean }>() + const emit = defineEmits<{ pathSelected: [path: string] }>() @@ -212,6 +215,10 @@ Promise.all([currentUser.promise.value, currentOrganization.promise.value]).then +
+ + +
@@ -286,4 +293,11 @@ Promise.all([currentUser.promise.value, currentOrganization.promise.value]).then .list-leave-active { position: absolute; } + +.fileNameInput { + width: 100%; + display: flex; + flex-direction: row; + color: white; +} From ce27ca39513aa150c68675fc768a63092a0ccdeb Mon Sep 17 00:00:00 2001 From: Adam Obuchowicz Date: Tue, 4 Feb 2025 16:32:10 +0100 Subject: [PATCH 2/8] Cloud file browser for write component --- .../widgets/WidgetCloudBrowser.vue | 3 +- .../GraphEditor/widgets/WidgetSelection.vue | 28 +++- .../components/widgets/FileBrowserWidget.vue | 148 ++++++++++++------ .../providers/interactionHandler.ts | 8 +- 4 files changed, 126 insertions(+), 61 deletions(-) diff --git a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetCloudBrowser.vue b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetCloudBrowser.vue index cdc637d97313..7576fa2b9e1d 100644 --- a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetCloudBrowser.vue +++ b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetCloudBrowser.vue @@ -22,7 +22,7 @@ const item: CustomDropdownItem = { computed(() => h(FileBrowserWidget, { writeMode: writeMode.value, - onPathSelected: (path: string) => { + onPathAccepted: (path: string) => { props.onUpdate({ portUpdate: { value: Ast.TextLiteral.new(path), origin: props.input.portId }, directInteraction: true, @@ -31,6 +31,7 @@ const item: CustomDropdownItem = { }, }), ), + true, ) }, } diff --git a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelection.vue b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelection.vue index 55fd7b77fc4f..8f27afb46f62 100644 --- a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelection.vue +++ b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelection.vue @@ -35,7 +35,7 @@ import { qnLastSegment, tryQualifiedName } from '@/util/qualifiedName' import { ToValue } from '@/util/reactivity' import { autoUpdate, offset, shift, size, useFloating } from '@floating-ui/vue' import type { Ref, RendererNode, VNode } from 'vue' -import { computed, proxyRefs, ref, shallowRef, toValue, watch } from 'vue' +import { computed, proxyRefs, reactive, ref, shallowRef, toValue, watch } from 'vue' const props = defineProps(widgetProps(widgetDefinition)) const suggestions = useSuggestionDbStore() @@ -53,6 +53,7 @@ const editedValue = ref | string | undefined>() const isHovered = ref(false) /** See @{link Actions.setActivity} */ const activity = shallowRef>() +const keptAliveActivities = reactive([] as string[]) // How much wider a dropdown can be than a port it is attached to, when a long text is present. // Any text beyond that limit will receive an ellipsis and sliding animation on hover. @@ -337,8 +338,20 @@ function toggleDropdownWidget() { } const dropdownActions: Actions = { - setActivity: (newActivity) => { + setActivity: (newActivity, keepAlive) => { activity.value = newActivity + if (keepAlive) { + const activity = toValue(newActivity) + if ( + typeof activity.type === 'object' && + 'name' in activity.type && + typeof activity.type.name === 'string' + ) { + keptAliveActivities.push(activity.type.name) + } else { + console.warn('DropDown activity wanted to be kept alive, but provides no name', activity) + } + } }, close: dropDownInteraction.end.bind(dropDownInteraction), } @@ -466,8 +479,11 @@ export interface Actions { * * For example, the {@link WidgetCloudBrowser} installs a custom entry that, when clicked, * opens a file browser where the dropdown was. + * @param keepAlive - when set, the `activity` instance will be kept between drop-down closing + * and opening. The activity component must not change it type (when being a ref) and provide + * `name` option explicitly. */ - setActivity: (activity: ToValue) => void + setActivity: (activity: ToValue, keepAlive: boolean) => void close: () => void } @@ -514,9 +530,9 @@ declare module '@/providers/widgetRegistry' { :style="activityStyles" > -
- -
+ + +
diff --git a/app/gui/src/project-view/components/widgets/FileBrowserWidget.vue b/app/gui/src/project-view/components/widgets/FileBrowserWidget.vue index 53f4c989980f..d8d4417ba1ed 100644 --- a/app/gui/src/project-view/components/widgets/FileBrowserWidget.vue +++ b/app/gui/src/project-view/components/widgets/FileBrowserWidget.vue @@ -1,38 +1,51 @@ + + @@ -294,10 +321,31 @@ Promise.all([currentUser.promise.value, currentOrganization.promise.value]).then position: absolute; } -.fileNameInput { +.fileNameBar { width: 100%; display: flex; flex-direction: row; - color: white; + padding: var(--border-width) 0 0 0; + gap: var(--border-width); +} + +.fileNameInput { + border-radius: var(--border-radius-inner); + height: calc(var(--border-radius-inner) * 2); + padding: 0 8px; + background-color: var(--color-frame-selected-bg); + flex-grow: 1; + appearance: textfield; + -moz-appearance: textfield; + user-select: all; +} + +.fileNameAcceptButton { + --color-menu-entry-hover-bg: color-mix(in oklab, var(--color-frame-selected-bg), black 10%); + border-radius: var(--border-radius-inner); + height: calc(var(--border-radius-inner) * 2); + margin: 0px; + padding: 4px 12px; + background-color: var(--color-frame-selected-bg); } diff --git a/app/gui/src/project-view/providers/interactionHandler.ts b/app/gui/src/project-view/providers/interactionHandler.ts index 3f81127a1bf5..6869985011f8 100644 --- a/app/gui/src/project-view/providers/interactionHandler.ts +++ b/app/gui/src/project-view/providers/interactionHandler.ts @@ -78,10 +78,10 @@ export class InteractionHandler { const handler = this.currentInteraction.value[handlerName] if (!handler) return false const handled = handler.bind(this.currentInteraction.value)(event) !== false - if (handled) { - event.stopImmediatePropagation() - event.preventDefault() - } + // if (handled) { + // event.stopImmediatePropagation() + // event.preventDefault() + // } return handled } } From 7811170ad50c2ffa604cc7247b79790f9b0ceae6 Mon Sep 17 00:00:00 2001 From: Adam Obuchowicz Date: Tue, 4 Feb 2025 16:46:09 +0100 Subject: [PATCH 3/8] CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42f0acc789e6..e9929db43cdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ File Browser][12208] - [Added support for rendering numbered and nested lists][12190]. - [Removed `#` from default colum name][12222] +- [Cloud File Browser will display input for file name in components writing to + (new) files.][12228] [11889]: https://github.com/enso-org/enso/pull/11889 [11836]: https://github.com/enso-org/enso/pull/11836 @@ -34,6 +36,7 @@ [12208]: https://github.com/enso-org/enso/pull/12208 [12190]: https://github.com/enso-org/enso/pull/12190 [12222]: https://github.com/enso-org/enso/pull/12222 +[12228]: https://github.com/enso-org/enso/pull/12228 #### Enso Standard Library From 45bb3f5337148ece922961eeee64ebe917da8c44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 17:48:25 +0000 Subject: [PATCH 4/8] Bump vitest from 3.0.3 to 3.0.5 in /app/common Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 3.0.3 to 3.0.5. - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v3.0.5/packages/vitest) --- updated-dependencies: - dependency-name: vitest dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- app/common/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/common/package.json b/app/common/package.json index 73322f15bcf4..fdd5227dd804 100644 --- a/app/common/package.json +++ b/app/common/package.json @@ -31,6 +31,6 @@ "@types/node": "^20.11.21", "lib0": "^0.2.99", "react": "^18.3.1", - "vitest": "3.0.3" + "vitest": "3.0.5" } } From 7b602b035ad34589a1fe6fe3ae81c583e604ffec Mon Sep 17 00:00:00 2001 From: Adam Obuchowicz Date: Wed, 5 Feb 2025 11:17:34 +0100 Subject: [PATCH 5/8] Self-review --- .../GraphEditor/widgets/WidgetSelection.vue | 19 +++++++++++-------- .../components/widgets/FileBrowserWidget.vue | 4 ---- .../providers/interactionHandler.ts | 14 +++++++------- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelection.vue b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelection.vue index 8f27afb46f62..d98da14df372 100644 --- a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelection.vue +++ b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetSelection.vue @@ -342,15 +342,18 @@ const dropdownActions: Actions = { activity.value = newActivity if (keepAlive) { const activity = toValue(newActivity) - if ( - typeof activity.type === 'object' && - 'name' in activity.type && - typeof activity.type.name === 'string' - ) { - keptAliveActivities.push(activity.type.name) - } else { + const activityName = + ( + typeof activity.type === 'object' && + 'name' in activity.type && + typeof activity.type.name === 'string' + ) ? + activity.type.name + : undefined + if (activityName == null) { console.warn('DropDown activity wanted to be kept alive, but provides no name', activity) - } + } else if (!keptAliveActivities.find((x) => x === activityName)) + keptAliveActivities.push(activityName) } }, close: dropDownInteraction.end.bind(dropDownInteraction), diff --git a/app/gui/src/project-view/components/widgets/FileBrowserWidget.vue b/app/gui/src/project-view/components/widgets/FileBrowserWidget.vue index d8d4417ba1ed..045f7d090426 100644 --- a/app/gui/src/project-view/components/widgets/FileBrowserWidget.vue +++ b/app/gui/src/project-view/components/widgets/FileBrowserWidget.vue @@ -174,7 +174,6 @@ async function enterDirByName(name: string, stack: Directory[]): Promise } onMounted(() => { - console.log('MOUNTED') Promise.all([currentUser.promise.value, currentOrganization.promise.value]).then( async ([user, organization]) => { if (!user) { @@ -193,9 +192,6 @@ onMounted(() => { }, ) }) -onActivated(() => console.log('ACTIVATED')) -onUnmounted(() => console.log('UNMOUNTED')) -onDeactivated(() => console.log('DEACTIVATED'))