@sanity/runtime-cli 1.0.2
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 +548 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +5 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +5 -0
- package/dist/actions/functions/dev.d.ts +1 -0
- package/dist/actions/functions/dev.js +5 -0
- package/dist/actions/functions/invoke.d.ts +2 -0
- package/dist/actions/functions/invoke.js +17 -0
- package/dist/actions/functions/logs.d.ts +1 -0
- package/dist/actions/functions/logs.js +14 -0
- package/dist/actions/functions/test.d.ts +2 -0
- package/dist/actions/functions/test.js +13 -0
- package/dist/commands/functions/dev.d.ts +9 -0
- package/dist/commands/functions/dev.js +15 -0
- package/dist/commands/functions/invoke.d.ts +13 -0
- package/dist/commands/functions/invoke.js +25 -0
- package/dist/commands/functions/logs.d.ts +9 -0
- package/dist/commands/functions/logs.js +14 -0
- package/dist/commands/functions/test.d.ts +14 -0
- package/dist/commands/functions/test.js +43 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +9 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/server/app.d.ts +3 -0
- package/dist/server/app.js +36 -0
- package/dist/server/static/api.js +50 -0
- package/dist/server/static/components/api-base.js +10 -0
- package/dist/server/static/components/app.css +155 -0
- package/dist/server/static/components/function-list.js +49 -0
- package/dist/server/static/components/network-spinner.js +71 -0
- package/dist/server/static/components/payload-panel.js +45 -0
- package/dist/server/static/components/response-panel.js +83 -0
- package/dist/server/static/index.html +55 -0
- package/dist/server/static/sanity-logo-sm.svg +1 -0
- package/dist/server/static/vendor/vendor.bundle.js +26857 -0
- package/dist/utils/build-payload.d.ts +2 -0
- package/dist/utils/build-payload.js +15 -0
- package/dist/utils/child-process-wrapper.js +33 -0
- package/dist/utils/invoke-local.d.ts +2 -0
- package/dist/utils/invoke-local.js +51 -0
- package/dist/utils/is-dependency.d.ts +1 -0
- package/dist/utils/is-dependency.js +7 -0
- package/dist/utils/is-json.d.ts +1 -0
- package/dist/utils/is-json.js +12 -0
- package/dist/utils/types.d.ts +16 -0
- package/dist/utils/types.js +1 -0
- package/oclif.manifest.json +179 -0
- package/package.json +85 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { invoke } from '../../actions/functions/invoke.js';
|
|
3
|
+
export default class Invoke extends Command {
|
|
4
|
+
static args = {
|
|
5
|
+
id: Args.string({ description: 'The ID of the function to invoke', required: true }),
|
|
6
|
+
};
|
|
7
|
+
static description = 'Invoke a remote Sanity Function';
|
|
8
|
+
static examples = [
|
|
9
|
+
`<%= config.bin %> <%= command.id %> <ID> --data '{ "id": 1 }'`,
|
|
10
|
+
`<%= config.bin %> <%= command.id %> <ID> --file 'payload.json'`,
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
data: Flags.string({ char: 'd', description: 'Data to send to the function', required: false }),
|
|
14
|
+
file: Flags.string({
|
|
15
|
+
char: 'f',
|
|
16
|
+
description: 'Read data from file and send to the function',
|
|
17
|
+
required: false,
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
async run() {
|
|
21
|
+
const { args, flags } = await this.parse(Invoke);
|
|
22
|
+
const result = await invoke(args.id, { data: flags.data, file: flags.file });
|
|
23
|
+
this.log(JSON.stringify(result, null, 2));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Logs extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Args, Command } from '@oclif/core';
|
|
2
|
+
import { logs } from '../../actions/functions/logs.js';
|
|
3
|
+
export default class Logs extends Command {
|
|
4
|
+
static args = {
|
|
5
|
+
id: Args.string({ description: 'The ID of the function to retrieve logs for', required: true }),
|
|
6
|
+
};
|
|
7
|
+
static description = 'Retrieve logs for a Sanity Function';
|
|
8
|
+
static examples = ['<%= config.bin %> <%= command.id %> <ID>'];
|
|
9
|
+
async run() {
|
|
10
|
+
const { args } = await this.parse(Logs);
|
|
11
|
+
const result = await logs(args.id);
|
|
12
|
+
this.log(JSON.stringify(result, null, 2));
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Test extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
path: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
data: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
timeout: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { testAction } from '../../actions/functions/test.js';
|
|
3
|
+
export default class Test extends Command {
|
|
4
|
+
static args = {
|
|
5
|
+
path: Args.string({ description: 'The path to the function source code', required: true }),
|
|
6
|
+
};
|
|
7
|
+
static description = 'Invoke a local Sanity Function';
|
|
8
|
+
static examples = [
|
|
9
|
+
`<%= config.bin %> <%= command.id %> ./test.ts --data '{ "id": 1 }'`,
|
|
10
|
+
`<%= config.bin %> <%= command.id %> ./test.js --file 'payload.json'`,
|
|
11
|
+
`<%= config.bin %> <%= command.id %> ./test.ts --data '{ "id": 1 }' --timeout 60`,
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
data: Flags.string({ char: 'd', description: 'Data to send to the function', required: false }),
|
|
15
|
+
file: Flags.string({
|
|
16
|
+
char: 'f',
|
|
17
|
+
description: 'Read data from file and send to the function',
|
|
18
|
+
required: false,
|
|
19
|
+
}),
|
|
20
|
+
timeout: Flags.integer({
|
|
21
|
+
char: 't',
|
|
22
|
+
description: 'Execution timeout value in seconds',
|
|
23
|
+
required: false,
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
async run() {
|
|
27
|
+
const { args, flags } = await this.parse(Test);
|
|
28
|
+
const { json, logs, error } = await testAction(args.path, {
|
|
29
|
+
data: flags.data,
|
|
30
|
+
file: flags.file,
|
|
31
|
+
timeout: flags.timeout,
|
|
32
|
+
});
|
|
33
|
+
if (!error) {
|
|
34
|
+
this.log('Logs:');
|
|
35
|
+
this.log(logs);
|
|
36
|
+
this.log('Response:');
|
|
37
|
+
this.log(JSON.stringify(json, null, 2));
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
this.log(error.toString());
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { cwd } from 'node:process';
|
|
4
|
+
import { serveStatic } from '@hono/node-server/serve-static';
|
|
5
|
+
import { Hono } from 'hono';
|
|
6
|
+
import invoke from '../utils/invoke-local.js';
|
|
7
|
+
import isDependency from '../utils/is-dependency.js';
|
|
8
|
+
function errorResponse(code, message, details = {}) {
|
|
9
|
+
return { error: { code, details, message } };
|
|
10
|
+
}
|
|
11
|
+
function getStaticPath() {
|
|
12
|
+
return isDependency('./server/static')
|
|
13
|
+
? './node_modules/@sanity/runtime-cli/dist/server/static'
|
|
14
|
+
: './src/server/static';
|
|
15
|
+
}
|
|
16
|
+
const app = new Hono();
|
|
17
|
+
app.use('*', serveStatic({ root: getStaticPath() }));
|
|
18
|
+
app.get('/blueprint', async (c) => {
|
|
19
|
+
const response = JSON.parse(readFileSync(join(cwd(), './blueprints.json')).toString());
|
|
20
|
+
return c.json(response);
|
|
21
|
+
});
|
|
22
|
+
app.post('/invoke', async (c) => {
|
|
23
|
+
const { data = {}, func } = await c.req.json();
|
|
24
|
+
const response = await invoke(func, { data: JSON.parse(data) });
|
|
25
|
+
return c.json(response);
|
|
26
|
+
});
|
|
27
|
+
app
|
|
28
|
+
.notFound((c) => c.json(errorResponse('NOT_FOUND', 'Not Found', {
|
|
29
|
+
method: c.req.method,
|
|
30
|
+
path: c.req.path,
|
|
31
|
+
}), 404))
|
|
32
|
+
.onError((err, c) => c.json(errorResponse('INTERNAL_SERVER_ERROR', 'Internal Server Error', {
|
|
33
|
+
error: err.message,
|
|
34
|
+
stack: err,
|
|
35
|
+
}), 500));
|
|
36
|
+
export default app;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* eslint-disable n/no-unsupported-features/node-builtins */
|
|
2
|
+
import {Store} from './vendor/vendor.bundle.js'
|
|
3
|
+
|
|
4
|
+
// eslint-disable-next-line new-cap
|
|
5
|
+
const store = Store()
|
|
6
|
+
|
|
7
|
+
export default function API() {
|
|
8
|
+
return {
|
|
9
|
+
blueprint,
|
|
10
|
+
invoke,
|
|
11
|
+
store,
|
|
12
|
+
subscribe: store.subscribe,
|
|
13
|
+
unsubscribe: store.unsubscribe,
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function invoke(payloadText = '{}') {
|
|
18
|
+
store.inprogress = true
|
|
19
|
+
const start = Date.now()
|
|
20
|
+
const payload = {
|
|
21
|
+
data: payloadText,
|
|
22
|
+
func: store.selectedIndex,
|
|
23
|
+
}
|
|
24
|
+
fetch('/invoke', {
|
|
25
|
+
body: JSON.stringify(payload),
|
|
26
|
+
headers: {
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
},
|
|
29
|
+
method: 'POST',
|
|
30
|
+
})
|
|
31
|
+
.then((response) => response.json())
|
|
32
|
+
.then((data) => {
|
|
33
|
+
store.inprogress = false
|
|
34
|
+
store.result = {
|
|
35
|
+
...data,
|
|
36
|
+
time: Date.now() - start,
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function blueprint() {
|
|
42
|
+
fetch('/blueprint')
|
|
43
|
+
.then((response) => response.json())
|
|
44
|
+
.then((json) => {
|
|
45
|
+
const functions = json?.resources.filter((resource) => resource.kind === 'function')
|
|
46
|
+
|
|
47
|
+
store.functions = functions
|
|
48
|
+
store.selectedIndex = functions[0].src
|
|
49
|
+
})
|
|
50
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--card-bg-color: light-dark(#f6f6f8, #0d0e12);
|
|
3
|
+
--card-border-color: light-dark(#dbdce2, #252837);
|
|
4
|
+
--text-color: light-dark(#242736, #e3e4e8);
|
|
5
|
+
--button-text-color: light-dark(white, #0d0e12);
|
|
6
|
+
--button-background-color: light-dark(rgb(85, 107, 252), rgb(120, 152, 255));
|
|
7
|
+
--button-background-color-hover: light-dark(rgb(64, 67, 231), rgb(170, 193, 255));
|
|
8
|
+
--button-border-color: light-dark(rgb(85, 107, 252), rgb(120, 152, 255));
|
|
9
|
+
--button-border-color-hover: light-dark(rgb(64, 67, 231), rgb(170, 193, 255));
|
|
10
|
+
}
|
|
11
|
+
html {
|
|
12
|
+
color-scheme: light dark;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
body {
|
|
16
|
+
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', 'Liberation Sans', Helvetica, Arial, system-ui, sans-serif;
|
|
17
|
+
background-color: var(--card-bg-color);
|
|
18
|
+
color: var(--text-color) !important;
|
|
19
|
+
min-height: 100vh;
|
|
20
|
+
display: grid;
|
|
21
|
+
grid-template-areas:
|
|
22
|
+
'header'
|
|
23
|
+
'left-sidebar'
|
|
24
|
+
'main'
|
|
25
|
+
'footer';
|
|
26
|
+
grid-template-rows: min-content min-content 1fr min-content;
|
|
27
|
+
}
|
|
28
|
+
body > header {
|
|
29
|
+
grid-area: header;
|
|
30
|
+
}
|
|
31
|
+
body > nav {
|
|
32
|
+
grid-area: left-sidebar;
|
|
33
|
+
}
|
|
34
|
+
body > main {
|
|
35
|
+
grid-area: main;
|
|
36
|
+
}
|
|
37
|
+
body > footer {
|
|
38
|
+
grid-area: footer;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@media (min-width: 40rem) {
|
|
42
|
+
body {
|
|
43
|
+
grid-template:
|
|
44
|
+
'header header' min-content
|
|
45
|
+
'left-sidebar main ' 1fr
|
|
46
|
+
'footer footer' min-content
|
|
47
|
+
/ minmax(auto, var(--layout-max-sidebar-width, 16rem)) minmax(var(--layout-min-content-width, 16rem), 1fr);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@media (max-width: 40rem) {
|
|
52
|
+
button {
|
|
53
|
+
width: 100%;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.block-lg {
|
|
57
|
+
display: block !important;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.hidden-lg {
|
|
61
|
+
display: none;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
header {
|
|
66
|
+
border-bottom: 1px solid var(--card-border-color);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
footer {
|
|
70
|
+
border-top: 1px solid var(--card-border-color);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.logo {
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
gap: 0.25rem;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.logo-image {
|
|
80
|
+
height: 2.25em;
|
|
81
|
+
width: auto;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.logo-image img {
|
|
85
|
+
width: 100%;
|
|
86
|
+
height: 100%;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.logo-text {
|
|
90
|
+
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', 'Liberation Sans', Helvetica, Arial, system-ui, sans-serif;
|
|
91
|
+
font-weight: 500;
|
|
92
|
+
margin: 0px;
|
|
93
|
+
line-height: calc(1.46154);
|
|
94
|
+
letter-spacing: 0px;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.footer-text {
|
|
98
|
+
position: relative;
|
|
99
|
+
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', 'Liberation Sans', Helvetica, Arial, system-ui, sans-serif;
|
|
100
|
+
font-weight: 400;
|
|
101
|
+
padding: 1px 0px;
|
|
102
|
+
margin: 0px;
|
|
103
|
+
font-size: 0.8125rem;
|
|
104
|
+
line-height: calc(1.46154);
|
|
105
|
+
letter-spacing: 0px;
|
|
106
|
+
transform: translateY(0.3125rem);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.sanity-button {
|
|
110
|
+
color: var(--button-text-color) !important;
|
|
111
|
+
background-color: var(--button-background-color) !important;
|
|
112
|
+
border-color: var(--button-border-color) !important;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.sanity-button:hover {
|
|
116
|
+
background-color: var(--button-background-color-hover) !important;
|
|
117
|
+
border-color: var(--button-border-color-hover) !important;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.selected {
|
|
121
|
+
color: var(--button-text-color);
|
|
122
|
+
background-color: var(--button-background-color);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
ol[type='content'] li:hover {
|
|
126
|
+
color: var(--button-text-color);
|
|
127
|
+
background-color: var(--button-background-color-hover) !important;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
m-box,
|
|
131
|
+
m-tabs {
|
|
132
|
+
background-color: var(--card-bg-color) !important;
|
|
133
|
+
border-color: var(--card-border-color) !important;
|
|
134
|
+
color: var(--text-color) !important;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
m-tabs {
|
|
138
|
+
& > :is(a, button) {
|
|
139
|
+
color: var(--text-color) !important;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
m-tabs {
|
|
144
|
+
& > :is(a, button) {
|
|
145
|
+
&[aria-selected='true'] {
|
|
146
|
+
border-color: var(--button-border-color) !important;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#payload,
|
|
152
|
+
#response {
|
|
153
|
+
color: black;
|
|
154
|
+
background-color: white !important;
|
|
155
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/* globals customElements */
|
|
2
|
+
import {ApiBaseElement} from './api-base.js'
|
|
3
|
+
|
|
4
|
+
const template = `<ol class="hidden-lg" type="content"></ol>
|
|
5
|
+
<fieldset class="pad-sm hidden block-lg"><select></select></fieldset>
|
|
6
|
+
`
|
|
7
|
+
|
|
8
|
+
class FunctionList extends ApiBaseElement {
|
|
9
|
+
functionClicked = (event) => {
|
|
10
|
+
// eslint-disable-next-line unicorn/prefer-dom-node-text-content
|
|
11
|
+
const target = this.api.store.functions.find((func) => func.name === event.srcElement.innerText)
|
|
12
|
+
this.api.store.selectedIndex = target.src
|
|
13
|
+
}
|
|
14
|
+
functionSelected = (event) => {
|
|
15
|
+
this.api.store.selectedIndex = event.srcElement.value
|
|
16
|
+
}
|
|
17
|
+
renderFunctions = () => {
|
|
18
|
+
this.list.innerHTML = this.api.store.functions
|
|
19
|
+
.map((func) => {
|
|
20
|
+
const selected = this.api.store.selectedIndex === func.src ? 'selected' : ''
|
|
21
|
+
return `<li class="pad-sm ${selected}">${func.name}</li>`
|
|
22
|
+
})
|
|
23
|
+
.join('')
|
|
24
|
+
this.select.innerHTML = this.api.store.functions
|
|
25
|
+
.map((func) => {
|
|
26
|
+
const selected = this.api.store.selectedIndex === func.src ? 'selected' : ''
|
|
27
|
+
return `<option value="${func.src}" ${selected}>${func.name}</option>`
|
|
28
|
+
})
|
|
29
|
+
.join('')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
connectedCallback() {
|
|
33
|
+
this.innerHTML = template
|
|
34
|
+
this.list = this.querySelector('ol')
|
|
35
|
+
this.select = this.querySelector('select')
|
|
36
|
+
this.list.addEventListener('click', this.functionClicked)
|
|
37
|
+
this.select.addEventListener('change', this.functionSelected)
|
|
38
|
+
this.api.subscribe(this.renderFunctions, ['functions', 'selectedIndex'])
|
|
39
|
+
this.api.blueprint()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
disconnectedCallback() {
|
|
43
|
+
this.list.removeEventListener('click', this.functionClicked)
|
|
44
|
+
this.select.removeEventListener('change', this.functionSelected)
|
|
45
|
+
this.api.unsubscribe(this.renderFunctions)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
customElements.define('function-list', FunctionList)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/* globals customElements HTMLElement */
|
|
2
|
+
const template = `<style>
|
|
3
|
+
network-spinner {
|
|
4
|
+
--track-width: 2px;
|
|
5
|
+
--track-color: var(--card-border-color);
|
|
6
|
+
--indicator-color: var(--text-color);
|
|
7
|
+
--speed: 2s;
|
|
8
|
+
|
|
9
|
+
display: inline-flex;
|
|
10
|
+
width: 1em;
|
|
11
|
+
height: 1em;
|
|
12
|
+
flex: none;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.spinner {
|
|
16
|
+
flex: 1 1 auto;
|
|
17
|
+
height: 100%;
|
|
18
|
+
width: 100%;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.spinner__track,
|
|
22
|
+
.spinner__indicator {
|
|
23
|
+
fill: none;
|
|
24
|
+
stroke-width: var(--track-width);
|
|
25
|
+
r: calc(0.5em - var(--track-width) / 2);
|
|
26
|
+
cx: 0.5em;
|
|
27
|
+
cy: 0.5em;
|
|
28
|
+
transform-origin: 50% 50%;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.spinner__track {
|
|
32
|
+
stroke: var(--track-color);
|
|
33
|
+
transform-origin: 0% 0%;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.spinner__indicator {
|
|
37
|
+
stroke: var(--indicator-color);
|
|
38
|
+
stroke-linecap: round;
|
|
39
|
+
stroke-dasharray: 150% 75%;
|
|
40
|
+
animation: spin var(--speed) linear infinite;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@keyframes spin {
|
|
44
|
+
0% {
|
|
45
|
+
transform: rotate(0deg);
|
|
46
|
+
stroke-dasharray: 0.05em, 3em;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
50% {
|
|
50
|
+
transform: rotate(450deg);
|
|
51
|
+
stroke-dasharray: 1.375em, 1.375em;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
100% {
|
|
55
|
+
transform: rotate(1080deg);
|
|
56
|
+
stroke-dasharray: 0.05em, 3em;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
</style>
|
|
60
|
+
<svg part="base" class="spinner" role="progressbar" aria-label="loading">
|
|
61
|
+
<circle class="spinner__track"></circle>
|
|
62
|
+
<circle class="spinner__indicator"></circle>
|
|
63
|
+
</svg>`
|
|
64
|
+
|
|
65
|
+
class NetworkSpinner extends HTMLElement {
|
|
66
|
+
connectedCallback() {
|
|
67
|
+
this.innerHTML = template
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
customElements.define('network-spinner', NetworkSpinner)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/* globals customElements */
|
|
2
|
+
import {EditorView, basicSetup, json} from '../vendor/vendor.bundle.js'
|
|
3
|
+
import {ApiBaseElement} from './api-base.js'
|
|
4
|
+
|
|
5
|
+
const template = `<m-box>
|
|
6
|
+
<h2 class="mar-t-0">Payload</h2>
|
|
7
|
+
<div id="payload" name="payload"></div>
|
|
8
|
+
<button ord="primary" class="mar-t-sm sanity-button">Invoke</button>
|
|
9
|
+
</m-box>
|
|
10
|
+
`
|
|
11
|
+
class PayloadPanel extends ApiBaseElement {
|
|
12
|
+
invoke = () => {
|
|
13
|
+
const payloadText = this.api.store.payload.state.doc.text.join('') || '{}'
|
|
14
|
+
this.api.invoke(payloadText)
|
|
15
|
+
}
|
|
16
|
+
updateButtonText = ({inprogress}) => {
|
|
17
|
+
if (inprogress) {
|
|
18
|
+
this.button.setAttribute('disabled', '')
|
|
19
|
+
this.button.innerHTML = '<network-spinner></network-spinner>'
|
|
20
|
+
} else {
|
|
21
|
+
this.button.removeAttribute('disabled')
|
|
22
|
+
this.button.innerText = 'Invoke'
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
connectedCallback() {
|
|
27
|
+
this.innerHTML = template
|
|
28
|
+
this.payload = this.querySelector('#payload')
|
|
29
|
+
this.button = this.querySelector('button')
|
|
30
|
+
this.button.addEventListener('click', this.invoke)
|
|
31
|
+
this.api.subscribe(this.updateButtonText, ['inprogress'])
|
|
32
|
+
|
|
33
|
+
this.api.store.payload = new EditorView({
|
|
34
|
+
doc: '\n\n\n\n',
|
|
35
|
+
extensions: [basicSetup, json()],
|
|
36
|
+
parent: this.payload,
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
disconnectedCallback() {
|
|
41
|
+
this.button.removeEventListener('click', this.invoke)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
customElements.define('payload-panel', PayloadPanel)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/* eslint-disable unicorn/prefer-dom-node-text-content */
|
|
2
|
+
/* globals customElements document */
|
|
3
|
+
import {
|
|
4
|
+
EditorState,
|
|
5
|
+
EditorView,
|
|
6
|
+
basicSetup,
|
|
7
|
+
json,
|
|
8
|
+
prettyBytes,
|
|
9
|
+
prettyMilliseconds,
|
|
10
|
+
} from '../vendor/vendor.bundle.js'
|
|
11
|
+
import {ApiBaseElement} from './api-base.js'
|
|
12
|
+
|
|
13
|
+
const template = `<m-box>
|
|
14
|
+
<m-tabs role="tablist">
|
|
15
|
+
<button id="a" role="tab" aria-selected="true">Response</button>
|
|
16
|
+
<button id="b" role="tab">Console</button>
|
|
17
|
+
</m-tabs>
|
|
18
|
+
<div role="tabpanel" data-tab-id="a" class="pad-t-sm">
|
|
19
|
+
<div class="mar-b-sm">
|
|
20
|
+
<span id="time"></span> <span id="size"></span>
|
|
21
|
+
</div>
|
|
22
|
+
<div id="response" name="response"></div>
|
|
23
|
+
</div>
|
|
24
|
+
<div role="tabpanel" data-tab-id="b" class="pad-t-sm" hidden><pre></pre></div>
|
|
25
|
+
</m-box>
|
|
26
|
+
`
|
|
27
|
+
class ResponsePanel extends ApiBaseElement {
|
|
28
|
+
switchTab = (e) => {
|
|
29
|
+
const selectedTabId = e.target.closest('[role=tab]').id
|
|
30
|
+
|
|
31
|
+
// Select the tab and its panel
|
|
32
|
+
for (const tab of e.currentTarget.querySelectorAll('[role=tab]')) {
|
|
33
|
+
tab.ariaSelected = tab.id === selectedTabId
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const panel of document.querySelectorAll('[role=tabpanel')) {
|
|
37
|
+
panel.hidden = panel.dataset.tabId !== selectedTabId
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
updateResponse = ({result}) => {
|
|
41
|
+
const {error, json, logs, time} = result
|
|
42
|
+
if (!error) {
|
|
43
|
+
const transaction = this.api.store.response.state.update({
|
|
44
|
+
changes: {
|
|
45
|
+
from: 0,
|
|
46
|
+
insert: JSON.stringify(json, null, 2),
|
|
47
|
+
to: this.api.store.response.state.doc.length,
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
this.api.store.response.dispatch(transaction)
|
|
51
|
+
|
|
52
|
+
this.size.innerText = json ? prettyBytes(JSON.stringify(json).length) : ''
|
|
53
|
+
this.time.innerText = prettyMilliseconds(time)
|
|
54
|
+
this.consoleTab.innerText = logs
|
|
55
|
+
} else {
|
|
56
|
+
this.consoleTab.innerText = error?.details?.error
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
connectedCallback() {
|
|
61
|
+
this.innerHTML = template
|
|
62
|
+
this.response = this.querySelector('#response')
|
|
63
|
+
this.size = this.querySelector('#size')
|
|
64
|
+
this.time = this.querySelector('#time')
|
|
65
|
+
this.consoleTab = this.querySelector('pre')
|
|
66
|
+
this.tabs = this.querySelector('m-tabs')
|
|
67
|
+
this.tabs.addEventListener('click', this.switchTab)
|
|
68
|
+
this.api.subscribe(this.updateResponse, ['result'])
|
|
69
|
+
|
|
70
|
+
this.api.store.response = new EditorView({
|
|
71
|
+
doc: '\n\n\n\n',
|
|
72
|
+
extensions: [basicSetup, json(), EditorState.readOnly.of(true)],
|
|
73
|
+
parent: this.response,
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
disconnectedCallback() {
|
|
78
|
+
this.tabs.removeEventListener('click', this.switchTab)
|
|
79
|
+
this.api.unsubscribe(this.updateResponse)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
customElements.define('response-panel', ResponsePanel)
|