mirror of
https://github.com/enso-org/enso.git
synced 2024-11-22 03:32:23 +03:00
Some of the missing documentation and fixes
This commit is contained in:
parent
0c9de42f14
commit
0bb0100296
@ -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)
|
||||
|
@ -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) => {
|
||||
|
@ -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()
|
||||
|
@ -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: [
|
||||
|
@ -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'() {
|
||||
|
@ -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()
|
||||
},
|
||||
}
|
||||
}
|
@ -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:'
|
||||
|
@ -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
|
||||
|
@ -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: {
|
||||
|
@ -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()) {
|
||||
|
@ -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 =
|
||||
|
@ -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>) => ({
|
||||
|
@ -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(() => {
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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 })]),
|
||||
|
@ -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)
|
||||
|
@ -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 })
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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: {
|
||||
|
@ -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: (
|
||||
|
@ -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),
|
||||
|
@ -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) => {
|
||||
|
@ -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) {
|
||||
|
@ -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>(
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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 */
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user