diff --git a/app/gui2/e2e/actions.ts b/app/gui2/e2e/actions.ts index a7e285488cd..dfbdf43eb23 100644 --- a/app/gui2/e2e/actions.ts +++ b/app/gui2/e2e/actions.ts @@ -24,6 +24,13 @@ export async function goToGraph(page: Page, closeDocPanel: boolean = true) { await expectNodePositionsInitialized(page, -16) } +/** + * Run assertions for nodes and edges positions being properly initialized. + * + * Usually, after opening project or entering a node, we need some ticks for placing both nodes + * and edges properly on the screen. If test relies on their positions, it must ensure this + * initialization is done. + */ export async function expectNodePositionsInitialized(page: Page, yPos: number) { // Wait until edges are initialized and displayed correctly. await expect(page.getByTestId('broken-edge')).toBeHidden() @@ -38,6 +45,7 @@ export async function expectNodePositionsInitialized(page: Page, yPos: number) { ) } +/** Exit the currently opened graph (of collapsed function). */ export async function exitFunction(page: Page, x = 300, y = 300) { await locate.graphEditor(page).dblclick({ position: { x, y } }) } @@ -46,7 +54,7 @@ export async function exitFunction(page: Page, x = 300, y = 300) { // === Drag Node === // ================= -/// Move node defined by the given binding by the given x and y. +/** Move node defined by the given binding by the given x and y. */ export async function dragNodeByBinding(page: Page, nodeBinding: string, x: number, y: number) { const node = graphNodeByBinding(page, nodeBinding) const grabHandle = node.locator('.grab-handle') @@ -56,13 +64,13 @@ export async function dragNodeByBinding(page: Page, nodeBinding: string, x: numb }) } -/// Move mouse away to avoid random hover events and wait for any circular menus to disappear. +/** Move mouse away to avoid random hover events and wait for any circular menus to disappear. */ export async function ensureNoCircularMenusVisibleDueToHovering(page: Page) { await page.mouse.move(-1000, 0) await expect(locate.circularMenu(page)).toBeHidden() } -/// Ensure no nodes are selected. +/** Ensure no nodes are selected. */ export async function deselectNodes(page: Page) { await page.mouse.click(0, 0) await expect(locate.selectedNodes(page)).toHaveCount(0) diff --git a/app/gui2/e2e/css.ts b/app/gui2/e2e/css.ts index d034e0f4bc5..2ca39ff291f 100644 --- a/app/gui2/e2e/css.ts +++ b/app/gui2/e2e/css.ts @@ -1,7 +1,8 @@ import type { ElementHandle } from 'playwright' -/** Returns text content of the element, including CSS ::before and ::after content in the element's tree. - * Currently whitespace produced around pseudo-elements is unspecified; block/inline logic is not implemented. +/** + * Returns text content of the element, including CSS ::before and ::after content in the element's tree. + * Currently whitespace produced around pseudo-elements is unspecified; block/inline logic is not implemented. */ export function computedContent(element: ElementHandle): Promise { return element.evaluate((element) => { diff --git a/app/gui2/e2e/customExpect.ts b/app/gui2/e2e/customExpect.ts index b764c600fe7..aa5b6d8ee09 100644 --- a/app/gui2/e2e/customExpect.ts +++ b/app/gui2/e2e/customExpect.ts @@ -1,8 +1,10 @@ import { expect as baseExpect, type Locator } from 'playwright/test' export const expect = baseExpect.extend({ - /** Ensures that at least one of the elements that the Locator points to, - * is an attached and visible DOM node. */ + /** + * Ensures that at least one of the elements that the Locator points to, + * is an attached and visible DOM node. + */ async toExist(locator: Locator) { // Counter-intuitive, but correct: // https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-be-visible diff --git a/app/gui2/e2e/locate.ts b/app/gui2/e2e/locate.ts index cc6d31ceea0..96932d8799d 100644 --- a/app/gui2/e2e/locate.ts +++ b/app/gui2/e2e/locate.ts @@ -44,20 +44,24 @@ function or(a: (page: Locator | Page) => Locator, b: (page: Locator | Page) => L return (page: Locator | Page) => a(page).or(b(page)) } +/** Show/hide visualization button */ export function toggleVisualizationButton(page: Locator | Page) { return page.getByLabel('Visualization', { exact: true }) } +/** Visualization Selector button */ export function toggleVisualizationSelectorButton(page: Locator | Page) { return page.getByLabel('Visualization Selector') } // === Fullscreen === +/** Enter fullscreen */ export function enterFullscreenButton(page: Locator | Page) { return page.getByLabel('Fullscreen') } +/** Exit fullscreen */ export function exitFullscreenButton(page: Locator | Page) { return page.getByLabel('Exit Fullscreen') } @@ -67,23 +71,31 @@ export const toggleFullscreenButton = or(enterFullscreenButton, exitFullscreenBu // === Nodes === declare const nodeLocatorBrand: unique symbol + +/** A locator which resolves to graph nodes only */ export type Node = Locator & { [nodeLocatorBrand]: never } +/** All nodes in graph */ export function graphNode(page: Page | Locator): Node { return page.locator('.GraphNode') as Node } +/** Node with given binding (name) */ export function graphNodeByBinding(page: Locator | Page, binding: string): Node { return graphNode(page).filter({ has: page.locator('.binding', { hasText: binding }) }) as Node } +/** Icon inside the node */ export function graphNodeIcon(node: Node) { return node.locator('.nodeCategoryIcon') } +/** All selected nodes */ export function selectedNodes(page: Page | Locator): Node { return page.locator('.GraphNode.selected') as Node } +/** All input nodes */ export function inputNode(page: Page | Locator): Node { return page.locator('.GraphNode.inputNode') as Node } +/** All output nodes */ export function outputNode(page: Page | Locator): Node { return page.locator('.GraphNode.outputNode') as Node } @@ -107,6 +119,11 @@ export const nodeOutputPort = componentLocator('.outputPortHoverArea') export const smallPlusButton = componentLocator('.SmallPlusButton') export const lexicalContent = componentLocator('.LexicalContent') +/** + * A not-selected variant of Component Browser Entry. + * + * It may be covered by selected one due to way we display them. + */ export function componentBrowserEntry( page: Locator | Page, filter?: (f: Filter) => { selector: string }, @@ -116,6 +133,7 @@ export function componentBrowserEntry( ) } +/** A selected variant of Component Browser Entry */ export function componentBrowserSelectedEntry( page: Locator | Page, filter?: (f: Filter) => { selector: string }, @@ -125,10 +143,12 @@ export function componentBrowserSelectedEntry( ) } +/** A not-selected variant of Component Browser entry with given label */ export function componentBrowserEntryByLabel(page: Locator | Page, label: string) { return componentBrowserEntry(page).filter({ has: page.getByText(label) }) } +/** Right-docked panel */ export function rightDock(page: Page) { return page.getByTestId('rightDock') } @@ -138,6 +158,7 @@ export function rightDockRoot(page: Page) { return page.getByTestId('rightDockRoot') } +/** Bottom-docked panel */ export function bottomDock(page: Page) { return page.getByTestId('bottomDock') } @@ -167,12 +188,14 @@ export const warningsVisualization = visualizationLocator('.WarningsVisualizatio // === Edge locators === +/** All edges going from a node with given binding. */ export async function edgesFromNodeWithBinding(page: Page, binding: string) { const node = graphNodeByBinding(page, binding).first() const nodeId = await node.getAttribute('data-node-id') return page.locator(`[data-source-node-id="${nodeId}"]`) } +/** All edges going to a node with given binding. */ export async function edgesToNodeWithBinding(page: Page, binding: string) { const node = graphNodeByBinding(page, binding).first() const nodeId = await node.getAttribute('data-node-id') @@ -181,8 +204,9 @@ export async function edgesToNodeWithBinding(page: Page, binding: string) { // === Output ports === -/** Returns a location that can be clicked to activate an output port. - * Using a `Locator` would be better, but `position` option of `click` doesn't work. +/** + * Returns a location that can be clicked to activate an output port. + * Using a `Locator` would be better, but `position` option of `click` doesn't work. */ export async function outputPortCoordinates(node: Locator) { const outputPortArea = await node.locator('.outputPortHoverArea').boundingBox() diff --git a/app/gui2/e2e/nodeClipboard.spec.ts b/app/gui2/e2e/nodeClipboard.spec.ts index 61efb1e0d9f..155b4b25bd2 100644 --- a/app/gui2/e2e/nodeClipboard.spec.ts +++ b/app/gui2/e2e/nodeClipboard.spec.ts @@ -5,8 +5,10 @@ import { CONTROL_KEY } from './keyboard' import * as locate from './locate' import { edgesFromNodeWithBinding, edgesToNodeWithBinding } from './locate' -/** Every edge consists of multiple parts. - * See e2e/edgeRendering.spec.ts for explanation. */ +/** + * Every edge consists of multiple parts. + * See e2e/edgeRendering.spec.ts for explanation. + */ const EDGE_PARTS = 2 test.beforeEach(async ({ page }) => { diff --git a/app/gui2/e2e/setup.ts b/app/gui2/e2e/setup.ts index 72b1b2c2b58..ddc8bcb2ea1 100644 --- a/app/gui2/e2e/setup.ts +++ b/app/gui2/e2e/setup.ts @@ -9,6 +9,11 @@ import { } from '../mock/projectManager' import pmSpec from './pm-openrpc.json' assert { type: 'json' } +/** + * Setup for all E2E tests. + * + * It runs mocked project manager server. + */ export default function setup() { const pm = new Server({ transportConfigs: [ diff --git a/app/gui2/env.d.ts b/app/gui2/env.d.ts index 4d194760997..3644e9c3942 100644 --- a/app/gui2/env.d.ts +++ b/app/gui2/env.d.ts @@ -12,7 +12,8 @@ interface Window { fileBrowserApi: FileBrowserApi } -/** `window.fileBrowserApi` is a context bridge to the main process, when we're running in an +/** + * `window.fileBrowserApi` is a context bridge to the main process, when we're running in an * Electron context. * * # Safety diff --git a/app/gui2/eslint.config.js b/app/gui2/eslint.config.js index 91420a71ae2..9eeb2af849c 100644 --- a/app/gui2/eslint.config.js +++ b/app/gui2/eslint.config.js @@ -1,5 +1,6 @@ import { FlatCompat } from '@eslint/eslintrc' import eslintJs from '@eslint/js' +import jsdoc from 'eslint-plugin-jsdoc' import * as path from 'node:path' import * as url from 'node:url' @@ -23,6 +24,7 @@ const conf = [ ...compat.extends('@vue/eslint-config-typescript', '@vue/eslint-config-prettier'), { // files: ['{**,src}/*.{vue,js,jsx,cjs,mjs,ts,tsx,cts,mts}'], + plugins: { jsdoc }, languageOptions: { parserOptions: { tsconfigRootDir: DIR_NAME, @@ -51,6 +53,34 @@ const conf = [ 'vue/multi-word-component-names': 0, }, }, + // JsDoc lints for typescript - the recommended set with some modifications. + { + ignores: ['**/*.js'], + ...jsdoc.configs['flat/recommended-typescript'], + rules: { + ...jsdoc.configs['flat/recommended-typescript'].rules, + 'jsdoc/check-param-names': [ + 'warn', + { checkDestructured: false, disableMissingParamChecks: true }, + ], + 'jsdoc/require-jsdoc': [ + 'warn', + { + publicOnly: true, + require: { + FunctionDeclaration: true, + MethodDefinition: true, + ClassDeclaration: true, + ArrowFunctionExpression: false, + FunctionExpression: true, + }, + }, + ], + 'jsdoc/require-param': 'off', + 'jsdoc/require-returns': 'off', + 'jsdoc/require-yields': 'off', + }, + }, // We must make sure our E2E tests await all steps, otherwise they're flaky. { files: ['e2e/**/*.spec.ts'], diff --git a/app/gui2/mock/dataServer.ts b/app/gui2/mock/dataServer.ts index cb67a478853..449526e8342 100644 --- a/app/gui2/mock/dataServer.ts +++ b/app/gui2/mock/dataServer.ts @@ -66,6 +66,7 @@ const PAYLOAD_CONSTRUCTOR = { [InboundPayload.CHECKSUM_BYTES_CMD]: ChecksumBytesCommand, } satisfies Record Table> +/** TODO: Add docs */ export function mockDataWSHandler( readFile: (segments: string[]) => Promise, cb?: (send: (data: string | Blob | ArrayBufferLike | ArrayBufferView) => void) => void, diff --git a/app/gui2/mock/engine.ts b/app/gui2/mock/engine.ts index 0879aa60f86..be439d66816 100644 --- a/app/gui2/mock/engine.ts +++ b/app/gui2/mock/engine.ts @@ -33,7 +33,7 @@ import { mockDataWSHandler as originalMockDataWSHandler } from './dataServer' const mockProjectId = random.uuidv4() as Uuid const standardBase = 'Standard.Base' as QualifiedName -export function placeholderGroups(): LibraryComponentGroup[] { +function placeholderGroups(): LibraryComponentGroup[] { return [ { color: '#4D9A29', name: 'Input', library: standardBase, exports: [] }, { color: '#B37923', name: 'Web', library: standardBase, exports: [] }, @@ -45,7 +45,7 @@ export function placeholderGroups(): LibraryComponentGroup[] { ] } -let mainFile = `\ +const mainFile = `\ ## Module documentation from Standard.Base import all @@ -77,14 +77,6 @@ main = selected = data.select_columns ` -export function getMainFile() { - return mainFile -} - -export function setMainFile(newMainFile: string) { - return (mainFile = newMainFile) -} - const fileTree = { src: { get 'Main.enso'() { diff --git a/app/gui2/mock/vue.ts b/app/gui2/mock/vue.ts deleted file mode 100644 index 07db92da20b..00000000000 --- a/app/gui2/mock/vue.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { type VueWrapper } from '@vue/test-utils' -import { nextTick } from 'vue' - -// It is currently not feasible to use generics here, as the type of the component's emits -// is not exposed. -export function handleEmit(wrapper: VueWrapper, event: string, fn: (...args: any[]) => void) { - let previousLength = 0 - return { - async run() { - const emitted = wrapper.emitted(event) - if (!emitted) return - for (let i = previousLength; i < emitted.length; i += 1) { - fn(...emitted[i]!) - } - previousLength = emitted.length - await nextTick() - }, - } -} diff --git a/app/gui2/playwright.config.ts b/app/gui2/playwright.config.ts index 31dd4fb931d..fc1f19b9f93 100644 --- a/app/gui2/playwright.config.ts +++ b/app/gui2/playwright.config.ts @@ -1,9 +1,11 @@ /** @file Playwright browser testing configuration. */ -/** Note that running Playwright in CI poses a number of issues: +/** + * Note that running Playwright in CI poses a number of issues: * - `backdrop-filter: blur` is disabled, due to issues with Chromium's `--disable-gpu` flag * (see below). * - System validation dialogs are not reliable between computers, as they may have different - * default fonts. */ + * default fonts. + */ import { defineConfig } from '@playwright/test' import net from 'net' diff --git a/app/gui2/project-manager-shim-middleware/index.ts b/app/gui2/project-manager-shim-middleware/index.ts index e7d2bbb3810..c60cc024061 100644 --- a/app/gui2/project-manager-shim-middleware/index.ts +++ b/app/gui2/project-manager-shim-middleware/index.ts @@ -1,5 +1,7 @@ -/** @file A HTTP server middleware which handles routes normally proxied through to - * the Project Manager. */ +/** + * @file A HTTP server middleware which handles routes normally proxied through to + * the Project Manager. + */ import * as fsSync from 'node:fs' import * as fs from 'node:fs/promises' import * as http from 'node:http' @@ -34,11 +36,13 @@ interface ProjectMetadata { readonly namespace: string /** The project id. */ readonly id: string - /** The Enso Engine version to use for the project, represented by a semver version + /** + * The Enso Engine version to use for the project, represented by a semver version * string. * * If the edition associated with the project could not be resolved, the - * engine version may be missing. */ + * engine version may be missing. + */ readonly engineVersion?: string /** The project creation time. */ readonly created: string @@ -406,8 +410,10 @@ export default function projectManagerShimMiddleware( } } -/** Return a {@link ProjectMetadata} if the metadata is a valid metadata object, - * else return `null`. */ +/** + * Return a {@link ProjectMetadata} if the metadata is a valid metadata object, + * else return `null`. + */ function extractProjectMetadata(yamlObj: unknown, jsonObj: unknown): ProjectMetadata | null { if ( typeof yamlObj !== 'object' || diff --git a/app/gui2/project-manager-shim-middleware/projectManagement.ts b/app/gui2/project-manager-shim-middleware/projectManagement.ts index 2e86517019d..b2f80940622 100644 --- a/app/gui2/project-manager-shim-middleware/projectManagement.ts +++ b/app/gui2/project-manager-shim-middleware/projectManagement.ts @@ -1,4 +1,5 @@ -/** @file This module contains functions for importing projects into the Project Manager. +/** + * @file This module contains functions for importing projects into the Project Manager. * * Eventually this module should be replaced with a new Project Manager API that supports * importing projects. @@ -6,7 +7,8 @@ * - if the project is already in the Project Manager's location, we just open it; * - if the project is in a different location, we copy it to the Project Manager's location * and open it. - * - if the project is a bundle, we extract it to the Project Manager's location and open it. */ + * - if the project is a bundle, we extract it to the Project Manager's location and open it. + */ import * as crypto from 'node:crypto' import * as fs from 'node:fs' import * as os from 'node:os' @@ -64,8 +66,10 @@ export async function uploadBundle( /** The Project Manager's metadata associated with a project. */ interface ProjectMetadata { - /** The ID of the project. It is only used in communication with project manager; - * it has no semantic meaning. */ + /** + * The ID of the project. It is only used in communication with project manager; + * it has no semantic meaning. + */ readonly id: string /** The project variant. This is currently always `UserProject`. */ readonly kind: 'UserProject' @@ -75,14 +79,16 @@ interface ProjectMetadata { readonly lastOpened: string } -/** A type guard function to check if an object conforms to the {@link ProjectMetadata} interface. +/** + * A type guard function to check if an object conforms to the {@link ProjectMetadata} interface. * * This function checks if the input object has the required properties and correct types * to match the {@link ProjectMetadata} interface. It can be used at runtime to validate that * a given object has the expected shape. * @param value - The object to check against the ProjectMetadata interface. * @returns A boolean value indicating whether the object matches - * the {@link ProjectMetadata} interface. */ + * the {@link ProjectMetadata} interface. + */ function isProjectMetadata(value: unknown): value is ProjectMetadata { return typeof value === 'object' && value != null && 'id' in value && typeof value.id === 'string' } @@ -132,10 +138,12 @@ function writeMetadata(projectRoot: string, metadata: ProjectMetadata): void { fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 4)) } -/** Update the project's metadata. +/** + * Update the project's metadata. * If the provided updater does not return anything, the metadata file is left intact. * - * Returns the metadata returned from the updater function. */ + * Returns the metadata returned from the updater function. + */ function updateMetadata( projectRoot: string, updater: (initialMetadata: ProjectMetadata) => ProjectMetadata, @@ -150,8 +158,10 @@ function updateMetadata( // === Project Directory === // ========================= -/** Check if the given path represents the root of an Enso project. - * This is decided by the presence of the Project Manager's metadata. */ +/** + * Check if the given path represents the root of an Enso project. + * This is decided by the presence of the Project Manager's metadata. + */ function isProjectRoot(candidatePath: string): boolean { const projectJsonPath = pathModule.join(candidatePath, PROJECT_METADATA_RELATIVE_PATH) try { @@ -162,13 +172,15 @@ function isProjectRoot(candidatePath: string): boolean { } } -/** Generate a name for a project using given base string. A suffix is added if there is a +/** + * Generate a name for a project using given base string. A suffix is added if there is a * collision. * * For example `Name` will become `Name_1` if there's already a directory named `Name`. * If given a name like `Name_1` it will become `Name_2` if there is already a directory named * `Name_1`. If a path containing multiple components is given, only the last component is used - * for the name. */ + * for the name. + */ function generateDirectoryName(name: string, directory = getProjectsDirectory()): string { // Use only the last path component. name = pathModule.parse(name).name @@ -198,8 +210,10 @@ function generateDirectoryName(name: string, directory = getProjectsDirectory()) return finalPath } -/** Take a path to a file, presumably located in a project's subtree.Returns the path - * to the project's root directory or `null` if the file is not located in a project. */ +/** + * Take a path to a file, presumably located in a project's subtree.Returns the path + * to the project's root directory or `null` if the file is not located in a project. + */ export function getProjectRoot(subtreePath: string): string | null { let currentPath = subtreePath while (!isProjectRoot(currentPath)) { diff --git a/app/gui2/scripts/downloadFonts.js b/app/gui2/scripts/downloadFonts.js index 793244723af..f2108073bc3 100644 --- a/app/gui2/scripts/downloadFonts.js +++ b/app/gui2/scripts/downloadFonts.js @@ -1,6 +1,8 @@ -/** @file ⚠️⚠️⚠️ THIS SCRIPT IS PROVIDED ONLY FOR CONVENIENCE. ⚠️⚠️⚠️ +/** + * @file ⚠️⚠️⚠️ THIS SCRIPT IS PROVIDED ONLY FOR CONVENIENCE. ⚠️⚠️⚠️ * The sources of truth are at `build/build/src/project/gui.rs` and - * `build/build/src/ide/web/fonts.rs`. */ + * `build/build/src/ide/web/fonts.rs`. + */ import * as fsSync from 'node:fs' import * as fs from 'node:fs/promises' @@ -23,8 +25,10 @@ const MPLUS1_FONT_URL = const DEJAVU_SANS_MONO_FONT_URL = 'https://sourceforge.net/projects/dejavu/files/dejavu/2.37/dejavu-fonts-ttf-2.37.tar.bz2' -/** @param {string | https.RequestOptions | URL} options - * @param {((res: import('node:http').IncomingMessage) => void) | undefined} [callback] */ +/** + * @param {string | https.RequestOptions | URL} options + * @param {((res: import('node:http').IncomingMessage) => void) | undefined} [callback] + */ function get(options, callback) { const protocol = typeof options === 'string' ? new URL(options).protocol : options.protocol ?? 'https:' diff --git a/app/gui2/scripts/generateIconMetadata.js b/app/gui2/scripts/generateIconMetadata.js index ab755ef1c2c..b358110a04a 100644 --- a/app/gui2/scripts/generateIconMetadata.js +++ b/app/gui2/scripts/generateIconMetadata.js @@ -15,6 +15,7 @@ await fs.writeFile( // Please run \`npm run generate-metadata\` to regenerate this file whenever \`icons.svg\` is changed. import iconNames from '@/util/iconList.json' +/** One of the possible icon names of SvgIcon component. */ export type Icon = ${iconNames?.map((name) => ` | '${name}'`).join('\n')} @@ -22,6 +23,7 @@ export { iconNames } const iconNamesSet = new Set(iconNames) +/** Check if string is one of the valid icon names for SvgIcon. */ export function isIconName(value: string): value is Icon { return iconNamesSet.has(value) } diff --git a/app/gui2/src/asyncApp.ts b/app/gui2/src/asyncApp.ts index 3500bcba102..9cd815915f8 100644 --- a/app/gui2/src/asyncApp.ts +++ b/app/gui2/src/asyncApp.ts @@ -1,5 +1,8 @@ import '@/assets/base.css' +/** + * Load App.vue asynchronously. + */ export async function AsyncApp() { const app = await import('@/App.vue') return app diff --git a/app/gui2/src/components/CodeEditor/codemirror.ts b/app/gui2/src/components/CodeEditor/codemirror.ts index a350c0f6434..f4d154ce64d 100644 --- a/app/gui2/src/components/CodeEditor/codemirror.ts +++ b/app/gui2/src/components/CodeEditor/codemirror.ts @@ -47,6 +47,7 @@ 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' +/** TODO: Add docs */ export function lsDiagnosticsToCMDiagnostics( source: string, diagnostics: LSDiagnostic[], @@ -172,10 +173,12 @@ class EnsoLanguage extends Language { const ensoLanguage = new EnsoLanguage() +/** TODO: Add docs */ export function enso() { return new LanguageSupport(ensoLanguage) } +/** TODO: Add docs */ export function hoverTooltip( create: ( ast: AstNode, @@ -199,6 +202,7 @@ export function hoverTooltip( }) } +/** TODO: Add docs */ export function textEditToChangeSpec({ range: [from, to], insert }: SourceRangeEdit): ChangeSpec { return { from, to, insert } } diff --git a/app/gui2/src/components/ColorRing/gradient.ts b/app/gui2/src/components/ColorRing/gradient.ts index 6c691d144d3..ddc78f0c653 100644 --- a/app/gui2/src/components/ColorRing/gradient.ts +++ b/app/gui2/src/components/ColorRing/gradient.ts @@ -21,10 +21,12 @@ function normalizeRangeInputs(inputs: Iterable, radius: number) { return normalizedInputs } +/** TODO: Add docs */ export function seminormalizeHue(value: number) { return value === 1 ? 1 : normalizeHue(value) } +/** TODO: Add docs */ export function rangesForInputs(inputs: Iterable, radius: number): FixedRange[] { if (radius === 0) return [] const ranges = new Array() @@ -80,6 +82,7 @@ export interface GradientPoint { angle: number angle2?: number } +/** TODO: Add docs */ export function cssAngularColorStop({ hue, angle, angle2 }: GradientPoint) { return [ formatCssColor(ensoColor(hue)), @@ -88,6 +91,7 @@ export function cssAngularColorStop({ hue, angle, angle2 }: GradientPoint) { ].join(' ') } +/** TODO: Add docs */ export function gradientPoints( inputRanges: Iterable, minStops?: number | undefined, diff --git a/app/gui2/src/components/ComponentBrowser/ai.ts b/app/gui2/src/components/ComponentBrowser/ai.ts index c03d5b8fc4d..e94b501f9fc 100644 --- a/app/gui2/src/components/ComponentBrowser/ai.ts +++ b/app/gui2/src/components/ComponentBrowser/ai.ts @@ -8,6 +8,10 @@ import type { ExternalId } from 'ydoc-shared/yjsModel' const AI_GOAL_PLACEHOLDER = '__$$GOAL$$__' const AI_STOP_SEQUENCE = '`' +/** + * A Composable for using AI prompts in Component Browser. Use `query` function to get AI result + * for given query. + */ export function useAI( graphDb: GraphDb = useGraphStore().db, project: { diff --git a/app/gui2/src/components/ComponentBrowser/component.ts b/app/gui2/src/components/ComponentBrowser/component.ts index 8cf4b2fdbb9..f6303699bec 100644 --- a/app/gui2/src/components/ComponentBrowser/component.ts +++ b/app/gui2/src/components/ComponentBrowser/component.ts @@ -23,12 +23,16 @@ interface ComponentLabel { matchedRanges?: Range[] | undefined } +/** + * A model of component suggestion displayed in the Component Browser. + */ export interface Component extends ComponentLabel { suggestionId: SuggestionId icon: Icon group?: number | undefined } +/** @returns the displayed label of given suggestion entry with information of highlighted ranges. */ export function labelOfEntry(entry: SuggestionEntry, match: MatchResult): ComponentLabelInfo { if (entry.memberOf && entry.selfType == null) { const ownerLastSegmentStart = qnLastSegmentIndex(entry.memberOf) + 1 @@ -68,12 +72,18 @@ function formatLabel(labelInfo: ComponentLabelInfo): ComponentLabel { } } +/** + * Suggestion entry with matching information. + */ export interface MatchedSuggestion { id: SuggestionId entry: SuggestionEntry match: MatchResult } +/** + * A suggestion comparator. The "lower" suggestion should be first in Component Browser's list. + */ export function compareSuggestions(a: MatchedSuggestion, b: MatchedSuggestion): number { const matchCompare = a.match.score - b.match.score if (matchCompare !== 0) return matchCompare @@ -87,12 +97,15 @@ export function compareSuggestions(a: MatchedSuggestion, b: MatchedSuggestion): return a.id - b.id } -export interface ComponentInfo { +interface ComponentInfo { id: number entry: SuggestionEntry match: MatchResult } +/** + * Create {@link Component} from information about suggestion and matching. + */ export function makeComponent({ id, entry, match }: ComponentInfo): Component { return { ...formatLabel(labelOfEntry(entry, match)), @@ -102,6 +115,9 @@ export function makeComponent({ id, entry, match }: ComponentInfo): Component { } } +/** + * Create {@link Component} list from filtered suggestions. + */ export function makeComponentList(db: SuggestionDb, filtering: Filtering): Component[] { function* matchSuggestions() { for (const [id, entry] of db.entries()) { diff --git a/app/gui2/src/components/ComponentBrowser/filtering.ts b/app/gui2/src/components/ComponentBrowser/filtering.ts index 07d5117f32c..94b1bb2219c 100644 --- a/app/gui2/src/components/ComponentBrowser/filtering.ts +++ b/app/gui2/src/components/ComponentBrowser/filtering.ts @@ -238,6 +238,7 @@ export class Filtering { selfArg?: SelfArg currentModule?: QualifiedName + /** TODO: Add docs */ constructor(filter: Filter, currentModule: Opt = undefined) { const { pattern, selfArg } = filter if (pattern) { @@ -253,6 +254,7 @@ export class Filtering { else return entry.selfType != null } + /** TODO: Add docs */ isMainView() { return this.pattern == null && this.selfArg == null } @@ -268,6 +270,7 @@ export class Filtering { return this.currentModule != null && entry.definedIn === this.currentModule } + /** TODO: Add docs */ filter(entry: SuggestionEntry): MatchResult | null { if (entry.isPrivate || entry.kind != SuggestionKind.Method || entry.memberOf == null) return null diff --git a/app/gui2/src/components/ComponentBrowser/input.ts b/app/gui2/src/components/ComponentBrowser/input.ts index 56b79ad5985..113004840bc 100644 --- a/app/gui2/src/components/ComponentBrowser/input.ts +++ b/app/gui2/src/components/ComponentBrowser/input.ts @@ -9,16 +9,17 @@ import { isIdentifier, type AstId, type Identifier } from '@/util/ast/abstract' import { Err, Ok, type Result } from '@/util/data/result' import { qnLastSegment, type QualifiedName } from '@/util/qualifiedName' import { useToast } from '@/util/toast' -import { computed, proxyRefs, readonly, ref, watch, type ComputedRef } from 'vue' +import { computed, proxyRefs, readonly, ref, type ComputedRef } from 'vue' /** Information how the component browser is used, needed for proper input initializing. */ export type Usage = | { type: 'newNode'; sourcePort?: AstId | undefined } | { type: 'editNode'; node: NodeId; cursorPos: number } -/** One of the modes of the component browser: - * * "component browsing" when user wants to add new component - * * "code editing" for editing existing, or just added nodes +/** + * One of the modes of the component browser: + * "component browsing" when user wants to add new component + * "code editing" for editing existing, or just added nodes * See https://github.com/enso-org/enso/issues/10598 for design details. */ export type ComponentBrowserMode = @@ -177,7 +178,8 @@ export function useComponentBrowserInput( } } - /** List of imports required for applied suggestions. + /** + * List of imports required for applied suggestions. * * If suggestion was manually edited by the user after accepting, it is not included. */ diff --git a/app/gui2/src/components/ComponentBrowser/placement.ts b/app/gui2/src/components/ComponentBrowser/placement.ts index 6d317fff5ad..5ed5c37b670 100644 --- a/app/gui2/src/components/ComponentBrowser/placement.ts +++ b/app/gui2/src/components/ComponentBrowser/placement.ts @@ -16,6 +16,9 @@ const orDefaultSize = (rect: Rect) => { return new Rect(rect.pos, new Vec2(width, height)) } +/** + * A composable with logic related to nodes placement. + */ export function usePlacement(nodeRects: ToValue>, screenBounds: ToValue) { const gap = themeGap() const environment = (selectedNodeRects: Iterable) => ({ @@ -24,10 +27,13 @@ export function usePlacement(nodeRects: ToValue>, screenBounds: T nodeRects: Array.from(toValue(nodeRects), orDefaultSize), }) return { + /** Find a free position for a new node. For details, see {@link previousNodeDictatedPlacement}. */ place: (selectedNodeRects: Iterable = [], nodeSize: Vec2 = DEFAULT_NODE_SIZE): Vec2 => previousNodeDictatedPlacement(nodeSize, environment(selectedNodeRects), gap), + /** Compute position of new collapsed node. For details, see {@link collapsedNodePlacement}. */ collapse: (selectedNodeRects: Iterable, nodeSize: Vec2 = DEFAULT_NODE_SIZE): Vec2 => collapsedNodePlacement(nodeSize, environment(selectedNodeRects), gap), + /** Compute position of an input node. For details, see {@link inputNodePlacement}. */ input: (nonInputNodeRects: Iterable, nodeSize: Vec2 = DEFAULT_NODE_SIZE): Vec2 => inputNodePlacement(nodeSize, { ...environment([]), nonInputNodeRects }, gap), } @@ -50,7 +56,8 @@ function themeGap(): Vec2 { return new Vec2(theme.node.horizontal_gap, theme.node.vertical_gap) } -/** The new node should appear at the center of the screen if there is enough space for the new node. +/** + * The new node should appear at the center of the screen if there is enough space for the new node. * Otherwise, it should be moved down to the closest free space. * * Specifically, this code, in order: @@ -60,7 +67,8 @@ function themeGap(): Vec2 { * - shifts the node down (if required) until there is sufficient vertical space - * the height of the node, in addition to the specified gap both above and below the node. * - * [Documentation](https://github.com/enso-org/design/blob/main/epics/component-browser/design.md#placement-of-newly-opened-component-browser) */ + * [Documentation](https://github.com/enso-org/design/blob/main/epics/component-browser/design.md#placement-of-newly-opened-component-browser) + */ export function nonDictatedPlacement( nodeSize: Vec2, { screenBounds, nodeRects }: NonDictatedEnvironment, @@ -70,7 +78,8 @@ export function nonDictatedPlacement( return seekVertical(new Rect(initialPosition, nodeSize), nodeRects, gap) } -/** The new node should be left aligned to the first selected node (order of selection matters). +/** + * The new node should be left aligned to the first selected node (order of selection matters). * The Panel should also be placed vertically directly below the lowest of all selected nodes. * * If there is not enough empty space, the Expression Input Panel should be moved right @@ -90,7 +99,8 @@ export function nonDictatedPlacement( * Note that the algorithm for finding free space is almost the same as for non-dictated placement, * except it searches horizontally instead of vertically. * - * [Documentation](https://github.com/enso-org/design/blob/main/epics/component-browser/design.md#placement-of-newly-opened-component-browser) */ + * [Documentation](https://github.com/enso-org/design/blob/main/epics/component-browser/design.md#placement-of-newly-opened-component-browser) + */ export function previousNodeDictatedPlacement( nodeSize: Vec2, { screenBounds, selectedNodeRects, nodeRects }: Environment, @@ -110,13 +120,15 @@ export function previousNodeDictatedPlacement( return seekHorizontal(new Rect(initialPosition, nodeSize), nodeRects, gap) } -/** The new node should appear exactly below the mouse. +/** + * The new node should appear exactly below the mouse. * * Specifically, this code assumes the node is fully rounded on the left and right sides, * so it adds half the node height (assumed to be the node radius) from the mouse x and y * positions. * - * [Documentation](https://github.com/enso-org/design/blob/main/epics/component-browser/design.md#placement-of-newly-opened-component-browser) */ + * [Documentation](https://github.com/enso-org/design/blob/main/epics/component-browser/design.md#placement-of-newly-opened-component-browser) + */ export function mouseDictatedPlacement( mousePosition: Vec2, nodeSize: Vec2 = DEFAULT_NODE_SIZE, @@ -145,7 +157,8 @@ export function inputNodePlacement( return seekHorizontal(new Rect(initialPosition, nodeSize), nodeRects, gap) } -/** The new node should appear at the average Y-position of selected nodes and with the X-position of the leftmost node. +/** + * The new node should appear at the average Y-position of selected nodes and with the X-position of the leftmost node. * * If the desired place is already occupied by non-selected node, it should be moved down to the closest free space. * @@ -181,8 +194,10 @@ export function collapsedNodePlacement( return seekVertical(new Rect(initialPosition, nodeSize), nonSelectedNodeRects, gap) } -/** Given a preferred location for a node, adjust the top as low as necessary for it not to collide with any of the - * provided `otherRects`. */ +/** + * Given a preferred location for a node, adjust the top as low as necessary for it not to collide with any of the + * provided `otherRects`. + */ export function seekVertical(preferredRect: Rect, otherRects: Iterable, gap = themeGap()) { const initialRect = orDefaultSize(preferredRect) const nodeRectsSorted = Array.from(otherRects, orDefaultSize).sort((a, b) => a.top - b.top) @@ -198,8 +213,10 @@ export function seekVertical(preferredRect: Rect, otherRects: Iterable, ga return new Vec2(initialRect.left, top) } -/** Given a preferred location for a node, adjust the left edge as much as necessary for it not to collide with any of - * the provided `otherRects`. */ +/** + * Given a preferred location for a node, adjust the left edge as much as necessary for it not to collide with any of + * the provided `otherRects`. + */ export function seekHorizontal(initialRect: Rect, otherRects: Iterable, gap = themeGap()) { return seekVertical( orDefaultSize(initialRect).reflectXY(), diff --git a/app/gui2/src/components/ComponentBrowser/scrolling.ts b/app/gui2/src/components/ComponentBrowser/scrolling.ts index b0b3256bb6a..f0c6d9129ca 100644 --- a/app/gui2/src/components/ComponentBrowser/scrolling.ts +++ b/app/gui2/src/components/ComponentBrowser/scrolling.ts @@ -7,6 +7,13 @@ export type ScrollTarget = | { type: 'selected' } | { type: 'offset'; offset: number } +/** + * Scrolling for the Component Browser List. + * + * The scrolling may be a bit different depending on if we want to scroll to selection (and then + * stick to it), or top/specific offset. The scroll value should be updated by setting + * `targetScroll` value, or by calling `scrollWithTransition` if we want animated scroll. + */ export function useScrolling(selectedPos: ToValue) { const targetScroll = ref({ type: 'top' }) const targetScrollPosition = computed(() => { diff --git a/app/gui2/src/components/DocumentationPanel/history.ts b/app/gui2/src/components/DocumentationPanel/history.ts index 4b6a96652db..c016e3c60d0 100644 --- a/app/gui2/src/components/DocumentationPanel/history.ts +++ b/app/gui2/src/components/DocumentationPanel/history.ts @@ -10,40 +10,47 @@ export class HistoryStack { private index: Ref public current: ComputedRef + /** TODO: Add docs */ constructor() { this.stack = reactive([]) this.index = ref(0) this.current = computed(() => this.stack[this.index.value] ?? undefined) } + /** TODO: Add docs */ public reset(current: SuggestionId) { this.stack.length = 0 this.stack.push(current) this.index.value = 0 } + /** TODO: Add docs */ public record(id: SuggestionId) { this.stack.splice(this.index.value + 1) this.stack.push(id) this.index.value = this.stack.length - 1 } + /** TODO: Add docs */ public forward() { if (this.canGoForward()) { this.index.value += 1 } } + /** TODO: Add docs */ public backward() { if (this.canGoBackward()) { this.index.value -= 1 } } + /** TODO: Add docs */ public canGoBackward(): boolean { return this.index.value > 0 } + /** TODO: Add docs */ public canGoForward(): boolean { return this.index.value < this.stack.length - 1 } diff --git a/app/gui2/src/components/DocumentationPanel/ir.ts b/app/gui2/src/components/DocumentationPanel/ir.ts index c38f5b9b48c..ee9b4a90fea 100644 --- a/app/gui2/src/components/DocumentationPanel/ir.ts +++ b/app/gui2/src/components/DocumentationPanel/ir.ts @@ -59,6 +59,9 @@ export interface Example { body: Doc.HtmlString } +/** + * Placeholder constructor. + */ export function placeholder(text: string): Placeholder { return { kind: 'Placeholder', text } } @@ -96,6 +99,9 @@ function filterSections(sections: Iterable): Sections { // === Lookup === +/** + * The main function for getting documentation page for given entry. + */ export function lookupDocumentation(db: SuggestionDb, id: SuggestionId): Docs { const entry = db.get(id) if (!entry) diff --git a/app/gui2/src/components/GraphEditor/GraphEdge.vue b/app/gui2/src/components/GraphEditor/GraphEdge.vue index aeeca5d8869..f4667ece31b 100644 --- a/app/gui2/src/components/GraphEditor/GraphEdge.vue +++ b/app/gui2/src/components/GraphEditor/GraphEdge.vue @@ -104,8 +104,10 @@ const sourceRect = computed(() => { } }) -/** Edges which do not have `sourceRect` and `targetPos` initialized are marked by a special - * `broken-edge` data-testid, for debugging and e2e test purposes. */ +/** + * Edges which do not have `sourceRect` and `targetPos` initialized are marked by a special + * `broken-edge` data-testid, for debugging and e2e test purposes. + */ const edgeIsBroken = computed( () => sourceRect.value == null || @@ -145,11 +147,15 @@ const edgeColor = computed(() => /** The inputs to the edge state computation. */ interface Inputs { - /** The width and height of the node that originates the edge, if any. - * The edge may begin anywhere around the bottom half of the node. */ + /** + * The width and height of the node that originates the edge, if any. + * The edge may begin anywhere around the bottom half of the node. + */ sourceSize: Vec2 - /** The coordinates of the node input port that is the edge's destination, relative to the source - * position. The edge enters the port from above. */ + /** + * The coordinates of the node input port that is the edge's destination, relative to the source + * position. The edge enters the port from above. + */ targetOffset: Vec2 } @@ -164,7 +170,8 @@ function circleIntersection(x: number, r1: number, r2: number): number { return Math.sqrt(r1 * r1 + r2 * r2 - xNorm * xNorm) } -/** Edge layout calculation. +/** + * Edge layout calculation. * * # Corners * @@ -200,7 +207,8 @@ function circleIntersection(x: number, r1: number, r2: number): number { * horizontal/vertical, has a one-to-one relationship with a sequence of corners. */ -/** Calculate the start and end positions of each 1-corner section composing an edge to the +/** + * Calculate the start and end positions of each 1-corner section composing an edge to the * given offset. Return the points and the maximum radius that should be used to draw the corners * connecting them. */ diff --git a/app/gui2/src/components/GraphEditor/GraphVisualization.vue b/app/gui2/src/components/GraphEditor/GraphVisualization.vue index ea5416c5c5f..c67be5c6a6f 100644 --- a/app/gui2/src/components/GraphEditor/GraphVisualization.vue +++ b/app/gui2/src/components/GraphEditor/GraphVisualization.vue @@ -16,9 +16,11 @@ import { Vec2 } from '@/util/data/vec2' import { computed, nextTick, onUnmounted, ref, toRef, watch, watchEffect } from 'vue' import { visIdentifierEquals, type VisualizationIdentifier } from 'ydoc-shared/yjsModel' -/** The minimum width must be at least the total width of: +/** + * The minimum width must be at least the total width of: * - both of toolbars that are always visible (32px + 60px), and - * - the 4px flex gap between the toolbars. */ + * - the 4px flex gap between the toolbars. + */ const MIN_WIDTH_PX = 200 const MIN_CONTENT_HEIGHT_PX = 32 const DEFAULT_CONTENT_HEIGHT_PX = 150 diff --git a/app/gui2/src/components/GraphEditor/GraphVisualization/visualizationData.ts b/app/gui2/src/components/GraphEditor/GraphVisualization/visualizationData.ts index 926ff23f00c..04c2f421704 100644 --- a/app/gui2/src/components/GraphEditor/GraphVisualization/visualizationData.ts +++ b/app/gui2/src/components/GraphEditor/GraphVisualization/visualizationData.ts @@ -29,7 +29,7 @@ import type { Opt } from 'ydoc-shared/util/data/opt' import type { Result } from 'ydoc-shared/util/data/result' import type { VisualizationIdentifier } from 'ydoc-shared/yjsModel' -// Used for testing. +/** Used for testing. */ export type RawDataSource = { type: 'raw'; data: any } export interface UseVisualizationDataOptions { @@ -38,6 +38,14 @@ export interface UseVisualizationDataOptions { dataSource: ToValue } +/** + * Visualization data composable for Visualization component. + * + * This composable manages picking the proper visualization component, attaching engine's + * visualization to get input data, and updating the preprocessor if requested. + * + * TODO[ao]: Docs about returned refs and functions. + */ export function useVisualizationData({ selectedVis, dataSource, diff --git a/app/gui2/src/components/GraphEditor/clipboard.ts b/app/gui2/src/components/GraphEditor/clipboard.ts index 51c19a2f88c..5685ce3b9e5 100644 --- a/app/gui2/src/components/GraphEditor/clipboard.ts +++ b/app/gui2/src/components/GraphEditor/clipboard.ts @@ -26,7 +26,7 @@ interface CopiedNode { metadata?: NodeMetadataFields } -/** @internal Exported for testing. */ +/** @internal */ export async function nodesFromClipboardContent( clipboardItems: ClipboardItems, ): Promise { @@ -54,6 +54,7 @@ function getClipboard(): ExtendedClipboard { return (window.navigator as any).mockClipboard ?? window.navigator.clipboard } +/** A composable for handling copying and pasting nodes in the GraphEditor. */ export function useGraphEditorClipboard( graphStore: GraphStore, selected: ToValue>, @@ -149,12 +150,13 @@ const spreadsheetDecoder: ClipboardDecoder = { const toTable = computed(() => Pattern.parse('__.to Table')) +/** Create Enso Expression generating table from this tsvData. */ export function tsvTableToEnsoExpression(tsvData: string) { const textLiteral = Ast.TextLiteral.new(tsvData) return toTable.value.instantiate(textLiteral.module, [textLiteral]).code() } -/** @internal Exported for testing. */ +/** @internal */ export function isSpreadsheetTsv(htmlContent: string) { // This is a very general criterion that can have some false-positives (e.g. pasting rich text that includes a table). // However, due to non-standardized browser HTML sanitization it is difficult to precisely recognize spreadsheet @@ -171,6 +173,7 @@ export function isSpreadsheetTsv(htmlContent: string) { export type MimeType = 'text/plain' | 'text/html' | typeof ENSO_MIME_TYPE export type MimeData = Partial> +/** Write data to clipboard */ export function writeClipboard(data: MimeData) { const dataBlobs = Object.fromEntries( Object.entries(data).map(([type, typeData]) => [type, new Blob([typeData], { type })]), @@ -191,11 +194,13 @@ function nodeStructuredData(node: Node): CopiedNode { } } +/** TODO: Add docs */ export function clipboardNodeData(nodes: CopiedNode[]): MimeData { const clipboardData: ClipboardData = { nodes } return { [ENSO_MIME_TYPE]: JSON.stringify(clipboardData) } } +/** TODO: Add docs */ export function nodesToClipboardData(nodes: Node[]): MimeData { return { ...clipboardNodeData(nodes.map(nodeStructuredData)), diff --git a/app/gui2/src/components/GraphEditor/collapsing.ts b/app/gui2/src/components/GraphEditor/collapsing.ts index 830e4f09ce7..84de430131d 100644 --- a/app/gui2/src/components/GraphEditor/collapsing.ts +++ b/app/gui2/src/components/GraphEditor/collapsing.ts @@ -31,7 +31,8 @@ interface ExtractedInfo { /** The information about the output value of the extracted function. */ interface Output { - /** The id of the node the expression of which should be replaced by the function call. + /** + * The id of the node the expression of which should be replaced by the function call. * This node is also included into `ids` of the {@link ExtractedInfo} and must be moved into the extracted function. */ node: NodeId @@ -51,7 +52,8 @@ interface RefactoredInfo { // === prepareCollapsedInfo === -/** Prepare the information necessary for collapsing nodes. +/** + * Prepare the information necessary for collapsing nodes. * @throws errors in case of failures, but it should not happen in normal execution. */ export function prepareCollapsedInfo( @@ -153,7 +155,8 @@ interface CollapsingResult { /** The ID of the node refactored to the collapsed function call. */ refactoredNodeId: NodeId refactoredExpressionAstId: Ast.AstId - /** IDs of nodes inside the collapsed function, except the output node. + /** + * IDs of nodes inside the collapsed function, except the output node. * The order of these IDs is reversed comparing to the order of nodes in the source code. */ collapsedNodeIds: NodeId[] diff --git a/app/gui2/src/components/GraphEditor/dragging.ts b/app/gui2/src/components/GraphEditor/dragging.ts index 2945a7c96ea..e88891976f3 100644 --- a/app/gui2/src/components/GraphEditor/dragging.ts +++ b/app/gui2/src/components/GraphEditor/dragging.ts @@ -17,12 +17,22 @@ interface PartialVec2 { y: number | null } +/** + * Snap Grid for dragged nodes. + * + * Created from existing nodes' rects, it allows "snapping" dragged nodes to another nodes on + * the scene, so the user could easily and nicely ailgn their nodes. + * + * The nodes will be snapped to align with every edge of any other node, and also at place above + * and below node leaving default vertical gap (same as when adding new node). + */ export class SnapGrid { leftAxes: ComputedRef rightAxes: ComputedRef topAxes: ComputedRef bottomAxes: ComputedRef + /** Create grid from existing nodes' rects */ constructor(rects: ComputedRef) { markRaw(this) this.leftAxes = computed(() => @@ -43,6 +53,12 @@ export class SnapGrid { ) } + /** + * Return "snapped" position of set of dragged nodes. + * @param rects rects of dragged nodes + * @param threshold a maximum distance from node's edge to the snap axe to have this node + * snapped. + */ snappedMany(rects: Rect[], threshold: number): Vec2 { const minSnap = rects.reduce( (minSnap, rect) => { @@ -54,6 +70,13 @@ export class SnapGrid { return new Vec2(minSnap.x ?? 0.0, minSnap.y ?? 0.0) } + /** + * Return "snapped" position of node with given rects. + * @param rect rect of dragged node + * @param threshold a maximum distance from node's edge to the snap axe to have this node + * snapped. + * @returns partial vector: the coordinate is missing if the node would not snap that direction. + */ snap(rect: Rect, threshold: number): PartialVec2 { const leftSnap = SnapGrid.boundSnap(rect.left, this.leftAxes.value, threshold) const rightSnap = SnapGrid.boundSnap(rect.right, this.rightAxes.value, threshold) @@ -111,6 +134,7 @@ export function useDragging() { grid: SnapGrid stopPositionUpdate: WatchStopHandle + /** Start dragging: initialize all properties and animations above */ constructor(movedId: NodeId) { markRaw(this) function* draggedNodes(): Generator<[NodeId, DraggedNode]> { @@ -131,6 +155,7 @@ export function useDragging() { this.stopPositionUpdate = watchEffect(() => this.updateNodesPosition()) } + /** Update drag offset and snap animation target */ updateOffset(newOffset: Vec2): void { const oldSnappedOffset = snappedOffset.value const rects: Rect[] = [] @@ -154,10 +179,13 @@ export function useDragging() { } } + /** Finish dragging and set nodes' positions */ finishDragging(): void { this.stopPositionUpdate() this.updateNodesPosition() } + + /** Cancel drag and reset nodes' positions */ cancelDragging(): void { console.log('cancelDragging') this.stopPositionUpdate() @@ -169,7 +197,7 @@ export function useDragging() { this.updateNodesPosition() } - createSnapGrid() { + private createSnapGrid() { const nonDraggedRects = computed(() => { const nonDraggedNodes = iteratorFilter( graphStore.db.nodeIds(), @@ -180,7 +208,7 @@ export function useDragging() { return new SnapGrid(nonDraggedRects) } - updateNodesPosition() { + private updateNodesPosition() { graphStore.batchEdits(() => { for (const [id, dragged] of this.draggedNodes) { const node = graphStore.db.nodeIdToNode.get(id) diff --git a/app/gui2/src/components/GraphEditor/nodeCreation.ts b/app/gui2/src/components/GraphEditor/nodeCreation.ts index cf7fb02ed45..8835e3701ca 100644 --- a/app/gui2/src/components/GraphEditor/nodeCreation.ts +++ b/app/gui2/src/components/GraphEditor/nodeCreation.ts @@ -1,8 +1,10 @@ import type { Pattern } from '@/util/ast/match' interface AllNodeCreationOptions { - /** If false, the Component Browser will be opened to edit the node. - * If true, the node will be created without further interaction. */ + /** + * If false, the Component Browser will be opened to edit the node. + * If true, the node will be created without further interaction. + */ commit: boolean /** The content of the node. If unspecified, it will be determined based on the source node. */ content?: Pattern | undefined diff --git a/app/gui2/src/components/GraphEditor/toasts.ts b/app/gui2/src/components/GraphEditor/toasts.ts index 033eb765056..fa958b63e41 100644 --- a/app/gui2/src/components/GraphEditor/toasts.ts +++ b/app/gui2/src/components/GraphEditor/toasts.ts @@ -2,6 +2,10 @@ import { useEvent } from '@/composables/events' import { type ProjectStore } from '@/stores/project' import { useToast } from '@/util/toast' +/** + * A composable which sets up several toasts for project management, and creates one for message + * about user's action error. + */ export function useGraphEditorToasts(projectStore: ProjectStore) { const toastStartup = useToast.info({ autoClose: false }) const toastConnectionLost = useToast.error({ autoClose: false }) diff --git a/app/gui2/src/components/GraphEditor/upload.ts b/app/gui2/src/components/GraphEditor/upload.ts index f8e616022ac..e458a9abb4a 100644 --- a/app/gui2/src/components/GraphEditor/upload.ts +++ b/app/gui2/src/components/GraphEditor/upload.ts @@ -15,6 +15,7 @@ import { Err, Ok, withContext, type Result } from 'ydoc-shared/util/data/result' const DATA_DIR_NAME = 'data' +/** @returns the expression for node reading an uploaded file. */ export function uploadedExpression(result: UploadResult) { switch (result.source) { case 'Project': { @@ -28,11 +29,20 @@ export function uploadedExpression(result: UploadResult) { // === Uploader === +/** Upload result, containing information about upload destination. */ export interface UploadResult { source: 'FileSystemRoot' | 'Project' name: string } +/** + * Uploader handles the uploading process of a single file to project directory. + * + * This will upload file chunks using binary protocol, updating information of progress in + * {@link Awareness} object. On error, the file will be deleted. + * + * Checking the checksum is not implemented yet because of https://github.com/enso-org/enso/issues/6691 + */ export class Uploader { private checksum: Hash private uploadedBytes: bigint @@ -54,6 +64,7 @@ export class Uploader { this.stackItem = markRaw(toRaw(stackItem)) } + /** Constructor */ static Create( rpc: LanguageServer, binary: DataServer, @@ -78,6 +89,7 @@ export class Uploader { ) } + /** Start the upload process */ async upload(): Promise> { // This non-standard property is defined in Electron. if ( @@ -195,9 +207,7 @@ export class Uploader { } } -/** - * Split filename into stem and (optional) extension. - */ +/** Split filename into stem and (optional) extension. */ function splitFilename(fileName: string): { stem: string; extension?: string } { const dotIndex = fileName.lastIndexOf('.') if (dotIndex !== -1 && dotIndex !== 0) { diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetFunctionName.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetFunctionName.vue index a9fb9ac5ae0..59a6de58fc7 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetFunctionName.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetFunctionName.vue @@ -52,7 +52,8 @@ export const FunctionName: unique symbol = Symbol.for('WidgetInput:FunctionName' declare module '@/providers/widgetRegistry' { export interface WidgetInput { [FunctionName]?: { - /** Id of expression which is accepted by Language Server's + /** + * Id of expression which is accepted by Language Server's * [`refactoring/renameSymbol` method](https://github.com/enso-org/enso/blob/develop/docs/language-server/protocol-language-server.md#refactoringrenamesymbol) */ editableName: ExpressionId diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetTableEditor/TableHeader.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetTableEditor/TableHeader.vue index a672e753389..8ffe338fefa 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetTableEditor/TableHeader.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetTableEditor/TableHeader.vue @@ -2,14 +2,16 @@ import type { IHeaderParams } from 'ag-grid-community' import { ref, watch } from 'vue' -/** Parameters recognized by this header component. +/** + * Parameters recognized by this header component. * * They are set through `headerComponentParams` option in AGGrid column definition. */ export interface HeaderParams { /** Setter called when column name is changed by the user. */ nameSetter?: (newName: string) => void - /** Column is virtual if it is not represented in the AST. Such column might be used + /** + * Column is virtual if it is not represented in the AST. Such column might be used * to create new one. */ virtualColumn?: boolean diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetTableEditor/tableNewArgument.ts b/app/gui2/src/components/GraphEditor/widgets/WidgetTableEditor/tableNewArgument.ts index 445a5647c4e..a29c9ae55b3 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetTableEditor/tableNewArgument.ts +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetTableEditor/tableNewArgument.ts @@ -23,14 +23,18 @@ export type RowData = { cells: Record } -/** A more specialized version of AGGrid's `MenuItemDef` to simplify testing (the tests need to provide - * only values actually used by the composable) */ +/** + * A more specialized version of AGGrid's `MenuItemDef` to simplify testing (the tests need to provide + * only values actually used by the composable) + */ export interface MenuItem extends MenuItemDef { action: (params: { node: { data: RowData | undefined } | null }) => void } -/** A more specialized version of AGGrid's `ColDef` to simplify testing (the tests need to provide - * only values actually used by the composable) */ +/** + * A more specialized version of AGGrid's `ColDef` to simplify testing (the tests need to provide + * only values actually used by the composable) + */ export interface ColumnDef extends ColDef { valueGetter: ({ data }: { data: RowData | undefined }) => any valueSetter?: ({ data, newValue }: { data: RowData; newValue: any }) => boolean @@ -39,6 +43,7 @@ export interface ColumnDef extends ColDef { } namespace cellValueConversion { + /** TODO: Add docs */ export function astToAgGrid(ast: Ast.Ast) { if (ast instanceof Ast.TextLiteral) return Ok(ast.rawTextContent) else if (ast instanceof Ast.Ident && ast.code() === NOTHING_NAME) return Ok(null) @@ -50,6 +55,7 @@ namespace cellValueConversion { } } + /** TODO: Add docs */ export function agGridToAst( value: unknown, module: Ast.MutableModule, @@ -104,6 +110,11 @@ function retrieveColumnsDefinitions(columnsAst: Ast.Vector) { return transposeResult(Array.from(columnsAst.values(), readColumn)) } +/** + * Check if given ast is a `Table.new` call which may be handled by the TableEditorWidget. + * + * This widget may handle table definitions filled with literals or `Nothing` values. + */ export function tableNewCallMayBeHandled(call: Ast.Ast) { const columnsAst = retrieveColumnsAst(call) if (!columnsAst.ok) return false @@ -121,7 +132,6 @@ export function tableNewCallMayBeHandled(call: Ast.Ast) { /** * A composable responsible for interpreting `Table.new` expressions, creating AGGrid column * definitions allowing also editing AST through AGGrid editing. - * * @param input the widget's input * @param graph the graph store * @param onUpdate callback called when AGGrid was edited by user, resulting in AST change. diff --git a/app/gui2/src/components/MarkdownEditor/ImagePlugin/imageNode.ts b/app/gui2/src/components/MarkdownEditor/ImagePlugin/imageNode.ts index 5830d773623..8e0a585f8f2 100644 --- a/app/gui2/src/components/MarkdownEditor/ImagePlugin/imageNode.ts +++ b/app/gui2/src/components/MarkdownEditor/ImagePlugin/imageNode.ts @@ -39,18 +39,22 @@ export type SerializedImageNode = Spread< SerializedLexicalNode > +/** TODO: Add docs */ export class ImageNode extends DecoratorNode { __src: string __altText: string + /** TODO: Add docs */ static override getType(): string { return 'image' } + /** TODO: Add docs */ static override clone(node: ImageNode): ImageNode { return new ImageNode(node.__src, node.__altText, node.__key) } + /** TODO: Add docs */ static override importJSON(serializedNode: SerializedImageNode): ImageNode { const { altText, src } = serializedNode return $createImageNode({ @@ -59,6 +63,7 @@ export class ImageNode extends DecoratorNode { }) } + /** TODO: Add docs */ static override importDOM(): DOMConversionMap | null { return { img: (_node: Node) => ({ @@ -68,12 +73,14 @@ export class ImageNode extends DecoratorNode { } } + /** TODO: Add docs */ constructor(src: string, altText: string, key?: NodeKey) { super(key) this.__src = src this.__altText = altText } + /** TODO: Add docs */ override exportDOM(): DOMExportOutput { const element = document.createElement('img') element.setAttribute('src', this.__src) @@ -81,6 +88,7 @@ export class ImageNode extends DecoratorNode { return { element } } + /** TODO: Add docs */ override exportJSON(): SerializedImageNode { return { altText: this.getAltText(), @@ -90,19 +98,23 @@ export class ImageNode extends DecoratorNode { } } + /** TODO: Add docs */ getSrc(): string { return this.__src } + /** TODO: Add docs */ getAltText(): string { return this.__altText } + /** TODO: Add docs */ setAltText(altText: string): void { const writable = this.getWritable() writable.__altText = altText } + /** TODO: Add docs */ update(payload: UpdateImagePayload): void { const writable = this.getWritable() const { altText } = payload @@ -113,6 +125,7 @@ export class ImageNode extends DecoratorNode { // View + /** TODO: Add docs */ override createDOM(config: EditorConfig): HTMLElement { const span = document.createElement('span') const className = config.theme.image @@ -122,6 +135,7 @@ export class ImageNode extends DecoratorNode { return span } + /** TODO: Add docs */ override updateDOM(_prevNode: ImageNode, dom: HTMLElement, config: EditorConfig): false { const className = config.theme.image if (className !== undefined) { @@ -130,6 +144,7 @@ export class ImageNode extends DecoratorNode { return false } + /** TODO: Add docs */ override decorate(): Component { return h(LexicalImage, { src: this.__src, @@ -138,10 +153,12 @@ export class ImageNode extends DecoratorNode { } } +/** TODO: Add docs */ export function $createImageNode({ altText, src, key }: ImagePayload): ImageNode { return $applyNodeReplacement(new ImageNode(src, altText, key)) } +/** TODO: Add docs */ export function $isImageNode(node: LexicalNode | null | undefined): node is ImageNode { return node instanceof ImageNode } diff --git a/app/gui2/src/components/MarkdownEditor/formatting.ts b/app/gui2/src/components/MarkdownEditor/formatting.ts index 81216c52518..51f92fe1d3f 100644 --- a/app/gui2/src/components/MarkdownEditor/formatting.ts +++ b/app/gui2/src/components/MarkdownEditor/formatting.ts @@ -34,6 +34,7 @@ import { } from 'lexical' import { ref } from 'vue' +/** TODO: Add docs */ export function useFormatting(editor: LexicalEditor) { const selectionReaders = new Array<(selection: RangeSelection) => void>() function onReadSelection(reader: (selection: RangeSelection) => void) { @@ -162,6 +163,7 @@ function isBlockType(value: string): value is BlockType { return value in blockTypeToBlockName } +/** TODO: Add docs */ export function normalizeHeadingLevel(heading: HeadingTagType): HeadingTagType & BlockType { return isBlockType(heading) ? heading : smallestEnabledHeading } @@ -237,6 +239,7 @@ function useBlockType( } } +/** TODO: Add docs */ export function lexicalRichTextTheme(themeCss: Record): EditorThemeClasses { const theme = lexicalTheme(themeCss) if (theme.heading) { diff --git a/app/gui2/src/components/MarkdownEditor/imageUrlTransformer.ts b/app/gui2/src/components/MarkdownEditor/imageUrlTransformer.ts index fee1e6e36c4..259037bb73c 100644 --- a/app/gui2/src/components/MarkdownEditor/imageUrlTransformer.ts +++ b/app/gui2/src/components/MarkdownEditor/imageUrlTransformer.ts @@ -26,6 +26,7 @@ export interface ResourceInfo { export type ResourceLocator = (url: Url) => Promise> | undefined> export type ResourceFetcher = (locator: T) => Promise> +/** TODO: Add docs */ export function fetcherUrlTransformer( locateResource: ResourceLocator, fetchResource: ResourceFetcher, diff --git a/app/gui2/src/components/MarkdownEditor/markdown.ts b/app/gui2/src/components/MarkdownEditor/markdown.ts index 9974c4be39a..b488ebe6f84 100644 --- a/app/gui2/src/components/MarkdownEditor/markdown.ts +++ b/app/gui2/src/components/MarkdownEditor/markdown.ts @@ -19,6 +19,7 @@ export interface LexicalMarkdownPlugin extends LexicalPlugin { transformers?: Transformer[] } +/** TODO: Add docs */ export function markdownPlugin( model: Ref, extensions: LexicalMarkdownPlugin[], diff --git a/app/gui2/src/components/lexical/LinkPlugin/autoMatcher.ts b/app/gui2/src/components/lexical/LinkPlugin/autoMatcher.ts index 448dde19261..cc521e2e65a 100644 --- a/app/gui2/src/components/lexical/LinkPlugin/autoMatcher.ts +++ b/app/gui2/src/components/lexical/LinkPlugin/autoMatcher.ts @@ -36,6 +36,7 @@ type MatchedLink = { export type LinkMatcher = (text: string) => MatchedLink | null +/** TODO: Add docs */ export function createLinkMatcherWithRegExp( regExp: RegExp, urlTransformer: (text: string) => string = (text) => text, @@ -403,6 +404,7 @@ function getTextNodesToMatch(textNode: TextNode): TextNode[] { return textNodesToMatch } +/** TODO: Add docs */ export function useAutoLink( editor: LexicalEditor, matchers: Array, diff --git a/app/gui2/src/components/lexical/LinkPlugin/index.ts b/app/gui2/src/components/lexical/LinkPlugin/index.ts index 29f7def720a..844d092308b 100644 --- a/app/gui2/src/components/lexical/LinkPlugin/index.ts +++ b/app/gui2/src/components/lexical/LinkPlugin/index.ts @@ -72,6 +72,7 @@ const LINK: Transformer = { type: 'text-match', } +/** TODO: Add docs */ export function $getSelectedLinkNode() { const selection = $getSelection() if (selection?.isCollapsed) { @@ -136,6 +137,7 @@ export const autoLinkPlugin: LexicalPlugin = { }, } +/** TODO: Add docs */ export function useLinkNode(editor: LexicalEditor) { const urlUnderCursor = shallowRef() editor.registerCommand( diff --git a/app/gui2/src/components/lexical/index.ts b/app/gui2/src/components/lexical/index.ts index f1ececb0016..f3e7da567b9 100644 --- a/app/gui2/src/components/lexical/index.ts +++ b/app/gui2/src/components/lexical/index.ts @@ -17,6 +17,7 @@ export interface LexicalPlugin { register: (editor: LexicalEditor) => void } +/** TODO: Add docs */ export function lexicalTheme(theme: Record): EditorThemeClasses { interface EditorThemeShape extends Record {} const editorClasses: EditorThemeShape = {} @@ -41,6 +42,7 @@ export function lexicalTheme(theme: Record): EditorThemeClasses return editorClasses } +/** TODO: Add docs */ export function useLexical( contentElement: Ref, namespace: string, diff --git a/app/gui2/src/components/lexical/sync.ts b/app/gui2/src/components/lexical/sync.ts index a6c39f4828f..de91c064e43 100644 --- a/app/gui2/src/components/lexical/sync.ts +++ b/app/gui2/src/components/lexical/sync.ts @@ -5,7 +5,8 @@ import { computed, shallowRef, toValue } from 'vue' const SYNC_TAG = 'ENSO_SYNC' -/** Enables two-way synchronization between the editor and a string model `content`. +/** + * Enables two-way synchronization between the editor and a string model `content`. * * By default, the editor's text contents are synchronized with the string. A content getter and setter may be provided * to synchronize a different view of the state, e.g. to transform to an encoding that keeps rich text information. @@ -20,6 +21,7 @@ export function useLexicalStringSync( }) } +/** TODO: Add docs */ export function useLexicalSync( editor: LexicalEditor, $read: () => T, @@ -49,10 +51,12 @@ export function useLexicalSync( } } +/** TODO: Add docs */ export function $getRootText() { return $getRoot().getTextContent() } +/** TODO: Add docs */ export function $setRootText(text: string) { const root = $getRoot() root.clear() diff --git a/app/gui2/src/components/shared/AgGridTableView.vue b/app/gui2/src/components/shared/AgGridTableView.vue index 03531a0e251..f3c3ab29aa3 100644 --- a/app/gui2/src/components/shared/AgGridTableView.vue +++ b/app/gui2/src/components/shared/AgGridTableView.vue @@ -114,7 +114,8 @@ function lockColumnSize(e: ColumnResizedEvent) { } } -/** Copy the provided TSV-formatted table data to the clipboard. +/** + * Copy the provided TSV-formatted table data to the clipboard. * * The data will be copied as `text/plain` TSV data for spreadsheet applications, and an Enso-specific MIME section for * pasting as a new table node. diff --git a/app/gui2/src/components/visualizations/GeoMapVisualization.vue b/app/gui2/src/components/visualizations/GeoMapVisualization.vue index 73839ea40ef..7145af273dd 100644 --- a/app/gui2/src/components/visualizations/GeoMapVisualization.vue +++ b/app/gui2/src/components/visualizations/GeoMapVisualization.vue @@ -96,8 +96,10 @@ const props = defineProps<{ data: Data }>() /** GeoMap Visualization. */ -/** Mapbox API access token. - * All the limits of API are listed here: https://docs.mapbox.com/api/#rate-limits */ +/** + * Mapbox API access token. + * All the limits of API are listed here: https://docs.mapbox.com/api/#rate-limits + */ const TOKEN = import.meta.env.VITE_ENSO_MAPBOX_API_TOKEN if (TOKEN == null) { console.warn( @@ -371,10 +373,7 @@ function extractVisualizationDataFromDataFrame(parsedData: DataFrame) { /** * Extracts the data form the given `parsedData`. Checks the type of input data and prepares our * internal data (`GeoPoints') for consumption in deck.gl. - * - * @param parsedData - All the parsed data to create points from. - * @param preparedDataPoints - List holding data points to push the GeoPoints into. - * @param ACCENT_COLOR - accent color of IDE if element doesn't specify one. + * @param parsedData - All the parsed data to create points from. */ function extractDataPoints(parsedData: Data) { if ('df_latitude' in parsedData && 'df_longitude' in parsedData) { diff --git a/app/gui2/src/components/visualizations/HistogramVisualization.vue b/app/gui2/src/components/visualizations/HistogramVisualization.vue index 4f4331a7adf..2a03c32f29b 100644 --- a/app/gui2/src/components/visualizations/HistogramVisualization.vue +++ b/app/gui2/src/components/visualizations/HistogramVisualization.vue @@ -368,8 +368,10 @@ function zoomed(event: d3.D3ZoomEvent) { } } -/** Return the zoom value computed from the initial right-mouse-button event to the current - * right-mouse event. */ +/** + * Return the zoom value computed from the initial right-mouse-button event to the current + * right-mouse event. + */ function rmbZoomValue(event: MouseEvent | WheelEvent | undefined) { const dX = (event?.clientX ?? 0) - startClientX const dY = (event?.clientY ?? 0) - startClientY @@ -521,10 +523,12 @@ function endBrushing() { d3Brush.value.call(brush.value.move, null) } -/** Zoom into the selected area of the plot. +/** + * Zoom into the selected area of the plot. * * Based on https://www.d3-graph-gallery.com/graph/interactivity_brush.html - * Section "Brushing for zooming". */ + * Section "Brushing for zooming". + */ function zoomToSelected(override?: boolean) { const shouldZoomToSelected = override ?? isBrushing.value if (!shouldZoomToSelected) { diff --git a/app/gui2/src/components/visualizations/SQLVisualization.vue b/app/gui2/src/components/visualizations/SQLVisualization.vue index 3316ecf5d8d..bf588a7b9a7 100644 --- a/app/gui2/src/components/visualizations/SQLVisualization.vue +++ b/app/gui2/src/components/visualizations/SQLVisualization.vue @@ -79,8 +79,10 @@ function convertColorToRgba(color: RGBA) { return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')' } -/** Replace the alpha component of a color (represented as a 4-element array), - * returning a new color. */ +/** + * Replace the alpha component of a color (represented as a 4-element array), + * returning a new color. + */ function replaceAlpha(color: RGBA, newAlpha: number) { return { red: color.red, diff --git a/app/gui2/src/components/visualizations/ScatterplotVisualization.vue b/app/gui2/src/components/visualizations/ScatterplotVisualization.vue index 8aa2d08da20..6b49f48db49 100644 --- a/app/gui2/src/components/visualizations/ScatterplotVisualization.vue +++ b/app/gui2/src/components/visualizations/ScatterplotVisualization.vue @@ -781,10 +781,12 @@ useEvent(document, 'auxclick', endBrushing) useEvent(document, 'contextmenu', endBrushing) useEvent(document, 'scroll', endBrushing) -/** Zoom into the selected area of the plot. +/** + * Zoom into the selected area of the plot. * * Based on https://www.d3-graph-gallery.com/graph/interactivity_brush.html - * Section "Brushing for zooming". */ + * Section "Brushing for zooming". + */ function zoomToSelected(override?: boolean) { shouldAnimate.value = true focus.value = undefined diff --git a/app/gui2/src/components/visualizations/tableVizToolbar.ts b/app/gui2/src/components/visualizations/tableVizToolbar.ts index 4549a4074b6..ea6f46c2ad7 100644 --- a/app/gui2/src/components/visualizations/tableVizToolbar.ts +++ b/app/gui2/src/components/visualizations/tableVizToolbar.ts @@ -183,6 +183,7 @@ function createFormatMenu({ textFormatterSelected }: FormatMenuOptions): Toolbar } } +/** TODO: Add docs */ export function useTableVizToolbar(options: Options): ComputedRef { const createNodesButton = useSortFilterNodesButton(options) const formatMenu = createFormatMenu(options) diff --git a/app/gui2/src/components/visualizations/toolbar.ts b/app/gui2/src/components/visualizations/toolbar.ts index 5d7e9e29107..fa117583e6e 100644 --- a/app/gui2/src/components/visualizations/toolbar.ts +++ b/app/gui2/src/components/visualizations/toolbar.ts @@ -32,14 +32,17 @@ export interface SelectionMenu { export type ToolbarItem = ActionButton | ToggleButton | SelectionMenu +/** {@link ActionButton} discriminant */ export function isActionButton(item: Readonly): item is ActionButton { return 'onClick' in item } +/** {@link ToggleButton} discriminant */ export function isToggleButton(item: Readonly): item is ToggleButton { return 'toggle' in item } +/** {@link SelectionMenu} discriminant */ export function isSelectionMenu(item: Readonly): item is SelectionMenu { return 'selected' in item } diff --git a/app/gui2/src/components/widgets/ListWidget.vue b/app/gui2/src/components/widgets/ListWidget.vue index f521eaec3ed..a328e6c2df2 100644 --- a/app/gui2/src/components/widgets/ListWidget.vue +++ b/app/gui2/src/components/widgets/ListWidget.vue @@ -14,19 +14,26 @@ const props = defineProps<{ modelValue: T[] newItem: () => T | undefined getKey?: (item: T) => string | number | undefined - /** If present, a {@link DataTransferItem} is added with a MIME type of `text/plain`. + /** + * If present, a {@link DataTransferItem} is added with a MIME type of `text/plain`. * This is useful if the drag payload has a representation that can be pasted in terminals, - * search bars, and/or address bars. */ + * search bars, and/or address bars. + */ toPlainText?: (item: T) => string - /** The MIME type for the payload output added by `toDragPayload`. + /** + * The MIME type for the payload output added by `toDragPayload`. * Unused if `toDragPayload` is not also present. * When in doubt, this should be `application/json`. - * Defaults to `application/octet-stream`, meaning the payload is arbitrary binary data. */ + * Defaults to `application/octet-stream`, meaning the payload is arbitrary binary data. + */ dragMimeType?: string - /** Convert the list item to a drag payload stored under `dragMimeType`. When in doubt, this - * should be `JSON.stringify` of data describing the object. */ + /** + * Convert the list item to a drag payload stored under `dragMimeType`. When in doubt, this + * should be `JSON.stringify` of data describing the object. + */ toDragPayload: (item: T) => string - /** Convert payload created by `toDragPayload` back to the list item. This function can be called + /** + * Convert payload created by `toDragPayload` back to the list item. This function can be called * on the payload received from a different application instance (e.g. another browser), so it * should not rely on any local state. */ diff --git a/app/gui2/src/composables/animation.ts b/app/gui2/src/composables/animation.ts index e89c6429d15..6c15a469bd9 100644 --- a/app/gui2/src/composables/animation.ts +++ b/app/gui2/src/composables/animation.ts @@ -22,7 +22,6 @@ const animTime = ref(0) * while the `active` watch source returns true value. * * For performing simple easing animations, see [`useApproach`]. - * * @param active As long as it returns true value, the `fn` callback will be called every frame. * @param fn The callback to call every animation frame. * @param priority When multiple callbacks are registered, the one with the lowest priority number @@ -90,7 +89,6 @@ function runRaf() { /** * Animate value over time using exponential approach. * http://badladns.com/stories/exp-approach - * * @param to Target value to approach. * @param timeHorizon Time at which the approach will be at 63% of the target value. Effectively * represents a speed of the approach. Lower values means faster animation. @@ -115,7 +113,6 @@ export function useApproach(to: WatchSource, timeHorizon: number = 100, /** * Animate a vector value over time using exponential approach. * http://badladns.com/stories/exp-approach - * * @param to Target vector value to approach. * @param timeHorizon Time at which the approach will be at 63% of the target value. Effectively * represents a speed of the approach. Lower values means faster animation. @@ -159,6 +156,7 @@ function useApproachBase( return readonly(proxyRefs({ value: current, skip })) } +/** TODO: Add docs */ export function useTransitioning(observedProperties?: Set) { const hasActiveAnimations = ref(false) let numActiveTransitions = 0 diff --git a/app/gui2/src/composables/astDocumentation.ts b/app/gui2/src/composables/astDocumentation.ts index daa1e7d2eb8..81cf012b3bc 100644 --- a/app/gui2/src/composables/astDocumentation.ts +++ b/app/gui2/src/composables/astDocumentation.ts @@ -3,6 +3,7 @@ import { type ToValue } from '@/util/reactivity' import { computed, toValue } from 'vue' import type { Ast } from 'ydoc-shared/ast' +/** A composable for reactively retrieving and setting documentation from given Ast node. */ export function useAstDocumentation(graphStore: GraphStore, ast: ToValue) { return { documentation: { diff --git a/app/gui2/src/composables/backend.ts b/app/gui2/src/composables/backend.ts index e150571c9ea..69704f2f0c4 100644 --- a/app/gui2/src/composables/backend.ts +++ b/app/gui2/src/composables/backend.ts @@ -37,6 +37,7 @@ function backendQueryOptions( } } +/** TODO: Add docs */ export function useBackendQuery( method: Method, args: ToValue | undefined>, @@ -45,6 +46,7 @@ export function useBackendQuery( return useQuery(backendQueryOptions(method, args, backend)) } +/** TODO: Add docs */ export function useBackendQueryPrefetching() { const queryClient = useQueryClient() const { backend } = injectBackend() diff --git a/app/gui2/src/composables/domSelection.ts b/app/gui2/src/composables/domSelection.ts index 51c6b1321ed..54cb20d2041 100644 --- a/app/gui2/src/composables/domSelection.ts +++ b/app/gui2/src/composables/domSelection.ts @@ -3,6 +3,7 @@ import { Rect } from '@/util/data/rect' import type { MaybeElement } from '@vueuse/core' import { shallowRef, watch, type Ref } from 'vue' +/** TODO: Add docs */ export function useSelectionBounds(boundingElement: Ref, includeCollapsed = false) { const bounds = shallowRef() const collapsed = shallowRef() diff --git a/app/gui2/src/composables/doubleClick.ts b/app/gui2/src/composables/doubleClick.ts index 6d3aa225c68..78336e439b6 100644 --- a/app/gui2/src/composables/doubleClick.ts +++ b/app/gui2/src/composables/doubleClick.ts @@ -1,9 +1,13 @@ -/** @file A Vue composable that calls one of two given callbacks, depending on whether a click is - * a single click or a double click. */ +/** + * @file A Vue composable that calls one of two given callbacks, depending on whether a click is + * a single click or a double click. + */ -/** Calls {@link onClick} if a click is a single click, or {@link onDoubleClick} if a click is +/** + * Calls {@link onClick} if a click is a single click, or {@link onDoubleClick} if a click is * a double click. For this function, a double click is defined as a second click that occurs within - * 200ms of the first click. The click count is reset to 0 upon double click, or after 200ms. */ + * 200ms of the first click. The click count is reset to 0 upon double click, or after 200ms. + */ export function useDoubleClick( onClick: (...args: Args) => void, onDoubleClick: (...args: Args) => void, diff --git a/app/gui2/src/composables/events.ts b/app/gui2/src/composables/events.ts index 68d57142308..c3fb65de2a7 100644 --- a/app/gui2/src/composables/events.ts +++ b/app/gui2/src/composables/events.ts @@ -19,17 +19,12 @@ import { } from 'vue' import { useRaf } from './animation' +/** TODO: Add docs */ export function isTriggeredByKeyboard(e: MouseEvent | PointerEvent) { if (e instanceof PointerEvent) return e.pointerType !== 'mouse' else return false } -/** - * Add an event listener for the duration of the component's lifetime. - * @param target element on which to register the event - * @param event name of event to register - * @param handler event handler - */ export function useEvent( target: Document, event: K, @@ -54,6 +49,12 @@ export function useEvent( handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions, ): void +/** + * Add an event listener for the duration of the component's lifetime. + * @param target element on which to register the event + * @param event name of event to register + * @param handler event handler + */ export function useEvent( target: EventTarget, event: string, @@ -64,13 +65,6 @@ export function useEvent( onScopeDispose(() => target.removeEventListener(event, handler, options)) } -/** - * Add an event listener for the duration of condition being true. - * @param target element on which to register the event - * @param condition the condition that determines if event is bound - * @param event name of event to register - * @param handler event handler - */ export function useEventConditional( target: Document, event: K, @@ -99,6 +93,14 @@ export function useEventConditional( handler: (event: unknown) => void, options?: boolean | AddEventListenerOptions, ): void +/** + * Add an event listener for the duration of condition being true. + * @param target element on which to register the event + * @param event name of event to register + * @param condition the condition that determines if event is bound + * @param handler event handler + * @param options listener options + */ export function useEventConditional( target: EventTarget, event: string, @@ -148,11 +150,13 @@ const hasWindow = typeof window !== 'undefined' const platform = hasWindow ? window.navigator?.platform ?? '' : '' export const isMacLike = /(Mac|iPhone|iPod|iPad)/i.test(platform) +/** Check if `mod` key (ctrl or cmd) appropriate for current platform is used */ export function modKey(e: KeyboardEvent | MouseEvent): boolean { return isMacLike ? e.metaKey : e.ctrlKey } -/** A helper for getting Element out of VueInstance, it allows using `useResizeObserver` with Vue components. +/** + * A helper for getting Element out of VueInstance, it allows using `useResizeObserver` with Vue components. * * Note that this function is only shallowly reactive: It will trigger its reactive scope if the value of `element` * changes, but not if the root `Element` of the provided `VueInstance` changes. This is because a @@ -230,7 +234,6 @@ const sharedResizeObserver: ResizeObserver | undefined = * # Warning: * Updating DOM node layout based on values derived from their size can introduce unwanted feedback * loops across the script and layout reflow. Avoid doing that. - * * @param elementRef DOM node to observe. * @returns Reactive value with the DOM node size. */ @@ -321,10 +324,13 @@ export const enum PointerButtonMask { * Options for `usePointer` composable. */ export interface UsePointerOptions { - /** Declare which buttons to look for. The value represents a `PointerEvent.buttons` mask. - * Defaults to main mouse button. */ + /** + * Declare which buttons to look for. The value represents a `PointerEvent.buttons` mask. + * Defaults to main mouse button. + */ requiredButtonMask?: number - /** Which element should capture pointer when drag starts: event's `target`, `currentTarget`, + /** + * Which element should capture pointer when drag starts: event's `target`, `currentTarget`, * or none. */ pointerCapturedBy?: 'target' | 'currentTarget' | 'none' @@ -334,11 +340,8 @@ export interface UsePointerOptions { /** * Register for a pointer dragging events. - * * @param handler callback on any pointer event. If `false` is returned from the callback, the * event will be considered _not_ handled and will propagate further. - * @param options - * @returns */ export function usePointer( handler: (pos: EventPosition, event: PointerEvent, eventType: PointerEventType) => void | boolean, @@ -465,11 +468,8 @@ export interface UseArrowsOptions { * always be Vec2.Zero (and thus, the absolute and relative positions will be equal). * * The "drag" starts on first arrow keypress and ends with last arrow key release. - * * @param handler callback on any event. The 'move' event is fired on every frame, and thus does * not have any event associated (`event` parameter will be undefined). - * @param options - * @returns */ export function useArrows( handler: ( @@ -572,7 +572,8 @@ export function useArrows( return { events, moving } } -/** Supports panning or zooming "capturing" wheel events. +/** + * Supports panning or zooming "capturing" wheel events. * * While events are captured, further events of the same type will continue the pan/zoom action. * The capture expires if no events are received within the specified `captureDurationMs`. diff --git a/app/gui2/src/composables/focus.ts b/app/gui2/src/composables/focus.ts index 0c845e1cad8..f7d6c395862 100644 --- a/app/gui2/src/composables/focus.ts +++ b/app/gui2/src/composables/focus.ts @@ -1,7 +1,8 @@ import { syncRef, useFocus, type MaybeElement } from '@vueuse/core' import { effectScope, nextTick, ref, watch, type WatchSource } from 'vue' -/** Maintain bidirectional synchronization between an element's focus state and a model value, which is returned. +/** + * Maintain bidirectional synchronization between an element's focus state and a model value, which is returned. * * This is similar to `syncRef(model, useFocus(element).focused)`, but correctly handles * the `element` being updated immediately before it is made visible in the DOM by delaying diff --git a/app/gui2/src/composables/keyboard.ts b/app/gui2/src/composables/keyboard.ts index 48086865a0f..c95c8ab3854 100644 --- a/app/gui2/src/composables/keyboard.ts +++ b/app/gui2/src/composables/keyboard.ts @@ -1,7 +1,9 @@ import { isMacLike, useEvent } from '@/composables/events' import { proxyRefs, ref } from 'vue' +/** {@link useKeyboard} composable object */ export type KeyboardComposable = ReturnType +/** Composable containing reactive flags for modifier's press state. */ export function useKeyboard() { const state = { alt: ref(false), diff --git a/app/gui2/src/composables/navigator.ts b/app/gui2/src/composables/navigator.ts index 5ad3ee5f876..2aee37afe5a 100644 --- a/app/gui2/src/composables/navigator.ts +++ b/app/gui2/src/composables/navigator.ts @@ -31,9 +31,11 @@ const ZOOM_LEVELS = [ ] const DEFAULT_SCALE_RANGE: ScaleRange = [Math.min(...ZOOM_LEVELS), Math.max(...ZOOM_LEVELS)] const ZOOM_LEVELS_REVERSED = [...ZOOM_LEVELS].reverse() -/** The fraction of the next zoom level. +/** + * The fraction of the next zoom level. * If we are that close to next zoom level, we should choose the next one instead - * to avoid small unnoticeable changes to zoom. */ + * to avoid small unnoticeable changes to zoom. + */ const ZOOM_SKIP_THRESHOLD = 0.05 const WHEEL_CAPTURE_DURATION_MS = 250 const LONGPRESS_TIMEOUT = 500 @@ -51,6 +53,7 @@ export interface NavigatorOptions { } export type NavigatorComposable = ReturnType +/** TODO: Add docs */ export function useNavigator( viewportNode: Ref, keyboard: KeyboardComposable, @@ -269,7 +272,8 @@ export function useNavigator( } }) - /** As `panTo`, but also follow the points if the viewport size is changing. + /** + * As `panTo`, but also follow the points if the viewport size is changing. * * The following is working until manual panning by user input or until the next call to any `pan…` function. */ @@ -278,7 +282,8 @@ export function useNavigator( panToImpl(points) } - /** Pan to include the given prioritized list of coordinates. + /** + * Pan to include the given prioritized list of coordinates. * * The view will be offset to include each coordinate, unless the coordinate cannot be fit in the viewport without * losing a previous (higher-priority) coordinate; in that case, shift the viewport as close as possible to the @@ -353,8 +358,10 @@ export function useNavigator( eventMousePos.value ? clientToScenePos(eventMousePos.value) : null, ) - /** Clamp the value to the given bounds, except if it is already outside the bounds allow the new value to be less - * outside the bounds. */ + /** + * Clamp the value to the given bounds, except if it is already outside the bounds allow the new value to be less + * outside the bounds. + */ function directedClamp(oldValue: number, newValue: number, [min, max]: ScaleRange): number { if (!Number.isFinite(newValue)) return oldValue else if (!Number.isFinite(oldValue)) return Math.max(min, Math.min(newValue, max)) @@ -368,9 +375,11 @@ export function useNavigator( scale.skip() } - /** Step to the next level from {@link ZOOM_LEVELS}. + /** + * Step to the next level from {@link ZOOM_LEVELS}. * @param zoomStepDelta step direction. If positive select larger zoom level; if negative select smaller. - * If 0, resets zoom level to 1.0. */ + * If 0, resets zoom level to 1.0. + */ function stepZoom(zoomStepDelta: number) { const oldValue = targetScale.value const insideThreshold = (level: number) => diff --git a/app/gui2/src/composables/nodeColors.ts b/app/gui2/src/composables/nodeColors.ts index e0b04077093..578e855968d 100644 --- a/app/gui2/src/composables/nodeColors.ts +++ b/app/gui2/src/composables/nodeColors.ts @@ -4,6 +4,7 @@ import { type Group } from '@/stores/suggestionDatabase' import { colorFromString } from '@/util/colors' import { computed } from 'vue' +/** TODO: Add docs */ export function useNodeColors(graphStore: GraphStore, getCssValue: (variable: string) => string) { function getNodeColor(node: NodeId) { const color = graphStore.db.getNodeColorStyle(node) @@ -34,6 +35,7 @@ export function useNodeColors(graphStore: GraphStore, getCssValue: (variable: st return { getNodeColor, getNodeColors } } +/** TODO: Add docs */ export function computeNodeColor( getType: () => NodeType, getGroup: () => Group | undefined, @@ -48,11 +50,13 @@ export function computeNodeColor( return 'var(--node-color-no-type)' } +/** TODO: Add docs */ export function groupColorVar(group: Group | undefined): string { const name = group ? `${group.project}-${group.name}`.replace(/[^\w]/g, '-') : 'fallback' return `--group-color-${name}` } +/** TODO: Add docs */ export function groupColorStyle(group: Group | undefined): string { return `var(${groupColorVar(group)})` } diff --git a/app/gui2/src/composables/nodeCreation.ts b/app/gui2/src/composables/nodeCreation.ts index e53c3349b83..46dc3fcca54 100644 --- a/app/gui2/src/composables/nodeCreation.ts +++ b/app/gui2/src/composables/nodeCreation.ts @@ -53,6 +53,7 @@ export interface NodeCreationOptions, @@ -240,7 +241,8 @@ function inferPrefixFromAst(expr: Ast.Ast): string | undefined { return undefined } -/** Convert Typename into short binding prefix. +/** + * Convert Typename into short binding prefix. * In general, we want to use the last segment of the qualified name. * In case of generic types, we want to discard any type parameters. */ @@ -254,13 +256,16 @@ function typeToPrefix(type: Typename): string { } } -/** Strip number suffix from binding name, effectively returning a valid prefix. - * The reverse of graphStore.generateLocallyUniqueIdent */ +/** + * Strip number suffix from binding name, effectively returning a valid prefix. + * The reverse of graphStore.generateLocallyUniqueIdent + */ function existingNameToPrefix(name: string): string { return name.replace(/\d+$/, '') } -/** Insert the given statements into the given block, at a location appropriate for new nodes. +/** + * Insert the given statements into the given block, at a location appropriate for new nodes. * * The location will be after any statements in the block that bind identifiers; if the block ends in an expression * statement, the location will be before it so that the value of the block will not be affected. diff --git a/app/gui2/src/composables/selection.ts b/app/gui2/src/composables/selection.ts index 2634b7b009c..bf90c1b29a4 100644 --- a/app/gui2/src/composables/selection.ts +++ b/app/gui2/src/composables/selection.ts @@ -20,7 +20,8 @@ interface BaseSelectionOptions { onDeselected?: (element: T) => void } interface SelectionPackingOptions { - /** The `pack` and `unpack` functions are used to maintain state in a transformed form. + /** + * The `pack` and `unpack` functions are used to maintain state in a transformed form. * * If provided, all operations that modify or query state will transparently operate on packed state. This can be * used to expose a selection interface based on one element type (`T`), while allowing the selection set to be @@ -45,6 +46,7 @@ export function useSelection( elementRects: Map, options: BaseSelectionOptions & SelectionPackingOptions, ): UseSelection +/** TODO: Add docs */ export function useSelection( navigator: NavigatorComposable, elementRects: Map, @@ -246,6 +248,7 @@ function useSelectionImpl( // === Hover tracking for nodes and ports === +/** TODO: Add docs */ export function useGraphHover(isPortEnabled: (port: PortId) => boolean) { const hoveredElement = shallowRef() diff --git a/app/gui2/src/composables/stackNavigator.ts b/app/gui2/src/composables/stackNavigator.ts index 735306ae25e..487b8370d0f 100644 --- a/app/gui2/src/composables/stackNavigator.ts +++ b/app/gui2/src/composables/stackNavigator.ts @@ -5,6 +5,7 @@ import { qnLastSegment, tryQualifiedName } from '@/util/qualifiedName' import { computed, onMounted, ref } from 'vue' import { methodPointerEquals, type StackItem } from 'ydoc-shared/languageServerTypes' +/** TODO: Add docs */ export function useStackNavigator(projectStore: ProjectStore, graphStore: GraphStore) { const breadcrumbs = ref([]) diff --git a/app/gui2/src/composables/vueQuery.ts b/app/gui2/src/composables/vueQuery.ts index 1b1682401b3..86a2f2352d1 100644 --- a/app/gui2/src/composables/vueQuery.ts +++ b/app/gui2/src/composables/vueQuery.ts @@ -2,6 +2,7 @@ import type { ToValue } from '@/util/reactivity' import type { QueryKey } from '@tanstack/vue-query' import { computed, toValue } from 'vue' +/** TODO: Add docs */ export function useQueryOptions( parameters: ToValue, queryKey: (parameters: Parameters) => QueryKey, diff --git a/app/gui2/src/entrypoint.ts b/app/gui2/src/entrypoint.ts index 30ed84331f5..74eda77f0cc 100644 --- a/app/gui2/src/entrypoint.ts +++ b/app/gui2/src/entrypoint.ts @@ -51,12 +51,14 @@ window.addEventListener('resize', () => { /** The entrypoint into the IDE. */ function main() { - /** Note: Signing out always redirects to `/`. It is impossible to make this work, + /** + * Note: Signing out always redirects to `/`. It is impossible to make this work, * as it is not possible to distinguish between having just logged out, and explicitly * opening a page with no URL parameters set. * * Client-side routing endpoints are explicitly not supported for live-reload, as they are - * transitional pages that should not need live-reload when running `gui watch`. */ + * transitional pages that should not need live-reload when running `gui watch`. + */ const url = new URL(location.href) const isInAuthenticationFlow = url.searchParams.has('code') && url.searchParams.has('state') const authenticationUrl = location.href diff --git a/app/gui2/src/providers/appClass.ts b/app/gui2/src/providers/appClass.ts index 9453da5780a..f774a3b24d0 100644 --- a/app/gui2/src/providers/appClass.ts +++ b/app/gui2/src/providers/appClass.ts @@ -7,6 +7,7 @@ const { provideFn, injectFn: injectAppClassSet } = createContextStore('App Class return reactive(new Map()) }) +/** TODO: Add docs */ export function useAppClass(watchSource: WatchSource>>) { const classSet = injectAppClassSet(true) if (classSet == null) return diff --git a/app/gui2/src/providers/index.ts b/app/gui2/src/providers/index.ts index a8570f3e434..db8e23d42b4 100644 --- a/app/gui2/src/providers/index.ts +++ b/app/gui2/src/providers/index.ts @@ -19,7 +19,6 @@ const MISSING = Symbol('MISSING') * * Under the hood, this uses Vue's [Context API], therefore it can only be used within a component's * setup function. - * * @param name A user-friendly name for the store, used for error messages and debugging. The name * has no influence on the standard runtime behavior of the store, and doesn't have to be unique. * @param factory A factory function that creates the store. The parameters expected by the factory @@ -36,7 +35,6 @@ export function createContextStore any>(name: stri /** * Create the instance of a store and store it in the current component's context. All child * components will be able to access that store using the corresponding inject function. - * * @param args The parameters that will be passed to the store factory function. * @returns The store instance created by the factory function. */ @@ -66,7 +64,6 @@ export function createContextStore any>(name: stri /** * Access a store instance provided by an ancestor component. When trying to access a store that * has never been provided, the behavior depends on the first parameter value. - * * @param missingBehavior determines what happens when trying to inject a store has never been provided: * - If `missingBehavior` is `false` or it is not provided, an exception is thrown. * - If `missingBehavior` is `true`, `undefined` is returned. This is also reflected in the return diff --git a/app/gui2/src/providers/interactionHandler.ts b/app/gui2/src/providers/interactionHandler.ts index a8e535c7558..36546357b90 100644 --- a/app/gui2/src/providers/interactionHandler.ts +++ b/app/gui2/src/providers/interactionHandler.ts @@ -7,9 +7,11 @@ const { provideFn, injectFn } = createContextStore( () => new InteractionHandler(), ) +/** TODO: Add docs */ export class InteractionHandler { private currentInteraction = shallowRef() + /** TODO: Add docs */ isActive(interaction: Interaction | undefined): interaction is Interaction { return interaction != null && interaction === this.currentInteraction.value } @@ -25,6 +27,7 @@ export class InteractionHandler { }) } + /** TODO: Add docs */ setCurrent(interaction: Interaction | undefined) { if (!this.isActive(interaction)) { this.currentInteraction.value?.end?.() @@ -32,6 +35,7 @@ export class InteractionHandler { } } + /** TODO: Add docs */ getCurrent(): Interaction | undefined { return this.currentInteraction.value } @@ -57,6 +61,7 @@ export class InteractionHandler { } } + /** TODO: Add docs */ handleCancel(): boolean { const hasCurrent = this.currentInteraction.value != null this.currentInteraction.value?.cancel?.() @@ -64,6 +69,7 @@ export class InteractionHandler { return hasCurrent } + /** TODO: Add docs */ handlePointerEvent( event: PointerEvent, handlerName: Interaction[HandlerName] extends InteractionEventHandler | undefined ? HandlerName @@ -90,8 +96,10 @@ export interface Interaction { end?(): void /** Uses a `capture` event handler to allow an interaction to respond to clicks over any element. */ pointerdown?: InteractionEventHandler - /** Uses a `capture` event handler to allow an interaction to respond to mouse button release + /** + * Uses a `capture` event handler to allow an interaction to respond to mouse button release * over any element. It is useful for interactions happening during mouse press (like dragging - * edges) */ + * edges) + */ pointerup?: InteractionEventHandler } diff --git a/app/gui2/src/providers/selectionArrow.ts b/app/gui2/src/providers/selectionArrow.ts index ddb4eac905d..14ec5975770 100644 --- a/app/gui2/src/providers/selectionArrow.ts +++ b/app/gui2/src/providers/selectionArrow.ts @@ -9,8 +9,10 @@ interface SelectionArrowInfo { id: AstId | PortId | TokenId | null /** Child widget can call this callback to request teleport of the arrow to specified element. */ requestArrow: (to: RendererElement) => void - /** Whether or not the arrow provided by this context instance was already requested. - * Do not request the arrow twice, it will be stolen from other elements! */ + /** + * Whether or not the arrow provided by this context instance was already requested. + * Do not request the arrow twice, it will be stolen from other elements! + */ handled: boolean /** * Child widget may set this flag to suppress arrow displaying. diff --git a/app/gui2/src/providers/visualizationConfig.ts b/app/gui2/src/providers/visualizationConfig.ts index 3512602ad21..9787ff28208 100644 --- a/app/gui2/src/providers/visualizationConfig.ts +++ b/app/gui2/src/providers/visualizationConfig.ts @@ -36,6 +36,7 @@ const { provideFn, injectFn } = createContextStore( // The visualization config public API should not expose the `allowMissing` parameter. It should // look like an ordinary vue composable. +/** TODO: Add docs */ export function useVisualizationConfig() { return injectFn() } diff --git a/app/gui2/src/providers/widgetRegistry.ts b/app/gui2/src/providers/widgetRegistry.ts index 1553470da3f..c196783702a 100644 --- a/app/gui2/src/providers/widgetRegistry.ts +++ b/app/gui2/src/providers/widgetRegistry.ts @@ -12,6 +12,7 @@ import type { WidgetEditHandlerParent } from './widgetRegistry/editHandler' export type WidgetComponent = Component> export namespace WidgetInput { + /** TODO: Add docs */ export function FromAst(ast: A): WidgetInput & { value: A } { return { portId: ast.id, @@ -19,6 +20,7 @@ export namespace WidgetInput { } } + /** TODO: Add docs */ export function FromAstWithPort( ast: A, ): WidgetInput & { value: A } { @@ -29,6 +31,7 @@ export namespace WidgetInput { } } + /** TODO: Add docs */ export function valueRepr(input: WidgetInput): string | undefined { if (typeof input.value === 'string') return input.value else return input.value?.code() @@ -53,6 +56,7 @@ export namespace WidgetInput { isPlaceholder(input) || input.value instanceof nodeType } + /** TODO: Add docs */ export function isAst(input: WidgetInput): input is WidgetInput & { value: Ast.Ast } { return input.value instanceof Ast.Ast } @@ -64,10 +68,12 @@ export namespace WidgetInput { return isPlaceholder(input) || isAst(input) } + /** TODO: Add docs */ export function isToken(input: WidgetInput): input is WidgetInput & { value: Ast.Token } { return input.value instanceof Ast.Token } + /** TODO: Add docs */ export function isFunctionCall( input: WidgetInput, ): input is WidgetInput & { value: Ast.App | Ast.Ident | Ast.PropertyAccess | Ast.OprApp } { @@ -198,7 +204,8 @@ type InputTy = : never export interface WidgetOptions { - /** The priority number determining the order in which the widgets are matched. Smaller numbers + /** + * The priority number determining the order in which the widgets are matched. Smaller numbers * have higher priority, and are matched first. */ priority: number @@ -215,7 +222,8 @@ export interface WidgetOptions { // A list of widget kinds that will be prevented from being used on the same node as this widget, // once this widget is used. prevent?: WidgetComponent[] - /** If false, this widget will be matched only when at least one another widget is guaranteed to + /** + * If false, this widget will be matched only when at least one another widget is guaranteed to * be matched for the same input. `true` by default. * * The widget marked with `false` *must* have a child with the same input, @@ -331,6 +339,7 @@ const { provideFn, injectFn } = createContextStore( (db: GraphDb) => new WidgetRegistry(db), ) +/** TODO: Add docs */ export class WidgetRegistry { loadedModules: WidgetModule[] = shallowReactive([]) sortedModules = computed(() => { @@ -338,8 +347,10 @@ export class WidgetRegistry { (a, b) => a.widgetDefinition.priority - b.widgetDefinition.priority, ) }) + /** TODO: Add docs */ constructor(private db: GraphDb) {} + /** TODO: Add docs */ loadWidgets(modules: [path: string, module: unknown][]) { for (const [path, mod] of modules) { if (isWidgetModule(mod)) this.registerWidgetModule(mod) diff --git a/app/gui2/src/providers/widgetRegistry/configuration.ts b/app/gui2/src/providers/widgetRegistry/configuration.ts index 84cc30d71ce..8889ca6aa24 100644 --- a/app/gui2/src/providers/widgetRegistry/configuration.ts +++ b/app/gui2/src/providers/widgetRegistry/configuration.ts @@ -1,6 +1,7 @@ import { z } from 'zod' -/** Intermediate step in the parsing process, when we rename `constructor` field to `kind`. +/** + * Intermediate step in the parsing process, when we rename `constructor` field to `kind`. * * It helps to avoid issues with TypeScript, which considers `constructor` as a reserved keyword in many contexts. */ @@ -115,22 +116,28 @@ export interface SingleChoice { values: Choice[] } -/** Dynamic configuration for a function call with a list of arguments with known dynamic configuration. - * This kind of config is not provided by the engine directly, but is derived from other config types by widgets. */ +/** + * Dynamic configuration for a function call with a list of arguments with known dynamic configuration. + * This kind of config is not provided by the engine directly, but is derived from other config types by widgets. + */ export interface FunctionCall { kind: 'FunctionCall' parameters: Map } -/** Dynamic configuration for one of the possible function calls. It is typically the case for dropdown widget. +/** + * Dynamic configuration for one of the possible function calls. It is typically the case for dropdown widget. * One of function calls will be chosen by WidgetFunction basing on the actual AST at the call site, * and the configuration will be used in child widgets. - * This kind of config is not provided by the engine directly, but is derived from other config types by widgets. */ + * This kind of config is not provided by the engine directly, but is derived from other config types by widgets. + */ export interface OneOfFunctionCalls { kind: 'OneOfFunctionCalls' - /** A list of possible function calls and their corresponding configuration. + /** + * A list of possible function calls and their corresponding configuration. * The key is typically a fully qualified or autoscoped name of the function, but in general it can be anything, - * depending on the widget implementation. */ + * depending on the widget implementation. + */ possibleFunctions: Map } diff --git a/app/gui2/src/providers/widgetRegistry/editHandler.ts b/app/gui2/src/providers/widgetRegistry/editHandler.ts index ea3f34b5770..b91dc2c1aa7 100644 --- a/app/gui2/src/providers/widgetRegistry/editHandler.ts +++ b/app/gui2/src/providers/widgetRegistry/editHandler.ts @@ -12,6 +12,7 @@ declare const brandWidgetId: unique symbol /** Uniquely identifies a widget type. */ export type WidgetId = string & { [brandWidgetId]: true } +/** TODO: Add docs */ export abstract class WidgetEditHandlerParent { private readonly activeChild: ShallowRef = shallowRef(undefined) @@ -64,6 +65,7 @@ export abstract class WidgetEditHandlerParent { this.parent?.onEdit(origin, value) } + /** TODO: Add docs */ addItem(): boolean { return this.hooks.addItem?.() ?? this.parent?.addItem() ?? false } @@ -73,6 +75,7 @@ export abstract class WidgetEditHandlerParent { else return this.activeChild.value ? this.activeChild.value.pointerdown(event) : false } + /** TODO: Add docs */ isActive() { return this.active.value } @@ -132,7 +135,9 @@ type ResumeCallback = () => void type WidgetInstanceId = `${string}||${WidgetId}` type ResumableWidgetEdits = Map +/** TODO: Add docs */ export class WidgetEditHandlerRoot extends WidgetEditHandlerParent implements Interaction { + /** TODO: Add docs */ constructor( private readonly widgetTree: CurrentEdit, private readonly interactionHandler: InteractionHandler, @@ -148,6 +153,7 @@ export class WidgetEditHandlerRoot extends WidgetEditHandlerParent implements In }) } + /** TODO: Add docs */ tryResumeRoot(widgetInstance: WidgetInstanceId) { const current = this.interactionHandler.getCurrent() if (current instanceof WidgetEditHandlerRoot) { @@ -155,14 +161,17 @@ export class WidgetEditHandlerRoot extends WidgetEditHandlerParent implements In } } + /** TODO: Add docs */ cancel() { this.onCancel() } + /** TODO: Add docs */ end() { this.onEnd() } + /** TODO: Add docs */ override pointerdown(event: PointerEvent) { return super.pointerdown(event) } @@ -171,10 +180,12 @@ export class WidgetEditHandlerRoot extends WidgetEditHandlerParent implements In return this } + /** TODO: Add docs */ override isActive() { return this.interactionHandler.isActive(this) } + /** TODO: Add docs */ currentEdit() { const leaf = this.activeLeaf() if (leaf !== this) return leaf @@ -210,6 +221,7 @@ export class WidgetEditHandlerRoot extends WidgetEditHandlerParent implements In * the top-most widget, and a widget may choose to delegate to its child (if any) by returning false. */ export class WidgetEditHandler extends WidgetEditHandlerParent { + /** TODO: Add docs */ constructor( readonly portId: PortId, hooks: WidgetEditHooks, @@ -220,6 +232,7 @@ export class WidgetEditHandler extends WidgetEditHandlerParent { super(parent ?? new WidgetEditHandlerRoot(widgetTree, interactionHandler), hooks) } + /** TODO: Add docs */ static New( widgetId: string, input: WidgetInput, @@ -235,18 +248,22 @@ export class WidgetEditHandler extends WidgetEditHandlerParent { return editHandler } + /** TODO: Add docs */ end() { this.onEnd(this.portId) } + /** TODO: Add docs */ cancel() { this.root().cancel() } + /** TODO: Add docs */ start() { this.onStart(this.portId) } + /** TODO: Add docs */ edit(value: Ast.Owned | string) { this.onEdit(this.portId, value) } @@ -270,7 +287,8 @@ export interface WidgetEditHooks extends Interaction { * to indicate that creating the new item has been handled and the child should not perform its action in this case. */ addItem?(): boolean - /** Hook called when the edit is aborted because the component instance is about to be unmounted due to a change in + /** + * Hook called when the edit is aborted because the component instance is about to be unmounted due to a change in * the graph. * * In this case, if a successor is identified in the graph, the interaction will be restarted. If this hook is diff --git a/app/gui2/src/providers/widgetTree.ts b/app/gui2/src/providers/widgetTree.ts index a51caf5c0aa..3116ac695fa 100644 --- a/app/gui2/src/providers/widgetTree.ts +++ b/app/gui2/src/providers/widgetTree.ts @@ -40,6 +40,7 @@ const { provideFn, injectFn } = createContextStore( }, ) +/** TODO: Add docs */ export function useCurrentEdit() { const currentEditRoot = shallowRef() return { diff --git a/app/gui2/src/stores/awareness.ts b/app/gui2/src/stores/awareness.ts index 6bd06d9f4f9..c46a8b8cf96 100644 --- a/app/gui2/src/stores/awareness.ts +++ b/app/gui2/src/stores/awareness.ts @@ -23,6 +23,7 @@ export class Awareness { public internal: YjsAwareness private uploadingFiles: Map + /** TODO: Add docs */ constructor(doc: Y.Doc) { this.internal = new YjsAwareness(doc) this.internal.setLocalState(initialState()) @@ -39,18 +40,21 @@ export class Awareness { }) } + /** TODO: Add docs */ public addOrUpdateUpload(name: FileName, file: UploadingFile) { this.withUploads((uploads) => { uploads[name] = file }) } + /** TODO: Add docs */ public removeUpload(name: FileName) { this.withUploads((uploads) => { delete uploads[name] }) } + /** TODO: Add docs */ public allUploads(): Iterable<[FileName, UploadingFile]> { return [...this.uploadingFiles.values()].flatMap((uploads) => [...Object.entries(uploads)]) } diff --git a/app/gui2/src/stores/graph/__tests__/graphDatabase.test.ts b/app/gui2/src/stores/graph/__tests__/graphDatabase.test.ts index 31d769461a7..c006bff9bd3 100644 --- a/app/gui2/src/stores/graph/__tests__/graphDatabase.test.ts +++ b/app/gui2/src/stores/graph/__tests__/graphDatabase.test.ts @@ -6,6 +6,7 @@ import { watchEffect } from 'vue' import type { AstId } from 'ydoc-shared/ast' import { IdMap, type ExternalId, type SourceRange } from 'ydoc-shared/yjsModel' +/** TODO: Add docs */ export function parseWithSpans>(code: string, spans: T) { const nameToEid = new Map() const eid = (name: keyof T) => nameToEid.get(name)! diff --git a/app/gui2/src/stores/graph/graphDatabase.ts b/app/gui2/src/stores/graph/graphDatabase.ts index 40da7ac4198..2c2174a6778 100644 --- a/app/gui2/src/stores/graph/graphDatabase.ts +++ b/app/gui2/src/stores/graph/graphDatabase.ts @@ -38,10 +38,12 @@ export interface BindingInfo { usages: Set } +/** TODO: Add docs */ export class BindingsDb { bindings = new ReactiveDb() identifierToBindingId = new ReactiveIndex(this.bindings, (id, info) => [[info.identifier, id]]) + /** TODO: Add docs */ readFunctionAst( func: Ast.Function, rawFunc: RawAst.Tree.Function | undefined, @@ -100,7 +102,8 @@ export class BindingsDb { } } - /** Create mappings between bindings' ranges and AST + /** + * Create mappings between bindings' ranges and AST * * The AliasAnalyzer is general and returns ranges, but we're interested in AST nodes. This * method creates mappings in both ways. For given range, only the shallowest AST node will be @@ -132,6 +135,7 @@ export class BindingsDb { } } +/** TODO: Add docs */ export class GraphDb { nodeIdToNode = new ReactiveDb() private readonly nodeSources = new Map() @@ -140,6 +144,7 @@ export class GraphDb { private readonly idFromExternalMap = reactive(new Map()) private bindings = new BindingsDb() + /** TODO: Add docs */ constructor( private suggestionDb: SuggestionDb, private groups: Ref, @@ -207,6 +212,7 @@ export class GraphDb { return this.suggestionDb.findByMethodPointer(method) }) + /** TODO: Add docs */ getNodeMainSuggestion(id: NodeId) { const suggestionId = this.nodeMainSuggestionId.lookup(id) if (suggestionId == null) return @@ -222,10 +228,12 @@ export class GraphDb { ) }) + /** TODO: Add docs */ getNodeFirstOutputPort(id: NodeId | undefined): AstId | undefined { return id ? set.first(this.nodeOutputPorts.lookup(id)) ?? this.idFromExternal(id) : undefined } + /** TODO: Add docs */ *getNodeUsages(id: NodeId): IterableIterator { const outputPorts = this.nodeOutputPorts.lookup(id) for (const outputPort of outputPorts) { @@ -233,44 +241,54 @@ export class GraphDb { } } + /** TODO: Add docs */ getExpressionNodeId(exprId: AstId | undefined): NodeId | undefined { return exprId && set.first(this.nodeIdToExprIds.reverseLookup(exprId)) } + /** TODO: Add docs */ getPatternExpressionNodeId(exprId: AstId | undefined): NodeId | undefined { return exprId && set.first(this.nodeIdToPatternExprIds.reverseLookup(exprId)) } + /** TODO: Add docs */ getIdentDefiningNode(ident: string): NodeId | undefined { const binding = set.first(this.bindings.identifierToBindingId.lookup(ident)) return this.getPatternExpressionNodeId(binding) } + /** TODO: Add docs */ getExpressionInfo(id: AstId | ExternalId | undefined): ExpressionInfo | undefined { const externalId = isUuid(id) ? id : this.idToExternal(id) return externalId && this.valuesRegistry.getExpressionInfo(externalId) } + /** TODO: Add docs */ getOutputPortIdentifier(source: AstId | undefined): string | undefined { return source ? this.bindings.bindings.get(source)?.identifier : undefined } + /** TODO: Add docs */ identifierUsed(ident: string): boolean { return this.bindings.identifierToBindingId.hasKey(ident) } + /** TODO: Add docs */ nodeIds(): IterableIterator { return this.nodeIdToNode.keys() } + /** TODO: Add docs */ isNodeId(externalId: ExternalId): boolean { return this.nodeIdToNode.has(asNodeId(externalId)) } + /** TODO: Add docs */ isKnownFunctionCall(id: AstId): boolean { return this.getMethodCallInfo(id) != null } + /** TODO: Add docs */ getMethodCall(id: AstId): MethodCall | undefined { const info = this.getExpressionInfo(id) if (info == null) return @@ -279,6 +297,7 @@ export class GraphDb { ) } + /** TODO: Add docs */ getMethodCallInfo(id: AstId): MethodCallInfo | undefined { const methodCall = this.getMethodCall(id) if (methodCall == null) return @@ -289,10 +308,12 @@ export class GraphDb { return { methodCall, methodCallSource: id, suggestion } } + /** TODO: Add docs */ getNodeColorStyle(id: NodeId): string { return this.nodeColor.lookup(id) ?? 'var(--node-color-no-type)' } + /** TODO: Add docs */ moveNodeToTop(id: NodeId) { const node = this.nodeIdToNode.get(id) if (!node) return @@ -450,6 +471,7 @@ export class GraphDb { this.bindings.readFunctionAst(functionAst_, rawFunction, moduleCode, getSpan) } + /** TODO: Add docs */ updateExternalIds(topLevel: Ast.Ast) { const idToExternalNew = new Map() const idFromExternalNew = new Map() @@ -481,6 +503,7 @@ export class GraphDb { } } + /** TODO: Add docs */ nodeByRootAstId(astId: Ast.AstId): Node | undefined { const nodeId = asNodeId(this.idToExternal(astId)) return nodeId != null ? this.nodeIdToNode.get(nodeId) : undefined @@ -490,7 +513,8 @@ export class GraphDb { idFromExternal(id: ExternalId | undefined): AstId | undefined { return id ? this.idFromExternalMap.get(id) : id } - /** Get the external ID corresponding to the given `AstId` as of the last synchronization. + /** + * Get the external ID corresponding to the given `AstId` as of the last synchronization. * * Note that if there is an edit in progress (i.e. a `MutableModule` containing changes that haven't been committed * and observed), this may be different from the value return by calling `toExternal` on the edited `Ast` object. @@ -508,10 +532,12 @@ export class GraphDb { return id ? this.idToExternalMap.get(id) : undefined } + /** TODO: Add docs */ static Mock(registry = ComputedValueRegistry.Mock(), db = new SuggestionDb()): GraphDb { return new GraphDb(db, ref([]), registry) } + /** TODO: Add docs */ mockNode(binding: string, id: NodeId, code?: string): Node { const edit = MutableModule.Transient() const pattern = Ast.parse(binding, edit) @@ -550,11 +576,15 @@ export class GraphDb { interface NodeSource { /** The outer AST of the node (see {@link NodeDataFromAst.outerExpr}). */ outerAst: Ast.Ast - /** Whether the node is `output` of the function or not. Mutually exclusive with `isInput`. - * Output node is the last node in a function body and has no pattern. */ + /** + * Whether the node is `output` of the function or not. Mutually exclusive with `isInput`. + * Output node is the last node in a function body and has no pattern. + */ isOutput: boolean - /** Whether the node is `input` of the function or not. Mutually exclusive with `isOutput`. - * Input node is a function argument. */ + /** + * Whether the node is `input` of the function or not. Mutually exclusive with `isOutput`. + * Input node is a function argument. + */ isInput: boolean /** The index of the argument in the function's argument list, if the node is an input node. */ argIndex: number | undefined @@ -567,6 +597,7 @@ export type NodeId = string & ExternalId & { [brandNodeId]: never } export type NodeType = 'component' | 'output' | 'input' export function asNodeId(id: ExternalId): NodeId export function asNodeId(id: ExternalId | undefined): NodeId | undefined +/** TODO: Add docs */ export function asNodeId(id: ExternalId | undefined): NodeId | undefined { return id != null ? (id as NodeId) : undefined } @@ -583,13 +614,18 @@ export interface NodeDataFromAst { outerExpr: Ast.Ast /** The left side of the assignment expression, if `outerExpr` is an assignment expression. */ pattern: Ast.Ast | undefined - /** The value of the node. The right side of the assignment, if `outerExpr` is an assignment - * expression, else the entire `outerExpr`. */ + /** + * The value of the node. The right side of the assignment, if `outerExpr` is an assignment + * expression, else the entire `outerExpr`. + */ rootExpr: Ast.Ast - /** The expression displayed by the node. This is `rootExpr`, minus the prefixes, which are in - * `prefixes`. */ + /** + * The expression displayed by the node. This is `rootExpr`, minus the prefixes, which are in + * `prefixes`. + */ innerExpr: Ast.Ast - /** Prefixes that are present in `rootExpr` but omitted in `innerExpr` to ensure a clean output. + /** + Prefixes that are present in `rootExpr` but omitted in `innerExpr` to ensure a clean output. */ prefixes: Record<'enableRecording', Ast.AstId[] | undefined> /** A child AST in a syntactic position to be a self-argument input to the node. */ diff --git a/app/gui2/src/stores/graph/imports.ts b/app/gui2/src/stores/graph/imports.ts index 64d7e31d5ee..b9ba43514dc 100644 --- a/app/gui2/src/stores/graph/imports.ts +++ b/app/gui2/src/stores/graph/imports.ts @@ -121,7 +121,8 @@ export function addImports(scope: Ast.MutableBodyBlock, importsToAdd: RequiredIm scope.insert(position, ...imports) } -/** Return a suitable location in the given block to insert an import statement. +/** + * Return a suitable location in the given block to insert an import statement. * * The location chosen will be before the first non-import line, and after all preexisting imports. * If there are any blank lines in that range, it will be before them. @@ -142,8 +143,9 @@ function newImportsLocation(scope: Ast.BodyBlock): number { return lastImport === undefined ? 0 : lastImport + 1 } -/** Create an AST representing the required import statement. - * @internal */ +/** + * Create an AST representing the required import statement. + @internal */ export function requiredImportToAst(value: RequiredImport, module?: MutableModule) { const module_ = module ?? MutableModule.Transient() switch (value.kind) { @@ -210,6 +212,7 @@ export function requiredImports( } } +/** TODO: Add docs */ export function requiredImportsByFQN( db: SuggestionDb, fqn: QualifiedName, @@ -238,6 +241,7 @@ function entryFQNFromRequiredImport(importStatement: RequiredImport): QualifiedN } } +/** TODO: Add docs */ export function requiredImportEquals(left: RequiredImport, right: RequiredImport): boolean { if (left.kind != right.kind) return false switch (left.kind) { @@ -274,6 +278,7 @@ export function covers(existing: Import, required: RequiredImport): boolean { return directlyImported || importedInList || importedWithAll } +/** TODO: Add docs */ export function filterOutRedundantImports( existing: Import[], required: RequiredImport[], @@ -294,6 +299,7 @@ export interface DetectedConflict { export type ConflictInfo = DetectedConflict | undefined /* Detect possible name clash when adding `importsForEntry` with `existingImports` present. */ +/** TODO: Add docs */ export function detectImportConflicts( suggestionDb: SuggestionDb, existingImports: Import[], diff --git a/app/gui2/src/stores/graph/index.ts b/app/gui2/src/stores/graph/index.ts index 53af5164458..868de9830d1 100644 --- a/app/gui2/src/stores/graph/index.ts +++ b/app/gui2/src/stores/graph/index.ts @@ -73,7 +73,9 @@ export interface NodeEditInfo { initialCursorPos: number } +/** TODO: Add docs */ export class PortViewInstance { + /** TODO: Add docs */ constructor( public rect: ShallowRef, public nodeId: NodeId, @@ -222,10 +224,11 @@ export const { injectFn: useGraphStore, provideFn: provideGraphStore } = createC return Ok(method) } - /** Generate unique identifier from `prefix` and some numeric suffix. + /** + * Generate unique identifier from `prefix` and some numeric suffix. * @param prefix - of the identifier * @param ignore - a list of identifiers to consider as unavailable. Useful when creating multiple identifiers in a batch. - * */ + */ function generateLocallyUniqueIdent( prefix?: string | undefined, ignore: Set = new Set(), @@ -524,8 +527,10 @@ export const { injectFn: useGraphStore, provideFn: provideGraphStore } = createC return getPortRelativeRect(id) != null } - /** Return the node ID that has the given `id` as its pattern or primary port. - * Technically this is either a component or the input node, as input nodes do not have patterns. */ + /** + * Return the node ID that has the given `id` as its pattern or primary port. + * Technically this is either a component or the input node, as input nodes do not have patterns. + */ function getSourceNodeId(id: AstId): NodeId | undefined { return db.getPatternExpressionNodeId(id) || getPortPrimaryInstance(id)?.nodeId } @@ -556,8 +561,8 @@ export const { injectFn: useGraphStore, provideFn: provideGraphStore } = createC return syncModule.value!.edit() } - /** Apply the given `edit` to the state. - * + /** + * Apply the given `edit` to the state. * @param skipTreeRepair - If the edit is known not to require any parenthesis insertion, this may be set to `true` * for better performance. */ @@ -575,10 +580,10 @@ export const { injectFn: useGraphStore, provideFn: provideGraphStore } = createC syncModule.value!.applyEdit(edit, origin) } - /** Edit the AST module. - * - * Optimization options: These are safe to use for metadata-only edits; otherwise, they require extreme caution. + /** + * Edit the AST module. * + * Optimization options: These are safe to use for metadata-only edits; otherwise, they require extreme caution. * @param skipTreeRepair - If the edit is certain not to produce incorrect or non-canonical syntax, this may be set * to `true` for better performance. */ @@ -598,7 +603,8 @@ export const { injectFn: useGraphStore, provideFn: provideGraphStore } = createC return result! } - /** Obtain a version of the given `Ast` for direct mutation. The `ast` must exist in the current module. + /** + * Obtain a version of the given `Ast` for direct mutation. The `ast` must exist in the current module. * This can be more efficient than creating and committing an edit, but skips tree-repair and cannot be aborted. */ function getMutable(ast: T): Ast.Mutable { @@ -799,6 +805,7 @@ export interface ConnectedEdge { target: PortId } +/** TODO: Add docs */ export function isConnected(edge: Edge): edge is ConnectedEdge { return edge.source != null && edge.target != null } diff --git a/app/gui2/src/stores/graph/unconnectedEdges.ts b/app/gui2/src/stores/graph/unconnectedEdges.ts index 7fd602abdb4..07c35012c34 100644 --- a/app/gui2/src/stores/graph/unconnectedEdges.ts +++ b/app/gui2/src/stores/graph/unconnectedEdges.ts @@ -36,6 +36,7 @@ export interface MouseEditedEdge { event: PointerEvent | undefined } +/** TODO: Add docs */ export function useUnconnectedEdges() { const mouseEditedEdge = ref() const cbEditedEdge = ref() diff --git a/app/gui2/src/stores/project/computedValueRegistry.ts b/app/gui2/src/stores/project/computedValueRegistry.ts index cd624c9d86f..3f5a2c1a58c 100644 --- a/app/gui2/src/stores/project/computedValueRegistry.ts +++ b/app/gui2/src/stores/project/computedValueRegistry.ts @@ -30,6 +30,7 @@ export class ComputedValueRegistry { markRaw(this) } + /** TODO: Add docs */ static WithExecutionContext(executionContext: ExecutionContext): ComputedValueRegistry { const self = new ComputedValueRegistry() self.executionContext = executionContext @@ -37,10 +38,12 @@ export class ComputedValueRegistry { return self } + /** TODO: Add docs */ static Mock(): ComputedValueRegistry { return new ComputedValueRegistry() } + /** TODO: Add docs */ processUpdates(updates: ExpressionUpdate[]) { for (const update of updates) { const info = this.db.get(update.expressionId) @@ -49,10 +52,12 @@ export class ComputedValueRegistry { } } + /** TODO: Add docs */ getExpressionInfo(exprId: ExpressionId): ExpressionInfo | undefined { return this.db.get(exprId) } + /** TODO: Add docs */ dispose() { this.executionContext?.off('expressionUpdates', this._updateHandler) } diff --git a/app/gui2/src/stores/project/executionContext.ts b/app/gui2/src/stores/project/executionContext.ts index 95dc0976fd5..5b9c0f6b0fe 100644 --- a/app/gui2/src/stores/project/executionContext.ts +++ b/app/gui2/src/stores/project/executionContext.ts @@ -109,6 +109,7 @@ export class ExecutionContext extends ObservableV2 private visualizationConfigs: Map = new Map() private _executionEnvironment: ExecutionEnvironment = 'Design' + /** TODO: Add docs */ constructor( private lsRpc: LanguageServer, entryPoint: EntryPoint, @@ -215,20 +216,24 @@ export class ExecutionContext extends ObservableV2 } } + /** TODO: Add docs */ get desiredStack() { return this._desiredStack } + /** TODO: Add docs */ set desiredStack(stack: StackItem[]) { this._desiredStack.length = 0 this._desiredStack.push(...stack) this.sync() } + /** TODO: Add docs */ push(expressionId: ExpressionId) { this.pushItem({ type: 'LocalCall', expressionId }) } + /** TODO: Add docs */ pop() { if (this._desiredStack.length === 1) { console.debug('Cannot pop last item from execution context stack') @@ -238,6 +243,7 @@ export class ExecutionContext extends ObservableV2 this.sync() } + /** TODO: Add docs */ setVisualization(id: Uuid, configuration: Opt) { if (configuration == null) { this.visualizationConfigs.delete(id) @@ -247,6 +253,7 @@ export class ExecutionContext extends ObservableV2 this.sync() } + /** TODO: Add docs */ recompute( expressionIds: 'all' | ExternalId[] = 'all', executionEnvironment?: ExecutionEnvironment, @@ -260,23 +267,28 @@ export class ExecutionContext extends ObservableV2 }) } + /** TODO: Add docs */ getStackBottom(): StackItem { return this._desiredStack[0]! } + /** TODO: Add docs */ getStackTop(): StackItem { return this._desiredStack[this._desiredStack.length - 1]! } + /** TODO: Add docs */ get executionEnvironment() { return this._executionEnvironment } + /** TODO: Add docs */ set executionEnvironment(env: ExecutionEnvironment) { this._executionEnvironment = env this.sync() } + /** TODO: Add docs */ dispose() { this.queue.pushTask(async (state) => { if (state.status === 'created') { diff --git a/app/gui2/src/stores/project/visualizationDataRegistry.ts b/app/gui2/src/stores/project/visualizationDataRegistry.ts index 0e2e940adef..f3c893676e9 100644 --- a/app/gui2/src/stores/project/visualizationDataRegistry.ts +++ b/app/gui2/src/stores/project/visualizationDataRegistry.ts @@ -21,8 +21,10 @@ export interface ExpressionInfo { /** This class holds the computed values that have been received from the language server. */ export class VisualizationDataRegistry { - /** This map stores only keys representing attached visualization. The responses for - * executeExpression are handled by project store's `executeExpression` method. */ + /** + * This map stores only keys representing attached visualization. The responses for + * executeExpression are handled by project store's `executeExpression` method. + */ private visualizationValues: Map | null> private dataServer: DataServer private executionContext: ExecutionContext @@ -30,6 +32,7 @@ export class VisualizationDataRegistry { private dataHandler = this.visualizationUpdate.bind(this) private errorHandler = this.visualizationError.bind(this) + /** TODO: Add docs */ constructor(executionContext: ExecutionContext, dataServer: DataServer) { this.executionContext = executionContext this.dataServer = dataServer @@ -80,10 +83,12 @@ export class VisualizationDataRegistry { } } + /** TODO: Add docs */ getRawData(visualizationId: Uuid): Result | null { return this.visualizationValues.get(visualizationId) ?? null } + /** TODO: Add docs */ dispose() { this.executionContext.off('visualizationsConfigured', this.reconfiguredHandler) this.dataServer.off(`${OutboundPayload.VISUALIZATION_UPDATE}`, this.dataHandler) diff --git a/app/gui2/src/stores/suggestionDatabase/documentation.ts b/app/gui2/src/stores/suggestionDatabase/documentation.ts index c67a1d4e0b2..3092d3c5dc4 100644 --- a/app/gui2/src/stores/suggestionDatabase/documentation.ts +++ b/app/gui2/src/stores/suggestionDatabase/documentation.ts @@ -44,6 +44,7 @@ export function getGroupIndex( return findIndexOpt(groups, (group) => `${group.project}.${group.name}` == normalized) } +/** TODO: Add docs */ export function documentationData( documentation: Opt, definedIn: QualifiedName, diff --git a/app/gui2/src/stores/suggestionDatabase/entry.ts b/app/gui2/src/stores/suggestionDatabase/entry.ts index 04473478c3e..952d45dfadc 100644 --- a/app/gui2/src/stores/suggestionDatabase/entry.ts +++ b/app/gui2/src/stores/suggestionDatabase/entry.ts @@ -22,7 +22,8 @@ export type { SuggestionId, } from 'ydoc-shared/languageServerTypes/suggestions' -/** An alias type for typename (for entry fields like `returnType`). +/** + * An alias type for typename (for entry fields like `returnType`). * * It's not QualifiedName, because it may be a type with parameters, or * a type union. @@ -51,8 +52,10 @@ export interface SuggestionEntry { aliases: string[] /** A type of the "self" argument. This field is present only for instance methods. */ selfType?: Typename - /** Argument lists of suggested object (atom or function). If the object does not take any - * arguments, the list is empty. */ + /** + * Argument lists of suggested object (atom or function). If the object does not take any + * arguments, the list is empty. + */ arguments: SuggestionEntryArgument[] /** A type returned by the suggested object. */ returnType: Typename @@ -93,6 +96,7 @@ export function entryMethodPointer(entry: SuggestionEntry): MethodPointer | unde } } +/** TODO: Add docs */ export function entryOwnerQn(entry: SuggestionEntry): QualifiedName | null { if (entry.kind == SuggestionKind.Module) { return qnParent(entry.definedIn) @@ -103,6 +107,7 @@ export function entryOwnerQn(entry: SuggestionEntry): QualifiedName | null { const DOCUMENTATION_ROOT = 'https://help.enso.org/docs/api' +/** TODO: Add docs */ export function suggestionDocumentationUrl(entry: SuggestionEntry): string | undefined { if (entry.kind !== SuggestionKind.Method && entry.kind !== SuggestionKind.Function) return const location = entry.memberOf ?? entry.definedIn @@ -136,11 +141,13 @@ function makeSimpleEntry( } } +/** TODO: Add docs */ export function makeModule(fqn: string): SuggestionEntry { assert(isQualifiedName(fqn)) return makeSimpleEntry(SuggestionKind.Module, fqn, qnLastSegment(fqn), fqn) } +/** TODO: Add docs */ export function makeType(fqn: string): SuggestionEntry { assert(isQualifiedName(fqn)) const [definedIn, name] = qnSplit(fqn) @@ -148,6 +155,7 @@ export function makeType(fqn: string): SuggestionEntry { return makeSimpleEntry(SuggestionKind.Type, definedIn, name, fqn) } +/** TODO: Add docs */ export function makeConstructor(fqn: string): SuggestionEntry { assert(isQualifiedName(fqn)) const [type, name] = qnSplit(fqn) @@ -160,6 +168,7 @@ export function makeConstructor(fqn: string): SuggestionEntry { } } +/** TODO: Add docs */ export function makeMethod(fqn: string, returnType: string = 'Any'): SuggestionEntry { assert(isQualifiedName(fqn)) assert(isQualifiedName(returnType)) @@ -174,6 +183,7 @@ export function makeMethod(fqn: string, returnType: string = 'Any'): SuggestionE } } +/** TODO: Add docs */ export function makeStaticMethod(fqn: string, returnType: string = 'Any'): SuggestionEntry { assert(isQualifiedName(fqn)) assert(isQualifiedName(returnType)) @@ -187,6 +197,7 @@ export function makeStaticMethod(fqn: string, returnType: string = 'Any'): Sugge } } +/** TODO: Add docs */ export function makeModuleMethod(fqn: string, returnType: string = 'Any'): SuggestionEntry { assert(isQualifiedName(fqn)) assert(isQualifiedName(returnType)) @@ -198,6 +209,7 @@ export function makeModuleMethod(fqn: string, returnType: string = 'Any'): Sugge } } +/** TODO: Add docs */ export function makeFunction( definedIn: string, name: string, @@ -209,6 +221,7 @@ export function makeFunction( return makeSimpleEntry(SuggestionKind.Function, definedIn, name, returnType) } +/** TODO: Add docs */ export function makeLocal( definedIn: string, name: string, @@ -220,6 +233,7 @@ export function makeLocal( return makeSimpleEntry(SuggestionKind.Local, definedIn, name, returnType) } +/** TODO: Add docs */ export function makeArgument(name: string, type: string = 'Any'): SuggestionEntryArgument { return { name, diff --git a/app/gui2/src/stores/suggestionDatabase/index.ts b/app/gui2/src/stores/suggestionDatabase/index.ts index c892830e5da..f1d326c0fba 100644 --- a/app/gui2/src/stores/suggestionDatabase/index.ts +++ b/app/gui2/src/stores/suggestionDatabase/index.ts @@ -17,6 +17,15 @@ import type { MethodPointer } from 'ydoc-shared/languageServerTypes' import * as lsTypes from 'ydoc-shared/languageServerTypes/suggestions' import { exponentialBackoff } from 'ydoc-shared/util/net' +/** + * Suggestion Database. + * + * The entries are retrieved (and updated) from engine throug Language Server API. They represent + * all entities available in current project (from the project and all imported libraries). + * + * It is used for code completion/component browser suggestions (thence the name), but also for + * retrieving information about method/function in widgets, and many more. + */ export class SuggestionDb extends ReactiveDb { nameToId = new ReactiveIndex(this, (id, entry) => [[entryQn(entry), id]]) childIdToParentId = new ReactiveIndex(this, (id, entry) => { @@ -29,6 +38,7 @@ export class SuggestionDb extends ReactiveDb { }) conflictingNames = new ReactiveIndex(this, (id, entry) => [[entry.name, id]]) + /** Get entry by its fully qualified name */ getEntryByQualifiedName(name: QualifiedName): SuggestionEntry | undefined { const [id] = this.nameToId.lookup(name) if (id) { @@ -36,6 +46,10 @@ export class SuggestionDb extends ReactiveDb { } } + /** + * Get entry of method/function by MethodPointer structure (received through expression + * updates. + */ findByMethodPointer(method: MethodPointer): SuggestionId | undefined { if (method == null) return const moduleName = tryQualifiedName(method.definedOnType) @@ -47,6 +61,12 @@ export class SuggestionDb extends ReactiveDb { } } +/** + * Component Group. + * + * These are groups displayed in the Component Browser. Also, nodes being a call to method from + * given group will inherit its color. + */ export interface Group { color?: string name: string @@ -149,6 +169,7 @@ class Synchronizer { } } +/** {@link useSuggestionDbStore} composable object */ export type SuggestionDbStore = ReturnType export const { provideFn: provideSuggestionDbStore, injectFn: useSuggestionDbStore } = createContextStore('suggestionDatabase', (projectStore: ProjectStore) => { diff --git a/app/gui2/src/stores/suggestionDatabase/lsUpdate.ts b/app/gui2/src/stores/suggestionDatabase/lsUpdate.ts index 03d75446903..182fe17ee69 100644 --- a/app/gui2/src/stores/suggestionDatabase/lsUpdate.ts +++ b/app/gui2/src/stores/suggestionDatabase/lsUpdate.ts @@ -122,6 +122,7 @@ function setLsDocumentation( if (data.iconName == null) delete entry.iconName } +/** TODO: Add docs */ export function entryFromLs( lsEntry: lsTypes.SuggestionEntry, groups: Group[], @@ -319,6 +320,7 @@ function applyArgumentsUpdate( } } +/** TODO: Add docs */ export function applyUpdate( entries: SuggestionDb, update: lsTypes.SuggestionsDatabaseUpdate, @@ -394,6 +396,7 @@ export function applyUpdate( } } +/** TODO: Add docs */ export function applyUpdates( entries: SuggestionDb, updates: lsTypes.SuggestionsDatabaseUpdate[], diff --git a/app/gui2/src/stores/visualization/compiler.ts b/app/gui2/src/stores/visualization/compiler.ts index 5735aa4d26c..4ddbc1536bf 100644 --- a/app/gui2/src/stores/visualization/compiler.ts +++ b/app/gui2/src/stores/visualization/compiler.ts @@ -25,7 +25,8 @@ * excluding the style and script compilation steps. * - (end `importVue`) An `AddUrlNotification` with path `/Viz.vue` is sent to the main * thread. - * - A `CompilationResultResponse` with id `1` and path `/Viz.vue` is sent to the main thread. */ + * - A `CompilationResultResponse` with id `1` and path `/Viz.vue` is sent to the main thread. + */ import { assertNever } from '@/util/assert' import { parse as babelParse } from '@babel/parser' @@ -39,14 +40,16 @@ import { compileScript, compileStyle, parse } from 'vue/compiler-sfc' // === Requests (Main Thread to Worker) === // ======================================== -/** A request to compile a visualization module. The Worker MUST reply with a +/** + * A request to compile a visualization module. The Worker MUST reply with a * {@link CompilationResultResponse} when compilation is done, or a {@link CompilationErrorResponse} * when compilation fails. The `id` is an arbitrary number that uniquely identifies the request. * The `path` is either an absolute URL (`http://doma.in/path/to/TheScript.vue`), or a root-relative * URL (`/visualizations/TheScript.vue`). Relative URLs (`./TheScript.vue`) are NOT valid. * * Note that compiling files other than Vue files (TypeScript, SVG etc.) is currently NOT - * supported. */ + * supported. + */ export interface CompileRequest { type: 'compile-request' id: number @@ -55,8 +58,10 @@ export interface CompileRequest { recompile?: boolean } -/** A request to mark modules as built-in, indicating that the compiler should re-write the imports - * into object destructures. */ +/** + * A request to mark modules as built-in, indicating that the compiler should re-write the imports + * into object destructures. + */ export interface RegisterBuiltinModulesRequest { type: 'register-builtin-modules-request' modules: string[] @@ -68,19 +73,23 @@ export interface RegisterBuiltinModulesRequest { // These are messages sent in response to a query. They contain the `id` of the original query. -/** Sent in response to a {@link CompileRequest}, with an `id` matching the `id` of the original +/** + * Sent in response to a {@link CompileRequest}, with an `id` matching the `id` of the original * request. Contains only the `path` of the resulting file (which should have also been sent in the * {@link CompileRequest}). - * The content itself will have been sent earlier as an {@link AddImportNotification}. */ + * The content itself will have been sent earlier as an {@link AddImportNotification}. + */ export interface CompilationResultResponse { type: 'compilation-result-response' id: number path: string } -/** Sent in response to a {@link CompileRequest}, with an `id` matching the `id` of the original +/** + * Sent in response to a {@link CompileRequest}, with an `id` matching the `id` of the original * request. Contains the `path` of the resulting file (which should have also been sent in the - * {@link CompileRequest}), and the `error` thrown during compilation. */ + * {@link CompileRequest}), and the `error` thrown during compilation. + */ export interface CompilationErrorResponse { type: 'compilation-error-response' id: number @@ -114,8 +123,10 @@ export interface FetchResultWorkerResponse { // === Worker Errors (Main Thread to Worker) === // ============================================= -/** Sent when fetching a dependency failed. Currently, the worker will forward this back to the - * main thread as a {@link FetchError}. */ +/** + * Sent when fetching a dependency failed. Currently, the worker will forward this back to the + * main thread as a {@link FetchError}. + */ export interface FetchWorkerError { type: 'fetch-worker-error' path: string @@ -128,30 +139,36 @@ export interface FetchWorkerError { // These are sent when a subtask successfully completes execution. -/** Sent after compiling `