Merge branch 'develop' into wip/frizi/bazel

This commit is contained in:
Paweł Grabarz 2024-08-08 14:49:39 +02:00
commit fd6824c88e
438 changed files with 6708 additions and 3709 deletions

View File

@ -1,7 +1,7 @@
# This file is auto-generated. Do not edit it manually!
# Edit the enso_build::ci_gen module instead and run `cargo run --package enso-build-ci-gen`.
name: GUI Tests
name: GUI Check
on:
push:
branches:
@ -27,7 +27,7 @@ jobs:
access_token: ${{ github.token }}
permissions:
actions: write
enso-build-ci-gen-job-gui-test-linux-x86_64:
enso-build-ci-gen-job-gui-check-linux-x86_64:
name: GUI tests (linux, x86_64)
runs-on:
- self-hosted
@ -56,7 +56,7 @@ jobs:
run: ./run git-clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: ./run gui test
- run: ./run gui check
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- if: failure() && runner.os == 'Windows'

View File

@ -155,7 +155,7 @@ jobs:
access_token: ${{ github.token }}
permissions:
actions: write
enso-build-ci-gen-job-new-gui-build-linux-x86_64:
enso-build-ci-gen-job-gui-build-linux-x86_64:
name: GUI build (linux, x86_64)
runs-on:
- self-hosted
@ -210,7 +210,7 @@ jobs:
run: ./run git-clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
enso-build-ci-gen-job-new-gui-build-macos-x86_64:
enso-build-ci-gen-job-gui-build-macos-x86_64:
name: GUI build (macos, x86_64)
runs-on:
- macos-12
@ -264,7 +264,7 @@ jobs:
run: ./run git-clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
enso-build-ci-gen-job-new-gui-build-windows-x86_64:
enso-build-ci-gen-job-gui-build-windows-x86_64:
name: GUI build (windows, x86_64)
runs-on:
- self-hosted

2
.gitignore vendored
View File

@ -73,6 +73,8 @@ node_modules/
.metals
tools/performance/engine-benchmarks/generated_site
*.tsbuildinfo
vite.config.ts.timestamp-*.mjs
vitest.config.ts.timestamp-*.mjs
############################
## Rendered Documentation ##

21
.pnpmfile.cjs Normal file
View File

@ -0,0 +1,21 @@
const IGNORED_DEPS = ['react-native-url-polyfill', 'react-native-get-random-values']
const unusedIgnores = new Set(IGNORED_DEPS)
module.exports.hooks = {
readPackage: (pkg, context) => {
for (const ignored of IGNORED_DEPS) {
if (pkg.dependencies[ignored]) {
delete pkg.dependencies[ignored]
context.log(`Ignoring dependency ${ignored} in ${pkg.name}`)
unusedIgnores.delete(ignored)
}
}
return pkg
},
afterAllResolved(lockfile, context) {
if (unusedIgnores.size > 0) {
context.log(`Unused dependency ignore declarations: ${Array.from(unusedIgnores).join(', ')}`)
}
return lockfile
},
}

View File

@ -39,7 +39,6 @@ app/ide-desktop/lib/dashboard/playwright/.cache/
app/ide-desktop/lib/dashboard/dist/
app/gui/view/documentation/assets/stylesheet.css
app/rust-ffi/pkg
app/rust-ffi/node-pkg
app/gui2/src/assets/font-*.css
Cargo.lock
build.json

View File

@ -6,9 +6,13 @@
comparisons.][10614]
- [Relative paths are now resolved relative to the project location, also in the
Cloud.][10660]
- [Added Newline option to Text_Cleanse/Text_Replace.][10761]
- [Support for reading from Tableau Hyper files.][10733]
[10614]: https://github.com/enso-org/enso/pull/10614
[10660]: https://github.com/enso-org/enso/pull/10660
[10761]: https://github.com/enso-org/enso/pull/10761
[10733]: https://github.com/enso-org/enso/pull/10733
# Enso 2023.3
@ -19,12 +23,14 @@
- [Space-precedence does not apply to value-level operators][10597]
- [Must specify `--repl` to enable debug server][10709]
- [Improved parser error reporting and performance][10734]
- [Import all available libraries in `--repl` mode][10746]
[10468]: https://github.com/enso-org/enso/pull/10468
[10535]: https://github.com/enso-org/enso/pull/10535
[10597]: https://github.com/enso-org/enso/pull/10597
[10709]: https://github.com/enso-org/enso/pull/10709
[10734]: https://github.com/enso-org/enso/pull/10734
[10746]: https://github.com/enso-org/enso/pull/10746
#### Enso IDE

53
Cargo.lock generated
View File

@ -894,7 +894,8 @@ dependencies = [
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "git+https://github.com/enso-org/console_error_panic_hook#cdd73b81709475104b9ebfe6271c6914ff71b7b2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if",
"wasm-bindgen",
@ -1452,15 +1453,6 @@ dependencies = [
"syn 2.0.53",
]
[[package]]
name = "enso-data-structures"
version = "0.2.0"
dependencies = [
"enso-prelude",
"failure",
"serde",
]
[[package]]
name = "enso-doc-parser"
version = "0.1.0"
@ -1601,7 +1593,6 @@ name = "enso-parser"
version = "0.1.0"
dependencies = [
"bincode",
"enso-data-structures",
"enso-metamodel",
"enso-parser-syntax-tree-visitor",
"enso-prelude",
@ -1765,28 +1756,6 @@ version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "failure"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
dependencies = [
"backtrace",
"failure_derive",
]
[[package]]
name = "failure_derive"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.107",
"synstructure",
]
[[package]]
name = "fastrand"
version = "1.9.0"
@ -4257,18 +4226,6 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "synstructure"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.107",
"unicode-xid",
]
[[package]]
name = "sysinfo"
version = "0.30.7"
@ -4748,12 +4705,6 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "unicode-xid"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"

View File

