From d0b67bc50df066a25c9f5cbf2b36849908a58466 Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Thu, 25 Jul 2024 18:54:51 +1000 Subject: [PATCH 01/15] Reenable "Local" settings tab (#10659) - Re-enable the "Local" settings tab - It contains a single input to change the root directory of - Add settings icon to jump to "Local" settings tab, next to "Local" category # Important Notes None --- .../src/layouts/CategorySwitcher.tsx | 27 ++++++++++++++++++- .../src/layouts/Settings/settingsData.tsx | 3 ++- app/ide-desktop/common/src/text/english.json | 1 + 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/app/dashboard/src/layouts/CategorySwitcher.tsx b/app/dashboard/src/layouts/CategorySwitcher.tsx index 5cf31257e935..6dadc7ccb122 100644 --- a/app/dashboard/src/layouts/CategorySwitcher.tsx +++ b/app/dashboard/src/layouts/CategorySwitcher.tsx @@ -1,11 +1,14 @@ /** @file Switcher to choose the currently visible assets table category. */ import * as React from 'react' +import { useSearchParams } from 'react-router-dom' + import type * as text from 'enso-common/src/text' import CloudIcon from '#/assets/cloud.svg' import ComputerIcon from '#/assets/computer.svg' import RecentIcon from '#/assets/recent.svg' +import SettingsIcon from '#/assets/settings.svg' import Trash2Icon from '#/assets/trash2.svg' import * as mimeTypes from '#/data/mimeTypes' @@ -15,6 +18,7 @@ import * as offlineHooks from '#/hooks/offlineHooks' import * as authProvider from '#/providers/AuthProvider' import * as backendProvider from '#/providers/BackendProvider' import * as modalProvider from '#/providers/ModalProvider' +import { TabType, useSetPage } from '#/providers/ProjectsProvider' import * as textProvider from '#/providers/TextProvider' import AssetEventType from '#/events/AssetEventType' @@ -168,6 +172,8 @@ export default function CategorySwitcher(props: CategorySwitcherProps) { const { getText } = textProvider.useText() const { isOffline } = offlineHooks.useOffline() const dispatchAssetEvent = eventListProvider.useDispatchAssetEvent() + const setPage = useSetPage() + const [, setSearchParams] = useSearchParams() const localBackend = backendProvider.useLocalBackend() /** The list of *visible* categories. */ @@ -284,8 +290,27 @@ export default function CategorySwitcher(props: CategorySwitcherProps) {
{element}
- ) : ( + ) : data.category !== Category.local ? ( element + ) : ( +
+ {element} + { + // eslint-disable-next-line @typescript-eslint/naming-convention + setSearchParams({ 'cloud-ide_SettingsTab': '"local"' }) + setPage(TabType.settings) + }} + /> +
) })} diff --git a/app/dashboard/src/layouts/Settings/settingsData.tsx b/app/dashboard/src/layouts/Settings/settingsData.tsx index 1f7321f0c407..6511e896e7e5 100644 --- a/app/dashboard/src/layouts/Settings/settingsData.tsx +++ b/app/dashboard/src/layouts/Settings/settingsData.tsx @@ -231,7 +231,7 @@ export const SETTINGS_TAB_DATA: Readonly context.localBackend != null, sections: [ @@ -376,6 +376,7 @@ export const SETTINGS_DATA: SettingsData = [ tabs: [ SETTINGS_TAB_DATA[SettingsTabType.account], SETTINGS_TAB_DATA[SettingsTabType.organization], + SETTINGS_TAB_DATA[SettingsTabType.local], ], }, { diff --git a/app/ide-desktop/common/src/text/english.json b/app/ide-desktop/common/src/text/english.json index 54ff848429ba..9f1b762c156a 100644 --- a/app/ide-desktop/common/src/text/english.json +++ b/app/ide-desktop/common/src/text/english.json @@ -411,6 +411,7 @@ "noResultsFound": "No results found.", "enableVersionChecker": "Enable Version Checker", "enableVersionCheckerDescription": "Show a dialog if the current version of the desktop app does not match the latest version.", + "changeLocalRootDirectoryInSettings": "Change your root folder in Settings.", "deleteLabelActionText": "delete the label '$0'", "deleteSelectedAssetActionText": "delete '$0'", From 2e56ffa63a7f4d22fb630947674061b84def4c44 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Thu, 25 Jul 2024 15:10:12 +0300 Subject: [PATCH 02/15] More detailed logs for locking logic (#10656) related: #10595 Locking log message now always includes the caller class. --- .../execution/JobExecutionEngine.scala | 4 +- .../execution/ReentrantLocking.scala | 97 ++++++++++++------- 2 files changed, 64 insertions(+), 37 deletions(-) diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/execution/JobExecutionEngine.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/execution/JobExecutionEngine.scala index 2b67d3e1648f..f43e86d59b43 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/execution/JobExecutionEngine.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/execution/JobExecutionEngine.scala @@ -152,7 +152,7 @@ final class JobExecutionEngine( val remaining = runningJobsRef.updateAndGet(_.filterNot(_.id == jobId)) logger.log( Level.FINEST, - "Number of remaining pending jobs: {}", + "Number of remaining pending jobs: {0}", remaining.size ) } @@ -160,7 +160,7 @@ final class JobExecutionEngine( val runningJob = RunningJob(jobId, job, future) val queue = runningJobsRef.updateAndGet(_ :+ runningJob) - logger.log(Level.FINE, "Number of pending jobs: {}", queue.size) + logger.log(Level.FINE, "Number of pending jobs: {0}", queue.size) promise.future } diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/execution/ReentrantLocking.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/execution/ReentrantLocking.scala index cdf1d2913704..59f92a04344e 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/execution/ReentrantLocking.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/execution/ReentrantLocking.scala @@ -65,17 +65,16 @@ class ReentrantLocking(logger: TruffleLogger) extends Locking { assertNotLocked( compilationLock, true, - "Cannot upgrade compilation read lock to write lock" + s"Cannot upgrade compilation read lock to write lock [${where.getSimpleName}]" ) assertNotLocked( pendingEditsLock, - s"Cannot acquire compilation write lock when having pending edits lock" + s"Cannot acquire compilation write lock when having pending edits lock [${where.getSimpleName}]" ) - assertNoFileLock("Cannot acquire write compilation lock") - logLockAcquisition( - compilationLock.writeLock(), - "write compilation for " + where.getSimpleName + assertNoFileLock( + s"Cannot acquire write compilation lock [${where.getSimpleName}]" ) + logLockAcquisition(compilationLock.writeLock(), "write compilation", where) } private def releaseWriteCompilationLock(): Unit = @@ -90,11 +89,15 @@ class ReentrantLocking(logger: TruffleLogger) extends Locking { def withWriteCompilationLock[T](where: Class[_], callable: Callable[T]): T = { var lockTimestamp: Long = 0 try { - lockTimestamp = acquireWriteCompilationLock(where); - callable.call(); + lockTimestamp = acquireWriteCompilationLock(where) + callable.call() } catch { case ie: InterruptedException => - logger.log(Level.WARNING, "Failed to acquire lock: interrupted", ie) + logger.log( + Level.WARNING, + "Failed [{0}] to acquire lock: interrupted", + Array[Any](where.getSimpleName, ie) + ) null.asInstanceOf[T] } finally { if (lockTimestamp != 0) { @@ -115,17 +118,16 @@ class ReentrantLocking(logger: TruffleLogger) extends Locking { // CloseFileCmd does: // ctx.locking.acquireReadCompilationLock() // ctx.locking.acquireFileLock(request.path) - assertNoFileLock("Cannot acquire read compilation lock") + assertNoFileLock( + s"Cannot acquire read compilation lock [${where.getSimpleName}]" + ) // CloseFileCmd also adds: // ctx.locking.acquirePendingEditsLock() assertNotLocked( pendingEditsLock, - s"Cannot acquire compilation read lock when having pending edits lock" - ) - logLockAcquisition( - compilationLock.readLock(), - "read compilation for " + where.getSimpleName + s"Cannot acquire compilation read lock when having pending edits lock [${where.getSimpleName}]" ) + logLockAcquisition(compilationLock.readLock(), "read compilation", where) } private def releaseReadCompilationLock(): Unit = @@ -134,11 +136,15 @@ class ReentrantLocking(logger: TruffleLogger) extends Locking { def withReadCompilationLock[T](where: Class[_], callable: Callable[T]): T = { var lockTimestamp: Long = 0 try { - lockTimestamp = acquireReadCompilationLock(where); - callable.call(); + lockTimestamp = acquireReadCompilationLock(where) + callable.call() } catch { case ie: InterruptedException => - logger.log(Level.WARNING, "Failed to acquire lock: interrupted", ie) + logger.log( + Level.WARNING, + "Failed [{0}] to acquire lock: interrupted", + Array[Any](where.getSimpleName, ie) + ) null.asInstanceOf[T] } finally { if (lockTimestamp != 0) { @@ -155,8 +161,8 @@ class ReentrantLocking(logger: TruffleLogger) extends Locking { } } - private def acquirePendingEditsLock(): Long = { - logLockAcquisition(pendingEditsLock, "pending edit") + private def acquirePendingEditsLock(where: Class[_]): Long = { + logLockAcquisition(pendingEditsLock, "pending edit", where) } private def releasePendingEditsLock(): Unit = @@ -169,11 +175,15 @@ class ReentrantLocking(logger: TruffleLogger) extends Locking { ): T = { var lockTimestamp: Long = 0 try { - lockTimestamp = acquirePendingEditsLock() + lockTimestamp = acquirePendingEditsLock(where) callable.call() } catch { case ie: InterruptedException => - logger.log(Level.WARNING, "Failed to acquire lock: interrupted", ie) + logger.log( + Level.WARNING, + "Failed [{0}] to acquire lock: interrupted", + Array[Any](where.getSimpleName, ie) + ) null.asInstanceOf[T] } finally { if (lockTimestamp != 0) { @@ -201,12 +211,17 @@ class ReentrantLocking(logger: TruffleLogger) extends Locking { try { contextLockTimestamp = logLockAcquisition( contextLock.lock, - "context lock" - ) //acquireContextLock(contextId); + "context lock", + where + ) //acquireContextLock(contextId) callable.call() } catch { case ie: InterruptedException => - logger.log(Level.WARNING, "Failed to acquire lock: interrupted", ie) + logger.log( + Level.WARNING, + "Failed [{0}] to acquire lock: interrupted", + Array[Any](where.getSimpleName, ie) + ) null.asInstanceOf[T] } finally { if (contextLockTimestamp != 0) { @@ -240,14 +255,14 @@ class ReentrantLocking(logger: TruffleLogger) extends Locking { } } - private def acquireFileLock(file: File): Long = { + private def acquireFileLock(file: File, where: Class[_]): Long = { // cannot have pendings lock as of EnsureCompiledJob.applyEdits assertNotLocked( pendingEditsLock, - s"Cannot acquire file ${file} lock when having pending edits lock" + s"Cannot acquire [${where.getSimpleName}] file $file lock when having pending edits lock" ) - assertNoContextLock(s"Cannot acquire file ${file}") - logLockAcquisition(getFileLock(file), "file") + assertNoContextLock(s"Cannot acquire [${where.getSimpleName}] file $file") + logLockAcquisition(getFileLock(file), "file", where) } private def releaseFileLock(file: File): Unit = getFileLock(file).unlock() @@ -260,11 +275,15 @@ class ReentrantLocking(logger: TruffleLogger) extends Locking { ): T = { var lockTimestamp: Long = 0 try { - lockTimestamp = acquireFileLock(file); + lockTimestamp = acquireFileLock(file, where) callable.call() } catch { case ie: InterruptedException => - logger.log(Level.WARNING, "Failed to acquire lock: interrupted", ie) + logger.log( + Level.WARNING, + "Failed [{0}] to acquire lock: interrupted", + Array[Any](where.getSimpleName, ie) + ) null.asInstanceOf[T] } finally { if (lockTimestamp != 0) { @@ -281,11 +300,19 @@ class ReentrantLocking(logger: TruffleLogger) extends Locking { } } - private def logLockAcquisition(lock: Lock, msg: String): Long = { + private def logLockAcquisition( + lock: Lock, + msg: String, + where: Class[_] + ): Long = { val now = System.currentTimeMillis() lock.lockInterruptibly() val now2 = System.currentTimeMillis() - logger.log(Level.FINEST, s"Waited ${now2 - now}ms for the $msg lock") + logger.log( + Level.FINEST, + "Waited [{0}] {1}ms for the {2} lock", + Array[Any](where.getSimpleName, now2 - now, msg) + ) now2 } @@ -352,6 +379,6 @@ class ReentrantLocking(logger: TruffleLogger) extends Locking { getContextLock(contextId) } - private case class ContextLockImpl(val lock: ReentrantLock, val uuid: UUID) - extends ContextLock {} + private case class ContextLockImpl(lock: ReentrantLock, uuid: UUID) + extends ContextLock } From 8a3180eb8942c00be55e01ee1742101e7f7f35da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Grabarz?= Date: Thu, 25 Jul 2024 16:26:29 +0200 Subject: [PATCH 03/15] Refactor node layout to handle arbitrary widget sizes, add `Table.new` widget stub (#10599) Implements #10484, also fixed an issue with dropdown arrow icon being pointed in the wrong direction. Changed the way we handle spacing around ports and other rounded widgets. Now it is the innermost token element that actually pads itself when appropriate, allowing rounded widgets to stay tightly nested together. This cleans up an issue we've had with an unnecessary padding at the end of node, and makes margins easier to control in general. image --- CHANGELOG.md | 3 + app/gui2/e2e/collapsingAndEntering.spec.ts | 2 +- app/gui2/e2e/edgeInteractions.spec.ts | 2 +- app/gui2/e2e/locate.ts | 52 ++++---- app/gui2/e2e/selectingNodes.spec.ts | 4 +- app/gui2/package.json | 2 +- app/gui2/shared/ast/ffiPolyglot.ts | 3 +- app/gui2/shared/ast/tree.ts | 4 +- app/gui2/src/assets/base.css | 35 ++++- app/gui2/src/components/CircularMenu.vue | 7 +- app/gui2/src/components/GraphEditor.vue | 41 ++---- .../src/components/GraphEditor/GraphEdge.vue | 1 + .../src/components/GraphEditor/GraphNode.vue | 61 ++------- .../GraphEditor/GraphNodeOutputPorts.vue | 10 +- .../GraphEditor/GraphNodeSelection.vue | 1 + .../src/components/GraphEditor/GraphNodes.vue | 102 ++++++++------ .../GraphEditor/GraphVisualization.vue | 9 +- .../src/components/GraphEditor/NodeWidget.vue | 11 +- .../components/GraphEditor/NodeWidgetTree.vue | 126 +++++++++++++----- .../GraphEditor/widgets/WidgetApplication.vue | 22 ++- .../widgets/WidgetArgumentName.vue | 15 +-- .../GraphEditor/widgets/WidgetBlank.vue | 2 +- .../GraphEditor/widgets/WidgetCheckbox.vue | 10 +- .../GraphEditor/widgets/WidgetFunction.vue | 58 +++++--- .../widgets/WidgetFunctionName.vue | 4 +- .../GraphEditor/widgets/WidgetGroup.vue | 7 +- .../GraphEditor/widgets/WidgetHierarchy.vue | 16 +-- .../GraphEditor/widgets/WidgetIcon.vue | 57 ++++++++ .../GraphEditor/widgets/WidgetNumber.vue | 2 +- .../GraphEditor/widgets/WidgetPort.vue | 46 ++----- .../GraphEditor/widgets/WidgetSelection.vue | 18 +-- .../widgets/WidgetSelfAccessChain.vue | 76 +++++++++++ .../GraphEditor/widgets/WidgetSelfIcon.vue | 33 ----- .../GraphEditor/widgets/WidgetTableEditor.vue | 65 +++++++++ .../GraphEditor/widgets/WidgetText.vue | 15 +-- .../GraphEditor/widgets/WidgetToken.vue | 2 +- .../widgets/WidgetTopLevelArgument.vue | 20 ++- app/gui2/src/components/GraphMouse.vue | 9 +- app/gui2/src/components/SelectionBrush.vue | 27 +++- app/gui2/src/components/SizeTransition.vue | 23 +++- app/gui2/src/components/SmallPlusButton.vue | 4 +- .../src/components/VisualizationContainer.vue | 15 ++- .../src/components/widgets/AutoSizedInput.vue | 2 +- .../src/components/widgets/CheckboxWidget.vue | 15 ++- .../src/components/widgets/DropdownWidget.vue | 3 +- .../src/components/widgets/ListWidget.vue | 8 +- .../components/widgets/NumericInputWidget.vue | 6 +- app/gui2/src/composables/events.ts | 11 +- app/gui2/src/composables/navigator.ts | 13 +- app/gui2/src/composables/selection.ts | 2 +- app/gui2/src/composables/stackNavigator.ts | 1 - app/gui2/src/providers/functionInfo.ts | 3 + app/gui2/src/providers/widgetRegistry.ts | 10 ++ app/gui2/src/providers/widgetTree.ts | 5 - .../src/stores/suggestionDatabase/entry.ts | 4 +- app/gui2/src/util/ast/__tests__/node.test.ts | 4 +- app/gui2/src/util/ast/node.ts | 12 +- app/gui2/src/util/callTree.ts | 7 +- app/gui2/src/util/symbols.ts | 11 -- pnpm-lock.yaml | 48 +++---- 60 files changed, 709 insertions(+), 478 deletions(-) create mode 100644 app/gui2/src/components/GraphEditor/widgets/WidgetIcon.vue create mode 100644 app/gui2/src/components/GraphEditor/widgets/WidgetSelfAccessChain.vue delete mode 100644 app/gui2/src/components/GraphEditor/widgets/WidgetSelfIcon.vue create mode 100644 app/gui2/src/components/GraphEditor/widgets/WidgetTableEditor.vue delete mode 100644 app/gui2/src/util/symbols.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 12b71ead5e71..cab877a441c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ - [Warning messages do not obscure visualization buttons][10546]. - [Output component in collapsed function changed][10577]. It cannot be deleted anymore, except by directily editing the code. +- [Improved handling of spacing around rounded node widgets][10599], added + support for widgets of arbitrary sizes. - [Multiselect drop-down widget visuals are improved][10607]. - [Text displayed in monospace and whitespace rendered as symbols][10563]. @@ -28,6 +30,7 @@ [10509]: https://github.com/enso-org/enso/pull/10509 [10546]: https://github.com/enso-org/enso/pull/10546 [10577]: https://github.com/enso-org/enso/pull/10577 +[10599]: https://github.com/enso-org/enso/pull/10599 [10607]: https://github.com/enso-org/enso/pull/10607 [10563]: https://github.com/enso-org/enso/pull/10563 diff --git a/app/gui2/e2e/collapsingAndEntering.spec.ts b/app/gui2/e2e/collapsingAndEntering.spec.ts index 6066e01a0160..6a9dcd901d4b 100644 --- a/app/gui2/e2e/collapsingAndEntering.spec.ts +++ b/app/gui2/e2e/collapsingAndEntering.spec.ts @@ -115,7 +115,7 @@ test('Output node', async ({ page }) => { const outputNode = locate.outputNode(page) await expect(outputNode).toHaveCount(1) // Output node with identifier should have only icon and no displayed identifiers - await expect(outputNode.locator('.WidgetSelfIcon')).toHaveCount(1) + await expect(outputNode.locator('.WidgetIcon')).toHaveCount(1) await expect(outputNode.locator('.WidgetToken')).toHaveCount(0) await outputNode.click() diff --git a/app/gui2/e2e/edgeInteractions.spec.ts b/app/gui2/e2e/edgeInteractions.spec.ts index 69c40c7338fd..87bc1cdfdf81 100644 --- a/app/gui2/e2e/edgeInteractions.spec.ts +++ b/app/gui2/e2e/edgeInteractions.spec.ts @@ -80,7 +80,7 @@ test('Conditional ports: Disabled', async ({ page }) => { const conditionalPort = node.locator('.WidgetPort').filter({ hasText: /^filter$/ }) // Check that the `enabled` CSS class is not set on disabled `WidgetPort`s. - await expect(node.locator('.WidgetSelfIcon')).toBeVisible() + await expect(node.locator('.WidgetIcon')).toBeVisible() await expect(conditionalPort).not.toHaveClass(/enabled/) // When a port is disabled, it doesn't react to hovering with a disconnected edge, diff --git a/app/gui2/e2e/locate.ts b/app/gui2/e2e/locate.ts index e7afbefcb760..75afb7cfcbd3 100644 --- a/app/gui2/e2e/locate.ts +++ b/app/gui2/e2e/locate.ts @@ -87,28 +87,22 @@ export function outputNode(page: Page | Locator): Node { // === Data locators === -type SanitizeClassName = - T extends `${infer A}.${infer B}` ? SanitizeClassName<`${A}${B}`> - : T extends `${infer A} ${infer B}` ? SanitizeClassName<`${A}${B}`> - : T - -function componentLocator(className: SanitizeClassName) { +function componentLocator(locatorStr: string) { return (page: Locator | Page, filter?: (f: Filter) => { selector: string }) => { - return page.locator(`.${className}${filter?.(new Filter()) ?? ''}`) + return page.locator(`${locatorStr}${filter?.(new Filter()) ?? ''}`) } } -export const graphEditor = componentLocator('GraphEditor') -export const codeEditor = componentLocator('CodeEditor') -// @ts-expect-error -export const anyVisualization = componentLocator('GraphVisualization > *') -export const loadingVisualization = componentLocator('LoadingVisualization') -export const circularMenu = componentLocator('CircularMenu') -export const addNewNodeButton = componentLocator('PlusButton') -export const componentBrowser = componentLocator('ComponentBrowser') -export const nodeOutputPort = componentLocator('outputPortHoverArea') -export const smallPlusButton = componentLocator('SmallPlusButton') -export const lexicalContent = componentLocator('LexicalContent') +export const graphEditor = componentLocator('.GraphEditor') +export const codeEditor = componentLocator('.CodeEditor') +export const anyVisualization = componentLocator('.GraphVisualization > *') +export const loadingVisualization = componentLocator('.LoadingVisualization') +export const circularMenu = componentLocator('.CircularMenu > .circle') +export const addNewNodeButton = componentLocator('.PlusButton') +export const componentBrowser = componentLocator('.ComponentBrowser') +export const nodeOutputPort = componentLocator('.outputPortHoverArea') +export const smallPlusButton = componentLocator('.SmallPlusButton') +export const lexicalContent = componentLocator('.LexicalContent') export function componentBrowserEntry( page: Locator | Page, @@ -140,17 +134,17 @@ export function bottomDock(page: Page) { return page.getByTestId('bottomDock') } -export const navBreadcrumb = componentLocator('NavBreadcrumb') -export const componentBrowserInput = componentLocator('ComponentEditor') -export const jsonVisualization = componentLocator('JSONVisualization') -export const tableVisualization = componentLocator('TableVisualization') -export const scatterplotVisualization = componentLocator('ScatterplotVisualization') -export const histogramVisualization = componentLocator('HistogramVisualization') -export const heatmapVisualization = componentLocator('HeatmapVisualization') -export const sqlVisualization = componentLocator('SqlVisualization') -export const geoMapVisualization = componentLocator('GeoMapVisualization') -export const imageBase64Visualization = componentLocator('ImageBase64Visualization') -export const warningsVisualization = componentLocator('WarningsVisualization') +export const navBreadcrumb = componentLocator('.NavBreadcrumb') +export const componentBrowserInput = componentLocator('.ComponentEditor') +export const jsonVisualization = componentLocator('.JSONVisualization') +export const tableVisualization = componentLocator('.TableVisualization') +export const scatterplotVisualization = componentLocator('.ScatterplotVisualization') +export const histogramVisualization = componentLocator('.HistogramVisualization') +export const heatmapVisualization = componentLocator('.HeatmapVisualization') +export const sqlVisualization = componentLocator('.SqlVisualization') +export const geoMapVisualization = componentLocator('.GeoMapVisualization') +export const imageBase64Visualization = componentLocator('.ImageBase64Visualization') +export const warningsVisualization = componentLocator('.WarningsVisualization') // === Edge locators === diff --git a/app/gui2/e2e/selectingNodes.spec.ts b/app/gui2/e2e/selectingNodes.spec.ts index 48f0df9126d7..2bb8569db6e7 100644 --- a/app/gui2/e2e/selectingNodes.spec.ts +++ b/app/gui2/e2e/selectingNodes.spec.ts @@ -58,8 +58,8 @@ test('Selecting nodes by area drag', async ({ page }) => { assert(node2BBox) await page.mouse.move(node1BBox.x - 50, node1BBox.y - 50) await page.mouse.down() - await page.mouse.move(node1BBox.x - 49, node1BBox.y - 49) - await expect(page.locator('.SelectionBrush')).toBeVisible() + await page.mouse.move(node1BBox.x - 40, node1BBox.y - 40) + // await expect(page.locator('.SelectionBrush')).toBeVisible() await page.mouse.move(node2BBox.x + node2BBox.width, node2BBox.y + node2BBox.height) await expect(node1).toBeSelected() await expect(node2).toBeSelected() diff --git a/app/gui2/package.json b/app/gui2/package.json index 0622b8f3ca3a..d43465a00c6e 100644 --- a/app/gui2/package.json +++ b/app/gui2/package.json @@ -149,7 +149,7 @@ "typescript": "^5.5.3", "unbzip2-stream": "^1.4.3", "vite": "^5.3.3", - "vite-plugin-vue-devtools": "7.3.5", + "vite-plugin-vue-devtools": "7.3.7", "vitest": "^1.3.1", "vue-react-wrapper": "^0.3.1", "vue-tsc": "^2.0.24", diff --git a/app/gui2/shared/ast/ffiPolyglot.ts b/app/gui2/shared/ast/ffiPolyglot.ts index 7dd6c3b990f4..de2285bdee16 100644 --- a/app/gui2/shared/ast/ffiPolyglot.ts +++ b/app/gui2/shared/ast/ffiPolyglot.ts @@ -18,7 +18,7 @@ declare global { export async function initializeFFI(_path?: string | undefined) {} -/* eslint-disable-next-line camelcase */ +/* eslint-disable camelcase */ export const { is_ident_or_operator, is_numeric_literal, @@ -26,3 +26,4 @@ export const { parse_tree, xxHash128, } = globalThis +/* eslint-enable camelcase */ diff --git a/app/gui2/shared/ast/tree.ts b/app/gui2/shared/ast/tree.ts index 429b4c7eb725..62a9b1302a03 100644 --- a/app/gui2/shared/ast/tree.ts +++ b/app/gui2/shared/ast/tree.ts @@ -670,7 +670,7 @@ export class App extends Ast { } // Some syntax trees, including many error conditions, involve unspaced applications. // If a parsed input lacked a space before the argument, reproduce it as-is. - const verbatimArgument = true + const verbatimArgument = !nameSpecification yield ensureSpacedOnlyIf(argument, !nameSpecification || spacedEquals, verbatimArgument) if (useParens) yield preferUnspaced(parens.close) } @@ -2659,7 +2659,7 @@ function makeEquals(): Token { function nameSpecification( name: StrictIdentLike | undefined, ): { name: NodeChild; equals: NodeChild } | undefined { - return name && { name: autospaced(toIdentStrict(name)), equals: unspaced(makeEquals()) } + return name && { name: unspaced(toIdentStrict(name)), equals: unspaced(makeEquals()) } } type KeysOfFieldType = { diff --git a/app/gui2/src/assets/base.css b/app/gui2/src/assets/base.css index fc13f9118dac..710635109077 100644 --- a/app/gui2/src/assets/base.css +++ b/app/gui2/src/assets/base.css @@ -4,7 +4,9 @@ @import url('./font-mplus1.css'); @import url('./font-dejavu.css'); -/* semantic color variables for this project */ +/************************************************* + *** semantic color variables for this project *** + *************************************************/ :root { --color-text: rgb(118 118 118); --color-primary: rgb(0 0 0 / 0.6); @@ -28,7 +30,34 @@ --color-error: rgb(234 67 53); } -/* non-color variables */ +/********************************* + *** Node graph core variables *** + *********************************/ +:root { + /** Minimum height of a node "pill" shape. Ports might be higher when widgets demand it. */ + --node-base-height: 32px; + /** The minimum height of a port widget. Ports might be higher when widgets inside them demand it. */ + --node-port-height: 24px; + /** Stroke width of an output port shape in its fully visible, not-hovered state. */ + --output-port-max-width: 4px; + /** Additional stroke width of an output port shape when it is being hovered. */ + --output-port-hovered-extra-width: 2px; + /** The amount of overlap for the port shape with the node. */ + --output-port-overlap: -8px; + /** Stroke width of output port active hover area shape. Should be large enough to allow easy targetting. */ + --output-port-hover-width: 20px; + /** The width of selection area around node. */ + --selected-node-border-width: 20px; + /** Padding added to token widgets to push them away from rounded corners of the parent widget. */ + --widget-token-pad-unit: 6px; + + --node-border-radius: calc(var(--node-base-height) / 2); + --node-port-border-radius: calc(var(--node-port-height) / 2); +} + +/********************************* + *** other non-color variables *** + *********************************/ :root { /* The z-index of fullscreen elements that should display over the entire GUI. */ --z-fullscreen: 1; @@ -38,10 +67,8 @@ * A `border-radius` of 100% does not work because the element becomes an ellipse. */ --radius-full: 9999px; --radius-default: 16px; - --node-border-radius: 16px; --node-port-height: 24px; --section-gap: 160px; - --selected-node-border-width: 20px; --font-sans: 'M PLUS 1', /* System sans-serif font stack */ system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', Arial, sans-serif; diff --git a/app/gui2/src/components/CircularMenu.vue b/app/gui2/src/components/CircularMenu.vue index ab6cdc31ac5a..d740e5e33280 100644 --- a/app/gui2/src/components/CircularMenu.vue +++ b/app/gui2/src/components/CircularMenu.vue @@ -119,6 +119,9 @@ function readableBinding(binding: keyof (typeof graphBindings)['bindings']) { diff --git a/app/gui2/src/components/GraphEditor/GraphEdge.vue b/app/gui2/src/components/GraphEditor/GraphEdge.vue index 62e7caee08c1..b8a882dad201 100644 --- a/app/gui2/src/components/GraphEditor/GraphEdge.vue +++ b/app/gui2/src/components/GraphEditor/GraphEdge.vue @@ -631,6 +631,7 @@ const sourceHoverAnimationStyle = computed(() => { fill: none; stroke: var(--edge-color); transition: stroke 0.2s ease; + contain: strict; } .arrow { diff --git a/app/gui2/src/components/GraphEditor/GraphNode.vue b/app/gui2/src/components/GraphEditor/GraphNode.vue index 2c35172223fe..5cf6cbfbbe62 100644 --- a/app/gui2/src/components/GraphEditor/GraphNode.vue +++ b/app/gui2/src/components/GraphEditor/GraphNode.vue @@ -34,21 +34,17 @@ import { prefixes } from '@/util/ast/node' import type { Opt } from '@/util/data/opt' import { Rect } from '@/util/data/rect' import { Vec2 } from '@/util/data/vec2' -import { displayedIconOf } from '@/util/getIconName' import type { ExternalId, VisualizationIdentifier } from 'shared/yjsModel' import { computed, onUnmounted, ref, shallowRef, watch, watchEffect } from 'vue' const MAXIMUM_CLICK_LENGTH_MS = 300 const MAXIMUM_CLICK_DISTANCE_SQ = 50 const CONTENT_PADDING = 4 -const CONTENT_PADDING_RIGHT = 8 const CONTENT_PADDING_PX = `${CONTENT_PADDING}px` -const CONTENT_PADDING_RIGHT_PX = `${CONTENT_PADDING_RIGHT}px` const MENU_CLOSE_TIMEOUT_MS = 300 const contentNodeStyle = { padding: CONTENT_PADDING_PX, - paddingRight: CONTENT_PADDING_RIGHT_PX, } const props = defineProps<{ @@ -85,11 +81,6 @@ const navigator = injectGraphNavigator(true) const nodeId = computed(() => asNodeId(props.node.rootExpr.externalId)) const potentialSelfArgumentId = computed(() => props.node.primarySubject) -const connectedSelfArgumentId = computed(() => - potentialSelfArgumentId.value && graph.isConnectedTarget(potentialSelfArgumentId.value) ? - potentialSelfArgumentId.value - : undefined, -) onUnmounted(() => graph.unregisterNodeRect(nodeId.value)) @@ -241,14 +232,6 @@ watch(isVisualizationPreviewed, (newVal, oldVal) => { const isVisualizationFullscreen = computed(() => props.node.vis?.fullscreen ?? false) -const bgStyleVariables = computed(() => { - const { x: width, y: height } = nodeSize.value - return { - '--node-width': `${width}px`, - '--node-height': `${height}px`, - } -}) - const transform = computed(() => { const { x, y } = props.node.position return `translate(${x}px, ${y}px)` @@ -307,23 +290,9 @@ const isRecordingOverridden = computed({ }) const expressionInfo = computed(() => graph.db.getExpressionInfo(props.node.innerExpr.externalId)) -const outputPortLabel = computed(() => expressionInfo.value?.typename ?? 'Unknown') const executionState = computed(() => expressionInfo.value?.payload.type ?? 'Unknown') const suggestionEntry = computed(() => graph.db.nodeMainSuggestion.lookup(nodeId.value)) const color = computed(() => graph.db.getNodeColorStyle(nodeId.value)) -const icon = computed(() => { - switch (props.node.type) { - default: - case 'component': - return displayedIconOf( - suggestionEntry.value, - expressionInfo.value?.methodCall?.methodPointer, - outputPortLabel.value, - ) - case 'output': - return 'data_output' - } -}) const documentationUrl = computed( () => suggestionEntry.value && suggestionDocumentationUrl(suggestionEntry.value), ) @@ -432,6 +401,8 @@ watchEffect(() => { '--node-group-color': color, ...(node.zIndex ? { 'z-index': node.zIndex } : {}), '--viz-below-node': `${graphSelectionSize.y - nodeSize.y}px`, + '--node-size-x': `${nodeSize.x}px`, + '--node-size-y': `${nodeSize.y}px`, }" :class="{ selected, @@ -444,9 +415,8 @@ watchEffect(() => { @pointerleave="(nodeHovered = false), updateNodeHover(undefined)" @pointermove="updateNodeHover" > - + { :ast="props.node.innerExpr" :nodeId="nodeId" :nodeElement="rootNode" + :nodeType="props.node.type" :nodeSize="nodeSize" - :icon="icon" - :connectedSelfArgumentId="connectedSelfArgumentId" :potentialSelfArgumentId="potentialSelfArgumentId" :conditionalPorts="props.node.conditionalPorts" :extended="isOnlyOneSelected" @@ -550,7 +519,7 @@ watchEffect(() => { :message="visibleMessage.text" :type="visibleMessage.type" /> - + { display: flex; --output-port-transform: translateY(var(--viz-below-node)); - --output-port-max-width: 4px; - --output-port-hovered-extra-width: 2px; - --output-port-overlap: -8px; - --output-port-hover-width: 20px; } .bgFill { - width: var(--node-width); - height: var(--node-height); + width: var(--node-size-x); + height: var(--node-size-y); rx: var(--node-border-radius); fill: var(--node-color-primary); @@ -597,7 +562,10 @@ watchEffect(() => { } .GraphNode { - --node-height: 32px; + position: absolute; + border-radius: var(--node-border-radius); + transition: box-shadow 0.2s ease-in-out; + box-sizing: border-box; --node-color-primary: color-mix( in oklab, @@ -611,11 +579,6 @@ watchEffect(() => { &.executionState-Pending { --node-color-primary: color-mix(in oklab, var(--node-group-color) 60%, #aaa 40%); } - - position: absolute; - border-radius: var(--node-border-radius); - transition: box-shadow 0.2s ease-in-out; - box-sizing: border-box; } .content { @@ -623,8 +586,6 @@ watchEffect(() => { position: relative; top: 0; left: 0; - caret-shape: bar; - height: var(--node-height); border-radius: var(--node-border-radius); display: flex; flex-direction: row; diff --git a/app/gui2/src/components/GraphEditor/GraphNodeOutputPorts.vue b/app/gui2/src/components/GraphEditor/GraphNodeOutputPorts.vue index 350dc5917a81..ee530a2d3cec 100644 --- a/app/gui2/src/components/GraphEditor/GraphNodeOutputPorts.vue +++ b/app/gui2/src/components/GraphEditor/GraphNodeOutputPorts.vue @@ -151,16 +151,16 @@ graph.suggestEdgeFromOutput(outputHovered) .outputPortHoverArea { x: calc(0px - var(--output-port-width) / 2); y: calc(0px - var(--output-port-width) / 2); - height: calc(var(--node-height) + var(--output-port-width)); - width: calc(var(--node-width) + var(--output-port-width)); + height: calc(var(--node-size-y) + var(--output-port-width)); + width: calc(var(--node-size-x) + var(--output-port-width)); rx: calc(var(--node-border-radius) + var(--output-port-width) / 2); fill: none; stroke: var(--node-color-port); stroke-width: calc(var(--output-port-width) + var(--output-port-overlap-anim)); transition: stroke 0.2s ease; - --horizontal-line: calc(var(--node-width) - var(--node-border-radius) * 2); - --vertical-line: calc(var(--node-height) - var(--node-border-radius) * 2); + --horizontal-line: calc(var(--node-size-x) - var(--node-border-radius) * 2); + --vertical-line: calc(var(--node-size-y) - var(--node-border-radius) * 2); --radius-arclength: calc((var(--node-border-radius) + var(--output-port-width) * 0.5) * 2 * pi); stroke-dasharray: calc(var(--horizontal-line) + var(--radius-arclength) * 0.5) 10000%; @@ -203,6 +203,6 @@ graph.suggestEdgeFromOutput(outputHovered) text-anchor: middle; opacity: calc(var(--hover-animation) * var(--hover-animation)); fill: var(--node-color-primary); - transform: translate(50%, calc(var(--node-height) + var(--output-port-max-width) + 16px)); + transform: translate(50%, calc(var(--node-size-y) + var(--output-port-max-width) + 16px)); } diff --git a/app/gui2/src/components/GraphEditor/GraphNodeSelection.vue b/app/gui2/src/components/GraphEditor/GraphNodeSelection.vue index 24b8d606e46c..1793c06c4cee 100644 --- a/app/gui2/src/components/GraphEditor/GraphNodeSelection.vue +++ b/app/gui2/src/components/GraphEditor/GraphNodeSelection.vue @@ -43,6 +43,7 @@ const rootStyle = computed(() => {
+ + +
+ diff --git a/app/gui2/src/components/GraphEditor/GraphVisualization.vue b/app/gui2/src/components/GraphEditor/GraphVisualization.vue index d13111693c31..f0db89ec99ca 100644 --- a/app/gui2/src/components/GraphEditor/GraphVisualization.vue +++ b/app/gui2/src/components/GraphEditor/GraphVisualization.vue @@ -43,7 +43,7 @@ import { const MIN_WIDTH_PX = 200 const MIN_CONTENT_HEIGHT_PX = 32 const DEFAULT_CONTENT_HEIGHT_PX = 150 -const TOP_WITH_TOOLBAR_PX = 72 +const TOOLBAR_HEIGHT_PX = 36 // Used for testing. type RawDataSource = { type: 'raw'; data: any } @@ -237,7 +237,7 @@ watchEffect(async () => { const isBelowToolbar = ref(false) -const toolbarHeight = computed(() => (isBelowToolbar.value ? TOP_WITH_TOOLBAR_PX : 0)) +const toolbarHeight = computed(() => (isBelowToolbar.value ? TOOLBAR_HEIGHT_PX : 0)) const rect = computed( () => @@ -246,7 +246,8 @@ const rect = computed( new Vec2( Math.max(props.width ?? MIN_WIDTH_PX, props.nodeSize.x), Math.max(props.height ?? DEFAULT_CONTENT_HEIGHT_PX, MIN_CONTENT_HEIGHT_PX) + - toolbarHeight.value, + toolbarHeight.value + + props.nodeSize.y, ), ), ) @@ -276,7 +277,7 @@ provideVisualizationConfig({ emit('update:width', value) }, get height() { - return rect.value.height - toolbarHeight.value + return rect.value.height - toolbarHeight.value - props.nodeSize.y }, set height(value) { emit('update:height', value) diff --git a/app/gui2/src/components/GraphEditor/NodeWidget.vue b/app/gui2/src/components/GraphEditor/NodeWidget.vue index c8f12c2073f4..7390ace8635b 100644 --- a/app/gui2/src/components/GraphEditor/NodeWidget.vue +++ b/app/gui2/src/components/GraphEditor/NodeWidget.vue @@ -95,7 +95,7 @@ const spanStart = computed(() => { { >🚫 - - diff --git a/app/gui2/src/components/GraphEditor/NodeWidgetTree.vue b/app/gui2/src/components/GraphEditor/NodeWidgetTree.vue index 4e7ab48651c1..cf400affe976 100644 --- a/app/gui2/src/components/GraphEditor/NodeWidgetTree.vue +++ b/app/gui2/src/components/GraphEditor/NodeWidgetTree.vue @@ -1,23 +1,23 @@ @@ -119,28 +135,76 @@ export const ICON_WIDTH = 16 color: white; outline: none; - height: 24px; + min-height: var(--node-port-height); display: flex; align-items: center; +} - &:has(.WidgetPort.newToConnect) { - margin-left: calc(4px - var(--widget-port-extra-pad)); +/** + * Implementation of token padding and its propagation through the widget tree. + * + * In widget tree, the margins around widgets require special care due to unusual set of + * desing requirements. When a node or a port contains a widget that "fits nicely" with + * within rounded corners, there shouldn't be any added margin between them. On the other + * hand, when a widget ends with a text node without any rounded container, it needs to + * maintain a certain padding from parent's rounding (e.g. rounding of the node shape). + * + * To implement that, we need to propagate the information of required left/right padding + * throughout the tree structure, and allow widgets to either modify their requirements, or + * to apply the required padding. We are using a set of special tree-scoped classes for that: + * + * - `.widgetRounded`: Signals that this widget has rounding, so it expects its content to + * have added padding when appropriate. All widgets that have 24px rounded + * corners need to have this class (e.g. ports, value inputs). + * + * - `.widgetResetRounding`: Resets the "rounding" state, setting padding expectation to 0. + * This allows the widget to implement its own padding that will be + * kept constant no matter the situation (e.g. `TopLevelArgument`). + * + * - `.widgetApplyPadding`: Keep distance from rounded corners, apply padding as required by + * parent widget structure. This should be applied to *all* text-only + * elements of a widget, anything that is or looks like a token. + * + * - `.widgetOutOfLayout`: An element that exists within a widget tree, but isn't taking any + * visible horizontal space. Those elements are ignored when propagating + * the padding information. It is important to add this class to any DOM + * element with absolute positioning when it is placed at the beginning or + * at the end of the widget template, so it doesn't prevent tokens around + * them from being properly padded. + */ +.NodeWidgetTree { + /* + * Core of the propagation logic. Prevent left/right margin from propagating to non-first non-last + * children of a widget. That way, only the innermost left/right deep child of a rounded widget will + * receive the propagated paddings. + */ + *:not(:nth-child(1 of :not(.widgetOutOfLayout, [data-transitioning='leave']))) { + --widget-token-pad-left: 0px; } - - &:has(.WidgetPort.newToConnect > .r-24:only-child) { - margin-left: 0px; + *:not(:nth-last-child(1 of :not(.widgetOutOfLayout, [data-transitioning='leave']))) { + --widget-token-pad-right: 0px; } -} -.GraphEditor.draggingEdge .NodeWidgetTree { - transition: margin 0.2s ease; -} + /* + * Any rounded widget sets expected padding variable, which is automatically inherited + * by all its children. + * Note that since the node itself is rounded, it behaves as a rounded container. + */ + &, + :deep(.widgetRounded.widgetRounded) { + --widget-token-pad-left: var(--widget-token-pad-unit); + --widget-token-pad-right: var(--widget-token-pad-unit); + } -.icon { - margin-right: 4px; -} + :deep(.widgetResetRounding.widgetResetPadding) { + --widget-token-pad-left: 0px; + --widget-token-pad-right: 0px; + } -.grab-handle { - color: white; + :deep(.widgetApplyPadding.widgetApplyPadding) { + margin-left: var(--widget-token-pad-left, 0); + margin-right: var(--widget-token-pad-right, 0); + transition: margin 0.2s ease-out; + } } diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetApplication.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetApplication.vue index 9d26643fbbde..7a44b66c56b5 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetApplication.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetApplication.vue @@ -64,17 +64,19 @@ export const widgetDefinition = defineWidget( diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetArgumentName.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetArgumentName.vue index 04da57ca92ee..4be45f864695 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetArgumentName.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetArgumentName.vue @@ -50,16 +50,13 @@ export const widgetDefinition = defineWidget( import.meta.hot, ) -export const ArgumentNameShownKey: unique symbol = Symbol('ArgumentNameShownKey') +export const ArgumentNameShownKey: unique symbol = Symbol.for('WidgetInput:ArgumentNameShown') @@ -68,15 +65,11 @@ export const ArgumentNameShownKey: unique symbol = Symbol('ArgumentNameShownKey' display: flex; flex-direction: row; align-items: center; + gap: var(--widget-token-pad-unit); } .placeholder, .name { color: rgb(255 255 255 / 0.5); - margin-right: 8px; - - &:last-child { - margin-right: 0px; - } } diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetBlank.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetBlank.vue index 3b7bc5cce4a4..ca5912bd1d85 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetBlank.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetBlank.vue @@ -16,7 +16,7 @@ export const widgetDefinition = defineWidget( diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue index 6320be0662cc..022b29834f53 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetFunction.vue @@ -11,6 +11,7 @@ import { type WidgetUpdate, } from '@/providers/widgetRegistry' import { useGraphStore } from '@/stores/graph' +import type { MethodCallInfo } from '@/stores/graph/graphDatabase' import { useProjectStore } from '@/stores/project' import { assert, assertUnreachable } from '@/util/assert' import { Ast } from '@/util/ast' @@ -24,12 +25,22 @@ import { } from '@/util/callTree' import { partitionPoint } from '@/util/data/array' import { isIdentifier } from '@/util/qualifiedName.ts' +import { methodPointerEquals, type MethodPointer } from 'shared/languageServerTypes' import { computed, proxyRefs } from 'vue' const props = defineProps(widgetProps(widgetDefinition)) const graph = useGraphStore() const project = useProjectStore() +const exprInfo = computed(() => graph.db.getExpressionInfo(props.input.value.externalId)) +const outputType = computed(() => exprInfo.value?.typename) + +const { methodCallInfo, application } = useWidgetFunctionCallInfo( + () => props.input, + graph.db, + project, +) + provideFunctionInfo( proxyRefs({ prefixCalls: computed(() => { @@ -41,29 +52,25 @@ provideFunctionInfo( } return ids }), + callInfo: methodCallInfo, + outputType, }), ) -const { methodCallInfo, application } = useWidgetFunctionCallInfo( - () => props.input, - graph.db, - project, -) - const innerInput = computed(() => { + let input: WidgetInput if (application.value instanceof ArgumentApplication) { - return application.value.toWidgetInput() - } else if (methodCallInfo.value) { - const definition = graph.getMethodAst(methodCallInfo.value.methodCall.methodPointer) - if (definition.ok) - return { - ...props.input, - [FunctionName]: { - editableName: definition.value.name.externalId, - }, - } + input = application.value.toWidgetInput() + } else { + input = { ...props.input } + } + const callInfo = methodCallInfo.value + if (callInfo) { + input[CallInfo] = callInfo + const definition = graph.getMethodAst(callInfo.methodCall.methodPointer) + if (definition.ok) input[FunctionName] = { editableName: definition.value.name.externalId } } - return props.input + return input }) /** @@ -205,6 +212,23 @@ function handleArgUpdate(update: WidgetUpdate): boolean { } + + + + + + diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetNumber.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetNumber.vue index 8173d0c2bc59..b38095435704 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetNumber.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetNumber.vue @@ -77,7 +77,7 @@ export const widgetDefinition = defineWidget( diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetSelection.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetSelection.vue index bea03a2c747d..b8eebc40de80 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetSelection.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetSelection.vue @@ -392,7 +392,7 @@ const arrowLocation = ref() + + + + + + diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetSelfIcon.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetSelfIcon.vue deleted file mode 100644 index 9daedce75679..000000000000 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetSelfIcon.vue +++ /dev/null @@ -1,33 +0,0 @@ - - - - - diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetTableEditor.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetTableEditor.vue new file mode 100644 index 000000000000..4f43876ad73b --- /dev/null +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetTableEditor.vue @@ -0,0 +1,65 @@ + + + + + + + diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetText.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetText.vue index cd9b2dd9d955..ddb2ce06da78 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetText.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetText.vue @@ -54,10 +54,6 @@ const inputTextLiteral = computed((): Ast.TextLiteral | undefined => { return Ast.TextLiteral.tryParse(valueStr) }) -function makeNewLiteral(value: string) { - return Ast.TextLiteral.new(value, MutableModule.Transient()) -} - function makeLiteralFromUserInput(value: string): Ast.Owned { if (props.input.value instanceof Ast.TextLiteral) { const literal = MutableModule.Transient().copy(props.input.value) @@ -68,7 +64,6 @@ function makeLiteralFromUserInput(value: string): Ast.Owned inputTextLiteral.value ?? emptyTextLiteral) const closeToken = computed(() => shownLiteral.value.close ?? shownLiteral.value.open) @@ -78,6 +73,11 @@ watch(textContents, (value) => (editedContents.value = value))