mirror of
https://github.com/enso-org/enso.git
synced 2024-12-01 22:23:30 +03:00
Merge branch 'develop' into wip/frizi/bazel
This commit is contained in:
commit
fd6824c88e
6
.github/workflows/gui-tests.yml
vendored
6
.github/workflows/gui-tests.yml
vendored
@ -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'
|
||||
|
6
.github/workflows/gui.yml
vendored
6
.github/workflows/gui.yml
vendored
@ -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
2
.gitignore
vendored
@ -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
21
.pnpmfile.cjs
Normal 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
|
||||
},
|
||||
}
|
@ -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
|
||||
|
@ -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
53
Cargo.lock
generated
@ -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"
|
||||
|
14
Cargo.toml
14
Cargo.toml
@ -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" }
|
||||
|
@ -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",
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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. */
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
@ -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": {
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -742,6 +742,7 @@ export default function AssetRow(props: AssetRowProps) {
|
||||
{!hidden && (
|
||||
<FocusRing>
|
||||
<tr
|
||||
data-testid="asset-row"
|
||||
tabIndex={0}
|
||||
ref={(element) => {
|
||||
rootRef.current = element
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -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.
|
||||
|
@ -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 }
|
||||
|
||||
|
@ -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/)
|
||||
})
|
@ -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
1
app/gui2/env.d.ts
vendored
@ -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 {
|
||||
|
1
app/gui2/env.story.d.ts
vendored
1
app/gui2/env.story.d.ts
vendored
@ -1 +0,0 @@
|
||||
/// <reference types="@histoire/plugin-vue/components" />
|
@ -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: {
|
||||
|
@ -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()
|
||||
|
@ -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] }>()
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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' }
|
||||
|
@ -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:*"
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
@ -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,
|
||||
|
@ -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 }
|
@ -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 */
|
@ -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
|
@ -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'),
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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: []
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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 },
|
||||
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
@ -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 = '`'
|
||||
|
@ -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. */
|
||||
|
50
app/gui2/src/components/ComponentDocumentation.vue
Normal file
50
app/gui2/src/components/ComponentDocumentation.vue
Normal 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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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] }>()
|
||||
|
@ -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 ===
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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),
|
||||
|
@ -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 }>()
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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())
|
||||
|
@ -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:
|
||||
|
@ -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 ===
|
||||
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
|
@ -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'
|
||||
|
@ -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))
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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],
|
||||
|
@ -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>
|
||||
|
@ -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))
|
||||
},
|
||||
})
|
||||
|
@ -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))
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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">
|
||||
|
@ -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<{
|
||||
|
@ -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', () => {
|
||||
|
@ -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', '*'],
|
||||
|
@ -63,6 +63,7 @@ export function useRaf(
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
console.log('onScopeDispose(unmountRaf)?')
|
||||
onScopeDispose(unmountRaf)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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[]>([])
|
||||
|
@ -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> {
|
||||
/**
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user