@ -1,11 +1,5 @@
[workspace]
resolver = "2"
# Listing only the "root" crates of each app/library. All path dependencies are included in the workspace automatically.
# If you want to add sub crate (like `app/gui/config` or `lib/rust/ensogl/examples`), just add it as a path dependency
# where plausible.
# Any GUI functionality that is not used by the main entry point should be defined somewhere with `examples` in the
# path, e.g. `lib/rust/ensogl/examples`, or `app/gui/view/examples`; this is used to optimize the application for
# loading the IDE.
members = [
"app/rust-ffi",
"build_tools/cli",
@ -16,7 +10,6 @@ members = [
"build_tools/install/config",
"build_tools/install/installer",
"build_tools/install/uninstaller",
"lib/rust/data-structures",
"lib/rust/enso-font",
"lib/rust/font",
"lib/rust/launcher-shims",
@ -37,12 +30,6 @@ members = [
"tools/language-server/wstest",
]
# We are using a version with extended functionality. The changes have been PR'd upstream:
# https://github.com/rustwasm/console_error_panic_hook/pull/24
# Remove this patch when the issue is resolved.
[patch.crates-io]
console_error_panic_hook = { git = 'https://github.com/enso-org/console_error_panic_hook' }
[profile.dev]
opt-level = 0
lto = false
@ -141,7 +128,6 @@ wasm-bindgen-test = { version = "0.3.34" }
windows = { version = "0.52.0", features = ["Win32", "Win32_UI", "Win32_UI_Shell", "Win32_System", "Win32_System_LibraryLoader", "Win32_Foundation", "Win32_System_Com"] }
winreg = { version = "0.52.0" }
anyhow = { version = "1.0.66" }
failure = { version = "0.1.8" }
derive_more = { version = "0.99" }
boolinator = { version = "2.4.0" }
derivative = { version = "2.2" }

View File

@ -51,7 +51,6 @@ crate.from_cargo(
"//:build_tools/install/uninstaller/Cargo.toml",
"//:build_tools/macros/lib/Cargo.toml",
"//:Cargo.toml",
"//:lib/rust/data-structures/Cargo.toml",
"//:lib/rust/enso-font/Cargo.toml",
"//:lib/rust/font/Cargo.toml",
"//:lib/rust/launcher-shims/Cargo.toml",

View File

@ -8,3 +8,5 @@ ENSO_CLOUD_AMPLIFY_USER_POOL_ID=mars_AAAAAAAAA
ENSO_CLOUD_AMPLIFY_USER_POOL_WEB_CLIENT_ID=zzzzzzzzzzzzzzzzzzzzzzzzzz
ENSO_CLOUD_AMPLIFY_DOMAIN=somewhere.auth.mars.amazoncognito.com
ENSO_CLOUD_AMPLIFY_REGION=mars
ENSO_POLYGLOT_YDOC_SERVER=false
ENSO_YDOC_LS_DEBUG=false

View File

@ -6,20 +6,20 @@ Execute all commands from the parent directory.
```sh
# Run tests normally
npm run test:e2e
pnpm run test:e2e
# Open UI to run tests
npm run test:e2e:debug
pnpm run test:e2e:debug
# Run tests in a specific file only
npm run test:e2e -- e2e/file-name-here.spec.ts
npm run test:e2e:debug -- e2e/file-name-here.spec.ts
pnpm run test:e2e -- e2e/file-name-here.spec.ts
pnpm run test:e2e:debug -- e2e/file-name-here.spec.ts
# Compile the entire app before running the tests.
# DOES NOT hot reload the tests.
# Prefer not using this when you are trying to fix a test;
# prefer using this when you just want to know which tests are failing (if any).
PROD=1 npm run test:e2e
PROD=1 npm run test:e2e:debug
PROD=1 npm run test:e2e -- e2e/file-name-here.spec.ts
PROD=1 npm run test:e2e:debug -- e2e/file-name-here.spec.ts
PROD=1 pnpm run test:e2e
PROD=1 pnpm run test:e2e:debug
PROD=1 pnpm run test:e2e -- e2e/file-name-here.spec.ts
PROD=1 pnpm run test:e2e:debug -- e2e/file-name-here.spec.ts
```
## Getting started

View File

@ -325,7 +325,7 @@ export function locateAssetsTable(page: test.Page) {
/** Find assets table rows (if any) on the current page. */
export function locateAssetRows(page: test.Page) {
return locateAssetsTable(page).locator('tbody').getByRole('row')
return locateAssetsTable(page).getByTestId('asset-row')
}
/** Find the name column of the given asset row. */

View File

@ -36,12 +36,9 @@ test.test('labels', async ({ page }) => {
page,
setupAPI: (api) => {
api.addLabel('aaaa', backend.COLORS[0])
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
api.addLabel('bbbb', backend.COLORS[1]!)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
api.addLabel('cccc', backend.COLORS[2]!)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
api.addLabel('dddd', backend.COLORS[3]!)
api.addLabel('bbbb', backend.COLORS[1])
api.addLabel('cccc', backend.COLORS[2])
api.addLabel('dddd', backend.COLORS[3])
},
})
const searchBarInput = actions.locateSearchBarInput(page)

View File

@ -11,12 +11,9 @@ test.test('drag labels onto single row', async ({ page }) => {
page,
setupAPI: (api) => {
api.addLabel(label, backend.COLORS[0])
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
api.addLabel('bbbb', backend.COLORS[1]!)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
api.addLabel('cccc', backend.COLORS[2]!)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
api.addLabel('dddd', backend.COLORS[3]!)
api.addLabel('bbbb', backend.COLORS[1])
api.addLabel('cccc', backend.COLORS[2])
api.addLabel('dddd', backend.COLORS[3])
api.addDirectory('foo')
api.addSecret('bar')
api.addFile('baz')
@ -41,12 +38,9 @@ test.test('drag labels onto multiple rows', async ({ page }) => {
page,
setupAPI: (api) => {
api.addLabel(label, backend.COLORS[0])
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
api.addLabel('bbbb', backend.COLORS[1]!)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
api.addLabel('cccc', backend.COLORS[2]!)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
api.addLabel('dddd', backend.COLORS[3]!)
api.addLabel('bbbb', backend.COLORS[1])
api.addLabel('cccc', backend.COLORS[2])
api.addLabel('dddd', backend.COLORS[3])
api.addDirectory('foo')
api.addSecret('bar')
api.addFile('baz')

View File

@ -15,16 +15,17 @@
},
"scripts": {
"compile": "tsc",
"typecheck": "tsc --noEmit",
"typecheck": "tsc",
"build": "vite build",
"lint": "eslint .",
"dev": "vite",
"dev:e2e": "vite -c vite.test.config.ts",
"dev:e2e:ci": "vite -c vite.test.config.ts build && vite preview --port 8080 --strictPort",
"test": "npm run test:unit && npm run test:e2e",
"test": "corepack pnpm run /^^^^test:.*/",
"test:unit": "vitest run",
"test:unit:debug": "vitest",
"test-dev:unit": "vitest",
"test:e2e": "cross-env NODE_ENV=production playwright test",
"test:e2e:debug": "cross-env NODE_ENV=production playwright test --ui"
"test-dev:e2e": "cross-env NODE_ENV=production playwright test --ui"
},
"dependencies": {
"@aws-amplify/auth": "5.6.5",
@ -89,7 +90,7 @@
"tailwindcss-animate": "1.0.7",
"tailwindcss-react-aria-components": "^1.1.1",
"typescript": "^5.5.3",
"vite": "^5.3.3",
"vite": "^5.3.5",
"vitest": "^1.3.1"
},
"overrides": {

View File

@ -57,7 +57,7 @@ export default test.defineConfig({
},
},
webServer: {
command: process.env.CI || process.env.PROD ? 'npm run dev:e2e:ci' : 'npm run dev:e2e',
command: `corepack pnpm run ${process.env.CI || process.env.PROD ? 'dev:e2e:ci' : 'dev:e2e'}`,
port: 8080,
reuseExistingServer: false,
},

View File

@ -742,6 +742,7 @@ export default function AssetRow(props: AssetRowProps) {
{!hidden && (
<FocusRing>
<tr
data-testid="asset-row"
tabIndex={0}
ref={(element) => {
rootRef.current = element

View File

@ -5,6 +5,7 @@ import * as reactQuery from '@tanstack/react-query'
import invariant from 'tiny-invariant'
import type * as text from 'enso-common/src/text'
import * as tabBar from 'enso-common/src/utilities/style/tabBar'
import * as projectHooks from '#/hooks/projectHooks'
@ -82,39 +83,10 @@ export default function TabBar(props: TabBarProps) {
selectedTabRef.current = element
const bounds = element.getBoundingClientRect()
const rootBounds = backgroundElement.getBoundingClientRect()
const tabLeft = bounds.left - rootBounds.left + TAB_RADIUS_PX
const tabRight = bounds.right - rootBounds.left - TAB_RADIUS_PX
const rightSegments = [
'M 0 0',
`L ${rootBounds.width + window.outerWidth} 0`,
`L ${rootBounds.width + window.outerWidth} ${rootBounds.height}`,
`L ${tabRight + TAB_RADIUS_PX} ${rootBounds.height}`,
`A ${TAB_RADIUS_PX} ${TAB_RADIUS_PX} 0 0 1 ${tabRight} ${rootBounds.height - TAB_RADIUS_PX}`,
]
const leftSegments = [
`A ${TAB_RADIUS_PX} ${TAB_RADIUS_PX} 0 0 1 ${tabLeft - TAB_RADIUS_PX} ${rootBounds.height}`,
`L 0 ${rootBounds.height}`,
'Z',
]
const segments = [
...rightSegments,
`L ${tabRight} ${TAB_RADIUS_PX}`,
`A ${TAB_RADIUS_PX} ${TAB_RADIUS_PX} 0 0 0 ${tabRight - TAB_RADIUS_PX} 0`,
`L ${tabLeft + TAB_RADIUS_PX} 0`,
`A ${TAB_RADIUS_PX} ${TAB_RADIUS_PX} 0 0 0 ${tabLeft} ${TAB_RADIUS_PX}`,
`L ${tabLeft} ${rootBounds.height - TAB_RADIUS_PX}`,
...leftSegments,
]
backgroundElement.style.clipPath = `path("${segments.join(' ')}")`
const rootSegments = [
...rightSegments,
`A ${TAB_RADIUS_PX} ${TAB_RADIUS_PX} 0 0 1 ${tabRight - TAB_RADIUS_PX} ${rootBounds.height}`,
`L ${tabLeft + TAB_RADIUS_PX} ${rootBounds.height}`,
`A ${TAB_RADIUS_PX} ${TAB_RADIUS_PX} 0 0 1 ${tabLeft} ${rootBounds.height - TAB_RADIUS_PX}`,
...leftSegments,
]
const { clipPath, rootClipPath } = tabBar.barClipPath(bounds, rootBounds, TAB_RADIUS_PX)
backgroundElement.style.clipPath = clipPath
if (rootElement) {
rootElement.style.clipPath = `path("${rootSegments.join(' ')}")`
rootElement.style.clipPath = rootClipPath
}
}
}
@ -214,18 +186,7 @@ export function Tab(props: InternalTabProps) {
const element = ref.current
if (element) {
const bounds = element.getBoundingClientRect()
const segments = [
`M 0 ${bounds.height}`,
`A ${TAB_RADIUS_PX} ${TAB_RADIUS_PX} 0 0 0 ${TAB_RADIUS_PX} ${bounds.height - TAB_RADIUS_PX}`,
`L ${TAB_RADIUS_PX} ${TAB_RADIUS_PX}`,
`A ${TAB_RADIUS_PX} ${TAB_RADIUS_PX} 0 0 1 ${TAB_RADIUS_PX * 2} 0`,
`L ${bounds.width - TAB_RADIUS_PX * 2} 0`,
`A ${TAB_RADIUS_PX} ${TAB_RADIUS_PX} 0 0 1 ${bounds.width - TAB_RADIUS_PX} ${TAB_RADIUS_PX}`,
`L ${bounds.width - TAB_RADIUS_PX} ${bounds.height - TAB_RADIUS_PX}`,
`A ${TAB_RADIUS_PX} ${TAB_RADIUS_PX} 0 0 0 ${bounds.width} ${bounds.height}`,
'Z',
]
element.style.clipPath = `path("${segments.join(' ')}")`
element.style.clipPath = tabBar.tabClipPath(bounds, TAB_RADIUS_PX)
}
}
})

View File

@ -196,7 +196,7 @@ export default [
eslintJs.configs.recommended,
{
// Playwright build cache and Vite build directory.
ignores: ['**/.cache/**', '**/playwright-report', '**/dist'],
ignores: ['**/.cache/**', '**/playwright-report', '**/dist', '**/build.mjs'],
},
{
settings: {
@ -403,6 +403,7 @@ export default [
},
{
files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'],
ignores: ['**/build.mjs'],
rules: {
'@typescript-eslint/no-var-requires': 'off',
// Parameter types must be specified using JSDoc in JS files.

View File

@ -1,5 +1,5 @@
import type { Page } from '@playwright/test'
import type { ExpressionUpdate, MethodCall } from 'shared/languageServerTypes'
import type { ExpressionUpdate, MethodCall } from 'ydoc-shared/languageServerTypes'
export type ExpressionLocator = string | { binding: string; expr: string }

View File

@ -1,5 +1,6 @@
import { expect, test } from 'playwright/test'
import * as actions from './actions'
import { mockMethodCallInfo } from './expressionUpdates'
import { CONTROL_KEY } from './keyboard'
import * as locate from './locate'
@ -48,3 +49,23 @@ test('Doc panel focus (regression #10471)', async ({ page }) => {
expect(content.includes('The main TEST method')).toBe(true)
await expect(locate.rightDock(page)).toContainText('The main TEST method')
})
test('Component help', async ({ page }) => {
await actions.goToGraph(page, false)
await locate.rightDock(page).getByRole('button', { name: 'Help' }).click()
await expect(locate.rightDock(page)).toHaveText(/Select a single component/)
await locate.graphNodeByBinding(page, 'final').click()
await expect(locate.rightDock(page)).toHaveText(/No documentation available/)
await mockMethodCallInfo(page, 'data', {
methodPointer: {
module: 'Standard.Base.Data',
definedOnType: 'Standard.Base.Data',
name: 'read',
},
notAppliedArguments: [0, 1, 2],
})
await locate.graphNodeByBinding(page, 'data').click()
await expect(locate.rightDock(page)).toHaveText(/Reads a file into Enso/)
})

View File

@ -18,7 +18,7 @@ export default function setup() {
id: 'websocket',
udp: true,
ipv6: true,
port: 30535,
port: 30536,
middleware: [],
},
},

1
app/gui2/env.d.ts vendored
View File

@ -2,7 +2,6 @@
declare const PROJECT_MANAGER_URL: string
declare const YDOC_SERVER_URL: string
declare const RUNNING_VITEST: boolean
declare const IS_CLOUD_BUILD: boolean
interface Document {

View File

@ -1 +0,0 @@
/// <reference types="@histoire/plugin-vue/components" />

View File

@ -10,8 +10,6 @@ const DIR_NAME = path.dirname(url.fileURLToPath(import.meta.url))
const conf = [
{
ignores: [
'rust-ffi/pkg',
'rust-ffi/node-pkg',
'dist',
'shared/ast/generated',
'templates',
@ -30,14 +28,7 @@ const conf = [
parserOptions: {
tsconfigRootDir: DIR_NAME,
ecmaVersion: 'latest',
project: [
'./tsconfig.app.json',
'./tsconfig.node.json',
'./tsconfig.server.json',
'./tsconfig.app.vitest.json',
'./tsconfig.server.vitest.json',
'./tsconfig.story.json',
],
project: ['./tsconfig.app.json', './tsconfig.node.json', './tsconfig.app.vitest.json'],
},
},
rules: {

View File

@ -2,8 +2,8 @@
import { useProjectStore } from '@/stores/project'
import { mockFsDirectoryHandle } from '@/util/convert/fsAccess'
import { MockWebSocket, type WebSocketHandler } from '@/util/net'
import { type Path as LSPath } from 'shared/languageServerTypes'
import { watchEffect } from 'vue'
import { type Path as LSPath } from 'ydoc-shared/languageServerTypes'
import { mockDataWSHandler } from './dataServer'
const projectStore = useProjectStore()

View File

@ -5,8 +5,8 @@
import { useProjectStore } from '@/stores/project'
import { Ast } from '@/util/ast'
import { SourceDocument } from 'shared/ast/sourceDocument'
import { reactive, watch } from 'vue'
import { SourceDocument } from 'ydoc-shared/ast/sourceDocument'
const props = defineProps<{ modelValue: string }>()
const emit = defineEmits<{ 'update:modelValue': [modelValue: string] }>()

View File

@ -27,9 +27,9 @@ import {
type AnyOutboundPayload,
type Offset,
type Table,
} from 'shared/binaryProtocol'
import { LanguageServerErrorCode } from 'shared/languageServerTypes'
import { uuidToBits } from 'shared/uuid'
} from 'ydoc-shared/binaryProtocol'
import { LanguageServerErrorCode } from 'ydoc-shared/languageServerTypes'
import { uuidToBits } from 'ydoc-shared/uuid'
const sha3 = createSHA3(224)

View File

@ -1,7 +1,9 @@
import { Pattern } from '@/util/ast/match'
import type { MockYdocProviderImpl } from '@/util/crdt'
import type { WebSocketHandler } from '@/util/net'
import type { QualifiedName } from '@/util/qualifiedName'
import * as random from 'lib0/random'
import * as Ast from 'shared/ast'
import * as Ast from 'ydoc-shared/ast'
import {
Builder,
EnsoUUID,
@ -9,8 +11,8 @@ import {
OutboundPayload,
VisualizationContext,
VisualizationUpdate,
} from 'shared/binaryProtocol'
import { ErrorCode } from 'shared/languageServer'
} from 'ydoc-shared/binaryProtocol'
import { ErrorCode } from 'ydoc-shared/languageServer'
import type {
ContextId,
ExpressionId,
@ -19,11 +21,10 @@ import type {
Uuid,
VisualizationConfiguration,
response,
} from 'shared/languageServerTypes'
import type { SuggestionEntry } from 'shared/languageServerTypes/suggestions'
import { uuidToBits } from 'shared/uuid'
import type { MockTransportData, WebSocketHandler } from 'src/util/net'
import type { QualifiedName } from 'src/util/qualifiedName'
} from 'ydoc-shared/languageServerTypes'
import type { SuggestionEntry } from 'ydoc-shared/languageServerTypes/suggestions'
import type { MockTransportData } from 'ydoc-shared/util/net'
import { uuidToBits } from 'ydoc-shared/uuid'
import * as Y from 'yjs'
import { mockFsDirectoryHandle, type FileTree } from '../src/util/convert/fsAccess'
import mockDb from '../stories/mockSuggestions.json' assert { type: 'json' }

View File

@ -8,20 +8,21 @@
"email": "contact@enso.org"
},
"scripts": {
"dev": "vite",
"build": "npm --workspace enso-dashboard run compile && run-p typecheck build-only",
"build:cloud": "cross-env CLOUD_BUILD=true npm run build",
"dev": "echo DEPRECATED! Use `pnpm dev:gui` in workspace root directory.",
"dev:vite": "vite",
"build": "corepack pnpm -r --filter enso-dashboard run compile && corepack pnpm run build:vite",
"build-cloud": "cross-env CLOUD_BUILD=true corepack pnpm run build",
"preview": "vite preview",
"test": "vitest run && playwright test",
"test:unit": "vitest",
"test": "corepack pnpm run /^^^^test:.*/",
"test:unit": "vitest run",
"test-dev:unit": "vitest",
"test:e2e": "playwright test",
"test-dev:e2e": "playwright test --ui",
"story:dev": "histoire dev",
"story:build": "histoire build",
"story:preview": "histoire preview",
"build-only": "vite build",
"build-ydoc-server-polyglot": "vite build --config vite.ydoc-server-polyglot.config.ts",
"compile-server": "tsc -p tsconfig.server.json",
"typecheck": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
"build:vite": "vite build",
"typecheck": "vue-tsc --noEmit -p tsconfig.app.json",
"lint": "eslint .",
"format": "prettier --version && prettier --write src/ && eslint . --fix",
"clean-old-generated-directory": "rimraf src/generated",
@ -61,24 +62,16 @@
"@lexical/utils": "^0.16.0",
"@lezer/common": "^1.1.0",
"@lezer/highlight": "^1.1.6",
"@noble/hashes": "^1.3.2",
"@open-rpc/client-js": "^1.8.1",
"@noble/hashes": "^1.4.0",
"@tanstack/vue-query": ">= 5.45.0 < 5.46.0",
"@vueuse/core": "^10.4.1",
"ag-grid-community": "^30.2.1",
"ag-grid-enterprise": "^30.2.1",
"codemirror": "^6.0.1",
"@codemirror/commands": "6.4.0",
"@codemirror/language": "6.10.1",
"@codemirror/lint": "6.5.0",
"@codemirror/search": "6.5.6",
"@codemirror/state": "6.4.1",
"@codemirror/view": "6.26.3",
"culori": "^3.2.0",
"enso-dashboard": "workspace:*",
"rust-ffi": "workspace:*",
"events": "^3.3.0",
"fast-diff": "^1.3.0",
"hash-sum": "^2.0.0",
"install": "^0.13.0",
"isomorphic-ws": "^5.0.0",
@ -86,23 +79,22 @@
"lib0": "^0.2.85",
"magic-string": "^0.30.3",
"murmurhash": "^2.0.1",
"partysocket": "^1.0.1",
"postcss-inline-svg": "^6.0.0",
"postcss-nesting": "^12.0.1",
"react-toastify": "^9.1.3",
"rimraf": "^5.0.5",
"sucrase": "^3.34.0",
"veaury": "^2.3.18",
"vue": "^3.4.19",
"ws": "^8.13.0",
"y-codemirror.next": "^0.3.2",
"y-protocols": "^1.0.5",
"y-textarea": "^1.0.0",
"y-websocket": "^1.5.0",
"ydoc-shared": "workspace:*",
"yjs": "^13.6.7",
"zod": "^3.22.4"
"zod": "^3.23.8"
},
"devDependencies": {
"@codemirror/theme-one-dark": "^6.1.2",
"@danmarshall/deckgl-typings": "^4.9.28",
"@eslint/eslintrc": "^3.0.2",
"@eslint/js": "^8.57.0",
@ -110,7 +102,7 @@
"@open-rpc/server-js": "^1.9.4",
"@playwright/test": "^1.40.0",
"@rushstack/eslint-patch": "^1.3.2",
"@tsconfig/node18": "^18.2.0",
"@tsconfig/node20": "^20.1.4",
"@types/css.escape": "^1.5.2",
"@types/culori": "^2.0.1",
"@types/d3": "^7.4.0",
@ -130,7 +122,6 @@
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.5.1",
"change-case": "^4.1.2",
"cross-env": "^7.0.3",
"css.escape": "^1.5.1",
"d3": "^7.4.0",
@ -138,7 +129,7 @@
"eslint": "^8.49.0",
"eslint-plugin-vue": "^9.22.0",
"floating-vue": "^2.0.0-beta.24",
"hash-wasm": "^4.10.0",
"hash-wasm": "^4.11.0",
"histoire": "^0.17.2",
"jsdom": "^24.1.0",
"playwright": "^1.39.0",
@ -154,11 +145,13 @@
"tsx": "^4.7.1",
"typescript": "^5.5.3",
"unbzip2-stream": "^1.4.3",
"vite": "^5.3.3",
"vite": "^5.3.5",
"vite-plugin-vue-devtools": "7.3.7",
"vite-plugin-wasm": "^3.3.0",
"vitest": "^1.3.1",
"vue-react-wrapper": "^0.3.1",
"vue-tsc": "^2.0.24",
"yaml": "^2.4.5"
"yaml": "^2.4.5",
"ydoc-server": "workspace:*"
}
}

View File

@ -1,17 +0,0 @@
import * as fs from 'node:fs'
import * as process from 'node:process'
import * as codegen from './codegen.js'
import * as Schema from './schema.js'
const schemaPath = process.argv[2]
const outputPath = process.argv[3]
if (!schemaPath || !outputPath) {
console.error('Usage: parser-codegen <schemaPath> <outputPath>')
process.exit(1)
}
console.log(`Generating ${outputPath} from ${schemaPath}.`)
const schema: Schema.Schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'))
const code = codegen.implement(schema)
fs.writeFileSync(outputPath, code)

View File

@ -114,8 +114,8 @@ export default defineConfig({
},
command:
process.env.CI || process.env.PROD ?
`npx vite build && npx vite preview --port ${PORT} --strictPort`
: `npx vite dev --port ${PORT}`,
`corepack pnpm build && corepack pnpm exec vite preview --port ${PORT} --strictPort`
: `corepack pnpm exec vite dev --port ${PORT}`,
// Build from scratch apparently can take a while on CI machines.
timeout: 120 * 1000,
port: PORT,

View File

@ -1,45 +0,0 @@
/**
* @file Provides the Rust ffi interface. The interface should be kept in sync with polyglot ffi inteface {@link module:ffiPolyglot}.
*
* @module ffi
*/
import { createXXHash128 } from 'hash-wasm'
import type { IDataType } from 'hash-wasm/dist/lib/util'
import init, {
is_ident_or_operator,
is_numeric_literal,
parse,
parse_doc_to_json,
} from 'rust-ffi/pkg/rust_ffi'
import { assertDefined } from '../util/assert'
import { isNode } from '../util/detect'
let xxHasher128: Awaited<ReturnType<typeof createXXHash128>> | undefined
export function xxHash128(input: IDataType) {
assertDefined(xxHasher128, 'Module should have been loaded with `initializeFFI`.')
xxHasher128.init()
xxHasher128.update(input)
return xxHasher128.digest()
}
export async function initializeFFI(path?: string | undefined) {
if (xxHasher128 != null) return
if (isNode) {
const fs = await import('node:fs/promises')
const { fileURLToPath, URL: nodeURL } = await import('node:url')
const buffer = fs.readFile(
path ?? fileURLToPath(new nodeURL('rust-ffi/pkg/rust_ffi_bg.wasm', import.meta.url)),
)
await init(buffer)
} else {
await init()
}
xxHasher128 = await createXXHash128()
}
// TODO[ao]: We cannot to that, because the ffi is used by cjs modules.
// await initializeFFI()
/* eslint-disable-next-line camelcase */
export { is_ident_or_operator, is_numeric_literal, parse_doc_to_json, parse as parse_tree }

View File

@ -1,29 +0,0 @@
/**
* @file This file is used as ffi {@link module:ffi} interface for building the polyglot ydoc server.
* All the exported methods are provided by the ydoc server implementation.
* The interface should be kept in sync with Rust ffi interface {@link module:ffi}.
*
* @module ffiPolyglot
*/
import type { IDataType } from 'hash-wasm/dist/lib/util'
declare global {
function parse_tree(code: string): Uint8Array
function parse_doc_to_json(docs: string): string
function is_ident_or_operator(code: string): number
function is_numeric_literal(code: string): boolean
function xxHash128(input: IDataType): string
}
export async function initializeFFI(_path?: string | undefined) {}
/* eslint-disable camelcase */
export const {
is_ident_or_operator,
is_numeric_literal,
parse_doc_to_json,
parse_tree,
xxHash128,
} = globalThis
/* eslint-enable camelcase */

View File

@ -1,91 +0,0 @@
/**
* This file is modified version of open-rpc/client-js WebSocketTransport implementation
* (https://github.com/open-rpc/client-js/blob/master/src/transports/WebSocketTransport.ts)
* which uses the automatically reconnecting websocket.
*/
import { ERR_UNKNOWN, JSONRPCError } from '@open-rpc/client-js/build/Error'
import {
getBatchRequests,
getNotifications,
type JSONRPCRequestData,
} from '@open-rpc/client-js/build/Request'
import { Transport } from '@open-rpc/client-js/build/transports/Transport'
import WS from 'isomorphic-ws'
import { WebSocket } from 'partysocket'
import { type WebSocketEventMap } from 'partysocket/ws'
export interface AddEventListenerOptions {
capture?: boolean
once?: boolean
passive?: boolean
signal?: AbortSignal
}
class ReconnectingWebSocketTransport extends Transport {
public connection: WebSocket
public uri: string
constructor(uri: string) {
super()
this.uri = uri
this.connection = new WebSocket(uri, undefined, { WebSocket: WS })
}
public connect(): Promise<any> {
return new Promise<void>((resolve) => {
this.connection.addEventListener('open', () => resolve(), { once: true })
this.connection.addEventListener('message', ({ data }: { data: string }) => {
this.transportRequestManager.resolveResponse(data)
})
})
}
public reconnect() {
this.connection.reconnect()
}
public async sendData(data: JSONRPCRequestData, timeout: number | null = 5000): Promise<any> {
let promise = this.transportRequestManager.addRequest(data, timeout)
const notifications = getNotifications(data)
try {
this.connection.send(JSON.stringify(this.parseData(data)))
this.transportRequestManager.settlePendingRequest(notifications)
} catch (err) {
const jsonError = new JSONRPCError((err as any).message, ERR_UNKNOWN, err)
this.transportRequestManager.settlePendingRequest(notifications, jsonError)
this.transportRequestManager.settlePendingRequest(getBatchRequests(data), jsonError)
promise = Promise.reject(jsonError)
}
return promise
}
public close(): void {
this.connection.close()
}
on<K extends keyof WebSocketEventMap>(
type: K,
cb: (
event: WebSocketEventMap[K] extends Event ? WebSocketEventMap[K] : never,
) => WebSocketEventMap[K] extends Event ? void : never,
options?: AddEventListenerOptions,
): void {
this.connection.addEventListener(type, cb, options)
}
off<K extends keyof WebSocketEventMap>(
type: K,
cb: (
event: WebSocketEventMap[K] extends Event ? WebSocketEventMap[K] : never,
) => WebSocketEventMap[K] extends Event ? void : never,
options?: AddEventListenerOptions,
): void {
this.connection.removeEventListener(type, cb, options)
}
}
export default ReconnectingWebSocketTransport

View File

@ -19,7 +19,6 @@ import { computed, markRaw, toRaw, toRef, watch } from 'vue'
import TooltipDisplayer from './components/TooltipDisplayer.vue'
import { provideTooltipRegistry } from './providers/tooltipState'
import { provideVisibility } from './providers/visibility'
import { initializePrefixes } from './util/ast/node'
import { urlParams } from './util/urlParams'
const props = defineProps<{
@ -37,8 +36,6 @@ provideBackend(() => markRaw(toRaw(props.backend)))
const classSet = provideAppClassSet()
const appTooltips = provideTooltipRegistry()
initializePrefixes()
const logger = provideEventLogger(toRef(props, 'logEvent'), toRef(props, 'projectId'))
watch(
toRef(props, 'projectId'),

View File

@ -1,9 +1,6 @@
import '@/assets/base.css'
export async function AsyncApp() {
const [_, app] = await Promise.all([
import('shared/ast/ffi').then((mod) => mod.initializeFFI()),
import('@/App.vue'),
])
const app = await import('@/App.vue')
return app
}

View File

@ -9,10 +9,10 @@ import { unwrap } from '@/util/data/result'
import { qnJoin, tryQualifiedName } from '@/util/qualifiedName'
import { EditorSelection } from '@codemirror/state'
import { createDebouncer } from 'lib0/eventloop'
import { MutableModule } from 'shared/ast'
import { textChangeToEdits, type SourceRangeEdit } from 'shared/util/data/text'
import { rangeEncloses, type Origin } from 'shared/yjsModel'
import { computed, onMounted, onUnmounted, ref, shallowRef, watch, watchEffect } from 'vue'
import { MutableModule } from 'ydoc-shared/ast'
import { textChangeToEdits, type SourceRangeEdit } from 'ydoc-shared/util/data/text'
import { rangeEncloses, type Origin } from 'ydoc-shared/yjsModel'
// Use dynamic imports to aid code splitting. The codemirror dependency is quite large.
const {

View File

@ -43,9 +43,9 @@ import {
} from '@lezer/common'
import { styleTags, tags } from '@lezer/highlight'
import { EditorView } from 'codemirror'
import type { Diagnostic as LSDiagnostic } from 'shared/languageServerTypes'
import { tryGetSoleValue } from 'shared/util/data/iterable'
import type { SourceRangeEdit } from 'shared/util/data/text'
import type { Diagnostic as LSDiagnostic } from 'ydoc-shared/languageServerTypes'
import { tryGetSoleValue } from 'ydoc-shared/util/data/iterable'
import type { SourceRangeEdit } from 'ydoc-shared/util/data/text'
export function lsDiagnosticsToCMDiagnostics(
source: string,

View File

@ -4,8 +4,8 @@ import { injectNodeColors } from '@/providers/graphNodeColors'
import { injectGraphSelection } from '@/providers/graphSelection'
import { useGraphStore, type NodeId } from '@/stores/graph'
import { filterDefined } from '@/util/data/iterable'
import { tryGetSoleValue } from 'shared/util/data/iterable'
import { ref } from 'vue'
import { tryGetSoleValue } from 'ydoc-shared/util/data/iterable'
const emit = defineEmits<{
close: []

View File

@ -1,5 +1,5 @@
import { ensoColor, formatCssColor, normalizeHue } from '@/util/colors'
import { Resumable } from 'shared/util/data/iterable'
import { Resumable } from 'ydoc-shared/util/data/iterable'
export interface FixedRange {
start: number

View File

@ -1,11 +1,11 @@
<script setup lang="ts">
import { componentBrowserBindings } from '@/bindings'
import ComponentEditor from '@/components/ComponentBrowser/ComponentEditor.vue'
import { default as DocumentationPanel } from '@/components/ComponentBrowser/DocumentationPanel.vue'
import { makeComponentList, type Component } from '@/components/ComponentBrowser/component'
import ComponentEditor from '@/components/ComponentBrowser/ComponentEditor.vue'
import { Filtering } from '@/components/ComponentBrowser/filtering'
import { useComponentBrowserInput, type Usage } from '@/components/ComponentBrowser/input'
import { useScrolling } from '@/components/ComponentBrowser/scrolling'
import DocumentationPanel from '@/components/DocumentationPanel.vue'
import GraphVisualization from '@/components/GraphEditor/GraphVisualization.vue'
import SvgIcon from '@/components/SvgIcon.vue'
import ToggleIcon from '@/components/ToggleIcon.vue'
@ -29,10 +29,10 @@ import { Rect } from '@/util/data/rect'
import { Vec2 } from '@/util/data/vec2'
import { DEFAULT_ICON, suggestionEntryToIcon } from '@/util/getIconName'
import { debouncedGetter } from '@/util/reactivity'
import type { SuggestionId } from 'shared/languageServerTypes/suggestions'
import type { VisualizationIdentifier } from 'shared/yjsModel'
import type { ComponentInstance, Ref } from 'vue'
import { computed, onMounted, onUnmounted, reactive, ref, watch, watchEffect } from 'vue'
import type { SuggestionId } from 'ydoc-shared/languageServerTypes/suggestions'
import type { VisualizationIdentifier } from 'ydoc-shared/yjsModel'
import LoadingSpinner from './LoadingSpinner.vue'
const ITEM_SIZE = 32
@ -390,15 +390,7 @@ function updateScroll() {
const docsVisible = ref(true)
const displayedDocs: Ref<Opt<SuggestionId>> = ref(null)
const docEntry = computed({
get() {
return displayedDocs.value
},
set(value) {
displayedDocs.value = value
},
})
const docEntry: Ref<Opt<SuggestionId>> = ref(null)
watch(selectedSuggestionId, (id) => {
docEntry.value = id
@ -603,6 +595,7 @@ const handler = componentBrowserBindings.handler({
--list-height: 0px;
--radius-default: 20px;
--background-color: #eaeaea;
--doc-panel-bottom-clip: 4px;
width: fit-content;
color: rgba(0, 0, 0, 0.6);
font-size: 11.5px;

View File

@ -13,9 +13,6 @@ import {
} from '@/stores/suggestionDatabase/entry'
import { unwrap } from '@/util/data/result'
import { tryQualifiedName, type QualifiedName } from '@/util/qualifiedName'
import { initializeFFI } from 'shared/ast/ffi'
await initializeFFI()
test.each([
{ ...makeModuleMethod('Standard.Base.Data.read'), groupIndex: 0 },

View File

@ -16,11 +16,9 @@ import {
} from '@/stores/suggestionDatabase/entry'
import { unwrap } from '@/util/data/result'
import { tryIdentifier, tryQualifiedName } from '@/util/qualifiedName'
import { initializeFFI } from 'shared/ast/ffi'
import { assertUnreachable } from 'shared/util/assert'
import { withSetup } from '@/util/testing'
import { expect, test } from 'vitest'
await initializeFFI()
import { assertUnreachable } from 'ydoc-shared/util/assert'
const aiMock = { query: assertUnreachable }
const operator1Id = '3d0e9b96-3ca0-4c35-a820-7d3a1649de55' as NodeId
@ -113,30 +111,32 @@ test.each([
selfArg?: { type: string; typename?: string }
},
) => {
const input = useComponentBrowserInput(mockGraphDb(), new SuggestionDb(), aiMock)
input.content.value = { text: code, selection: { start: cursorPos, end: cursorPos } }
const context = input.context.value
const filter = input.filter.value
expect(context.type).toStrictEqual(expContext.type)
switch (context.type) {
case 'insert':
expect(context.position).toStrictEqual(expContext.position)
expect(
context.oprApp != null ? Array.from(context.oprApp.componentsReprs()) : undefined,
).toStrictEqual(expContext.oprApp)
break
case 'changeIdentifier':
expect(context.identifier.repr()).toStrictEqual(expContext.identifier)
expect(
context.oprApp != null ? Array.from(context.oprApp.componentsReprs()) : undefined,
).toStrictEqual(expContext.oprApp)
break
case 'changeLiteral':
expect(context.literal.repr()).toStrictEqual(expContext.literal)
}
expect(filter.pattern).toStrictEqual(expFiltering.pattern)
expect(filter.qualifiedNamePattern).toStrictEqual(expFiltering.qualifiedNamePattern)
expect(filter.selfArg).toStrictEqual(expFiltering.selfArg)
withSetup(() => {
const input = useComponentBrowserInput(mockGraphDb(), new SuggestionDb(), aiMock)
input.content.value = { text: code, selection: { start: cursorPos, end: cursorPos } }
const context = input.context.value
const filter = input.filter.value
expect(context.type).toStrictEqual(expContext.type)
switch (context.type) {
case 'insert':
expect(context.position).toStrictEqual(expContext.position)
expect(
context.oprApp != null ? Array.from(context.oprApp.componentsReprs()) : undefined,
).toStrictEqual(expContext.oprApp)
break
case 'changeIdentifier':
expect(context.identifier.repr()).toStrictEqual(expContext.identifier)
expect(
context.oprApp != null ? Array.from(context.oprApp.componentsReprs()) : undefined,
).toStrictEqual(expContext.oprApp)
break
case 'changeLiteral':
expect(context.literal.repr()).toStrictEqual(expContext.literal)
}
expect(filter.pattern).toStrictEqual(expFiltering.pattern)
expect(filter.qualifiedNamePattern).toStrictEqual(expFiltering.qualifiedNamePattern)
expect(filter.selfArg).toStrictEqual(expFiltering.selfArg)
})
},
)
@ -263,7 +263,7 @@ const insideBracketsCases = makeComplexCase('(', ')')
const insideListCases = makeComplexCase('[foo, ', ', bar]')
test.each([
...baseCases,
...simpleCases,
...insideInfixCases,
...insideBracketsCases,
...insideListCases,
@ -281,19 +281,21 @@ test.each([
])(
'Applying suggestion $suggestion.name to $code',
({ code, cursorPos, suggestion, expected, expectedCursorPos }) => {
cursorPos = cursorPos ?? code.length
expectedCursorPos = expectedCursorPos ?? expected.length
const db = new SuggestionDb()
const dummyId = 1
db.set(dummyId, suggestion)
const graphMock = GraphDb.Mock()
const input = useComponentBrowserInput(graphMock, db, aiMock)
input.content.value = { text: code, selection: { start: cursorPos, end: cursorPos } }
input.applySuggestion(dummyId)
expect(input.code.value).toEqual(expected)
expect(input.selection.value).toStrictEqual({
start: expectedCursorPos,
end: expectedCursorPos,
withSetup(() => {
cursorPos = cursorPos ?? code.length
expectedCursorPos = expectedCursorPos ?? expected.length
const db = new SuggestionDb()
const dummyId = 1
db.set(dummyId, suggestion)
const graphMock = GraphDb.Mock()
const input = useComponentBrowserInput(graphMock, db, aiMock)
input.content.value = { text: code, selection: { start: cursorPos, end: cursorPos } }
input.applySuggestion(dummyId)
expect(input.code.value).toEqual(expected)
expect(input.selection.value).toStrictEqual({
start: expectedCursorPos,
end: expectedCursorPos,
})
})
},
)
@ -344,23 +346,25 @@ test.each([
] as ImportsCase[])(
'$description',
({ suggestionId, initialCode, manuallyEditedCode, expectedCode, expectedImports }) => {
initialCode = initialCode ?? ''
const db = new SuggestionDb()
db.set(1, makeModule('Standard.Base'))
db.set(2, makeType('Standard.Base.Table'))
db.set(3, makeConstructor('Standard.Base.Table.new'))
const graphMock = GraphDb.Mock(undefined, db)
const input = useComponentBrowserInput(graphMock, db, aiMock)
input.content.value = {
text: initialCode,
selection: { start: initialCode.length, end: initialCode.length },
}
input.applySuggestion(suggestionId)
if (manuallyEditedCode != null) {
input.content.value = { ...input.content.value, text: manuallyEditedCode }
}
expect(input.code.value).toEqual(expectedCode)
expect(input.importsToAdd()).toEqual(expectedImports)
withSetup(() => {
initialCode = initialCode ?? ''
const db = new SuggestionDb()
db.set(1, makeModule('Standard.Base'))
db.set(2, makeType('Standard.Base.Table'))
db.set(3, makeConstructor('Standard.Base.Table.new'))
const graphMock = GraphDb.Mock(undefined, db)
const input = useComponentBrowserInput(graphMock, db, aiMock)
input.content.value = {
text: initialCode,
selection: { start: initialCode.length, end: initialCode.length },
}
input.applySuggestion(suggestionId)
if (manuallyEditedCode != null) {
input.content.value = { ...input.content.value, text: manuallyEditedCode }
}
expect(input.code.value).toEqual(expectedCode)
expect(input.importsToAdd()).toEqual(expectedImports)
})
},
)
test.each`
@ -370,45 +374,49 @@ test.each`
${'+'} | ${'operator1 +'}
${'>='} | ${'operator1 >='}
`('Initialize input for new node and type $typed', ({ typed, finalCodeWithSourceNode }) => {
const mockDb = mockGraphDb()
const sourceNode = operator1Id
const sourcePort = mockDb.getNodeFirstOutputPort(asNodeId(sourceNode))
const input = useComponentBrowserInput(mockDb, new SuggestionDb(), aiMock)
withSetup(() => {
const mockDb = mockGraphDb()
const sourceNode = operator1Id
const sourcePort = mockDb.getNodeFirstOutputPort(asNodeId(sourceNode))
const input = useComponentBrowserInput(mockDb, new SuggestionDb(), aiMock)
// Without source node
input.reset({ type: 'newNode' })
expect(input.code.value).toBe('')
expect(input.selection.value).toEqual({ start: 0, end: 0 })
expect(input.anyChange.value).toBeFalsy()
input.content.value = { text: typed, selection: { start: typed.length, end: typed.length } }
expect(input.code.value).toBe(typed)
expect(input.selection.value).toEqual({ start: typed.length, end: typed.length })
expect(input.anyChange.value).toBeTruthy()
// Without source node
input.reset({ type: 'newNode' })
expect(input.code.value).toBe('')
expect(input.selection.value).toEqual({ start: 0, end: 0 })
expect(input.anyChange.value).toBeFalsy()
input.content.value = { text: typed, selection: { start: typed.length, end: typed.length } }
expect(input.code.value).toBe(typed)
expect(input.selection.value).toEqual({ start: typed.length, end: typed.length })
expect(input.anyChange.value).toBeTruthy()
// With source node
input.reset({ type: 'newNode', sourcePort })
expect(input.code.value).toBe('operator1.')
expect(input.text.value).toBe('')
expect(input.selection.value).toEqual({ start: 0, end: 0 })
expect(input.anyChange.value).toBeFalsy()
input.content.value = { text: typed, selection: { start: typed.length, end: typed.length } }
expect(input.code.value).toBe(finalCodeWithSourceNode)
expect(input.selection.value).toEqual({
start: input.text.value.length,
end: input.text.value.length,
// With source node
input.reset({ type: 'newNode', sourcePort })
expect(input.code.value).toBe('operator1.')
expect(input.text.value).toBe('')
expect(input.selection.value).toEqual({ start: 0, end: 0 })
expect(input.anyChange.value).toBeFalsy()
input.content.value = { text: typed, selection: { start: typed.length, end: typed.length } }
expect(input.code.value).toBe(finalCodeWithSourceNode)
expect(input.selection.value).toEqual({
start: input.text.value.length,
end: input.text.value.length,
})
expect(input.anyChange.value).toBeTruthy()
})
expect(input.anyChange.value).toBeTruthy()
})
test('Initialize input for edited node', () => {
const input = useComponentBrowserInput(mockGraphDb(), new SuggestionDb(), aiMock)
input.reset({ type: 'editNode', node: asNodeId(operator1Id), cursorPos: 4 })
expect(input.code.value).toBe('Data.read')
expect(input.selection.value).toEqual({ start: 4, end: 4 })
expect(input.anyChange.value).toBeFalsy()
// Typing anything should not affected existing code (in contrary to new node creation)
input.content.value = { text: `Data.+.read`, selection: { start: 5, end: 5 } }
expect(input.code.value).toEqual('Data.+.read')
expect(input.selection.value).toEqual({ start: 5, end: 5 })
expect(input.anyChange.value).toBeTruthy()
withSetup(() => {
const input = useComponentBrowserInput(mockGraphDb(), new SuggestionDb(), aiMock)
input.reset({ type: 'editNode', node: asNodeId(operator1Id), cursorPos: 4 })
expect(input.code.value).toBe('Data.read')
expect(input.selection.value).toEqual({ start: 4, end: 4 })
expect(input.anyChange.value).toBeFalsy()
// Typing anything should not affected existing code (in contrary to new node creation)
input.content.value = { text: `Data.+.read`, selection: { start: 5, end: 5 } }
expect(input.code.value).toEqual('Data.+.read')
expect(input.selection.value).toEqual({ start: 5, end: 5 })
expect(input.anyChange.value).toBeTruthy()
})
})

View File

@ -1,9 +1,9 @@
import { useGraphStore } from '@/stores/graph'
import type { GraphDb } from '@/stores/graph/graphDatabase'
import { useProjectStore } from '@/stores/project'
import type { LanguageServer } from 'shared/languageServer'
import { Err, Ok, withContext, type Result } from 'shared/util/data/result'
import type { ExternalId } from 'shared/yjsModel'
import type { LanguageServer } from 'ydoc-shared/languageServer'
import { Err, Ok, withContext, type Result } from 'ydoc-shared/util/data/result'
import type { ExternalId } from 'ydoc-shared/yjsModel'
const AI_GOAL_PLACEHOLDER = '__$$GOAL$$__'
const AI_STOP_SEQUENCE = '`'

View File

@ -26,8 +26,8 @@ import {
} from '@/util/qualifiedName'
import { useToast } from '@/util/toast'
import { equalFlat } from 'lib0/array'
import { sourceRangeKey, type SourceRange } from 'shared/yjsModel'
import { computed, readonly, ref, type ComputedRef } from 'vue'
import { sourceRangeKey, type SourceRange } from 'ydoc-shared/yjsModel'
import { useAI } from './ai'
/** Information how the component browser is used, needed for proper input initializing. */

View File

@ -0,0 +1,50 @@
<script setup lang="ts">
import DocumentationPanel from '@/components/DocumentationPanel.vue'
import { injectGraphSelection } from '@/providers/graphSelection'
import { useGraphStore } from '@/stores/graph'
import { ref, watchEffect } from 'vue'
import type { SuggestionId } from 'ydoc-shared/languageServerTypes/suggestions'
import { Err, Ok, type Result } from 'ydoc-shared/util/data/result'
const selection = injectGraphSelection()
const graphStore = useGraphStore()
const displayedDocs = ref<Result<SuggestionId>>()
function docsForSelection() {
const selected = selection.tryGetSoleSelection()
if (!selected.ok) return Err('Select a single component to display help')
const suggestionId = graphStore.db.nodeMainSuggestionId.lookup(selected.value)
if (suggestionId == null) return Err('No documentation available for selected component')
return Ok(suggestionId)
}
watchEffect(() => (displayedDocs.value = docsForSelection()))
</script>
<template>
<DocumentationPanel
v-if="displayedDocs?.ok"
:selectedEntry="displayedDocs.value"
@update:selectedEntry="displayedDocs = Ok($event)"
/>
<div v-else-if="displayedDocs?.ok === false" class="help-placeholder">
{{ displayedDocs.error.payload }}.
</div>
</template>
<style scoped>
.DocumentationPanel {
--list-height: 0px;
--radius-default: 20px;
--background-color: #fff;
--group-color-fallback: var(--color-dim);
}
.help-placeholder {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
</style>

View File

@ -6,15 +6,21 @@ import ToggleIcon from '@/components/ToggleIcon.vue'
import { useResizeObserver } from '@/composables/events'
import { Rect } from '@/util/data/rect'
import { Vec2 } from '@/util/data/vec2'
import { tabClipPath } from 'enso-common/src/utilities/style/tabBar'
import { computed, ref } from 'vue'
const MIN_DOCK_SIZE_PX = 200
const TAB_EDGE_MARGIN_PX = 4
const TAB_SIZE_PX = { width: 48 - TAB_EDGE_MARGIN_PX, height: 48 }
const TAB_RADIUS_PX = 8
const toolbarElement = ref<HTMLElement>()
const slideInPanel = ref<HTMLElement>()
type Tab = 'docs' | 'help'
const show = defineModel<boolean>('show', { required: true })
const size = defineModel<number | undefined>('size')
const tab = defineModel<Tab>('tab')
const slideInPanel = ref<HTMLElement>()
const computedSize = useResizeObserver(slideInPanel)
const computedBounds = computed(() => new Rect(Vec2.Zero, computedSize.value))
@ -26,6 +32,14 @@ function clampSize(size: number) {
const style = computed(() => ({
width: size.value != null ? `${clampSize(size.value)}px` : 'var(--right-dock-default-width)',
}))
const tabStyle = {
clipPath: tabClipPath(TAB_SIZE_PX, TAB_RADIUS_PX, 'right'),
width: `${TAB_SIZE_PX.width}px`,
height: `${TAB_SIZE_PX.height}px`,
margin: `${-TAB_RADIUS_PX}px ${TAB_EDGE_MARGIN_PX}px ${-TAB_RADIUS_PX}px 0`,
paddingLeft: `${TAB_EDGE_MARGIN_PX / 2}px`,
}
</script>
<template>
@ -38,9 +52,25 @@ const style = computed(() => ({
<SizeTransition width :duration="100">
<div v-if="show" ref="slideInPanel" :style="style" class="DockPanel" data-testid="rightDock">
<div class="content">
<div ref="toolbarElement" class="toolbar"></div>
<div class="scrollArea">
<slot :toolbar="toolbarElement" />
<slot v-if="tab == 'docs'" name="docs" />
<slot v-else-if="tab == 'help'" name="help" />
</div>
<div class="tabBar">
<div class="tab" :style="tabStyle">
<ToggleIcon
:modelValue="tab == 'docs'"
@update:modelValue="tab = 'docs'"
title="Documentation Editor"
icon="text"
/>
</div>
<div class="tab" :style="tabStyle">
<ToggleIcon
:modelValue="tab == 'help'"
@update:modelValue="tab = 'help'"
title="Component Help"
icon="help"
/>
</div>
</div>
<ResizeHandles left :modelValue="computedBounds" @update:modelValue="size = $event.width" />
@ -50,36 +80,45 @@ const style = computed(() => ({
<style scoped>
.DockPanel {
background-color: rgb(255, 255, 255);
position: relative;
--icon-margin: 16px;
--icon-size: 16px;
display: flex;
flex-direction: row;
justify-content: stretch;
}
.content {
width: 100%;
height: 100%;
background-color: #fff;
min-width: 0;
}
.tabBar {
flex: none;
width: calc(2 * var(--icon-margin) + var(--icon-size));
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding-top: calc(2 * var(--icon-margin) + var(--icon-size));
}
.scrollArea {
width: 100%;
overflow-y: auto;
padding-left: 10px;
/* Prevent touchpad back gesture, which can be triggered while panning. */
overscroll-behavior-x: none;
flex-grow: 1;
}
.toolbar {
height: 48px;
padding-left: 4px;
flex-shrink: 0;
.tab {
display: flex;
align-items: center;
justify-content: center;
&:has(.toggledOn) {
background-color: #fff;
}
}
.toggleDock {
--icon-margin: 16px; /* Must match `--icon-margin` defined above, which is not in scope because of the teleport. */
z-index: 1;
position: absolute;
right: 16px;
top: 16px;
right: var(--icon-margin);
top: var(--icon-margin);
}
</style>

View File

@ -4,14 +4,14 @@ import { fetcherUrlTransformer } from '@/components/MarkdownEditor/imageUrlTrans
import { useGraphStore } from '@/stores/graph'
import { useProjectStore } from '@/stores/project'
import type { ToValue } from '@/util/reactivity'
import type { Path } from 'shared/languageServerTypes'
import { Err, Ok, mapOk, withContext, type Result } from 'shared/util/data/result'
import { toRef, toValue } from 'vue'
import { ref, toRef, toValue } from 'vue'
import type { Path } from 'ydoc-shared/languageServerTypes'
import { Err, Ok, mapOk, withContext, type Result } from 'ydoc-shared/util/data/result'
const documentation = defineModel<string>({ required: true })
const _props = defineProps<{
toolbarContainer: HTMLElement | undefined
}>()
const _props = defineProps<{}>()
const toolbarElement = ref<HTMLElement>()
const graphStore = useGraphStore()
const projectStore = useProjectStore()
@ -69,9 +69,36 @@ function useDocumentationImages(
</script>
<template>
<MarkdownEditor
v-model="documentation"
:transformImageUrl="transformImageUrl"
:toolbarContainer="toolbarContainer"
/>
<div class="DocumentationEditor">
<div ref="toolbarElement" class="toolbar"></div>
<div class="scrollArea">
<MarkdownEditor
v-model="documentation"
:transformImageUrl="transformImageUrl"
:toolbarContainer="toolbarElement"
/>
</div>
</div>
</template>
<style scoped>
.DocumentationEditor {
display: flex;
flex-direction: column;
}
.scrollArea {
width: 100%;
overflow-y: auto;
padding-left: 10px;
/* Prevent touchpad back gesture, which can be triggered while panning. */
overscroll-behavior-x: none;
flex-grow: 1;
}
.toolbar {
height: 48px;
padding-left: 4px;
flex-shrink: 0;
}
</style>

View File

@ -189,9 +189,9 @@ function openDocs(url: string) {
line-height: 160%;
color: var(--enso-docs-text-color);
background-color: var(--enso-docs-background-color);
padding: 4px 12px 4px 4px;
padding: 4px 12px var(--doc-panel-bottom-clip, 0) 4px;
white-space: normal;
clip-path: inset(0 0 4px 0);
clip-path: inset(0 0 var(--doc-panel-bottom-clip, 0) 0);
height: 100%;
overflow-y: auto;
display: flex;

View File

@ -3,11 +3,10 @@ import SvgIcon from '@/components/SvgIcon.vue'
import type { Icon } from '@/util/iconName'
const props = defineProps<{ text: string; icon?: Icon | undefined }>()
const emit = defineEmits<{ click: [] }>()
</script>
<template>
<div class="Breadcrumb clickable" @click.stop="emit('click')">
<div class="Breadcrumb">
<SvgIcon v-if="props.icon" :name="props.icon || ''" />
<span v-text="props.text"></span>
</div>

View File

@ -44,7 +44,9 @@ function shrinkFactor(index: number): number {
:text="breadcrumb.label"
:icon="index === props.breadcrumbs.length - 1 ? props.icon : undefined"
:style="{ 'flex-shrink': shrinkFactor(index) }"
@click="emit('click', index)"
:class="{ nonInteractive: index === props.breadcrumbs.length - 1 }"
class="clickable"
@click.stop="emit('click', index)"
/>
</template>
</TransitionGroup>
@ -81,4 +83,8 @@ function shrinkFactor(index: number): number {
.breadcrumbs-enter-from {
opacity: 0;
}
.nonInteractive {
pointer-events: none;
}
</style>

View File

@ -2,8 +2,11 @@
import type { FunctionDocs, TypeDocs } from '@/components/DocumentationPanel/ir'
import type { Doc } from '@/util/docParser'
import { qnSplit } from '@/util/qualifiedName'
import type { SuggestionEntryArgument, SuggestionId } from 'shared/languageServerTypes/suggestions'
import { computed } from 'vue'
import type {
SuggestionEntryArgument,
SuggestionId,
} from 'ydoc-shared/languageServerTypes/suggestions'
const props = defineProps<{ items: ListItems }>()
const emit = defineEmits<{ linkClicked: [id: SuggestionId] }>()

View File

@ -3,7 +3,7 @@ import type { SuggestionEntry, SuggestionId } from '@/stores/suggestionDatabase/
import { SuggestionKind, entryQn } from '@/stores/suggestionDatabase/entry'
import type { Doc } from '@/util/docParser'
import type { QualifiedName } from '@/util/qualifiedName'
import type { SuggestionEntryArgument } from 'shared/languageServerTypes/suggestions'
import type { SuggestionEntryArgument } from 'ydoc-shared/languageServerTypes/suggestions'
// === Types ===

View File

@ -11,6 +11,7 @@ import CodeEditor from '@/components/CodeEditor.vue'
import ComponentBrowser from '@/components/ComponentBrowser.vue'
import { type Usage } from '@/components/ComponentBrowser/input'
import { usePlacement } from '@/components/ComponentBrowser/placement'
import ComponentDocumentation from '@/components/ComponentDocumentation.vue'
import DockPanel from '@/components/DockPanel.vue'
import DocumentationEditor from '@/components/DocumentationEditor.vue'
import GraphEdges from '@/components/GraphEditor/GraphEdges.vue'
@ -24,6 +25,7 @@ import GraphMouse from '@/components/GraphMouse.vue'
import PlusButton from '@/components/PlusButton.vue'
import SceneScroller from '@/components/SceneScroller.vue'
import TopBar from '@/components/TopBar.vue'
import { builtinWidgets } from '@/components/widgets'
import { useAstDocumentation } from '@/composables/astDocumentation'
import { useDoubleClick } from '@/composables/doubleClick'
import { keyboardBusy, keyboardBusyExceptIn, unrefElement, useEvent } from '@/composables/events'
@ -37,6 +39,7 @@ import { provideGraphSelection } from '@/providers/graphSelection'
import { provideStackNavigator } from '@/providers/graphStackNavigator'
import { provideInteractionHandler } from '@/providers/interactionHandler'
import { provideKeyboard } from '@/providers/keyboard'
import { injectVisibility } from '@/providers/visibility'
import { provideWidgetRegistry } from '@/providers/widgetRegistry'
import { provideGraphStore, type NodeId } from '@/stores/graph'
import type { RequiredImport } from '@/stores/graph/imports'
@ -50,14 +53,11 @@ import { colorFromString } from '@/util/colors'
import { partition } from '@/util/data/array'
import { every, filterDefined } from '@/util/data/iterable'
import { Rect } from '@/util/data/rect'
import { unwrapOr } from '@/util/data/result'
import { Err, Ok, unwrapOr, type Result } from '@/util/data/result'
import { Vec2 } from '@/util/data/vec2'
import { computedFallback } from '@/util/reactivity'
import { until } from '@vueuse/core'
import { encoding, set } from 'lib0'
import { encodeMethodPointer } from 'shared/languageServerTypes'
import * as iterable from 'shared/util/data/iterable'
import { isDevMode } from 'shared/util/detect'
import {
computed,
onMounted,
@ -69,9 +69,9 @@ import {
watch,
type ComponentInstance,
} from 'vue'
import { builtinWidgets } from '@/components/widgets'
import { injectVisibility } from '@/providers/visibility'
import { encodeMethodPointer } from 'ydoc-shared/languageServerTypes'
import * as iterable from 'ydoc-shared/util/data/iterable'
import { isDevMode } from 'ydoc-shared/util/detect'
const keyboard = provideKeyboard()
const projectStore = useProjectStore()
@ -101,7 +101,8 @@ const graphNavigator: GraphNavigator = provideGraphNavigator(viewportNode, keybo
// === Client saved state ===
const storedShowDocumentationEditor = ref()
const storedShowRightDock = ref()
const storedRightDockTab = ref()
const rightDockWidth = ref<number>()
/**
@ -119,6 +120,8 @@ interface GraphStoredState {
s: number
/** Whether or not the documentation panel is open. */
doc: boolean
/** The selected tab in the right-side panel. */
rtab: string
/** Width of the right dock. */
rwidth: number | null
}
@ -145,7 +148,8 @@ useSyncLocalStorage<GraphStoredState>({
x: graphNavigator.targetCenter.x,
y: graphNavigator.targetCenter.y,
s: graphNavigator.targetScale,
doc: storedShowDocumentationEditor.value,
doc: storedShowRightDock.value,
rtab: storedRightDockTab.value,
rwidth: rightDockWidth.value ?? null,
}
},
@ -154,7 +158,8 @@ useSyncLocalStorage<GraphStoredState>({
const pos = new Vec2(restored.x ?? 0, restored.y ?? 0)
const scale = restored.s ?? 1
graphNavigator.setCenterAndScale(pos, scale)
storedShowDocumentationEditor.value = restored.doc ?? undefined
storedShowRightDock.value = restored.doc ?? undefined
storedRightDockTab.value = restored.rtab ?? undefined
rightDockWidth.value = restored.rwidth ?? undefined
} else {
await until(visibleAreasReady).toBe(true)
@ -352,27 +357,22 @@ const graphBindingsHandler = graphBindings.handler({
showColorPicker.value = true
},
openDocumentation() {
const failure = 'Unable to show node documentation'
const selected = getSoleSelectionOrToast(failure)
if (selected == null) return
const suggestion = graphStore.db.nodeMainSuggestion.lookup(selected)
const documentation = suggestion && suggestionDocumentationUrl(suggestion)
if (documentation) {
window.open(documentation, '_blank')
} else {
toasts.userActionFailed.show(`${failure}: no documentation available for selected node.`)
const result = tryGetSelectionDocUrl()
if (!result.ok) {
toasts.userActionFailed.show(result.error.message('Unable to show node documentation'))
return
}
window.open(result.value, '_blank')
},
})
function getSoleSelectionOrToast(context: string) {
if (nodeSelection.selected.size === 0) {
toasts.userActionFailed.show(`${context}: no node selected.`)
} else if (nodeSelection.selected.size > 1) {
toasts.userActionFailed.show(`${context}: multiple nodes selected.`)
} else {
return set.first(nodeSelection.selected)
}
function tryGetSelectionDocUrl() {
const selected = nodeSelection.tryGetSoleSelection()
if (!selected.ok) return selected
const suggestion = graphStore.db.getNodeMainSuggestion(selected.value)
const documentation = suggestion && suggestionDocumentationUrl(suggestion)
if (!documentation) return Err('No external documentation available for selected component')
return Ok(documentation)
}
const { handleClick } = useDoubleClick(
@ -405,15 +405,16 @@ const codeEditorHandler = codeEditorBindings.handler({
const docEditor = shallowRef<ComponentInstance<typeof DocumentationEditor>>()
const documentationEditorArea = computed(() => unrefElement(docEditor))
const showDocumentationEditor = computedFallback(
storedShowDocumentationEditor,
const showRightDock = computedFallback(
storedShowRightDock,
// Show documentation editor when documentation exists on first graph visit.
() => !!documentation.state.value,
)
const rightDockTab = computedFallback(storedRightDockTab, () => 'docs')
const documentationEditorHandler = documentationEditorBindings.handler({
toggle() {
showDocumentationEditor.value = !showDocumentationEditor.value
showRightDock.value = !showRightDock.value
},
})
@ -662,6 +663,7 @@ const groupColors = computed(() => {
<div
class="GraphEditor"
:class="{ draggingEdge: graphStore.mouseEditedEdge != null }"
:style="groupColors"
@dragover.prevent
@drop.prevent="handleFileDrop($event)"
>
@ -669,7 +671,6 @@ const groupColors = computed(() => {
<div
ref="viewportNode"
class="viewport"
:style="groupColors"
v-on.="graphNavigator.pointerEvents"
v-on..="nodeSelection.events"
@click="handleClick"
@ -696,10 +697,10 @@ const groupColors = computed(() => {
v-model:recordMode="projectStore.recordMode"
v-model:showColorPicker="showColorPicker"
v-model:showCodeEditor="showCodeEditor"
v-model:showDocumentationEditor="showDocumentationEditor"
v-model:showDocumentationEditor="showRightDock"
:zoomLevel="100.0 * graphNavigator.targetScale"
:componentsSelected="nodeSelection.selected.size"
:class="{ extraRightSpace: !showDocumentationEditor }"
:class="{ extraRightSpace: !showRightDock }"
@fitToAllClicked="zoomToSelected"
@zoomIn="graphNavigator.stepZoom(+1)"
@zoomOut="graphNavigator.stepZoom(-1)"
@ -719,15 +720,21 @@ const groupColors = computed(() => {
</Suspense>
</BottomPanel>
</div>
<DockPanel v-model:show="showDocumentationEditor" v-model:size="rightDockWidth">
<template #default="{ toolbar }">
<DockPanel
v-model:show="showRightDock"
v-model:size="rightDockWidth"
v-model:tab="rightDockTab"
>
<template #docs>
<DocumentationEditor
ref="docEditor"
:modelValue="documentation.state.value"
:toolbarContainer="toolbar"
@update:modelValue="documentation.set"
/>
</template>
<template #help>
<ComponentDocumentation />
</template>
</DockPanel>
</div>
</template>

View File

@ -34,8 +34,8 @@ 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 type { ExternalId, VisualizationIdentifier } from 'shared/yjsModel'
import { computed, onUnmounted, ref, shallowRef, watch, watchEffect } from 'vue'
import type { ExternalId, VisualizationIdentifier } from 'ydoc-shared/yjsModel'
const MAXIMUM_CLICK_LENGTH_MS = 300
const MAXIMUM_CLICK_DISTANCE_SQ = 50
@ -291,7 +291,7 @@ const isRecordingOverridden = computed({
const expressionInfo = computed(() => graph.db.getExpressionInfo(props.node.innerExpr.externalId))
const executionState = computed(() => expressionInfo.value?.payload.type ?? 'Unknown')
const suggestionEntry = computed(() => graph.db.nodeMainSuggestion.lookup(nodeId.value))
const suggestionEntry = computed(() => graph.db.getNodeMainSuggestion(nodeId.value))
const color = computed(() => graph.db.getNodeColorStyle(nodeId.value))
const documentationUrl = computed(
() => suggestionEntry.value && suggestionDocumentationUrl(suggestionEntry.value),

View File

@ -4,7 +4,6 @@ import { useDoubleClick } from '@/composables/doubleClick'
import { useGraphStore, type NodeId } from '@/stores/graph'
import { isDef } from '@vueuse/core'
import { setIfUndefined } from 'lib0/map'
import type { AstId } from 'shared/ast'
import {
computed,
effectScope,
@ -14,6 +13,7 @@ import {
watchEffect,
type EffectScope,
} from 'vue'
import type { AstId } from 'ydoc-shared/ast'
const props = defineProps<{ nodeId: NodeId; forceVisible: boolean }>()

View File

@ -12,8 +12,8 @@ import { useProjectStore } from '@/stores/project'
import type { AstId } from '@/util/ast/abstract'
import type { Vec2 } from '@/util/data/vec2'
import { set } from 'lib0'
import { stackItemsEqual } from 'shared/languageServerTypes'
import { computed, shallowRef, toRaw } from 'vue'
import { stackItemsEqual } from 'ydoc-shared/languageServerTypes'
const emit = defineEmits<{
nodeOutputPortDoubleClick: [portId: AstId]

View File

@ -23,8 +23,6 @@ import type { URLString } from '@/util/data/urlString'
import { Vec2 } from '@/util/data/vec2'
import type { Icon } from '@/util/iconName'
import { computedAsync } from '@vueuse/core'
import { isIdentifier } from 'shared/ast'
import { visIdentifierEquals, type VisualizationIdentifier } from 'shared/yjsModel'
import {
computed,
nextTick,
@ -36,6 +34,8 @@ import {
watchEffect,
type ShallowRef,
} from 'vue'
import { isIdentifier } from 'ydoc-shared/ast'
import { visIdentifierEquals, type VisualizationIdentifier } from 'ydoc-shared/yjsModel'
/** The minimum width must be at least the total width of:
* - both of toolbars that are always visible (32px + 60px), and

View File

@ -101,7 +101,7 @@ const widgetTree = provideWidgetTree(
)
const expressionInfo = computed(() => graph.db.getExpressionInfo(props.ast.externalId))
const suggestionEntry = computed(() => graph.db.nodeMainSuggestion.lookup(props.nodeId))
const suggestionEntry = computed(() => graph.db.getNodeMainSuggestion(props.nodeId))
const topLevelIcon = computed(() => {
switch (props.nodeType) {
default:

View File

@ -7,15 +7,11 @@ import {
} from '@/components/GraphEditor/clipboard'
import { type Node } from '@/stores/graph'
import { Ast } from '@/util/ast'
import { initializePrefixes, nodeFromAst } from '@/util/ast/node'
import { nodeFromAst } from '@/util/ast/node'
import { Blob } from 'node:buffer'
import { initializeFFI } from 'shared/ast/ffi'
import { assertDefined } from 'shared/util/assert'
import { type VisualizationMetadata } from 'shared/yjsModel'
import { expect, test } from 'vitest'
await initializeFFI()
initializePrefixes()
import { assertDefined } from 'ydoc-shared/util/assert'
import { type VisualizationMetadata } from 'ydoc-shared/yjsModel'
test.each([
{
@ -74,7 +70,7 @@ const testNodeInputs: {
const testNodes = testNodeInputs.map(({ code, visualization, colorOverride }) => {
const root = Ast.Ast.parse(code)
root.setNodeMetadata({ visualization, colorOverride })
const node = nodeFromAst(root)
const node = nodeFromAst(root, false)
assertDefined(node)
// `nodesToClipboardData` only needs the `NodeDataFromAst` fields of `Node`, because it reads the metadata directly
// from the AST.

View File

@ -2,15 +2,10 @@ import { prepareCollapsedInfo } from '@/components/GraphEditor/collapsing'
import { GraphDb, type NodeId } from '@/stores/graph/graphDatabase'
import { assert } from '@/util/assert'
import { Ast, RawAst } from '@/util/ast'
import { initializePrefixes } from '@/util/ast/node'
import { unwrap } from '@/util/data/result'
import { tryIdentifier } from '@/util/qualifiedName'
import { initializeFFI } from 'shared/ast/ffi'
import { expect, test } from 'vitest'
await initializeFFI()
initializePrefixes()
function setupGraphDb(code: string, graphDb: GraphDb) {
const { root, toRaw, getSpan } = Ast.parseExtended(code)
const expressions = Array.from(root.statements())

View File

@ -5,8 +5,8 @@ import { Pattern } from '@/util/ast/match'
import { filterDefined } from '@/util/data/iterable'
import { Vec2 } from '@/util/data/vec2'
import type { ToValue } from '@/util/reactivity'
import type { NodeMetadataFields } from 'shared/ast'
import { computed, toValue } from 'vue'
import type { NodeMetadataFields } from 'ydoc-shared/ast'
// MIME type in *vendor tree*; see https://www.rfc-editor.org/rfc/rfc6838#section-3.2
// The `web ` prefix is required by Chromium:

View File

@ -4,12 +4,12 @@ import type { DataServer } from '@/util/net/dataServer'
import { Keccak, sha3_224 as SHA3 } from '@noble/hashes/sha3'
import type { Hash } from '@noble/hashes/utils'
import { bytesToHex } from '@noble/hashes/utils'
import { escapeTextLiteral } from 'shared/ast'
import type { LanguageServer } from 'shared/languageServer'
import { ErrorCode, RemoteRpcError } from 'shared/languageServer'
import type { Path, StackItem, Uuid } from 'shared/languageServerTypes'
import { Err, Ok, withContext, type Result } from 'shared/util/data/result'
import { markRaw, toRaw } from 'vue'
import { escapeTextLiteral } from 'ydoc-shared/ast/text'
import type { LanguageServer } from 'ydoc-shared/languageServer'
import { ErrorCode, RemoteRpcError } from 'ydoc-shared/languageServer'
import type { Path, StackItem, Uuid } from 'ydoc-shared/languageServerTypes'
import { Err, Ok, withContext, type Result } from 'ydoc-shared/util/data/result'
// === Constants ===

View File

@ -4,8 +4,8 @@ import { injectPortInfo } from '@/providers/portInfo'
import { Score, WidgetInput, defineWidget, widgetProps } from '@/providers/widgetRegistry'
import { Ast } from '@/util/ast'
import { ApplicationKind, ArgumentInfoKey, ArgumentPlaceholder } from '@/util/callTree'
import type { SuggestionEntryArgument } from 'shared/languageServerTypes/suggestions'
import { computed } from 'vue'
import type { SuggestionEntryArgument } from 'ydoc-shared/languageServerTypes/suggestions'
const props = defineProps(widgetProps(widgetDefinition))

View File

@ -10,8 +10,8 @@ import type { RequiredImport } from '@/stores/graph/imports'
import { Ast } from '@/util/ast'
import { Pattern } from '@/util/ast/match'
import { ArgumentInfoKey } from '@/util/callTree'
import { TextLiteral } from 'shared/ast'
import { computed } from 'vue'
import { TextLiteral } from 'ydoc-shared/ast'
const props = defineProps(widgetProps(widgetDefinition))
const graph = useGraphStore()

View File

@ -25,8 +25,8 @@ 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'
import { methodPointerEquals, type MethodPointer } from 'ydoc-shared/languageServerTypes'
const props = defineProps(widgetProps(widgetDefinition))
const graph = useGraphStore()

View File

@ -9,9 +9,9 @@ import {
makeStaticMethod,
} from '@/stores/suggestionDatabase/entry'
import { assert } from '@/util/assert'
import type { Opt } from 'shared/util/data/opt'
import { expect, test } from 'vitest'
import { ref, type Ref } from 'vue'
import type { Opt } from 'ydoc-shared/util/data/opt'
import {
GET_WIDGETS_METHOD,
WIDGETS_ENSO_MODULE,

View File

@ -17,9 +17,9 @@ import {
} from '@/util/callTree'
import type { Result } from '@/util/data/result'
import type { ToValue } from '@/util/reactivity'
import type { Opt } from 'shared/util/data/opt'
import type { ExternalId } from 'shared/yjsModel'
import { computed, toValue, type Ref } from 'vue'
import type { Opt } from 'ydoc-shared/util/data/opt'
import type { ExternalId } from 'ydoc-shared/yjsModel'
export const WIDGETS_ENSO_MODULE = 'Standard.Visualization.Widgets'
export const GET_WIDGETS_METHOD = 'get_widget_json'

View File

@ -5,9 +5,9 @@ import { useProjectStore } from '@/stores/project'
import { Ast } from '@/util/ast'
import { Err, Ok, type Result } from '@/util/data/result'
import { useToast } from '@/util/toast'
import { PropertyAccess } from 'shared/ast'
import type { ExpressionId } from 'shared/languageServerTypes'
import { computed, ref, watchEffect } from 'vue'
import { PropertyAccess } from 'ydoc-shared/ast'
import type { ExpressionId } from 'ydoc-shared/languageServerTypes'
import NodeWidget from '../NodeWidget.vue'
const props = defineProps(widgetProps(widgetDefinition))

View File

@ -14,7 +14,6 @@ import { Ast } from '@/util/ast'
import { ArgumentInfoKey } from '@/util/callTree'
import { Rect } from '@/util/data/rect'
import { cachedGetter } from '@/util/reactivity'
import { isUuid } from 'shared/yjsModel'
import {
computed,
nextTick,
@ -26,6 +25,7 @@ import {
watch,
watchEffect,
} from 'vue'
import { isUuid } from 'ydoc-shared/yjsModel'
const props = defineProps(widgetProps(widgetDefinition))

View File

@ -3,8 +3,8 @@ import NodeWidget from '@/components/GraphEditor/NodeWidget.vue'
import { injectSelectionArrow } from '@/providers/selectionArrow'
import { Score, WidgetInput, defineWidget, widgetProps } from '@/providers/widgetRegistry'
import { Ast } from '@/util/ast'
import { assert } from 'shared/util/assert'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { assert } from 'ydoc-shared/util/assert'
import { ArgumentNameShownKey } from './WidgetArgumentName.vue'
const props = defineProps(widgetProps(widgetDefinition))

View File

@ -6,8 +6,8 @@ import type { PortId } from '@/providers/portInfo'
import { Score, WidgetInput, defineWidget, widgetProps } from '@/providers/widgetRegistry'
import { WidgetEditHandler } from '@/providers/widgetRegistry/editHandler'
import { Ast } from '@/util/ast'
import { isAstId } from 'shared/ast'
import { computed, shallowRef, toRef, toValue, watchEffect, type WatchSource } from 'vue'
import { isAstId } from 'ydoc-shared/ast'
const props = defineProps(widgetProps(widgetDefinition))

View File

@ -4,8 +4,8 @@ import {
type TransformUrlResult,
} from '@/components/MarkdownEditor/imageUrlTransformer'
import { computedAsync } from '@vueuse/core'
import { Ok } from 'shared/util/data/result'
import { computed, onUnmounted, type Ref } from 'vue'
import { Ok } from 'ydoc-shared/util/data/result'
const DEFAULT_ALT_TEXT = 'Image'

View File

@ -43,15 +43,15 @@ export class ImageNode extends DecoratorNode<Component> {
__src: string
__altText: string
static getType(): string {
static override getType(): string {
return 'image'
}
static clone(node: ImageNode): ImageNode {
static override clone(node: ImageNode): ImageNode {
return new ImageNode(node.__src, node.__altText, node.__key)
}
static importJSON(serializedNode: SerializedImageNode): ImageNode {
static override importJSON(serializedNode: SerializedImageNode): ImageNode {
const { altText, src } = serializedNode
return $createImageNode({
altText,
@ -59,7 +59,7 @@ export class ImageNode extends DecoratorNode<Component> {
})
}
static importDOM(): DOMConversionMap | null {
static override importDOM(): DOMConversionMap | null {
return {
img: (_node: Node) => ({
conversion: $convertImageElement,
@ -74,14 +74,14 @@ export class ImageNode extends DecoratorNode<Component> {
this.__altText = altText
}
exportDOM(): DOMExportOutput {
override exportDOM(): DOMExportOutput {
const element = document.createElement('img')
element.setAttribute('src', this.__src)
element.setAttribute('alt', this.__altText)
return { element }
}
exportJSON(): SerializedImageNode {
override exportJSON(): SerializedImageNode {
return {
altText: this.getAltText(),
src: this.getSrc(),
@ -113,7 +113,7 @@ export class ImageNode extends DecoratorNode<Component> {
// View
createDOM(config: EditorConfig): HTMLElement {
override createDOM(config: EditorConfig): HTMLElement {
const span = document.createElement('span')
const className = config.theme.image
if (className !== undefined) {
@ -122,7 +122,7 @@ export class ImageNode extends DecoratorNode<Component> {
return span
}
updateDOM(_prevNode: ImageNode, dom: HTMLElement, config: EditorConfig): false {
override updateDOM(_prevNode: ImageNode, dom: HTMLElement, config: EditorConfig): false {
const className = config.theme.image
if (className !== undefined) {
dom.className = className
@ -130,7 +130,7 @@ export class ImageNode extends DecoratorNode<Component> {
return false
}
decorate(): Component {
override decorate(): Component {
return h(LexicalImage, {
src: this.__src,
alt: this.__altText,

View File

@ -6,7 +6,7 @@ import {
import type { LexicalMarkdownPlugin } from '@/components/MarkdownEditor/markdown'
import type { TextMatchTransformer } from '@lexical/markdown'
import type { LexicalEditor } from 'lexical'
import { assertDefined } from 'shared/util/assert'
import { assertDefined } from 'ydoc-shared/util/assert'
export const IMAGE: TextMatchTransformer = {
dependencies: [ImageNode],

View File

@ -1,7 +1,7 @@
import { createContextStore } from '@/providers'
import type { ToValue } from '@/util/reactivity'
import { mapOk, Ok, type Result } from 'shared/util/data/result'
import { toValue } from 'vue'
import { mapOk, Ok, type Result } from 'ydoc-shared/util/data/result'
export type TransformUrlResult = Result<{ url: string; dispose?: () => void }>
export type UrlTransformer = (url: string) => Promise<TransformUrlResult>

View File

@ -64,6 +64,6 @@ const markdownSyncPlugin = (model: Ref<string>, transformers: Transformer[]): Le
},
)
watch(model, (newContent) => content.set(newContent), { immediate: true })
watch(content.state, (newContent) => (model.value = newContent))
watch(content.editedContent, (newContent) => (model.value = newContent))
},
})

View File

@ -20,7 +20,7 @@ const textSync: LexicalPlugin = {
register: (editor) => {
const { content } = useLexicalStringSync(editor)
watch(text, (newContent) => content.set(newContent), { immediate: true })
watch(content.state, (newContent) => (text.value = newContent))
watch(content.editedContent, (newContent) => (text.value = newContent))
},
}

View File

@ -2,8 +2,8 @@
import SvgIcon from '@/components/SvgIcon.vue'
import { useVisualizationStore } from '@/stores/visualization'
import { useAutoBlur } from '@/util/autoBlur'
import { visIdentifierEquals, type VisualizationIdentifier } from 'shared/yjsModel'
import { onMounted, ref } from 'vue'
import { visIdentifierEquals, type VisualizationIdentifier } from 'ydoc-shared/yjsModel'
const props = defineProps<{
types: Iterable<VisualizationIdentifier>

View File

@ -22,7 +22,7 @@ import {
COMMAND_PRIORITY_LOW,
TextNode,
} from 'lexical'
import { assert } from 'shared/util/assert'
import { assert } from 'ydoc-shared/util/assert'
type ChangeHandler = (url: string | null, prevUrl: string | null) => void

View File

@ -7,8 +7,8 @@ import type {
LexicalNodeReplacement,
} from 'lexical'
import { createEditor } from 'lexical'
import { assertDefined } from 'shared/util/assert'
import { markRaw, onMounted, type Ref } from 'vue'
import { assertDefined } from 'ydoc-shared/util/assert'
type NodeDefinition = KlassConstructor<typeof LexicalNode> | LexicalNodeReplacement

View File

@ -32,13 +32,11 @@ export function useLexicalSync<T>(
state.value = editorState
})
const getContent = computed(() => {
return state.value.read($read)
})
const getContent = () => editor.getEditorState().read($read)
return {
content: {
state: getContent,
editedContent: computed(() => state.value.read($read)),
set: (content: T) => {
editor.update(() => $write(content, getContent), {
discrete: true,

View File

@ -6,7 +6,7 @@ import { useAppClass } from '@/providers/appClass'
import { Range } from '@/util/data/range'
import { Vec2 } from '@/util/data/vec2'
import { uuidv4 } from 'lib0/random'
import { computed, nextTick, ref, shallowReactive, watchEffect, watchPostEffect } from 'vue'
import { computed, ref, shallowReactive, watchEffect, watchPostEffect } from 'vue'
</script>
<script setup lang="ts" generic="T">

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { usePointer } from '@/composables/events'
import { isNumericLiteral } from 'shared/ast/tree'
import { computed, ref, watch, type CSSProperties, type ComponentInstance } from 'vue'
import { isNumericLiteral } from 'ydoc-shared/ast/tree'
import AutoSizedInput from './AutoSizedInput.vue'
const props = defineProps<{

View File

@ -1,25 +1,20 @@
import { useNavigator } from '@/composables/navigator'
import { Rect } from '@/util/data/rect'
import { Vec2 } from '@/util/data/vec2'
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import { effectScope, ref } from 'vue'
import { withSetup } from '@/util/testing'
import { describe, expect, test, vi } from 'vitest'
import { ref } from 'vue'
import { useKeyboard } from '../keyboard'
describe('useNavigator', async () => {
let scope = effectScope()
beforeEach(() => {
scope = effectScope()
})
afterEach(() => scope.stop())
function makeTestNavigator() {
return scope.run(() => {
return withSetup(() => {
const node = document.createElement('div')
vi.spyOn(node, 'getBoundingClientRect').mockReturnValue(new DOMRect(150, 150, 800, 400))
const viewportNode = ref(node)
const keyboard = useKeyboard()
return useNavigator(viewportNode, keyboard)
})!
})[0]!
}
test('initializes with centered non-zoomed viewport', () => {

View File

@ -1,10 +1,7 @@
import { insertNodeStatements } from '@/composables/nodeCreation'
import { Ast } from '@/util/ast'
import { identifier } from 'shared/ast'
import { initializeFFI } from 'shared/ast/ffi'
import { expect, test } from 'vitest'
await initializeFFI()
import { identifier } from 'ydoc-shared/ast'
test.each([
['node1 = 123', '*'],

View File

@ -63,6 +63,7 @@ export function useRaf(
},
{ immediate: true },
)
console.log('onScopeDispose(unmountRaf)?')
onScopeDispose(unmountRaf)
}

View File

@ -1,7 +1,7 @@
import { type GraphStore } from '@/stores/graph'
import { type ToValue } from '@/util/reactivity'
import type { Ast } from 'shared/ast'
import { computed, toValue } from 'vue'
import type { Ast } from 'ydoc-shared/ast'
export function useAstDocumentation(graphStore: GraphStore, ast: ToValue<Ast | undefined>) {
return {

View File

@ -17,9 +17,9 @@ import { Rect } from '@/util/data/rect'
import { Vec2 } from '@/util/data/vec2'
import { qnLastSegment, tryQualifiedName } from '@/util/qualifiedName'
import type { ToValue } from '@/util/reactivity'
import { assert, assertNever } from 'shared/util/assert'
import { mustExtend } from 'shared/util/types'
import { toValue } from 'vue'
import { assert, assertNever } from 'ydoc-shared/util/assert'
import { mustExtend } from 'ydoc-shared/util/types'
export type NodeCreation = ReturnType<typeof useNodeCreation>

View File

@ -10,6 +10,7 @@ import type { Vec2 } from '@/util/data/vec2'
import { dataAttribute, elementHierarchy } from '@/util/dom'
import * as set from 'lib0/set'
import { computed, ref, shallowReactive, shallowRef } from 'vue'
import { Err, Ok, type Result } from 'ydoc-shared/util/data/result'
interface BaseSelectionOptions<T> {
margin?: number
@ -119,6 +120,17 @@ function useSelectionImpl<T, PackedT>(
setSelection(newSelection)
}
/** Returns the single selected component, or an error. */
function tryGetSoleSelection(): Result<T, string> {
if (selected.value.size === 0) {
return Err('No component selected')
} else if (selected.value.size > 1) {
return Err('Multiple components selected')
} else {
return Ok(set.first(selected.value)!)
}
}
const selectionEventHandler = selectionMouseBindings.handler({
replace() {
setSelection(elementsToSelect.value)
@ -215,6 +227,7 @@ function useSelectionImpl<T, PackedT>(
},
committedSelection,
setSelection,
tryGetSoleSelection,
// === Selection changes ===
anchor,
isChanging,

View File

@ -2,8 +2,8 @@ import type { BreadcrumbItem } from '@/components/NavBreadcrumbs.vue'
import { type GraphStore, type NodeId } from '@/stores/graph'
import { type ProjectStore } from '@/stores/project'
import { qnLastSegment, tryQualifiedName } from '@/util/qualifiedName'
import { methodPointerEquals, type StackItem } from 'shared/languageServerTypes'
import { computed, onMounted, ref } from 'vue'
import { methodPointerEquals, type StackItem } from 'ydoc-shared/languageServerTypes'
export function useStackNavigator(projectStore: ProjectStore, graphStore: GraphStore) {
const breadcrumbs = ref<StackItem[]>([])

View File

@ -1,9 +1,9 @@
import { useAbortScope } from '@/util/net'
import { debouncedWatch, useLocalStorage } from '@vueuse/core'
import { encoding } from 'lib0'
import { xxHash128 } from 'shared/ast/ffi'
import { AbortScope } from 'shared/util/net'
import { computed, getCurrentInstance, ref, watch, withCtx } from 'vue'
import { xxHash128 } from 'ydoc-shared/ast/ffi'
import { AbortScope } from 'ydoc-shared/util/net'
export interface SyncLocalStorageOptions<StoredState> {
/**

View File

@ -5,14 +5,14 @@
*/
import { MockYdocProvider } from '@/util/crdt'
import { MockTransport, MockWebSocket } from '@/util/net'
import { MockWebSocket, MockWebSocketTransport } from '@/util/net'
import { mockDataHandler, mockLSHandler, mockYdocProvider } from '../mock/engine'
import 'enso-dashboard/src/tailwind.css'
import { createApp } from 'vue'
import { AsyncApp } from './asyncApp'
MockTransport.addMock('engine', mockLSHandler)
MockWebSocketTransport.addMock('engine', mockLSHandler)
MockWebSocket.addMock('data', mockDataHandler)
MockYdocProvider.addMock('engine', mockYdocProvider)

View File

@ -5,7 +5,6 @@ import { isOnLinux } from 'enso-common/src/detect'
import * as commonQuery from 'enso-common/src/queryClient'
import * as dashboard from 'enso-dashboard'
import 'enso-dashboard/src/tailwind.css'
import { isDevMode } from 'shared/util/detect'
import { lazyVueInReact } from 'veaury'
import { type App } from 'vue'
@ -14,6 +13,7 @@ import { AsyncApp } from './asyncApp'
const INITIAL_URL_KEY = `Enso-initial-url`
const SCAM_WARNING_TIMEOUT = 1000
export const isDevMode = process.env.NODE_ENV === 'development'
function printScamWarning() {
if (isDevMode) return

View File

@ -9,14 +9,11 @@ import {
import { GraphDb } from '@/stores/graph/graphDatabase'
import { Ast } from '@/util/ast'
import { ApplicationKind, ArgumentInfoKey } from '@/util/callTree'
import { initializeFFI } from 'shared/ast/ffi'
import { describe, expect, test } from 'vitest'
import { defineComponent } from 'vue'
import type { PortId } from '../portInfo'
import { DisplayMode, argsWidgetConfigurationSchema } from '../widgetRegistry/configuration'
await initializeFFI()
describe('WidgetRegistry', () => {
function makeMockWidget<T extends WidgetInput>(
name: string,

View File

@ -3,8 +3,8 @@ import { useGraphHover, useSelection, type SelectionOptions } from '@/composable
import { createContextStore } from '@/providers'
import { type NodeId } from '@/stores/graph'
import type { Rect } from '@/util/data/rect'
import type { ExternalId } from 'shared/yjsModel'
import { proxyRefs } from 'vue'
import type { ExternalId } from 'ydoc-shared/yjsModel'
const SELECTION_BRUSH_MARGIN_PX = 6

View File

@ -3,8 +3,8 @@ import { createContextStore } from '@/providers'
import type { URLString } from '@/util/data/urlString'
import { Vec2 } from '@/util/data/vec2'
import type { Icon } from '@/util/iconName'
import type { VisualizationIdentifier } from 'shared/yjsModel'
import { reactive } from 'vue'
import type { VisualizationIdentifier } from 'ydoc-shared/yjsModel'
export interface VisualizationConfig {
background?: string

View File

@ -4,8 +4,8 @@ import type { WidgetConfiguration } from '@/providers/widgetRegistry/configurati
import type { GraphDb } from '@/stores/graph/graphDatabase'
import type { Typename } from '@/stores/suggestionDatabase/entry'
import { Ast } from '@/util/ast'
import { MutableModule } from '@/util/ast/abstract.ts'
import type { ViteHotContext } from 'vite/types/hot'
import { MutableModule } from '@/util/ast/abstract'
import { ViteHotContext } from 'vite/types/hot.js'
import { computed, shallowReactive, type Component, type PropType } from 'vue'
import type { WidgetEditHandlerParent } from './widgetRegistry/editHandler'

View File

@ -2,9 +2,9 @@ import type { GraphNavigator } from '@/providers/graphNavigator'
import { InteractionHandler } from '@/providers/interactionHandler'
import type { PortId } from '@/providers/portInfo'
import { useCurrentEdit, type CurrentEdit } from '@/providers/widgetTree'
import { assert } from 'shared/util/assert'
import { expect, test, vi, type Mock } from 'vitest'
import { proxyRefs } from 'vue'
import { assert } from 'ydoc-shared/util/assert'
import { WidgetEditHandler, type WidgetEditHooks } from '../editHandler'
// If widget's name is a prefix of another widget's name, then it is its ancestor.

Some files were not shown because too many files have changed in this diff Show More