@skillcdn/console 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/SKILLCDN.md +7 -5
- package/dist/api.d.ts +5 -4
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +5 -4
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +569 -48
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts +34 -3
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +283 -7
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +44 -7
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/plugin-check.d.ts +26 -0
- package/dist/cli/plugin-check.d.ts.map +1 -0
- package/dist/cli/plugin-check.js +396 -0
- package/dist/cli/plugin-check.js.map +1 -0
- package/dist/cli/plugin-cli.d.ts +20 -0
- package/dist/cli/plugin-cli.d.ts.map +1 -0
- package/dist/cli/plugin-cli.js +189 -0
- package/dist/cli/plugin-cli.js.map +1 -0
- package/dist/cli/plugin-init.d.ts +37 -0
- package/dist/cli/plugin-init.d.ts.map +1 -0
- package/dist/cli/plugin-init.js +206 -0
- package/dist/cli/plugin-init.js.map +1 -0
- package/dist/cli/plugin-main.d.ts +3 -0
- package/dist/cli/plugin-main.d.ts.map +1 -0
- package/dist/cli/plugin-main.js +28 -0
- package/dist/cli/plugin-main.js.map +1 -0
- package/dist/client.d.ts +100 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +79 -7
- package/dist/client.js.map +1 -1
- package/dist/compat.d.ts +1 -1
- package/dist/compat.js +2 -2
- package/dist/components/board.d.ts +8 -0
- package/dist/components/board.d.ts.map +1 -1
- package/dist/components/board.js +59 -15
- package/dist/components/board.js.map +1 -1
- package/dist/components/comments.d.ts.map +1 -1
- package/dist/components/comments.js +1 -1
- package/dist/components/comments.js.map +1 -1
- package/dist/components/decision-form.d.ts +2 -1
- package/dist/components/decision-form.d.ts.map +1 -1
- package/dist/components/decision-form.js +36 -4
- package/dist/components/decision-form.js.map +1 -1
- package/dist/components/decision-list.d.ts +4 -0
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +29 -3
- package/dist/components/decision-list.js.map +1 -1
- package/dist/components/event-feed.d.ts +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +33 -1
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/my-decisions.d.ts +2 -0
- package/dist/components/my-decisions.d.ts.map +1 -1
- package/dist/components/my-decisions.js +2 -2
- package/dist/components/my-decisions.js.map +1 -1
- package/dist/components/plugin-frame.d.ts +52 -0
- package/dist/components/plugin-frame.d.ts.map +1 -0
- package/dist/components/plugin-frame.js +314 -0
- package/dist/components/plugin-frame.js.map +1 -0
- package/dist/components/plugin-settings.d.ts +38 -0
- package/dist/components/plugin-settings.d.ts.map +1 -0
- package/dist/components/plugin-settings.js +348 -0
- package/dist/components/plugin-settings.js.map +1 -0
- package/dist/components/shell.d.ts +2 -0
- package/dist/components/shell.d.ts.map +1 -1
- package/dist/components/shell.js +26 -1
- package/dist/components/shell.js.map +1 -1
- package/dist/components/task-view.d.ts +2 -1
- package/dist/components/task-view.d.ts.map +1 -1
- package/dist/components/task-view.js.map +1 -1
- package/dist/components/workspace.d.ts +7 -1
- package/dist/components/workspace.d.ts.map +1 -1
- package/dist/components/workspace.js +1 -1
- package/dist/components/workspace.js.map +1 -1
- package/dist/console.css +330 -1
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +152 -41
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +9 -2
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +45 -9
- package/dist/data.js.map +1 -1
- package/dist/i18n/en.d.ts +155 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +166 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +150 -0
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +2 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +2 -0
- package/dist/limits.js.map +1 -1
- package/dist/plugin/args.d.ts +148 -0
- package/dist/plugin/args.d.ts.map +1 -0
- package/dist/plugin/args.js +369 -0
- package/dist/plugin/args.js.map +1 -0
- package/dist/plugin/bridge.d.ts +146 -0
- package/dist/plugin/bridge.d.ts.map +1 -0
- package/dist/plugin/bridge.js +125 -0
- package/dist/plugin/bridge.js.map +1 -0
- package/dist/plugin/capabilities.d.ts +20 -0
- package/dist/plugin/capabilities.d.ts.map +1 -0
- package/dist/plugin/capabilities.js +133 -0
- package/dist/plugin/capabilities.js.map +1 -0
- package/dist/plugin/context.d.ts +380 -0
- package/dist/plugin/context.d.ts.map +1 -0
- package/dist/plugin/context.js +2 -0
- package/dist/plugin/context.js.map +1 -0
- package/dist/plugin/define.d.ts +208 -0
- package/dist/plugin/define.d.ts.map +1 -0
- package/dist/plugin/define.js +482 -0
- package/dist/plugin/define.js.map +1 -0
- package/dist/plugin/definition-error.d.ts +11 -0
- package/dist/plugin/definition-error.d.ts.map +1 -0
- package/dist/plugin/definition-error.js +17 -0
- package/dist/plugin/definition-error.js.map +1 -0
- package/dist/plugin/limits.d.ts +67 -0
- package/dist/plugin/limits.d.ts.map +1 -0
- package/dist/plugin/limits.js +70 -0
- package/dist/plugin/limits.js.map +1 -0
- package/dist/plugin/load-worker.d.ts +7 -0
- package/dist/plugin/load-worker.d.ts.map +1 -0
- package/dist/plugin/load-worker.js +41 -0
- package/dist/plugin/load-worker.js.map +1 -0
- package/dist/plugin/load.d.ts +18 -0
- package/dist/plugin/load.d.ts.map +1 -0
- package/dist/plugin/load.js +62 -0
- package/dist/plugin/load.js.map +1 -0
- package/dist/plugin/manifest.d.ts +237 -0
- package/dist/plugin/manifest.d.ts.map +1 -0
- package/dist/plugin/manifest.js +411 -0
- package/dist/plugin/manifest.js.map +1 -0
- package/dist/plugin/refusals.d.ts +49 -0
- package/dist/plugin/refusals.d.ts.map +1 -0
- package/dist/plugin/refusals.js +79 -0
- package/dist/plugin/refusals.js.map +1 -0
- package/dist/plugin/rest-schemas.d.ts +556 -0
- package/dist/plugin/rest-schemas.d.ts.map +1 -0
- package/dist/plugin/rest-schemas.js +262 -0
- package/dist/plugin/rest-schemas.js.map +1 -0
- package/dist/plugin/sandbox.d.ts +23 -0
- package/dist/plugin/sandbox.d.ts.map +1 -0
- package/dist/plugin/sandbox.js +99 -0
- package/dist/plugin/sandbox.js.map +1 -0
- package/dist/plugin/testing.d.ts +45 -0
- package/dist/plugin/testing.d.ts.map +1 -0
- package/dist/plugin/testing.js +420 -0
- package/dist/plugin/testing.js.map +1 -0
- package/dist/plugin/web-react.d.ts +11 -0
- package/dist/plugin/web-react.d.ts.map +1 -0
- package/dist/plugin/web-react.js +34 -0
- package/dist/plugin/web-react.js.map +1 -0
- package/dist/plugin/web.d.ts +95 -0
- package/dist/plugin/web.d.ts.map +1 -0
- package/dist/plugin/web.js +245 -0
- package/dist/plugin/web.js.map +1 -0
- package/dist/plugin-nav.d.ts +29 -0
- package/dist/plugin-nav.d.ts.map +1 -0
- package/dist/plugin-nav.js +92 -0
- package/dist/plugin-nav.js.map +1 -0
- package/dist/plugin-testing.d.ts +2 -0
- package/dist/plugin-testing.d.ts.map +1 -0
- package/dist/plugin-testing.js +4 -0
- package/dist/plugin-testing.js.map +1 -0
- package/dist/plugin-web-react.d.ts +3 -0
- package/dist/plugin-web-react.d.ts.map +1 -0
- package/dist/plugin-web-react.js +3 -0
- package/dist/plugin-web-react.js.map +1 -0
- package/dist/plugin-web.d.ts +4 -0
- package/dist/plugin-web.d.ts.map +1 -0
- package/dist/plugin-web.js +5 -0
- package/dist/plugin-web.js.map +1 -0
- package/dist/plugin.d.ts +11 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +15 -0
- package/dist/plugin.js.map +1 -0
- package/dist/router.d.ts +28 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +47 -0
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +4 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +6 -0
- package/dist/routes.js.map +1 -1
- package/dist/schemas.d.ts +194 -9
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +98 -17
- package/dist/schemas.js.map +1 -1
- package/dist/tokens.d.ts +2 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +101 -0
- package/dist/tokens.js.map +1 -0
- package/dist/vocabulary.d.ts +11 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +24 -0
- package/dist/vocabulary.js.map +1 -1
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +2 -0
- package/dist/web.js.map +1 -1
- package/docs/specs/cli.md +12 -5
- package/docs/specs/plugins.md +488 -0
- package/docs/specs/rest.md +11 -9
- package/package.json +26 -8
- package/skills/building-a-console/SKILL.md +25 -26
- package/skills/building-a-console/references/configuring.md +40 -0
- package/skills/building-a-console/references/the-default-console.md +1 -1
- package/skills/building-a-console/references/the-look-from-a-website.md +5 -5
- package/skills/building-a-plugin/SKILL.md +137 -0
- package/skills/deploying-a-console/SKILL.md +24 -26
- package/skills/deploying-a-console/references/access.md +13 -9
- package/skills/deploying-a-console/references/resources.md +25 -10
- package/skills/deploying-a-console/references/running-it-again.md +15 -0
- package/skills/deploying-a-console/references/sizing.md +2 -0
- package/skills/planning-the-board/SKILL.md +96 -0
- package/skills/planning-the-board/references/a-task.md +36 -0
- package/skills/planning-the-board/references/setting-up.md +44 -0
- package/skills/working-the-board/SKILL.md +43 -34
- package/skills/working-the-board/references/the-pack.md +19 -0
- package/src/api.ts +12 -0
- package/src/cli/cli.ts +737 -55
- package/src/cli/format.ts +368 -10
- package/src/cli/help.ts +50 -6
- package/src/cli/plugin-check.ts +497 -0
- package/src/cli/plugin-cli.ts +237 -0
- package/src/cli/plugin-init.ts +257 -0
- package/src/cli/plugin-main.ts +30 -0
- package/src/client.ts +322 -9
- package/src/compat.ts +2 -2
- package/src/components/board.tsx +62 -14
- package/src/components/comments.tsx +1 -0
- package/src/components/decision-form.tsx +92 -11
- package/src/components/decision-list.tsx +71 -2
- package/src/components/event-feed.tsx +42 -1
- package/src/components/my-decisions.tsx +4 -1
- package/src/components/plugin-frame.tsx +437 -0
- package/src/components/plugin-settings.tsx +1111 -0
- package/src/components/shell.tsx +82 -14
- package/src/components/task-view.tsx +2 -1
- package/src/components/workspace.tsx +18 -0
- package/src/console.tsx +304 -31
- package/src/data.ts +59 -11
- package/src/i18n/en.ts +175 -0
- package/src/i18n/ko.ts +155 -0
- package/src/index.ts +12 -0
- package/src/limits.ts +2 -0
- package/src/plugin/args.ts +526 -0
- package/src/plugin/bridge.ts +314 -0
- package/src/plugin/capabilities.ts +150 -0
- package/src/plugin/context.ts +421 -0
- package/src/plugin/define.ts +792 -0
- package/src/plugin/definition-error.ts +19 -0
- package/src/plugin/limits.ts +70 -0
- package/src/plugin/load-worker.ts +50 -0
- package/src/plugin/load.ts +82 -0
- package/src/plugin/manifest.ts +561 -0
- package/src/plugin/refusals.ts +94 -0
- package/src/plugin/rest-schemas.ts +354 -0
- package/src/plugin/sandbox.ts +123 -0
- package/src/plugin/testing.ts +612 -0
- package/src/plugin/web-react.ts +45 -0
- package/src/plugin/web.ts +403 -0
- package/src/plugin-nav.ts +132 -0
- package/src/plugin-testing.ts +10 -0
- package/src/plugin-web-react.ts +4 -0
- package/src/plugin-web.ts +35 -0
- package/src/plugin.ts +126 -0
- package/src/router.ts +73 -0
- package/src/routes.ts +7 -0
- package/src/schemas.ts +115 -18
- package/src/styles/console.css +330 -1
- package/src/tokens.ts +101 -0
- package/src/vocabulary.ts +30 -0
- package/src/web.ts +2 -0
- package/template/plugin/.gitignore +2 -0
- package/template/plugin/README.md +26 -0
- package/template/plugin/SKILLCDN.md +13 -0
- package/template/plugin/build.mjs +40 -0
- package/template/plugin/console-plugin.json +50 -0
- package/template/plugin/package.json +27 -0
- package/template/plugin/skills/__key__/SKILL.md +23 -0
- package/template/plugin/src/check.test.ts +34 -0
- package/template/plugin/src/server/index.test.ts +41 -0
- package/template/plugin/src/server/index.ts +72 -0
- package/template/plugin/src/web/index.css +51 -0
- package/template/plugin/src/web/index.ts +115 -0
- package/template/plugin/tsconfig.json +17 -0
- package/skills/working-the-board/references/setting-up.md +0 -3
- /package/skills/{working-the-board → planning-the-board}/references/project-rules.md +0 -0
package/src/client.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
connectPath,
|
|
6
6
|
personTokensPath,
|
|
7
7
|
personWorkPath,
|
|
8
|
+
pluginPath,
|
|
8
9
|
projectPath,
|
|
9
10
|
REST_ROUTES,
|
|
10
11
|
restPath,
|
|
@@ -52,6 +53,21 @@ import {
|
|
|
52
53
|
type RestPerson,
|
|
53
54
|
type RestPersonPatch,
|
|
54
55
|
type RestPersonWork,
|
|
56
|
+
type RestPluginCommandInput,
|
|
57
|
+
type RestPluginDetail,
|
|
58
|
+
type RestPluginEnableInput,
|
|
59
|
+
type RestPluginEvents,
|
|
60
|
+
type RestPluginFileHandle,
|
|
61
|
+
type RestPluginGrant,
|
|
62
|
+
type RestPluginGrantInput,
|
|
63
|
+
type RestPluginGrants,
|
|
64
|
+
type RestPluginInvocation,
|
|
65
|
+
type RestPluginInvokeInput,
|
|
66
|
+
type RestPluginRecordPage,
|
|
67
|
+
type RestPluginRecordRead,
|
|
68
|
+
type RestPluginRecordVersions,
|
|
69
|
+
type RestPluginSettingsPatch,
|
|
70
|
+
type RestPlugins,
|
|
55
71
|
type RestProject,
|
|
56
72
|
type RestProjectInput,
|
|
57
73
|
type RestProjectPatch,
|
|
@@ -108,6 +124,16 @@ import {
|
|
|
108
124
|
restPeopleSchema,
|
|
109
125
|
restPersonSchema,
|
|
110
126
|
restPersonWorkSchema,
|
|
127
|
+
restPluginDetailSchema,
|
|
128
|
+
restPluginEventsSchema,
|
|
129
|
+
restPluginFileHandleSchema,
|
|
130
|
+
restPluginGrantSchema,
|
|
131
|
+
restPluginGrantsSchema,
|
|
132
|
+
restPluginInvocationSchema,
|
|
133
|
+
restPluginRecordPageSchema,
|
|
134
|
+
restPluginRecordReadSchema,
|
|
135
|
+
restPluginRecordVersionsSchema,
|
|
136
|
+
restPluginsSchema,
|
|
111
137
|
restProjectRulesSchema,
|
|
112
138
|
restProjectSchema,
|
|
113
139
|
restProjectsSchema,
|
|
@@ -299,7 +325,8 @@ export interface ProjectClient {
|
|
|
299
325
|
* while the decision waits, for as long as it allows, and answers the decision as it stands.
|
|
300
326
|
*/
|
|
301
327
|
awaitDecision(id: string, waitSeconds: number, signal?: AbortSignal): Promise<RestDecision>;
|
|
302
|
-
|
|
328
|
+
/** Raises a decision; with `files`, the files put on it (ADR-0055) go in the one request. */
|
|
329
|
+
raiseDecision(input: RestDecisionInput, files?: readonly FileUpload[]): Promise<RestDecision>;
|
|
303
330
|
answerDecision(id: string, input: RestAnswerInput): Promise<RestDecision>;
|
|
304
331
|
/** Grows a decision's record: its context, or what followed. */
|
|
305
332
|
updateDecision(id: string, patch: RestDecisionPatch): Promise<RestDecision>;
|
|
@@ -317,7 +344,7 @@ export interface ProjectClient {
|
|
|
317
344
|
handIn(runId: string, input: RestArtifactInput): Promise<RestRun>;
|
|
318
345
|
/** Hands in a file, on a run of the asker's: the console keeps the bytes. */
|
|
319
346
|
handInFile(runId: string, input: FileUpload): Promise<RestRun>;
|
|
320
|
-
/** Where the bytes of a file of the project are read, by its id: handed in by a run, put in a comment, or attached to a page. */
|
|
347
|
+
/** Where the bytes of a file of the project are read, by its id: handed in by a run, put in a comment or on a decision, or attached to a page. */
|
|
321
348
|
fileUrl(fileId: string): string;
|
|
322
349
|
/** The bytes of a file of the project, by its id, with the name and the type the console says. */
|
|
323
350
|
readFile(fileId: string, signal?: AbortSignal): Promise<FileRead>;
|
|
@@ -404,6 +431,126 @@ export interface ProjectClient {
|
|
|
404
431
|
updateGoal(id: string, patch: RestGoalPatch): Promise<RestGoal>;
|
|
405
432
|
}
|
|
406
433
|
|
|
434
|
+
/** What a read of a plugin's collection is narrowed by (docs/specs/plugins.md, "Records"). */
|
|
435
|
+
export interface RecordFilter {
|
|
436
|
+
/** Conditions on indexed fields, as `ctx.records().list` takes them. */
|
|
437
|
+
readonly where?: Readonly<Record<string, unknown>> | undefined;
|
|
438
|
+
/** A field, with `-` before it for descending. */
|
|
439
|
+
readonly order?: string | undefined;
|
|
440
|
+
readonly limit?: number | undefined;
|
|
441
|
+
/** The cursor the last page answered with. */
|
|
442
|
+
readonly after?: string | undefined;
|
|
443
|
+
/** Words to search the collection's search fields for. */
|
|
444
|
+
readonly q?: string | undefined;
|
|
445
|
+
/** The key of a project, for the records linked to it. */
|
|
446
|
+
readonly project?: string | undefined;
|
|
447
|
+
readonly archived?: boolean | undefined;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* The client of one plugin (docs/specs/plugins.md, "REST"): its commands and queries run as the
|
|
452
|
+
* person the client acts as, its records read within their roles, its skills, its events; and,
|
|
453
|
+
* for an administrator, enabling, configuring and granting. Every answer is in the console's
|
|
454
|
+
* generic shapes; what a command or a query itself answers is the plugin's own.
|
|
455
|
+
*/
|
|
456
|
+
export interface PluginClient {
|
|
457
|
+
readonly key: string;
|
|
458
|
+
/** The plugin whole: its commands and queries with their arguments, its skills, its settings. */
|
|
459
|
+
get(signal?: AbortSignal): Promise<RestPluginDetail>;
|
|
460
|
+
/** Runs a command or a query by the words as typed, which the console parses against the definition. */
|
|
461
|
+
invoke(input: RestPluginInvokeInput): Promise<RestPluginInvocation>;
|
|
462
|
+
/** Runs a command with its arguments as JSON, by name, for a page. */
|
|
463
|
+
command(name: string, input: RestPluginCommandInput): Promise<RestPluginInvocation>;
|
|
464
|
+
/** Runs a query with its arguments as JSON; the answer is the query's own JSON. */
|
|
465
|
+
query(
|
|
466
|
+
name: string,
|
|
467
|
+
args: Readonly<Record<string, unknown>>,
|
|
468
|
+
options?: { readonly project?: string | undefined },
|
|
469
|
+
signal?: AbortSignal,
|
|
470
|
+
): Promise<unknown>;
|
|
471
|
+
/** Where a query's answer is read as bytes, for one that answers a file. */
|
|
472
|
+
queryUrl(
|
|
473
|
+
name: string,
|
|
474
|
+
args: Readonly<Record<string, unknown>>,
|
|
475
|
+
options?: { readonly project?: string | undefined },
|
|
476
|
+
): string;
|
|
477
|
+
/** Uploads a file for a command, with the `files` capability: the handle a `file` argument takes, good for an hour. */
|
|
478
|
+
upload(input: FileUpload): Promise<RestPluginFileHandle>;
|
|
479
|
+
/** A page of a collection's records, as the person may read them. */
|
|
480
|
+
records(
|
|
481
|
+
collection: string,
|
|
482
|
+
filter?: RecordFilter,
|
|
483
|
+
signal?: AbortSignal,
|
|
484
|
+
): Promise<RestPluginRecordPage>;
|
|
485
|
+
/** One record, or one version of it. */
|
|
486
|
+
record(
|
|
487
|
+
collection: string,
|
|
488
|
+
id: string,
|
|
489
|
+
version?: number,
|
|
490
|
+
signal?: AbortSignal,
|
|
491
|
+
): Promise<RestPluginRecordRead>;
|
|
492
|
+
recordVersions(
|
|
493
|
+
collection: string,
|
|
494
|
+
id: string,
|
|
495
|
+
signal?: AbortSignal,
|
|
496
|
+
): Promise<RestPluginRecordVersions>;
|
|
497
|
+
/** Where a record's file is read, by its id. */
|
|
498
|
+
fileUrl(fileId: string): string;
|
|
499
|
+
/** The bytes of a record's file, with the name and the type the console says. */
|
|
500
|
+
readFile(fileId: string, signal?: AbortSignal): Promise<FileRead>;
|
|
501
|
+
/** Where the frame of a page or a widget is (ADR-0054), on a project when its key is given. */
|
|
502
|
+
frameUrl(options: {
|
|
503
|
+
readonly page?: string | undefined;
|
|
504
|
+
readonly widget?: string | undefined;
|
|
505
|
+
readonly project?: string | undefined;
|
|
506
|
+
}): string;
|
|
507
|
+
/** The events the plugin emitted after number `after`, oldest first: what a frame hears. */
|
|
508
|
+
pluginEvents(
|
|
509
|
+
page: { readonly after: number; readonly limit: number },
|
|
510
|
+
signal?: AbortSignal,
|
|
511
|
+
): Promise<RestPluginEvents>;
|
|
512
|
+
/** Removes everything the console keeps of a disabled plugin, the key named again; for an administrator. */
|
|
513
|
+
purge(confirm: string): Promise<void>;
|
|
514
|
+
/** The plugin's skills, a pack of its own read from its package, as a project's five reads. */
|
|
515
|
+
skills(signal?: AbortSignal): Promise<RestSkills>;
|
|
516
|
+
skill(path: string, cursor?: string, signal?: AbortSignal): Promise<RestSkillLoad>;
|
|
517
|
+
skillFile(
|
|
518
|
+
path: string,
|
|
519
|
+
page?: { readonly offset?: number | undefined; readonly limit?: number | undefined },
|
|
520
|
+
signal?: AbortSignal,
|
|
521
|
+
): Promise<RestSkillFile>;
|
|
522
|
+
skillBrowse(
|
|
523
|
+
page?: {
|
|
524
|
+
readonly path?: string | undefined;
|
|
525
|
+
readonly cursor?: string | undefined;
|
|
526
|
+
readonly limit?: number | undefined;
|
|
527
|
+
},
|
|
528
|
+
signal?: AbortSignal,
|
|
529
|
+
): Promise<RestSkillBrowse>;
|
|
530
|
+
skillFind(
|
|
531
|
+
query: string,
|
|
532
|
+
options?: {
|
|
533
|
+
readonly path?: string | undefined;
|
|
534
|
+
readonly cursor?: string | undefined;
|
|
535
|
+
readonly limit?: number | undefined;
|
|
536
|
+
},
|
|
537
|
+
signal?: AbortSignal,
|
|
538
|
+
): Promise<RestSkillFind>;
|
|
539
|
+
/** The plugin's events, newest first, a page at a time. */
|
|
540
|
+
events(page?: Page, signal?: AbortSignal): Promise<RestEvents>;
|
|
541
|
+
/** Enables the plugin, with consent to every capability it asks; for an administrator. */
|
|
542
|
+
enable(input: RestPluginEnableInput): Promise<RestPluginDetail>;
|
|
543
|
+
disable(): Promise<RestPluginDetail>;
|
|
544
|
+
/** Changes the settings' values, without secrets; `null` clears one. */
|
|
545
|
+
updateSettings(patch: RestPluginSettingsPatch): Promise<RestPluginDetail>;
|
|
546
|
+
/** Seals a secret for the plugin; it is answered by nothing from then on. */
|
|
547
|
+
setSecret(name: string, value: string): Promise<void>;
|
|
548
|
+
removeSecret(name: string): Promise<void>;
|
|
549
|
+
grants(signal?: AbortSignal): Promise<RestPluginGrants>;
|
|
550
|
+
grant(input: RestPluginGrantInput): Promise<RestPluginGrant>;
|
|
551
|
+
revoke(grantId: string): Promise<RestPluginGrant>;
|
|
552
|
+
}
|
|
553
|
+
|
|
407
554
|
export interface ConsoleClient {
|
|
408
555
|
me(signal?: AbortSignal): Promise<RestMe>;
|
|
409
556
|
/** Gives up the token this client acts with, so that it is good for nothing from now on: what `console logout` does. For a token; a session has none. */
|
|
@@ -443,6 +590,10 @@ export interface ConsoleClient {
|
|
|
443
590
|
createProject(input: RestProjectInput): Promise<RestProject>;
|
|
444
591
|
/** The board of one project. */
|
|
445
592
|
project(key: string): ProjectClient;
|
|
593
|
+
/** The plugins the asker may know of: the enabled ones, every one for an administrator. */
|
|
594
|
+
plugins(signal?: AbortSignal): Promise<RestPlugins>;
|
|
595
|
+
/** One plugin, by its key. */
|
|
596
|
+
plugin(key: string): PluginClient;
|
|
446
597
|
/** The workspace's own events, the ones about no project, after event number `after`. */
|
|
447
598
|
events(after: number, signal?: AbortSignal): Promise<RestEvents>;
|
|
448
599
|
/** Where a picture from elsewhere is read through the console (ADR-0048), given the https URL that names it. */
|
|
@@ -624,11 +775,9 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
624
775
|
return form;
|
|
625
776
|
};
|
|
626
777
|
|
|
627
|
-
/**
|
|
628
|
-
const
|
|
629
|
-
const
|
|
630
|
-
form.set("body", input.body);
|
|
631
|
-
for (const file of input.files ?? []) {
|
|
778
|
+
/** The files appended to a form, a part `file` each, with its name and its type. */
|
|
779
|
+
const appendFiles = (form: FormData, files: readonly FileUpload[]): void => {
|
|
780
|
+
for (const file of files) {
|
|
632
781
|
const bytes =
|
|
633
782
|
file.bytes instanceof Blob ? file.bytes : new Blob([Uint8Array.from(file.bytes)]);
|
|
634
783
|
form.append(
|
|
@@ -637,6 +786,21 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
637
786
|
file.name,
|
|
638
787
|
);
|
|
639
788
|
}
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
/** A comment with files as the parts of a form: the part `body`, and a part `file` for each file. */
|
|
792
|
+
const commentFormOf = (input: CommentInput): FormData => {
|
|
793
|
+
const form = new FormData();
|
|
794
|
+
form.set("body", input.body);
|
|
795
|
+
appendFiles(form, input.files ?? []);
|
|
796
|
+
return form;
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
/** A decision with files as the parts of a form (ADR-0055): the part `decision`, what the JSON would say, and a part `file` for each file. */
|
|
800
|
+
const decisionFormOf = (input: RestDecisionInput, files: readonly FileUpload[]): FormData => {
|
|
801
|
+
const form = new FormData();
|
|
802
|
+
form.set("decision", JSON.stringify(input));
|
|
803
|
+
appendFiles(form, files);
|
|
640
804
|
return form;
|
|
641
805
|
};
|
|
642
806
|
|
|
@@ -710,8 +874,13 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
710
874
|
restDecisionSchema,
|
|
711
875
|
signal,
|
|
712
876
|
),
|
|
713
|
-
raiseDecision: (input) =>
|
|
714
|
-
request(
|
|
877
|
+
raiseDecision: (input, files = []) =>
|
|
878
|
+
request(
|
|
879
|
+
"POST",
|
|
880
|
+
`${base}${at("decisions")}`,
|
|
881
|
+
files.length > 0 ? decisionFormOf(input, files) : input,
|
|
882
|
+
restDecisionSchema,
|
|
883
|
+
),
|
|
715
884
|
answerDecision: (id, input) =>
|
|
716
885
|
request("POST", `${base}${at("decisions", id)}/answer`, input, restDecisionSchema),
|
|
717
886
|
updateDecision: (id, patch) =>
|
|
@@ -915,6 +1084,147 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
915
1084
|
};
|
|
916
1085
|
};
|
|
917
1086
|
|
|
1087
|
+
/** A schema that takes whatever JSON came: a query's own answer. */
|
|
1088
|
+
const anything: Schema<unknown> = { safeParse: (data) => ({ success: true, data }) };
|
|
1089
|
+
|
|
1090
|
+
const pluginClient = (key: string): PluginClient => {
|
|
1091
|
+
const at = (...parts: readonly string[]) => pluginPath(key, ...parts);
|
|
1092
|
+
const skillsQuery = (part: string, params: Record<string, string | undefined>): string =>
|
|
1093
|
+
withQuery(at("skills", part), params);
|
|
1094
|
+
const queryUrl = (
|
|
1095
|
+
name: string,
|
|
1096
|
+
args: Readonly<Record<string, unknown>>,
|
|
1097
|
+
options: { readonly project?: string | undefined } = {},
|
|
1098
|
+
): string =>
|
|
1099
|
+
withQuery(at("queries", name), { args: JSON.stringify(args), project: options.project });
|
|
1100
|
+
return {
|
|
1101
|
+
key,
|
|
1102
|
+
get: (signal) => request("GET", `${base}${at()}`, undefined, restPluginDetailSchema, signal),
|
|
1103
|
+
invoke: (input) =>
|
|
1104
|
+
request("POST", `${base}${at("invoke")}`, input, restPluginInvocationSchema),
|
|
1105
|
+
command: (name, input) =>
|
|
1106
|
+
request("POST", `${base}${at("commands", name)}`, input, restPluginInvocationSchema),
|
|
1107
|
+
query: (name, args, options = {}, signal) =>
|
|
1108
|
+
request("GET", queryUrl(name, args, options), undefined, anything, signal),
|
|
1109
|
+
queryUrl,
|
|
1110
|
+
upload: (input) =>
|
|
1111
|
+
request("POST", `${base}${at("files")}`, formOf(input), restPluginFileHandleSchema),
|
|
1112
|
+
records: (collection, filter = {}, signal) =>
|
|
1113
|
+
request(
|
|
1114
|
+
"GET",
|
|
1115
|
+
withQuery(at("records", collection), {
|
|
1116
|
+
where: filter.where === undefined ? undefined : JSON.stringify(filter.where),
|
|
1117
|
+
order: filter.order,
|
|
1118
|
+
limit: number(filter.limit),
|
|
1119
|
+
after: filter.after,
|
|
1120
|
+
q: filter.q,
|
|
1121
|
+
project: filter.project,
|
|
1122
|
+
archived: flag(filter.archived),
|
|
1123
|
+
}),
|
|
1124
|
+
undefined,
|
|
1125
|
+
restPluginRecordPageSchema,
|
|
1126
|
+
signal,
|
|
1127
|
+
),
|
|
1128
|
+
record: (collection, id, version, signal) =>
|
|
1129
|
+
request(
|
|
1130
|
+
"GET",
|
|
1131
|
+
withQuery(at("records", collection, id), { version: number(version) }),
|
|
1132
|
+
undefined,
|
|
1133
|
+
restPluginRecordReadSchema,
|
|
1134
|
+
signal,
|
|
1135
|
+
),
|
|
1136
|
+
recordVersions: (collection, id, signal) =>
|
|
1137
|
+
request(
|
|
1138
|
+
"GET",
|
|
1139
|
+
`${base}${at("records", collection, id, "versions")}`,
|
|
1140
|
+
undefined,
|
|
1141
|
+
restPluginRecordVersionsSchema,
|
|
1142
|
+
signal,
|
|
1143
|
+
),
|
|
1144
|
+
fileUrl: (fileId) => `${base}${at("files", fileId)}`,
|
|
1145
|
+
readFile: (fileId, signal) => read(`${base}${at("files", fileId)}`, fileId, signal),
|
|
1146
|
+
frameUrl: (options) =>
|
|
1147
|
+
withQuery(at("frame"), {
|
|
1148
|
+
page: options.page,
|
|
1149
|
+
widget: options.widget,
|
|
1150
|
+
project: options.project,
|
|
1151
|
+
}),
|
|
1152
|
+
pluginEvents: (page, signal) =>
|
|
1153
|
+
request(
|
|
1154
|
+
"GET",
|
|
1155
|
+
withQuery(at("plugin-events"), { after: String(page.after), limit: String(page.limit) }),
|
|
1156
|
+
undefined,
|
|
1157
|
+
restPluginEventsSchema,
|
|
1158
|
+
signal,
|
|
1159
|
+
),
|
|
1160
|
+
purge: (confirm) => request("POST", `${base}${at("purge")}`, { confirm }, undefined),
|
|
1161
|
+
skills: (signal) =>
|
|
1162
|
+
request("GET", `${base}${at("skills")}`, undefined, restSkillsSchema, signal),
|
|
1163
|
+
skill: (path, cursor, signal) =>
|
|
1164
|
+
request(
|
|
1165
|
+
"GET",
|
|
1166
|
+
skillsQuery("one", { path, cursor }),
|
|
1167
|
+
undefined,
|
|
1168
|
+
restSkillLoadSchema,
|
|
1169
|
+
signal,
|
|
1170
|
+
),
|
|
1171
|
+
skillFile: (path, page = {}, signal) =>
|
|
1172
|
+
request(
|
|
1173
|
+
"GET",
|
|
1174
|
+
skillsQuery("file", { path, offset: number(page.offset), limit: number(page.limit) }),
|
|
1175
|
+
undefined,
|
|
1176
|
+
restSkillFileSchema,
|
|
1177
|
+
signal,
|
|
1178
|
+
),
|
|
1179
|
+
skillBrowse: (page = {}, signal) =>
|
|
1180
|
+
request(
|
|
1181
|
+
"GET",
|
|
1182
|
+
skillsQuery("browse", {
|
|
1183
|
+
path: page.path,
|
|
1184
|
+
cursor: page.cursor,
|
|
1185
|
+
limit: number(page.limit),
|
|
1186
|
+
}),
|
|
1187
|
+
undefined,
|
|
1188
|
+
restSkillBrowseSchema,
|
|
1189
|
+
signal,
|
|
1190
|
+
),
|
|
1191
|
+
skillFind: (query, options = {}, signal) =>
|
|
1192
|
+
request(
|
|
1193
|
+
"GET",
|
|
1194
|
+
skillsQuery("find", {
|
|
1195
|
+
query,
|
|
1196
|
+
path: options.path,
|
|
1197
|
+
cursor: options.cursor,
|
|
1198
|
+
limit: number(options.limit),
|
|
1199
|
+
}),
|
|
1200
|
+
undefined,
|
|
1201
|
+
restSkillFindSchema,
|
|
1202
|
+
signal,
|
|
1203
|
+
),
|
|
1204
|
+
events: (page = {}, signal) =>
|
|
1205
|
+
request(
|
|
1206
|
+
"GET",
|
|
1207
|
+
withQuery(at("events"), { limit: number(page.limit), offset: number(page.offset) }),
|
|
1208
|
+
undefined,
|
|
1209
|
+
restEventsSchema,
|
|
1210
|
+
signal,
|
|
1211
|
+
),
|
|
1212
|
+
enable: (input) => request("POST", `${base}${at("enable")}`, input, restPluginDetailSchema),
|
|
1213
|
+
disable: () => request("POST", `${base}${at("disable")}`, undefined, restPluginDetailSchema),
|
|
1214
|
+
updateSettings: (patch) =>
|
|
1215
|
+
request("PATCH", `${base}${at("settings")}`, patch, restPluginDetailSchema),
|
|
1216
|
+
setSecret: (name, value) =>
|
|
1217
|
+
request("PUT", `${base}${at("secrets", name)}`, { value }, undefined),
|
|
1218
|
+
removeSecret: (name) =>
|
|
1219
|
+
request("DELETE", `${base}${at("secrets", name)}`, undefined, undefined),
|
|
1220
|
+
grants: (signal) =>
|
|
1221
|
+
request("GET", `${base}${at("grants")}`, undefined, restPluginGrantsSchema, signal),
|
|
1222
|
+
grant: (input) => request("POST", `${base}${at("grants")}`, input, restPluginGrantSchema),
|
|
1223
|
+
revoke: (grantId) =>
|
|
1224
|
+
request("DELETE", `${base}${at("grants", grantId)}`, undefined, restPluginGrantSchema),
|
|
1225
|
+
};
|
|
1226
|
+
};
|
|
1227
|
+
|
|
918
1228
|
return {
|
|
919
1229
|
me: (signal) => request("GET", `${base}${REST_ROUTES.me}`, undefined, restMeSchema, signal),
|
|
920
1230
|
giveUpToken: () => request("DELETE", `${base}${REST_ROUTES.meToken}`, undefined, undefined),
|
|
@@ -972,6 +1282,9 @@ export function createClient(options: ClientOptions = {}): ConsoleClient {
|
|
|
972
1282
|
createProject: (input) =>
|
|
973
1283
|
request("POST", `${base}${REST_ROUTES.projects}`, input, restProjectSchema),
|
|
974
1284
|
project: projectClient,
|
|
1285
|
+
plugins: (signal) =>
|
|
1286
|
+
request("GET", `${base}${REST_ROUTES.plugins}`, undefined, restPluginsSchema, signal),
|
|
1287
|
+
plugin: pluginClient,
|
|
975
1288
|
events: (after, signal) =>
|
|
976
1289
|
request(
|
|
977
1290
|
"GET",
|
package/src/compat.ts
CHANGED
|
@@ -113,7 +113,7 @@ export interface ClientPolicy {
|
|
|
113
113
|
* last changed what the command reads or sends (ADR-0038), whose changelog entry begins with
|
|
114
114
|
* `Breaking:`; `pnpm check` holds it to a version the package has, or is about to have.
|
|
115
115
|
*/
|
|
116
|
-
export const CLIENT_POLICY: ClientPolicy = { atLeast: "0.4.
|
|
116
|
+
export const CLIENT_POLICY: ClientPolicy = { atLeast: "0.4.2" };
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
119
|
* The oldest console this command works with (ADR-0038): raised by the release that last
|
|
@@ -122,7 +122,7 @@ export const CLIENT_POLICY: ClientPolicy = { atLeast: "0.4.1" };
|
|
|
122
122
|
* itself, naming the command to install instead; `pnpm check` holds it as it holds the
|
|
123
123
|
* console's floor.
|
|
124
124
|
*/
|
|
125
|
-
export const CONSOLE_AT_LEAST = "0.4.
|
|
125
|
+
export const CONSOLE_AT_LEAST = "0.4.2";
|
|
126
126
|
|
|
127
127
|
/** `common` from `atLeast` on, where that floor is within it; `common` as it is otherwise. */
|
|
128
128
|
function narrowed(common: VersionRange, atLeast: string | undefined): VersionRange {
|
package/src/components/board.tsx
CHANGED
|
@@ -80,7 +80,28 @@ const HOLD_MS = 300;
|
|
|
80
80
|
const HOLD_SLACK = 8;
|
|
81
81
|
/** How near the pointer comes to the board's edge before the board scrolls to show more columns. */
|
|
82
82
|
const EDGE = 48;
|
|
83
|
-
|
|
83
|
+
/** How fast the board scrolls, in pixels a second, with the pointer at the edge itself; slower further in. */
|
|
84
|
+
const EDGE_SPEED = 720;
|
|
85
|
+
/** The most a frame counts for, so that a tab that was away does not leap when it is back. */
|
|
86
|
+
const FRAME_AT_MOST_MS = 64;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* How far the board scrolls in one frame for a pointer at `x`, the board spanning `left` to
|
|
90
|
+
* `right` on the screen: nothing away from the edges; towards the edge the pointer is near, the
|
|
91
|
+
* nearer the faster, and no faster beyond it. Asked every frame while a card is held, not only
|
|
92
|
+
* when the pointer moves, so that a card held still at the edge carries the board on, as an icon
|
|
93
|
+
* held at the edge of a phone's screen turns the page. Negative is leftwards.
|
|
94
|
+
*/
|
|
95
|
+
export function edgeScrollOf(x: number, left: number, right: number, elapsedMs: number): number {
|
|
96
|
+
const ms = Math.min(Math.max(elapsedMs, 0), FRAME_AT_MOST_MS);
|
|
97
|
+
const depth = (distance: number) => Math.min(1, Math.max(0, (EDGE - distance) / EDGE));
|
|
98
|
+
const leftward = depth(x - left);
|
|
99
|
+
const rightward = depth(right - x);
|
|
100
|
+
if (leftward === 0 && rightward === 0) {
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
return ((leftward > rightward ? -leftward : rightward) * EDGE_SPEED * ms) / 1000;
|
|
104
|
+
}
|
|
84
105
|
|
|
85
106
|
/** The state a column stands for, read off its `data-column`; nothing for a value that is not one. */
|
|
86
107
|
export function columnStateOf(value: string | null | undefined): TaskState | undefined {
|
|
@@ -422,10 +443,10 @@ export function Board(props: BoardProps) {
|
|
|
422
443
|
}, []);
|
|
423
444
|
|
|
424
445
|
// While a card is held, the pointer is followed over the whole document: the column under it
|
|
425
|
-
// is marked, the board scrolls
|
|
426
|
-
// A finger's hold is timed here, and once the card has come loose the
|
|
427
|
-
// under the finger, nor asks for the menu a long press would otherwise
|
|
428
|
-
// follows a drag opens nothing.
|
|
446
|
+
// is marked, the board scrolls while the pointer is near its edge, moving or still, and the
|
|
447
|
+
// release moves the task. A finger's hold is timed here, and once the card has come loose the
|
|
448
|
+
// page no longer scrolls under the finger, nor asks for the menu a long press would otherwise
|
|
449
|
+
// bring up; the click that follows a drag opens nothing.
|
|
429
450
|
const held = drag !== null;
|
|
430
451
|
useEffect(() => {
|
|
431
452
|
if (!held) {
|
|
@@ -435,8 +456,42 @@ export function Board(props: BoardProps) {
|
|
|
435
456
|
columnStateOf(
|
|
436
457
|
document.elementFromPoint(x, y)?.closest<HTMLElement>("[data-column]")?.dataset.column,
|
|
437
458
|
);
|
|
459
|
+
// Where the pointer last was, for the frames between its moves.
|
|
460
|
+
const pointer = { x: current.current?.x ?? 0, y: current.current?.y ?? 0 };
|
|
461
|
+
// The board is carried on every frame the loose card is near its edge; what a frame's share
|
|
462
|
+
// comes to short of a whole pixel is kept for the next, since a scroll position holds whole
|
|
463
|
+
// ones, and the column under the pointer is marked again where the board moved under it.
|
|
464
|
+
let frame = 0;
|
|
465
|
+
let at: number | undefined;
|
|
466
|
+
let rest = 0;
|
|
467
|
+
const carry = (now: number) => {
|
|
468
|
+
frame = window.requestAnimationFrame(carry);
|
|
469
|
+
const elapsed = at === undefined ? 0 : now - at;
|
|
470
|
+
at = now;
|
|
471
|
+
const element = board.current;
|
|
472
|
+
if (element === null || !loose.current) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const rect = element.getBoundingClientRect();
|
|
476
|
+
const share = edgeScrollOf(pointer.x, rect.left, rect.right, elapsed) + rest;
|
|
477
|
+
const whole = Math.trunc(share);
|
|
478
|
+
rest = share - whole;
|
|
479
|
+
if (whole === 0) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
const was = element.scrollLeft;
|
|
483
|
+
element.scrollLeft = was + whole;
|
|
484
|
+
if (element.scrollLeft !== was) {
|
|
485
|
+
const over = columnUnder(pointer.x, pointer.y);
|
|
486
|
+
setDrag((before) =>
|
|
487
|
+
before === null || before.over === over ? before : { ...before, over },
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
};
|
|
438
491
|
const follow = (event: PointerEvent) => {
|
|
439
492
|
const { clientX: x, clientY: y } = event;
|
|
493
|
+
pointer.x = x;
|
|
494
|
+
pointer.y = y;
|
|
440
495
|
setDrag((before) => {
|
|
441
496
|
if (before === null) {
|
|
442
497
|
return before;
|
|
@@ -455,15 +510,6 @@ export function Board(props: BoardProps) {
|
|
|
455
510
|
}
|
|
456
511
|
return { ...before, x, y, moving: true, over: columnUnder(x, y) };
|
|
457
512
|
});
|
|
458
|
-
const element = board.current;
|
|
459
|
-
if (element !== null && loose.current) {
|
|
460
|
-
const rect = element.getBoundingClientRect();
|
|
461
|
-
if (x < rect.left + EDGE) {
|
|
462
|
-
element.scrollLeft -= EDGE_STEP;
|
|
463
|
-
} else if (x > rect.right - EDGE) {
|
|
464
|
-
element.scrollLeft += EDGE_STEP;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
513
|
};
|
|
468
514
|
const loosen = () => {
|
|
469
515
|
setDrag((before) => {
|
|
@@ -502,6 +548,7 @@ export function Board(props: BoardProps) {
|
|
|
502
548
|
};
|
|
503
549
|
const quiet = (event: Event) => event.preventDefault();
|
|
504
550
|
const timer = current.current?.hold === true ? window.setTimeout(loosen, HOLD_MS) : undefined;
|
|
551
|
+
frame = window.requestAnimationFrame(carry);
|
|
505
552
|
window.addEventListener("pointermove", follow);
|
|
506
553
|
window.addEventListener("pointerup", release);
|
|
507
554
|
window.addEventListener("pointercancel", release);
|
|
@@ -511,6 +558,7 @@ export function Board(props: BoardProps) {
|
|
|
511
558
|
if (timer !== undefined) {
|
|
512
559
|
window.clearTimeout(timer);
|
|
513
560
|
}
|
|
561
|
+
window.cancelAnimationFrame(frame);
|
|
514
562
|
window.removeEventListener("pointermove", follow);
|
|
515
563
|
window.removeEventListener("pointerup", release);
|
|
516
564
|
window.removeEventListener("pointercancel", release);
|