@trypattern/sdk 0.1.33 → 0.1.34
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/dist/app.d.ts +6 -2
- package/dist/app.js +6 -1
- package/dist/blocks.js +11 -3
- package/dist/bookings.d.ts +78 -0
- package/dist/bookings.js +7 -1
- package/dist/bookingsStore.d.ts +78 -0
- package/dist/cli/app.js +17 -2
- package/dist/cli/requests.d.ts +5 -0
- package/dist/cli/requests.js +115 -0
- package/dist/components/AppFrame.js +12 -2
- package/dist/components/MenuItems.d.ts +1 -0
- package/dist/components/MenuItems.js +13 -2
- package/dist/components/SegmentedControl.d.ts +1 -0
- package/dist/components/SegmentedControl.js +9 -2
- package/dist/components/Sidebar.d.ts +1 -0
- package/dist/components/Sidebar.js +18 -2
- package/dist/components/TableRow.d.ts +2 -1
- package/dist/components/TableRow.js +5 -2
- package/dist/components/icons.js +1 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/inquiriesStore.d.ts +134 -0
- package/dist/inquiriesStore.js +64 -0
- package/dist/patternAccess.d.ts +8 -2
- package/dist/patternAccess.js +17 -5
- package/dist/publishWire.d.ts +866 -0
- package/dist/publishWire.js +78 -0
- package/dist/requests.d.ts +361 -0
- package/dist/requests.js +180 -0
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +1 -0
- package/dist/server/authService.d.ts +1 -0
- package/dist/server/bookings.d.ts +2 -9
- package/dist/server/bookings.js +149 -31
- package/dist/server/createServer.js +4 -0
- package/dist/server/env.d.ts +3 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/inquiries.d.ts +9 -0
- package/dist/server/inquiries.js +150 -0
- package/dist/server/patternViewer.js +7 -1
- package/dist/server/requests.d.ts +76 -0
- package/dist/server/requests.js +328 -0
- package/dist/server/services.js +9 -1
- package/package.json +1 -1
package/dist/app.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { AppStore } from './appInfo.ts';
|
|
|
4
4
|
import type { AppBlock } from './blocks.ts';
|
|
5
5
|
import type { bookingRequests } from './bookings.ts';
|
|
6
6
|
import type { Bookings } from './bookingsStore.ts';
|
|
7
|
+
import type { Inquiries } from './inquiriesStore.ts';
|
|
8
|
+
import { requestOperations } from './requests.ts';
|
|
7
9
|
import { SessionStore } from './auth/sessionStore.ts';
|
|
8
10
|
import type { Cache } from './cache.ts';
|
|
9
11
|
import type { Clock } from './clock.ts';
|
|
@@ -74,11 +76,13 @@ export type PublicStores<Group extends Tables> = {
|
|
|
74
76
|
never
|
|
75
77
|
] ? never : `public${Capitalize<Key>}`]: PublicTableStore<Group[Key]>;
|
|
76
78
|
};
|
|
77
|
-
export type BlockStores<Group extends Tables> = [
|
|
79
|
+
export type BlockStores<Group extends Tables> = ([
|
|
78
80
|
typeof bookingRequests
|
|
79
81
|
] extends [Group[keyof Group]] ? {
|
|
80
82
|
bookings: Bookings;
|
|
81
|
-
} : Record<never, never
|
|
83
|
+
} : Record<never, never>) & ([typeof requestOperations] extends [Group[keyof Group]] ? {
|
|
84
|
+
inquiries: Inquiries;
|
|
85
|
+
} : Record<never, never>);
|
|
82
86
|
export type StoresOf<Group extends Tables, Custom, Item extends Action = Action> = {
|
|
83
87
|
session: SessionStore;
|
|
84
88
|
app: AppStore;
|
package/dist/app.js
CHANGED
|
@@ -2,6 +2,8 @@ import { z } from 'zod';
|
|
|
2
2
|
import { viewerOf } from "./access.js";
|
|
3
3
|
import { AppStore } from "./appInfo.js";
|
|
4
4
|
import { blockWiringProblem } from "./blocks.js";
|
|
5
|
+
import { inquiries } from "./inquiriesStore.js";
|
|
6
|
+
import { requestOperations } from "./requests.js";
|
|
5
7
|
import { SessionStore } from "./auth/sessionStore.js";
|
|
6
8
|
import { realClock } from "./clock.js";
|
|
7
9
|
import { ActionsStore } from "./actions.js";
|
|
@@ -75,7 +77,10 @@ function withBlocks(definition, blocks) {
|
|
|
75
77
|
return { ...definition, services, memory };
|
|
76
78
|
}
|
|
77
79
|
export function defineApp(definition) {
|
|
78
|
-
const
|
|
80
|
+
const listed = definition.blocks ?? [];
|
|
81
|
+
const takesRequests = Object.values(definition.tables).includes(requestOperations) &&
|
|
82
|
+
!listed.some((block) => block.name === inquiries.name);
|
|
83
|
+
const blocks = takesRequests ? [...listed, inquiries] : listed;
|
|
79
84
|
const wiring = blockWiringProblem(definition.tables, blocks, 'app');
|
|
80
85
|
if (wiring !== undefined)
|
|
81
86
|
throw new Error(wiring);
|
package/dist/blocks.js
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { bookingBlackouts, bookingRequests, bookingRules } from "./bookings.js";
|
|
2
|
+
import { requestTables } from "./requests.js";
|
|
2
3
|
const KNOWN_BLOCKS = [
|
|
3
4
|
{
|
|
4
5
|
name: 'bookings',
|
|
5
|
-
tables:
|
|
6
|
+
tables: { bookingRules, bookingBlackouts, bookingRequests },
|
|
6
7
|
tablesExport: 'bookingTables',
|
|
7
8
|
server: "bookings({ timezone: 'America/Toronto' })"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: 'inquiries',
|
|
12
|
+
tables: requestTables,
|
|
13
|
+
tablesExport: 'requestTables',
|
|
14
|
+
server: 'inquiries([orders])'
|
|
8
15
|
}
|
|
9
16
|
];
|
|
10
17
|
function includesAll(tables, wanted) {
|
|
@@ -26,8 +33,9 @@ export function blockWiringProblem(tables, blocks, side) {
|
|
|
26
33
|
for (const known of KNOWN_BLOCKS) {
|
|
27
34
|
if (!includesAny(tables, known.tables))
|
|
28
35
|
continue;
|
|
29
|
-
if (blocks.some((block) => block.
|
|
36
|
+
if (blocks.some((block) => includesAll(block.tables, known.tables))) {
|
|
30
37
|
continue;
|
|
38
|
+
}
|
|
31
39
|
return side === 'server'
|
|
32
40
|
? `tables has the ${known.name} block's tables, so the server needs the block: add blocks: [${known.server}] from '@trypattern/sdk/server' to createServer in server/worker.ts`
|
|
33
41
|
: `tables has the ${known.name} block's tables, so the app needs the block: add blocks: [${known.name}] from '@trypattern/sdk' to defineApp in src/App.ts`;
|
package/dist/bookings.d.ts
CHANGED
|
@@ -64,6 +64,84 @@ export declare const bookingRequests: import("./table.ts").TableOf<"bookingReque
|
|
|
64
64
|
readonly writes: "confirmed";
|
|
65
65
|
}>;
|
|
66
66
|
export declare const bookingTables: {
|
|
67
|
+
requestStates: import("./table.ts").TableOf<"requestStates", {
|
|
68
|
+
readonly columns: {
|
|
69
|
+
readonly source: z.ZodString;
|
|
70
|
+
readonly requestId: z.ZodString;
|
|
71
|
+
readonly state: z.ZodEnum<{
|
|
72
|
+
done: "done";
|
|
73
|
+
open: "open";
|
|
74
|
+
}>;
|
|
75
|
+
readonly revision: z.ZodNumber;
|
|
76
|
+
readonly sequence: z.ZodNumber;
|
|
77
|
+
readonly updatedAt: z.ZodString;
|
|
78
|
+
};
|
|
79
|
+
readonly access: {
|
|
80
|
+
readonly read: "server";
|
|
81
|
+
readonly create: "server";
|
|
82
|
+
readonly update: "server";
|
|
83
|
+
readonly delete: "server";
|
|
84
|
+
};
|
|
85
|
+
readonly writes: "confirmed";
|
|
86
|
+
}>;
|
|
87
|
+
requestEvents: import("./table.ts").TableOf<"requestEvents", {
|
|
88
|
+
readonly columns: {
|
|
89
|
+
readonly source: z.ZodString;
|
|
90
|
+
readonly requestId: z.ZodString;
|
|
91
|
+
readonly kind: z.ZodEnum<{
|
|
92
|
+
done: "done";
|
|
93
|
+
accepted: "accepted";
|
|
94
|
+
declined: "declined";
|
|
95
|
+
replied: "replied";
|
|
96
|
+
received: "received";
|
|
97
|
+
}>;
|
|
98
|
+
};
|
|
99
|
+
readonly access: {
|
|
100
|
+
readonly read: "server";
|
|
101
|
+
readonly create: "server";
|
|
102
|
+
readonly update: "server";
|
|
103
|
+
readonly delete: "server";
|
|
104
|
+
};
|
|
105
|
+
readonly writes: "confirmed";
|
|
106
|
+
}>;
|
|
107
|
+
requestOutbox: import("./table.ts").TableOf<"requestOutbox", {
|
|
108
|
+
readonly columns: {
|
|
109
|
+
readonly source: z.ZodString;
|
|
110
|
+
readonly requestId: z.ZodString;
|
|
111
|
+
readonly subject: z.ZodString;
|
|
112
|
+
readonly text: z.ZodString;
|
|
113
|
+
readonly replyTo: z.ZodString;
|
|
114
|
+
readonly status: z.ZodEnum<{
|
|
115
|
+
failed: "failed";
|
|
116
|
+
pending: "pending";
|
|
117
|
+
sent: "sent";
|
|
118
|
+
sending: "sending";
|
|
119
|
+
}>;
|
|
120
|
+
readonly attempts: z.ZodNumber;
|
|
121
|
+
readonly nextAt: z.ZodNullable<z.ZodString>;
|
|
122
|
+
readonly sentAt: z.ZodNullable<z.ZodString>;
|
|
123
|
+
readonly claimedAt: z.ZodNullable<z.ZodString>;
|
|
124
|
+
};
|
|
125
|
+
readonly access: {
|
|
126
|
+
readonly read: "server";
|
|
127
|
+
readonly create: "server";
|
|
128
|
+
readonly update: "server";
|
|
129
|
+
readonly delete: "server";
|
|
130
|
+
};
|
|
131
|
+
readonly writes: "confirmed";
|
|
132
|
+
}>;
|
|
133
|
+
requestOperations: import("./table.ts").TableOf<"requestOperations", {
|
|
134
|
+
readonly columns: {
|
|
135
|
+
readonly result: z.ZodString;
|
|
136
|
+
};
|
|
137
|
+
readonly access: {
|
|
138
|
+
readonly read: "server";
|
|
139
|
+
readonly create: "server";
|
|
140
|
+
readonly update: "server";
|
|
141
|
+
readonly delete: "server";
|
|
142
|
+
};
|
|
143
|
+
readonly writes: "confirmed";
|
|
144
|
+
}>;
|
|
67
145
|
bookingRules: import("./table.ts").TableOf<"bookingRules", {
|
|
68
146
|
readonly columns: {
|
|
69
147
|
readonly windows: z.ZodString;
|
package/dist/bookings.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { requestTables } from "./requests.js";
|
|
2
3
|
import { call, defineServices } from "./services.js";
|
|
3
4
|
import { patternTable } from "./table.js";
|
|
4
5
|
const DATE = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
|
|
@@ -78,7 +79,12 @@ export const bookingRequests = patternTable('bookingRequests', {
|
|
|
78
79
|
},
|
|
79
80
|
writes: 'confirmed'
|
|
80
81
|
});
|
|
81
|
-
export const bookingTables = {
|
|
82
|
+
export const bookingTables = {
|
|
83
|
+
bookingRules,
|
|
84
|
+
bookingBlackouts,
|
|
85
|
+
bookingRequests,
|
|
86
|
+
...requestTables
|
|
87
|
+
};
|
|
82
88
|
export const bookingWindow = z.object({
|
|
83
89
|
weekday: z.enum(WEEKDAYS),
|
|
84
90
|
label: z.string().min(1).max(60),
|
package/dist/bookingsStore.d.ts
CHANGED
|
@@ -281,6 +281,84 @@ export declare class Bookings extends Bookings_base {
|
|
|
281
281
|
export declare const bookings: {
|
|
282
282
|
readonly name: "bookings";
|
|
283
283
|
readonly tables: {
|
|
284
|
+
requestStates: import("./table.ts").TableOf<"requestStates", {
|
|
285
|
+
readonly columns: {
|
|
286
|
+
readonly source: z.ZodString;
|
|
287
|
+
readonly requestId: z.ZodString;
|
|
288
|
+
readonly state: z.ZodEnum<{
|
|
289
|
+
done: "done";
|
|
290
|
+
open: "open";
|
|
291
|
+
}>;
|
|
292
|
+
readonly revision: z.ZodNumber;
|
|
293
|
+
readonly sequence: z.ZodNumber;
|
|
294
|
+
readonly updatedAt: z.ZodString;
|
|
295
|
+
};
|
|
296
|
+
readonly access: {
|
|
297
|
+
readonly read: "server";
|
|
298
|
+
readonly create: "server";
|
|
299
|
+
readonly update: "server";
|
|
300
|
+
readonly delete: "server";
|
|
301
|
+
};
|
|
302
|
+
readonly writes: "confirmed";
|
|
303
|
+
}>;
|
|
304
|
+
requestEvents: import("./table.ts").TableOf<"requestEvents", {
|
|
305
|
+
readonly columns: {
|
|
306
|
+
readonly source: z.ZodString;
|
|
307
|
+
readonly requestId: z.ZodString;
|
|
308
|
+
readonly kind: z.ZodEnum<{
|
|
309
|
+
done: "done";
|
|
310
|
+
accepted: "accepted";
|
|
311
|
+
declined: "declined";
|
|
312
|
+
replied: "replied";
|
|
313
|
+
received: "received";
|
|
314
|
+
}>;
|
|
315
|
+
};
|
|
316
|
+
readonly access: {
|
|
317
|
+
readonly read: "server";
|
|
318
|
+
readonly create: "server";
|
|
319
|
+
readonly update: "server";
|
|
320
|
+
readonly delete: "server";
|
|
321
|
+
};
|
|
322
|
+
readonly writes: "confirmed";
|
|
323
|
+
}>;
|
|
324
|
+
requestOutbox: import("./table.ts").TableOf<"requestOutbox", {
|
|
325
|
+
readonly columns: {
|
|
326
|
+
readonly source: z.ZodString;
|
|
327
|
+
readonly requestId: z.ZodString;
|
|
328
|
+
readonly subject: z.ZodString;
|
|
329
|
+
readonly text: z.ZodString;
|
|
330
|
+
readonly replyTo: z.ZodString;
|
|
331
|
+
readonly status: z.ZodEnum<{
|
|
332
|
+
failed: "failed";
|
|
333
|
+
pending: "pending";
|
|
334
|
+
sent: "sent";
|
|
335
|
+
sending: "sending";
|
|
336
|
+
}>;
|
|
337
|
+
readonly attempts: z.ZodNumber;
|
|
338
|
+
readonly nextAt: z.ZodNullable<z.ZodString>;
|
|
339
|
+
readonly sentAt: z.ZodNullable<z.ZodString>;
|
|
340
|
+
readonly claimedAt: z.ZodNullable<z.ZodString>;
|
|
341
|
+
};
|
|
342
|
+
readonly access: {
|
|
343
|
+
readonly read: "server";
|
|
344
|
+
readonly create: "server";
|
|
345
|
+
readonly update: "server";
|
|
346
|
+
readonly delete: "server";
|
|
347
|
+
};
|
|
348
|
+
readonly writes: "confirmed";
|
|
349
|
+
}>;
|
|
350
|
+
requestOperations: import("./table.ts").TableOf<"requestOperations", {
|
|
351
|
+
readonly columns: {
|
|
352
|
+
readonly result: z.ZodString;
|
|
353
|
+
};
|
|
354
|
+
readonly access: {
|
|
355
|
+
readonly read: "server";
|
|
356
|
+
readonly create: "server";
|
|
357
|
+
readonly update: "server";
|
|
358
|
+
readonly delete: "server";
|
|
359
|
+
};
|
|
360
|
+
readonly writes: "confirmed";
|
|
361
|
+
}>;
|
|
284
362
|
bookingRules: import("./table.ts").TableOf<"bookingRules", {
|
|
285
363
|
readonly columns: {
|
|
286
364
|
readonly windows: z.ZodString;
|
package/dist/cli/app.js
CHANGED
|
@@ -22,6 +22,7 @@ import { readStarter, starterContentOf, withStarter } from "./starter.js";
|
|
|
22
22
|
import { addLiveMedia, login, publish, pull, restore, versions as listVersions } from "./publishing.js";
|
|
23
23
|
import { CHANGE_SET_ROWS } from "../publishWire.js";
|
|
24
24
|
import { DATA_USAGE, data } from "./data.js";
|
|
25
|
+
import { REQUESTS_USAGE, requests } from "./requests.js";
|
|
25
26
|
import { withStartingValues } from "./startingValues.js";
|
|
26
27
|
import { toolBin } from "./tools.js";
|
|
27
28
|
import { isNewer, latestPattern, newerPattern, updateNote } from "./updates.js";
|
|
@@ -93,7 +94,8 @@ const VALUE_FLAGS = new Set([
|
|
|
93
94
|
'--row',
|
|
94
95
|
'--set',
|
|
95
96
|
'--file',
|
|
96
|
-
'--asked'
|
|
97
|
+
'--asked',
|
|
98
|
+
'--text'
|
|
97
99
|
]);
|
|
98
100
|
const PHOTO_TYPES = {
|
|
99
101
|
'.jpg': 'image/jpeg',
|
|
@@ -379,6 +381,19 @@ collected changes. Up to ${CHANGE_SET_ROWS} changes per request. For this comput
|
|
|
379
381
|
argumentCount: 3,
|
|
380
382
|
flags: ['--row', '--set', '--file', '--asked']
|
|
381
383
|
},
|
|
384
|
+
requests: {
|
|
385
|
+
summary: 'Read the live site’s requests and write replies for the owner (app requests --help)',
|
|
386
|
+
details: `${REQUESTS_USAGE}
|
|
387
|
+
Requests are what visitors send from the live site through a request source: bookings, and each
|
|
388
|
+
inquiry() form. The owner reads and answers them on onpattern.app (Requests), and gets an email for
|
|
389
|
+
each one. With no command this lists them as source/id; show prints one. draft writes a reply for the
|
|
390
|
+
owner to read on onpattern.app: it waits there with --asked (their request, in their words) beside
|
|
391
|
+
it, and nothing reaches the customer until the owner presses Send there. Accepting or declining a
|
|
392
|
+
booking, and marking a request done, are the owner's to do on onpattern.app.`,
|
|
393
|
+
run: (_argument, options, rest) => requests(publishingIo(), ROOT, rest, options),
|
|
394
|
+
argumentCount: 2,
|
|
395
|
+
flags: ['--text', '--file', '--asked']
|
|
396
|
+
},
|
|
382
397
|
update: {
|
|
383
398
|
summary: 'Bring this project to the newest Pattern',
|
|
384
399
|
details: `Installs the newest @trypattern/sdk from npm into this project (pnpm add, exact version).
|
|
@@ -1617,7 +1632,7 @@ async function update() {
|
|
|
1617
1632
|
return {
|
|
1618
1633
|
code: 0,
|
|
1619
1634
|
data: { current, latest, updated: true },
|
|
1620
|
-
text: `✓ Updated Pattern ${current} → ${latest}\n next run app check, then app publish --message 'Updated Pattern' (straight away if nothing else is in progress) so the live site gets it`
|
|
1635
|
+
text: `✓ Updated Pattern ${current} → ${latest}\n next run app migration pattern_${latest.replaceAll('.', '_')} (it writes nothing when Pattern's own tables didn't change), then app check, then app publish --message 'Updated Pattern' (straight away if nothing else is in progress) so the live site gets it`
|
|
1621
1636
|
};
|
|
1622
1637
|
}
|
|
1623
1638
|
async function setup() {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Outcome, PublishingIo } from './publishing.ts';
|
|
2
|
+
export declare const REQUESTS_USAGE = "app requests\napp requests show <source/id>\napp requests draft <source/id> --text \"<the reply>\" --asked \"<the owner's request, in their words>\"\napp requests draft <source/id> --file reply.txt --asked \"\u2026\"";
|
|
3
|
+
type RequestOptions = ReadonlyMap<string, string | true>;
|
|
4
|
+
export declare function requests(io: PublishingIo, root: string, [action, key]: readonly (string | undefined)[], options: RequestOptions): Promise<Outcome>;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { DASHBOARD_URL, requestDraft, requestList } from "../publishWire.js";
|
|
4
|
+
import { readProjectFile } from "../vite/projectFile.js";
|
|
5
|
+
import { callJson, failure, isOutcome, sayHost, signedIn } from "./publishing.js";
|
|
6
|
+
export const REQUESTS_USAGE = `app requests
|
|
7
|
+
app requests show <source/id>
|
|
8
|
+
app requests draft <source/id> --text "<the reply>" --asked "<the owner's request, in their words>"
|
|
9
|
+
app requests draft <source/id> --file reply.txt --asked "…"`;
|
|
10
|
+
function refusal(text, code = 1) {
|
|
11
|
+
return { code, data: { error: text }, text: `✗ ${text}` };
|
|
12
|
+
}
|
|
13
|
+
function valueOf(options, flag) {
|
|
14
|
+
const value = options.get(flag);
|
|
15
|
+
return typeof value === 'string' ? value : undefined;
|
|
16
|
+
}
|
|
17
|
+
function lineOf(entry) {
|
|
18
|
+
const when = entry.when === null ? '' : ` ${entry.when}`;
|
|
19
|
+
return ` ${entry.source}/${entry.id} ${entry.status} ${entry.name} <${entry.email}> ${entry.label}${entry.summary === '' ? '' : ` · ${entry.summary}`}${when}`;
|
|
20
|
+
}
|
|
21
|
+
function detailOf(entry) {
|
|
22
|
+
return [
|
|
23
|
+
`${entry.title} from ${entry.name} <${entry.email}> (${entry.status})`,
|
|
24
|
+
...entry.fields.map((field) => ` ${field.label}: ${field.value}`),
|
|
25
|
+
...entry.contact.map((field) => ` ${field.label}: ${field.value}`),
|
|
26
|
+
...(entry.message === '' ? [] : ['', entry.message]),
|
|
27
|
+
...entry.messages.map((message) => ` ${message.kind} (${message.status}): ${message.text}`),
|
|
28
|
+
...(entry.draft === null
|
|
29
|
+
? []
|
|
30
|
+
: [` draft waiting for the owner: ${entry.draft.text}`])
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
async function listed(io, name) {
|
|
34
|
+
return signedIn(io, 'app requests', () => callJson(`/api/apps/${name}/requests`, requestList));
|
|
35
|
+
}
|
|
36
|
+
function textGiven(root, options) {
|
|
37
|
+
const text = valueOf(options, '--text');
|
|
38
|
+
if (text !== undefined)
|
|
39
|
+
return text;
|
|
40
|
+
const file = valueOf(options, '--file');
|
|
41
|
+
if (file === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
const path = resolve(root, file);
|
|
44
|
+
if (!existsSync(path))
|
|
45
|
+
throw new Error(`There is no file at ${file}`);
|
|
46
|
+
return readFileSync(path, 'utf8');
|
|
47
|
+
}
|
|
48
|
+
export async function requests(io, root, [action, key], options) {
|
|
49
|
+
try {
|
|
50
|
+
const { name } = readProjectFile(root);
|
|
51
|
+
sayHost(io);
|
|
52
|
+
const list = await listed(io, name);
|
|
53
|
+
if (isOutcome(list))
|
|
54
|
+
return list;
|
|
55
|
+
if (list.status === 'outdated') {
|
|
56
|
+
return refusal('Publish once with the latest Pattern so its requests reach onpattern.app');
|
|
57
|
+
}
|
|
58
|
+
if (action === undefined) {
|
|
59
|
+
const open = list.requests.filter((entry) => entry.needsAnswer);
|
|
60
|
+
return {
|
|
61
|
+
code: 0,
|
|
62
|
+
data: { requests: list.requests },
|
|
63
|
+
text: [
|
|
64
|
+
`${open.length} of ${list.requests.length} requests on ${name} need an answer`,
|
|
65
|
+
...list.requests.map(lineOf)
|
|
66
|
+
].join('\n')
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const [source = '', id = ''] = (key ?? '').split('/');
|
|
70
|
+
const entry = list.requests.find((item) => item.source === source && item.id === id);
|
|
71
|
+
if (entry === undefined) {
|
|
72
|
+
return refusal(`Say which request as source/id, as app requests lists it\n${REQUESTS_USAGE}`, 2);
|
|
73
|
+
}
|
|
74
|
+
if (action === 'show') {
|
|
75
|
+
return {
|
|
76
|
+
code: 0,
|
|
77
|
+
data: { request: entry },
|
|
78
|
+
text: detailOf(entry).join('\n')
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (action !== 'draft') {
|
|
82
|
+
return refusal(`Unknown requests command: ${action}\n${REQUESTS_USAGE}`, 2);
|
|
83
|
+
}
|
|
84
|
+
if (!entry.actions.includes('reply')) {
|
|
85
|
+
return refusal(`${entry.title} can’t take a written reply: the owner answers it with ${entry.actions.join(' or ') || 'nothing now'} on onpattern.app`);
|
|
86
|
+
}
|
|
87
|
+
const text = textGiven(root, options);
|
|
88
|
+
const asked = valueOf(options, '--asked');
|
|
89
|
+
if (text === undefined || text.trim() === '') {
|
|
90
|
+
return refusal('Give the reply with --text "…" or --file reply.txt', 2);
|
|
91
|
+
}
|
|
92
|
+
if (asked === undefined || asked.trim() === '') {
|
|
93
|
+
return refusal('Say what the owner asked for with --asked "<their words>". They see it beside Send.', 2);
|
|
94
|
+
}
|
|
95
|
+
const draft = await signedIn(io, 'app requests draft', () => callJson(`/api/apps/${name}/requests/${source}/${id}/drafts`, requestDraft, {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
headers: { 'content-type': 'application/json' },
|
|
98
|
+
body: JSON.stringify({ text: text.trim(), asked: asked.trim() })
|
|
99
|
+
}));
|
|
100
|
+
if (isOutcome(draft))
|
|
101
|
+
return draft;
|
|
102
|
+
const link = `${DASHBOARD_URL}/sites/${name}/requests`;
|
|
103
|
+
return {
|
|
104
|
+
code: 0,
|
|
105
|
+
data: { draft },
|
|
106
|
+
text: [
|
|
107
|
+
`✓ Your reply to ${entry.name} is waiting for the owner. Nothing was sent.`,
|
|
108
|
+
` next tell them: "I've written a reply to ${entry.name}. Open ${link}, read it, change anything you like, then press Send."`
|
|
109
|
+
].join('\n')
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
return failure(error);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -15,12 +15,22 @@ export function AppFrame({ look = 'shell', workspace, links, workspaceMenu = [],
|
|
|
15
15
|
: [
|
|
16
16
|
...workspaceMenu,
|
|
17
17
|
...(workspaceMenu.length > 0 ? ['separator'] : []),
|
|
18
|
-
{
|
|
18
|
+
{
|
|
19
|
+
id: 'signOut',
|
|
20
|
+
label: 'Log out',
|
|
21
|
+
icon: 'logOut',
|
|
22
|
+
onSelect: account.signOut
|
|
23
|
+
}
|
|
19
24
|
];
|
|
20
25
|
const accountEntries = [
|
|
21
26
|
...accountMenu,
|
|
22
27
|
...(accountMenu.length > 0 ? ['separator'] : []),
|
|
23
|
-
{
|
|
28
|
+
{
|
|
29
|
+
id: 'signOut',
|
|
30
|
+
label: 'Log out',
|
|
31
|
+
icon: 'logOut',
|
|
32
|
+
onSelect: account.signOut
|
|
33
|
+
}
|
|
24
34
|
];
|
|
25
35
|
const search = onSearch === undefined
|
|
26
36
|
? undefined
|
|
@@ -2,11 +2,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Menu as BaseMenu } from '@base-ui/react/menu';
|
|
3
3
|
import { useNavigate } from 'react-router';
|
|
4
4
|
import * as stylex from '@stylexjs/stylex';
|
|
5
|
-
import { colors, space } from "../theme/tokens.stylex.js";
|
|
5
|
+
import { colors, iconSizes, radii, space } from "../theme/tokens.stylex.js";
|
|
6
6
|
import { Divider } from "./Divider.js";
|
|
7
7
|
import { Icon } from "./Icon.js";
|
|
8
8
|
import { interact } from "./interaction.js";
|
|
9
9
|
import { PopupItem } from "./PopupItem.js";
|
|
10
|
+
import { Text } from "./Text.js";
|
|
10
11
|
export function MenuItems({ items, primitive }) {
|
|
11
12
|
const navigate = useNavigate();
|
|
12
13
|
return items.map((entry, index) => entry === 'separator' ? (_jsx(Divider, { style: styles.separator }, `separator-${index}`)) : (_jsxs(BaseMenu.Item, { onClick: () => {
|
|
@@ -15,7 +16,7 @@ export function MenuItems({ items, primitive }) {
|
|
|
15
16
|
entry.onSelect();
|
|
16
17
|
else
|
|
17
18
|
void navigate(entry.to);
|
|
18
|
-
}, render: (props, state) => (_jsx(PopupItem, { ...props, kind: "menu", isHighlighted: state.highlighted, isDanger: entry.isDanger ?? false })), children: [entry.icon === undefined ? null : (_jsx("span", { ...stylex.props(styles.icon, entry.isDanger && styles.dangerIcon), children: _jsx(Icon, { name: entry.icon }) })), entry.label] }, entry.id)));
|
|
19
|
+
}, render: (props, state) => (_jsx(PopupItem, { ...props, kind: "menu", isHighlighted: state.highlighted, isDanger: entry.isDanger ?? false })), children: [entry.mark === undefined ? null : (_jsx("span", { ...stylex.props(styles.mark), children: _jsx(Text, { variant: "caption", children: entry.mark.charAt(0) }) })), entry.icon === undefined ? null : (_jsx("span", { ...stylex.props(styles.icon, entry.isDanger && styles.dangerIcon), children: _jsx(Icon, { name: entry.icon }) })), entry.label] }, entry.id)));
|
|
19
20
|
}
|
|
20
21
|
const styles = stylex.create({
|
|
21
22
|
separator: {
|
|
@@ -26,6 +27,16 @@ const styles = stylex.create({
|
|
|
26
27
|
display: 'flex',
|
|
27
28
|
color: colors.secondaryForeground
|
|
28
29
|
},
|
|
30
|
+
mark: {
|
|
31
|
+
display: 'grid',
|
|
32
|
+
flexShrink: 0,
|
|
33
|
+
placeItems: 'center',
|
|
34
|
+
width: iconSizes.md,
|
|
35
|
+
height: iconSizes.md,
|
|
36
|
+
color: colors.onBrandSolidForeground,
|
|
37
|
+
backgroundColor: colors.brandSolid,
|
|
38
|
+
borderRadius: radii.sm
|
|
39
|
+
},
|
|
29
40
|
dangerIcon: {
|
|
30
41
|
color: 'inherit'
|
|
31
42
|
}
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Radio } from '@base-ui/react/radio';
|
|
3
3
|
import { RadioGroup } from '@base-ui/react/radio-group';
|
|
4
4
|
import * as stylex from '@stylexjs/stylex';
|
|
5
|
-
import { borders, colors, fontWeights, motion, radii } from "../theme/tokens.stylex.js";
|
|
5
|
+
import { borders, colors, fontSizes, fontWeights, lineHeights, motion, radii, space } from "../theme/tokens.stylex.js";
|
|
6
6
|
import { interact } from "./interaction.js";
|
|
7
7
|
import { buttonCorners, controlSizes, focusRing } from "./controlLook.js";
|
|
8
8
|
export function SegmentedControl({ label, value, options, size = 'md', disabled = false, onChange, style }) {
|
|
@@ -10,7 +10,7 @@ export function SegmentedControl({ label, value, options, size = 'md', disabled
|
|
|
10
10
|
return (_jsxs(RadioGroup, { "aria-label": label, value: value, disabled: disabled, onValueChange: (next) => {
|
|
11
11
|
interact({ primitive: 'SegmentedControl', event: 'change', label });
|
|
12
12
|
onChange(next);
|
|
13
|
-
}, className: stylex.props(styles.group, style).className, children: [_jsx("span", { ...stylex.props(styles.indicator(index, options.length)) }), options.map((option) => (
|
|
13
|
+
}, className: stylex.props(styles.group, style).className, children: [_jsx("span", { ...stylex.props(styles.indicator(index, options.length)) }), options.map((option) => (_jsxs(Radio.Root, { value: option.value, className: (state) => stylex.props(styles.option, controlSizes[size], buttonCorners[size], !disabled && styles.hoverable, state.checked && styles.optionChecked, disabled && styles.disabled, focusRing.visible).className, children: [option.label, option.count === undefined ? null : (_jsx("span", { ...stylex.props(styles.count), children: option.count }))] }, option.value)))] }));
|
|
14
14
|
}
|
|
15
15
|
const styles = stylex.create({
|
|
16
16
|
group: {
|
|
@@ -44,6 +44,7 @@ const styles = stylex.create({
|
|
|
44
44
|
position: 'relative',
|
|
45
45
|
zIndex: 1,
|
|
46
46
|
display: 'inline-flex',
|
|
47
|
+
gap: `calc(${space.xs} + ${space.xxs})`,
|
|
47
48
|
alignItems: 'center',
|
|
48
49
|
justifyContent: 'center',
|
|
49
50
|
color: colors.secondaryForeground,
|
|
@@ -64,5 +65,11 @@ const styles = stylex.create({
|
|
|
64
65
|
},
|
|
65
66
|
disabled: {
|
|
66
67
|
color: colors.disabledForeground
|
|
68
|
+
},
|
|
69
|
+
count: {
|
|
70
|
+
fontSize: fontSizes.captionLg,
|
|
71
|
+
fontWeight: fontWeights.normal,
|
|
72
|
+
lineHeight: lineHeights.caption,
|
|
73
|
+
color: colors.tertiaryForeground
|
|
67
74
|
}
|
|
68
75
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as stylex from '@stylexjs/stylex';
|
|
3
3
|
import { NavLink } from 'react-router';
|
|
4
|
-
import { breakpoints, colors, controls, iconSizes, layers, motion, radii, shadows, space, widths } from "../theme/tokens.stylex.js";
|
|
4
|
+
import { breakpoints, colors, controls, fontSizes, fontWeights, iconSizes, layers, lineHeights, motion, radii, shadows, space, widths } from "../theme/tokens.stylex.js";
|
|
5
5
|
import { Badge } from "./Badge.js";
|
|
6
6
|
import { focusRing } from "./controlLook.js";
|
|
7
7
|
import { Button } from "./Button.js";
|
|
@@ -16,7 +16,8 @@ export function Sidebar({ workspace, workspaceMenu, links, isToggled, isAnimatin
|
|
|
16
16
|
onFollowLink();
|
|
17
17
|
};
|
|
18
18
|
return (_jsxs("nav", { ...stylex.props(styles.sidebar, isToggled && styles.sidebarToggled, isAnimating && styles.sidebarAnimating), children: [_jsx("div", { ...stylex.props(styles.header), children: _jsx(Menu, { trigger: _jsxs(_Fragment, { children: [_jsx("span", { ...stylex.props(styles.logo), children: _jsx(Text, { variant: "caption", children: workspace.charAt(0) }) }), _jsx(Text, { variant: "label", isTruncated: true, children: workspace }), _jsx(Icon, { name: "chevronDown" })] }), items: workspaceMenu }) }), onSearch === undefined ? null : (_jsx(Button, { variant: "secondary", icon: "search", shortcut: ['mod', 'k'], isFullWidth: true, align: "start", onClick: onSearch, children: "Search" })), _jsx("div", { ...stylex.props(styles.body), children: _jsx(ScrollArea, { children: _jsxs("div", { ...stylex.props(styles.group), children: [_jsx("span", { ...stylex.props(styles.groupLabel), children: _jsx(Text, { variant: "label", tone: "tertiary", children: "Workspace" }) }), links.map((item) => (_jsxs(NavLink, { onClick: () => followLink(item), to: item.to, end: item.to === '/' ||
|
|
19
|
-
links.some((other) => String(other.to).startsWith(`${String(item.to)}/`)), className: ({ isActive }) => stylex.props(styles.item, isActive && styles.itemCurrent, focusRing.visible, focusRing.inside).className ?? '', children: [_jsx(Icon, { name: item.icon }), _jsx(Text, { variant: "label", isTruncated: true, style: styles.itemLabel, children: item.label }), typeof item.count !== 'number' ? null :
|
|
19
|
+
links.some((other) => String(other.to).startsWith(`${String(item.to)}/`)), className: ({ isActive }) => stylex.props(styles.item, isActive && styles.itemCurrent, focusRing.visible, focusRing.inside).className ?? '', children: [_jsx(Icon, { name: item.icon }), _jsx(Text, { variant: "label", isTruncated: true, style: styles.itemLabel, children: item.label }), typeof item.count !== 'number' ? null : item.countTone ===
|
|
20
|
+
'brand' ? (_jsx("span", { ...stylex.props(styles.count), children: item.count })) : (_jsx(Badge, { variant: "transparent", children: item.count }))] }, item.to)))] }) }) })] }));
|
|
20
21
|
}
|
|
21
22
|
const styles = stylex.create({
|
|
22
23
|
sidebar: {
|
|
@@ -132,6 +133,21 @@ const styles = stylex.create({
|
|
|
132
133
|
color: colors.primaryForeground,
|
|
133
134
|
backgroundColor: colors.transparentHover
|
|
134
135
|
},
|
|
136
|
+
count: {
|
|
137
|
+
display: 'inline-flex',
|
|
138
|
+
flexShrink: 0,
|
|
139
|
+
alignItems: 'center',
|
|
140
|
+
justifyContent: 'center',
|
|
141
|
+
minWidth: `calc(${space.lg} + ${space.xxs})`,
|
|
142
|
+
height: `calc(${space.lg} + ${space.xxs})`,
|
|
143
|
+
paddingInline: `calc(${space.xs} + ${space.xxs} / 2)`,
|
|
144
|
+
fontSize: fontSizes.captionSm,
|
|
145
|
+
fontWeight: fontWeights.medium,
|
|
146
|
+
lineHeight: lineHeights.caption,
|
|
147
|
+
color: colors.onBrandSolidForeground,
|
|
148
|
+
backgroundColor: colors.brandSolid,
|
|
149
|
+
borderRadius: radii.full
|
|
150
|
+
},
|
|
135
151
|
itemLabel: {
|
|
136
152
|
flexGrow: 1
|
|
137
153
|
}
|
|
@@ -2,8 +2,9 @@ import type { ReactNode } from 'react';
|
|
|
2
2
|
type TableRowProps = {
|
|
3
3
|
label: string;
|
|
4
4
|
isCurrent?: boolean;
|
|
5
|
+
size?: 'md' | 'lg';
|
|
5
6
|
onClick: () => void;
|
|
6
7
|
children: ReactNode;
|
|
7
8
|
};
|
|
8
|
-
export declare function TableRow({ label, isCurrent, onClick, children }: TableRowProps): import("react").JSX.Element;
|
|
9
|
+
export declare function TableRow({ label, isCurrent, size, onClick, children }: TableRowProps): import("react").JSX.Element;
|
|
9
10
|
export {};
|
|
@@ -4,11 +4,11 @@ import * as stylex from '@stylexjs/stylex';
|
|
|
4
4
|
import { borders, colors, space } from "../theme/tokens.stylex.js";
|
|
5
5
|
import { focusRing } from "./controlLook.js";
|
|
6
6
|
import { interact } from "./interaction.js";
|
|
7
|
-
export function TableRow({ label, isCurrent = false, onClick, children }) {
|
|
7
|
+
export function TableRow({ label, isCurrent = false, size = 'md', onClick, children }) {
|
|
8
8
|
return (_jsx(BaseButton, { "aria-label": label, "aria-pressed": isCurrent, onClick: () => {
|
|
9
9
|
interact({ primitive: 'TableRow', event: 'click', label });
|
|
10
10
|
onClick();
|
|
11
|
-
}, className: stylex.props(styles.row, isCurrent && styles.current, focusRing.visible, focusRing.inside).className, children: children }));
|
|
11
|
+
}, className: stylex.props(styles.row, size === 'lg' && styles.large, isCurrent && styles.current, focusRing.visible, focusRing.inside).className, children: children }));
|
|
12
12
|
}
|
|
13
13
|
const styles = stylex.create({
|
|
14
14
|
row: {
|
|
@@ -30,6 +30,9 @@ const styles = stylex.create({
|
|
|
30
30
|
borderBottomStyle: 'solid',
|
|
31
31
|
borderBottomWidth: borders.thin
|
|
32
32
|
},
|
|
33
|
+
large: {
|
|
34
|
+
height: `calc(${space.xxxl} + ${space.sm})`
|
|
35
|
+
},
|
|
33
36
|
current: {
|
|
34
37
|
backgroundColor: colors.transparentHover
|
|
35
38
|
}
|