Some of the missing documentation and fixes

This commit is contained in:
Adam Obuchowicz 2024-10-02 16:21:51 +02:00
parent 0c9de42f14
commit 0bb0100296
36 changed files with 239 additions and 173 deletions

View File

@ -24,7 +24,13 @@ export async function goToGraph(page: Page, closeDocPanel: boolean = true) {
await expectNodePositionsInitialized(page, -16)
}
/** TODO: Add docs */
/**
* 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()
@ -39,7 +45,7 @@ export async function expectNodePositionsInitialized(page: Page, yPos: number) {
)
}
/** TODO: Add docs */
/** 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 } })
}
@ -48,8 +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.
/** TODO: Add docs */
/** 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')
@ -59,15 +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.
/** TODO: Add docs */
/** 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.
/** TODO: Add docs */
/** Ensure no nodes are selected. */
export async function deselectNodes(page: Page) {
await page.mouse.click(0, 0)
await expect(locate.selectedNodes(page)).toHaveCount(0)

View File

@ -2,7 +2,7 @@ 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.
* Currently whitespace produced around pseudo-elements is unspecified; block/inline logic is not implemented.
*/
export function computedContent(element: ElementHandle<HTMLElement | SVGElement>): Promise<string> {
return element.evaluate<string>((element) => {

View File

@ -44,24 +44,24 @@ function or(a: (page: Locator | Page) => Locator, b: (page: Locator | Page) => L
return (page: Locator | Page) => a(page).or(b(page))
}
/** TODO: Add docs */
/** Show/hide visualization button */
export function toggleVisualizationButton(page: Locator | Page) {
return page.getByLabel('Visualization', { exact: true })
}
/** TODO: Add docs */
/** Visualization Selector button */
export function toggleVisualizationSelectorButton(page: Locator | Page) {
return page.getByLabel('Visualization Selector')
}
// === Fullscreen ===
/** TODO: Add docs */
/** Enter fullscreen */
export function enterFullscreenButton(page: Locator | Page) {
return page.getByLabel('Fullscreen')
}
/** TODO: Add docs */
/** Exit fullscreen */
export function exitFullscreenButton(page: Locator | Page) {
return page.getByLabel('Exit Fullscreen')
}
@ -71,29 +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 }
/** TODO: Add docs */
/** All nodes in graph */
export function graphNode(page: Page | Locator): Node {
return page.locator('.GraphNode') as Node
}
/** TODO: Add docs */
/** 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
}
/** TODO: Add docs */
/** Icon inside the node */
export function graphNodeIcon(node: Node) {
return node.locator('.nodeCategoryIcon')
}
/** TODO: Add docs */
/** All selected nodes */
export function selectedNodes(page: Page | Locator): Node {
return page.locator('.GraphNode.selected') as Node
}
/** TODO: Add docs */
/** All input nodes */
export function inputNode(page: Page | Locator): Node {
return page.locator('.GraphNode.inputNode') as Node
}
/** TODO: Add docs */
/** All output nodes */
export function outputNode(page: Page | Locator): Node {
return page.locator('.GraphNode.outputNode') as Node
}
@ -117,7 +119,11 @@ export const nodeOutputPort = componentLocator('.outputPortHoverArea')
export const smallPlusButton = componentLocator('.SmallPlusButton')
export const lexicalContent = componentLocator('.LexicalContent')
/** TODO: Add docs */
/**
* 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 },
@ -127,7 +133,7 @@ export function componentBrowserEntry(
)
}
/** TODO: Add docs */
/** A selected variant of Component Browser Entry */
export function componentBrowserSelectedEntry(
page: Locator | Page,
filter?: (f: Filter) => { selector: string },
@ -137,12 +143,12 @@ export function componentBrowserSelectedEntry(
)
}
/** TODO: Add docs */
/** 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) })
}
/** TODO: Add docs */
/** Right-docked panel */
export function rightDock(page: Page) {
return page.getByTestId('rightDock')
}
@ -152,7 +158,7 @@ export function rightDockRoot(page: Page) {
return page.getByTestId('rightDockRoot')
}
/** TODO: Add docs */
/** Bottom-docked panel */
export function bottomDock(page: Page) {
return page.getByTestId('bottomDock')
}
@ -182,14 +188,14 @@ export const warningsVisualization = visualizationLocator('.WarningsVisualizatio
// === Edge locators ===
/** TODO: Add docs */
/** 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}"]`)
}
/** TODO: Add docs */
/** 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')
@ -200,7 +206,7 @@ export async function edgesToNodeWithBinding(page: Page, binding: string) {
/**
* 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.
* 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()

View File

@ -9,7 +9,11 @@ import {
} from '../mock/projectManager'
import pmSpec from './pm-openrpc.json' assert { type: 'json' }
/** TODO: Add docs */
/**
* Setup for all E2E tests.
*
* It runs mocked project manager server.
*/
export default function setup() {
const pm = new Server({
transportConfigs: [

View File

@ -33,8 +33,7 @@ import { mockDataWSHandler as originalMockDataWSHandler } from './dataServer'
const mockProjectId = random.uuidv4() as Uuid
const standardBase = 'Standard.Base' as QualifiedName
/** TODO: Add docs */
export function placeholderGroups(): LibraryComponentGroup[] {
function placeholderGroups(): LibraryComponentGroup[] {
return [
{ color: '#4D9A29', name: 'Input', library: standardBase, exports: [] },
{ color: '#B37923', name: 'Web', library: standardBase, exports: [] },
@ -46,7 +45,7 @@ export function placeholderGroups(): LibraryComponentGroup[] {
]
}
let mainFile = `\
const mainFile = `\
## Module documentation
from Standard.Base import all
@ -78,16 +77,6 @@ main =
selected = data.select_columns
`
/** TODO: Add docs */
export function getMainFile() {
return mainFile
}
/** TODO: Add docs */
export function setMainFile(newMainFile: string) {
return (mainFile = newMainFile)
}
const fileTree = {
src: {
get 'Main.enso'() {

View File

@ -1,20 +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.
/** TODO: Add docs */
export function handleEmit(wrapper: VueWrapper<any>, 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()
},
}
}

View File

@ -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:'

View File

@ -1,6 +1,8 @@
import '@/assets/base.css'
/** TODO: Add docs */
/**
* Load App.vue asynchronously.
*/
export async function AsyncApp() {
const app = await import('@/App.vue')
return app

View File

@ -8,7 +8,10 @@ import type { ExternalId } from 'ydoc-shared/yjsModel'
const AI_GOAL_PLACEHOLDER = '__$$GOAL$$__'
const AI_STOP_SEQUENCE = '`'
/** TODO: Add docs */
/**
* 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: {

View File

@ -23,13 +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
}
/** TODO: Add docs */
/** @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
@ -69,13 +72,18 @@ function formatLabel(labelInfo: ComponentLabelInfo): ComponentLabel {
}
}
/**
* Suggestion entry with matching information.
*/
export interface MatchedSuggestion {
id: SuggestionId
entry: SuggestionEntry
match: MatchResult
}
/** TODO: Add docs */
/**
* 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
@ -89,13 +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
}
/** TODO: Add docs */
/**
* Create {@link Component} from information about suggestion and matching.
*/
export function makeComponent({ id, entry, match }: ComponentInfo): Component {
return {
...formatLabel(labelOfEntry(entry, match)),
@ -105,7 +115,9 @@ export function makeComponent({ id, entry, match }: ComponentInfo): Component {
}
}
/** TODO: Add docs */
/**
* Create {@link Component} list from filtered suggestions.
*/
export function makeComponentList(db: SuggestionDb, filtering: Filtering): Component[] {
function* matchSuggestions() {
for (const [id, entry] of db.entries()) {

View File

@ -9,7 +9,7 @@ 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 =

View File

@ -16,7 +16,12 @@ const orDefaultSize = (rect: Rect) => {
return new Rect(rect.pos, new Vec2(width, height))
}
/** TODO: Add docs */
/**
* A composable with logic related to nodes placement.
* @returns object with three functions: `place` specifying a free place for new node. `collapse`
* returning position for new collapsed node, and `input` returning default position for next
* input component.
*/
export function usePlacement(nodeRects: ToValue<Iterable<Rect>>, screenBounds: ToValue<Rect>) {
const gap = themeGap()
const environment = (selectedNodeRects: Iterable<Rect>) => ({

View File

@ -7,7 +7,13 @@ export type ScrollTarget =
| { type: 'selected' }
| { type: 'offset'; offset: number }
/** TODO: Add docs */
/**
* 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 transitive scroll.
*/
export function useScrolling(selectedPos: ToValue<number>) {
const targetScroll = ref<ScrollTarget>({ type: 'top' })
const targetScrollPosition = computed(() => {

View File

@ -59,7 +59,9 @@ export interface Example {
body: Doc.HtmlString
}
/** TODO: Add docs */
/**
* Placeholder constructor.
*/
export function placeholder(text: string): Placeholder {
return { kind: 'Placeholder', text }
}
@ -97,7 +99,9 @@ function filterSections(sections: Iterable<Doc.Section>): Sections {
// === Lookup ===
/** TODO: Add docs */
/**
* 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)

View File

@ -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,7 +38,14 @@ export interface UseVisualizationDataOptions {
dataSource: ToValue<VisualizationDataSource | RawDataSource | undefined>
}
/** TODO: Add docs */
/**
* 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,

View File

@ -26,7 +26,7 @@ interface CopiedNode {
metadata?: NodeMetadataFields
}
* @internal
/** @internal */
export async function nodesFromClipboardContent(
clipboardItems: ClipboardItems,
): Promise<CopiedNode[]> {
@ -54,7 +54,7 @@ function getClipboard(): ExtendedClipboard {
return (window.navigator as any).mockClipboard ?? window.navigator.clipboard
}
/** TODO: Add docs */
/** A composable for handling copying and pasting nodes in the GraphEditor. */
export function useGraphEditorClipboard(
graphStore: GraphStore,
selected: ToValue<Set<NodeId>>,
@ -150,13 +150,13 @@ const spreadsheetDecoder: ClipboardDecoder<CopiedNode[]> = {
const toTable = computed(() => Pattern.parse('__.to Table'))
/** TODO: Add docs */
/** 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
/** @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
@ -173,7 +173,7 @@ export function isSpreadsheetTsv(htmlContent: string) {
export type MimeType = 'text/plain' | 'text/html' | typeof ENSO_MIME_TYPE
export type MimeData = Partial<Record<MimeType, string>>
/** TODO: Add docs */
/** Write data to clipboard */
export function writeClipboard(data: MimeData) {
const dataBlobs = Object.fromEntries(
Object.entries(data).map(([type, typeData]) => [type, new Blob([typeData], { type })]),

View File

@ -17,14 +17,22 @@ interface PartialVec2 {
y: number | null
}
/** TODO: Add docs */
/**
* 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<number[]>
rightAxes: ComputedRef<number[]>
topAxes: ComputedRef<number[]>
bottomAxes: ComputedRef<number[]>
/** TODO: Add docs */
/** Create grid from existing nodes' rects */
constructor(rects: ComputedRef<Rect[]>) {
markRaw(this)
this.leftAxes = computed(() =>
@ -45,7 +53,12 @@ export class SnapGrid {
)
}
/** TODO: Add docs */
/**
* 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<PartialVec2>(
(minSnap, rect) => {
@ -57,7 +70,13 @@ export class SnapGrid {
return new Vec2(minSnap.x ?? 0.0, minSnap.y ?? 0.0)
}
/** TODO: Add docs */
/**
* 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)
@ -115,7 +134,7 @@ export function useDragging() {
grid: SnapGrid
stopPositionUpdate: WatchStopHandle
/** TODO: Add docs */
/** Start dragging: initialize all properties and animations above */
constructor(movedId: NodeId) {
markRaw(this)
function* draggedNodes(): Generator<[NodeId, DraggedNode]> {
@ -136,7 +155,7 @@ export function useDragging() {
this.stopPositionUpdate = watchEffect(() => this.updateNodesPosition())
}
/** TODO: Add docs */
/** Update drag offset and snap animation target */
updateOffset(newOffset: Vec2): void {
const oldSnappedOffset = snappedOffset.value
const rects: Rect[] = []
@ -160,12 +179,13 @@ export function useDragging() {
}
}
/** TODO: Add docs */
/** Finish dragging and set nodes' positions */
finishDragging(): void {
this.stopPositionUpdate()
this.updateNodesPosition()
}
/** TODO: Add docs */
/** Cancel drag and reset nodes' positions */
cancelDragging(): void {
console.log('cancelDragging')
this.stopPositionUpdate()
@ -177,8 +197,7 @@ export function useDragging() {
this.updateNodesPosition()
}
/** TODO: Add docs */
createSnapGrid() {
private createSnapGrid() {
const nonDraggedRects = computed(() => {
const nonDraggedNodes = iteratorFilter(
graphStore.db.nodeIds(),
@ -189,8 +208,7 @@ export function useDragging() {
return new SnapGrid(nonDraggedRects)
}
/** TODO: Add docs */
updateNodesPosition() {
private updateNodesPosition() {
graphStore.batchEdits(() => {
for (const [id, dragged] of this.draggedNodes) {
const node = graphStore.db.nodeIdToNode.get(id)

View File

@ -2,7 +2,10 @@ import { useEvent } from '@/composables/events'
import { type ProjectStore } from '@/stores/project'
import { useToast } from '@/util/toast'
/** TODO: Add docs */
/**
* 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 })

View File

@ -15,7 +15,7 @@ import { Err, Ok, withContext, type Result } from 'ydoc-shared/util/data/result'
const DATA_DIR_NAME = 'data'
/** TODO: Add docs */
/** @returns the expression for node reading an uploaded file. */
export function uploadedExpression(result: UploadResult) {
switch (result.source) {
case 'Project': {
@ -29,12 +29,20 @@ export function uploadedExpression(result: UploadResult) {
// === Uploader ===
/** Upload result, containing information about upload destination. */
export interface UploadResult {
source: 'FileSystemRoot' | 'Project'
name: string
}
/** TODO: Add docs */
/**
* 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<Keccak>
private uploadedBytes: bigint
@ -56,7 +64,7 @@ export class Uploader {
this.stackItem = markRaw(toRaw(stackItem))
}
/** TODO: Add docs */
/** Constructor */
static Create(
rpc: LanguageServer,
binary: DataServer,
@ -81,7 +89,7 @@ export class Uploader {
)
}
/** TODO: Add docs */
/** Start the upload process */
async upload(): Promise<Result<UploadResult>> {
// This non-standard property is defined in Electron.
if (
@ -199,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) {

View File

@ -110,7 +110,11 @@ function retrieveColumnsDefinitions(columnsAst: Ast.Vector) {
return transposeResult(Array.from(columnsAst.values(), readColumn))
}
/** TODO: Add docs */
/**
* 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

View File

@ -373,9 +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) {

View File

@ -32,17 +32,17 @@ export interface SelectionMenu {
export type ToolbarItem = ActionButton | ToggleButton | SelectionMenu
/** TODO: Add docs */
/** {@link ActionButton} discriminant */
export function isActionButton(item: Readonly<ToolbarItem>): item is ActionButton {
return 'onClick' in item
}
/** TODO: Add docs */
/** {@link ToggleButton} discriminant */
export function isToggleButton(item: Readonly<ToolbarItem>): item is ToggleButton {
return 'toggle' in item
}
/** TODO: Add docs */
/** {@link SelectionMenu} discriminant */
export function isSelectionMenu(item: Readonly<ToolbarItem>): item is SelectionMenu {
return 'selected' in item
}

View File

@ -3,7 +3,7 @@ import { type ToValue } from '@/util/reactivity'
import { computed, toValue } from 'vue'
import type { Ast } from 'ydoc-shared/ast'
/** TODO: Add docs */
/** A composable for reactively retrieving and setting documentation from given Ast node. */
export function useAstDocumentation(graphStore: GraphStore, ast: ToValue<Ast | undefined>) {
return {
documentation: {

View File

@ -25,12 +25,6 @@ export function isTriggeredByKeyboard(e: MouseEvent | PointerEvent) {
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<K extends keyof DocumentEventMap>(
target: Document,
event: K,
@ -55,7 +49,12 @@ export function useEvent(
handler: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions,
): void
/** TODO: Add docs */
/**
* 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,
@ -66,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<K extends keyof DocumentEventMap>(
target: Document,
event: K,
@ -101,7 +93,14 @@ export function useEventConditional(
handler: (event: unknown) => void,
options?: boolean | AddEventListenerOptions,
): void
/** TODO: Add docs */
/**
* 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,
@ -151,7 +150,7 @@ const hasWindow = typeof window !== 'undefined'
const platform = hasWindow ? window.navigator?.platform ?? '' : ''
export const isMacLike = /(Mac|iPhone|iPod|iPad)/i.test(platform)
/** TODO: Add docs */
/** 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
}
@ -343,8 +342,6 @@ 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,
@ -473,8 +470,6 @@ export interface UseArrowsOptions {
* 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: (

View File

@ -1,8 +1,9 @@
import { isMacLike, useEvent } from '@/composables/events'
import { proxyRefs, ref } from 'vue'
/** {@link useKeyboard} composable object */
export type KeyboardComposable = ReturnType<typeof useKeyboard>
/** TODO: Add docs */
/** Composable containing reactive flags for modifier's press state. */
export function useKeyboard() {
const state = {
alt: ref(false),

View File

@ -17,7 +17,15 @@ import type { MethodPointer } from 'ydoc-shared/languageServerTypes'
import * as lsTypes from 'ydoc-shared/languageServerTypes/suggestions'
import { exponentialBackoff } from 'ydoc-shared/util/net'
/** TODO: Add docs */
/**
* 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<SuggestionId, SuggestionEntry> {
nameToId = new ReactiveIndex(this, (id, entry) => [[entryQn(entry), id]])
childIdToParentId = new ReactiveIndex(this, (id, entry) => {
@ -30,7 +38,7 @@ export class SuggestionDb extends ReactiveDb<SuggestionId, SuggestionEntry> {
})
conflictingNames = new ReactiveIndex(this, (id, entry) => [[entry.name, id]])
/** TODO: Add docs */
/** Get entry by its fully qualified name */
getEntryByQualifiedName(name: QualifiedName): SuggestionEntry | undefined {
const [id] = this.nameToId.lookup(name)
if (id) {
@ -38,7 +46,10 @@ export class SuggestionDb extends ReactiveDb<SuggestionId, SuggestionEntry> {
}
}
/** TODO: Add docs */
/**
* 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)
@ -50,6 +61,12 @@ export class SuggestionDb extends ReactiveDb<SuggestionId, SuggestionEntry> {
}
}
/**
* 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
@ -152,6 +169,7 @@ class Synchronizer {
}
}
/** {@link useSuggestionDbStore} composable object */
export type SuggestionDbStore = ReturnType<typeof useSuggestionDbStore>
export const { provideFn: provideSuggestionDbStore, injectFn: useSuggestionDbStore } =
createContextStore('suggestionDatabase', (projectStore: ProjectStore) => {

View File

@ -164,10 +164,8 @@ class AstExtended<T extends Tree | Token = Tree | Token, HasIdMap extends boolea
}
/**
* Recursively visit AST nodes in depth-first order. The children of a node will be skipped when
* `visit` callback returns `false`.
* @param node Root node of the tree to walk. It will be visited first.
* @param visit Callback that is called for each node. If it returns `false`, the children of that
* Recursively visit AST nodes in depth-first order.
* @param visitor Callback that is called for each node. If it returns `false`, the children of that
* node will be skipped, and the walk will continue to the next sibling.
*/
visitRecursive(visitor: (t: AstExtended<Tree | Token, HasIdMap>) => boolean) {

View File

@ -127,7 +127,6 @@ export type Operand<HasIdMap extends boolean = true> =
* an operator application (of this specific operator if provided), `this` will be returned as
* a single operand.
* @param ast the subtree which we assume is an operator application chain.
* @param code the code from which the entire AST was generated.
* @param expectedOpr if specified, the chain will be of specific operator.
*/
export function* operandsOfLeftAssocOprChain<HasIdMap extends boolean = true>(

View File

@ -21,7 +21,10 @@ export function useAutoBlur(root: ToValue<HTMLElement | SVGElement | undefined>)
const autoBlurRoots = new Set<HTMLElement | SVGElement | MathMLElement>()
/** TODO: Add docs */
/**
* This function should be called on application mount to make all {@link useAutoBlur} work
* properly.
*/
export function registerAutoBlurHandler() {
useEvent(
window,

View File

@ -7,7 +7,7 @@ useMode(modeRgb)
const oklch = converter('oklch')
/** TODO: Add docs */
/** Check if given css is supported in the browser. */
export function cssSupported(css: string): boolean {
return typeof CSS !== 'undefined' && 'supports' in CSS && CSS.supports(css)
}
@ -15,16 +15,14 @@ export function cssSupported(css: string): boolean {
/** Whether the browser supports `oklch` colorspace. */
export const browserSupportsOklch: boolean = cssSupported('color: oklch(0 0 0)')
/* Generate a CSS color value from the provided string. */
/** TODO: Add docs */
/** Generate a CSS color value from the provided string. */
export function colorFromString(s: string) {
const hash: number = hashString(s)
const hue = mapInt32(hash & 0x3ff, 0, 1, 10)
return formatCssColor(ensoColor(hue))
}
/* Returns the enso color for a given hue, in the range 0-1. */
/** TODO: Add docs */
/** Returns the enso color for a given hue, in the range 0-1. */
export function ensoColor(hue: number): Oklch {
return {
mode: 'oklch',
@ -34,8 +32,7 @@ export function ensoColor(hue: number): Oklch {
}
}
/* Normalize a value to the range 0-1, as used for hues. */
/** TODO: Add docs */
/** Normalize a value to the range 0-1, as used for hues. */
export function normalizeHue(value: number) {
return ((value % 1) + 1) % 1
}
@ -45,13 +42,12 @@ export function formatCssColor(color: Oklch) {
return browserSupportsOklch ? formatCss(color) : formatRgb(color)
}
/* Parse the input as a CSS color value; convert it to Oklch if it isn't already. */
/** TODO: Add docs */
/** Parse the input as a CSS color value; convert it to Oklch if it isn't already. */
export function parseCssColor(cssColor: string): Oklch | undefined {
return oklch(cssColor)
}
/* Map `bits`-wide unsigned integer to the range `[rangeStart, rangeEnd)`. */
/** Map `bits`-wide unsigned integer to the range `[rangeStart, rangeEnd)`. */
function mapInt32(value: number, rangeStart: number, rangeEnd: number, bits: number) {
const maxInt = 2 ** bits
return (value / maxInt) * (rangeEnd - rangeStart) + rangeStart

View File

@ -7,7 +7,9 @@ function arrayIsSame(a: unknown[], b: unknown) {
return Array.isArray(b) && a.length === b.length && a.every((item, i) => b[i] === item)
}
/** TODO: Add docs */
/**
* TODO: add docs here.
*/
export function mockFsFileHandle(
contents: string | ArrayBuffer,
name: string,

View File

@ -35,8 +35,8 @@ type ReactiveDBNotification<K, V> = {
* Represents a reactive database adapter that extends the behaviors of `Map`.
* It emits an `entryAdded` event when a new entry is added to the database,
* facilitating reactive tracking of database insertions and deletions.
* @typeParam K - The key type for the database entries.
* @typeParam V - The value type for the database entries.
* @template K - The key type for the database entries.
* @template V - The value type for the database entries.
*/
export class ReactiveDb<K, V>
extends ObservableV2<ReactiveDBNotification<K, V>>
@ -192,10 +192,10 @@ export type Indexer<K, V, IK, IV> = (key: K, value: V) => [IK, IV][]
* Utilizes both forward and reverse mapping for efficient lookups and reverse lookups.
* The index updates not only after key/value change from db, but also on any change of
* reactive dependency.
* @typeParam K - The key type of the ReactiveDb.
* @typeParam V - The value type of the ReactiveDb.
* @typeParam IK - The key type of the index.
* @typeParam IV - The value type of the index.
* @template K - The key type of the ReactiveDb.
* @template V - The value type of the ReactiveDb.
* @template IK - The key type of the index.
* @template IV - The value type of the index.
*/
export class ReactiveIndex<K, V, IK, IV> {
/** Forward map from index keys to a set of index values */
@ -334,9 +334,9 @@ export type Mapper<K, V, IV> = (key: K, value: V) => IV | undefined
* It can be thought of as a collection of `computed` values per each key in the `ReactiveDb`. The
* mapping is automatically updated when any of its dependencies change, and is properly cleaned up
* when any key is removed from {@link ReactiveDb}. Only accessed keys are ever actually computed.
* @typeParam K - The key type of the ReactiveDb.
* @typeParam V - The value type of the ReactiveDb.
* @typeParam M - The type of a mapped value.
* @template K - The key type of the ReactiveDb.
* @template V - The value type of the ReactiveDb.
* @template M - The type of a mapped value.
*/
export class ReactiveMapping<K, V, M> {
/** Forward map from index keys to a mapped computed value */

View File

@ -1,6 +1,6 @@
import { parse_doc_to_json } from 'ydoc-shared/ast/ffi'
/** TODO: Add docs */
/** Parse Enso documentation. */
export function parseDocs(docs: string): Doc.Section[] {
const json = parse_doc_to_json(docs)
return JSON.parse(json)

View File

@ -1,4 +1,4 @@
/** TODO: Add docs */
/** Set "drag image" in given event Drag to blank image. */
export function setDragImageToBlank(event: DragEvent) {
const image = new Image()
// Blank GIF

View File

@ -1,4 +1,4 @@
/** TODO: Add docs */
/** Same as `===` operator - used as functino parameter, */
export function defaultEquality(a: unknown, b: unknown): boolean {
return a === b
}

View File

@ -18,24 +18,24 @@ const identifierRegexPart = '(?:(?:[a-zA-Z_][0-9]*)+|[!$%&*+,-./:;<=>?@\\^|~]+)'
const qnRegex = new RegExp(`^${identifierRegexPart}(?:\\.${identifierRegexPart})*$`)
const mainSegmentRegex = new RegExp(`^(${identifierRegexPart}\\.${identifierRegexPart})\\.Main`)
/** TODO: Add docs */
/** Returns the string if it's a valid {@link Identifier}. */
export function tryIdentifier(str: string): Result<Identifier> {
return isIdentifier(str) ? Ok(str) : Err(`"${str}" is not a valid identifier`)
}
/** TODO: Add docs */
/** Returns the string if it's a valid {@link IdIdentifierOrOperatorIdentifierentifier}. */
export function tryIdentifierOrOperatorIdentifier(
str: string,
): Result<IdentifierOrOperatorIdentifier> {
return isIdentifierOrOperatorIdentifier(str) ? Ok(str) : Err(`"${str}" is not a valid identifier`)
}
/** TODO: Add docs */
/** Checks if the string is a valid {@link QualifiedName} */
export function isQualifiedName(str: string): str is QualifiedName {
return qnRegex.test(str)
}
/** TODO: Add docs */
/** Returns the string if it's a valid {@link QualifiedName} */
export function tryQualifiedName(str: string): Result<QualifiedName> {
return isQualifiedName(str) ? Ok(str) : Err(`"${str}" is not a valid qualified name`)
}
@ -75,22 +75,22 @@ export function qnParent(name: QualifiedName): QualifiedName | null {
return separator > 1 ? (name.substring(0, separator) as QualifiedName) : null
}
/** TODO: Add docs */
/** Create new name by joining segments of two qualified names */
export function qnJoin(left: QualifiedName, right: QualifiedName): QualifiedName {
return `${left}.${right}` as QualifiedName
}
/** TODO: Add docs */
/** Create {@link QualifiedName} from list of segments. */
export function qnFromSegments(segments: Iterable<IdentifierOrOperatorIdentifier>): QualifiedName {
return [...segments].join('.') as QualifiedName
}
/** TODO: Add docs */
/** Return typed list of {@link QualifiedName} segments */
export function qnSegments(name: QualifiedName): IdentifierOrOperatorIdentifier[] {
return name.split('.').map((segment) => segment as IdentifierOrOperatorIdentifier)
}
/** TODO: Add docs */
/** Returns substring of a qualified name. `start` and `end` are indexes of segments. */
export function qnSlice(
name: QualifiedName,
start?: number | undefined,