@gaia-ai/addon-gaia-ui 0.9.2 → 0.10.0
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/src/Component/footer-actions/index.d.ts +58 -0
- package/dist/src/Component/footer-actions/index.js +111 -0
- package/dist/src/Component/list-chrome/index.d.ts +19 -0
- package/dist/src/Component/list-chrome/index.js +61 -0
- package/dist/src/Component/markdown/index.d.ts +68 -0
- package/dist/src/Component/markdown/index.js +133 -0
- package/dist/src/Component/section-list/index.d.ts +115 -23
- package/dist/src/Component/section-list/index.js +120 -34
- package/dist/src/Component/top-tabs/index.d.ts +65 -6
- package/dist/src/Component/top-tabs/index.js +29 -6
- package/dist/src/Entity/collection.js +1 -1
- package/dist/src/Entity/conductor/Screen/conductors.d.ts +26 -41
- package/dist/src/Entity/conductor/Screen/conductors.js +65 -123
- package/dist/src/Entity/project/Screen/project-picker.d.ts +20 -31
- package/dist/src/Entity/project/Screen/project-picker.js +48 -58
- package/dist/src/Entity/project/Screen/projects-screen.d.ts +4 -2
- package/dist/src/Entity/project/Screen/projects-screen.js +11 -8
- package/dist/src/Entity/run/Component/run-detail/index.js +15 -7
- package/dist/src/Entity/run/Data/query.d.ts +10 -0
- package/dist/src/Entity/run/Data/query.js +16 -1
- package/dist/src/Entity/run/Data/runs.d.ts +2 -0
- package/dist/src/Entity/run/Data/runs.js +19 -1
- package/dist/src/Entity/run/Screen/run-detail.d.ts +16 -23
- package/dist/src/Entity/run/Screen/run-detail.js +27 -34
- package/dist/src/Entity/run/Screen/runs.d.ts +7 -3
- package/dist/src/Entity/run/Screen/runs.js +31 -48
- package/dist/src/Entity/run/Screen/user-runs.d.ts +79 -0
- package/dist/src/Entity/run/Screen/user-runs.js +98 -0
- package/dist/src/Entity/statistics/Data/statistics.d.ts +31 -6
- package/dist/src/Entity/statistics/Data/statistics.js +39 -6
- package/dist/src/Entity/statistics/Screen/dashboard.d.ts +10 -2
- package/dist/src/Entity/statistics/Screen/dashboard.js +111 -34
- package/dist/src/Entity/ticket/Component/comment-item/index.d.ts +14 -1
- package/dist/src/Entity/ticket/Component/comment-item/index.js +15 -1
- package/dist/src/Entity/ticket/Component/sections/index.js +16 -1
- package/dist/src/Entity/ticket/Component/ticket-detail/index.d.ts +102 -13
- package/dist/src/Entity/ticket/Component/ticket-detail/index.js +440 -72
- package/dist/src/Entity/ticket/Data/detail.d.ts +14 -0
- package/dist/src/Entity/ticket/Data/detail.js +15 -1
- package/dist/src/Entity/ticket/Data/query.d.ts +7 -0
- package/dist/src/Entity/ticket/Data/query.js +18 -2
- package/dist/src/Entity/ticket/Data/tickets.d.ts +24 -0
- package/dist/src/Entity/ticket/Data/tickets.js +41 -1
- package/dist/src/Entity/ticket/Screen/create-ticket-form.d.ts +10 -4
- package/dist/src/Entity/ticket/Screen/create-ticket-form.js +37 -16
- package/dist/src/Entity/ticket/Screen/edit-assignment-form.d.ts +6 -4
- package/dist/src/Entity/ticket/Screen/edit-assignment-form.js +16 -15
- package/dist/src/Entity/ticket/Screen/parent-filter-form.d.ts +6 -4
- package/dist/src/Entity/ticket/Screen/parent-filter-form.js +14 -11
- package/dist/src/Entity/ticket/Screen/ticket-agents.d.ts +5 -2
- package/dist/src/Entity/ticket/Screen/ticket-agents.js +14 -14
- package/dist/src/Entity/ticket/Screen/ticket-detail.d.ts +89 -4
- package/dist/src/Entity/ticket/Screen/ticket-detail.js +295 -74
- package/dist/src/Entity/ticket/Screen/tickets.d.ts +66 -3
- package/dist/src/Entity/ticket/Screen/tickets.js +140 -76
- package/dist/src/Entity/ticket/Screen/user-tickets.d.ts +91 -0
- package/dist/src/Entity/ticket/Screen/user-tickets.js +94 -0
- package/dist/src/Entity/ticket/Screen/who-filter-form.d.ts +6 -4
- package/dist/src/Entity/ticket/Screen/who-filter-form.js +14 -11
- package/dist/src/Entity/user/Data/users.d.ts +49 -0
- package/dist/src/Entity/user/Data/users.js +46 -0
- package/dist/src/Entity/user/Screen/user-overview.d.ts +33 -0
- package/dist/src/Entity/user/Screen/user-overview.js +159 -0
- package/dist/src/Entity/user/Screen/users.d.ts +39 -0
- package/dist/src/Entity/user/Screen/users.js +136 -0
- package/dist/src/Extension/contract.d.ts +58 -0
- package/dist/src/Extension/contract.js +1 -0
- package/dist/src/Extension/load.d.ts +10 -0
- package/dist/src/Extension/load.js +21 -0
- package/dist/src/Extension/store.d.ts +11 -0
- package/dist/src/Extension/store.js +44 -0
- package/dist/src/Kernel/chrome.d.ts +49 -12
- package/dist/src/Kernel/chrome.js +150 -57
- package/dist/src/Kernel/key-input.js +1 -1
- package/dist/src/Kernel/mount.d.ts +1 -1
- package/dist/src/Kernel/mount.js +1 -1
- package/dist/src/Kernel/regions.d.ts +64 -0
- package/dist/src/Kernel/regions.js +328 -0
- package/dist/src/Kernel/renderer.d.ts +25 -0
- package/dist/src/Kernel/renderer.js +142 -0
- package/dist/src/Kernel/splash.d.ts +27 -0
- package/dist/src/Kernel/splash.js +72 -0
- package/dist/src/Kernel/tui-kernel.js +139 -434
- package/dist/src/Routing/context.d.ts +5 -0
- package/dist/src/Routing/context.js +6 -0
- package/dist/src/Routing/registry.js +4 -2
- package/dist/src/Routing/router.js +5 -1
- package/dist/src/Routing/routes.js +8 -0
- package/dist/src/Screen/async-modal-screen.d.ts +26 -0
- package/dist/src/Screen/async-modal-screen.js +44 -0
- package/dist/src/Screen/async-screen.d.ts +32 -40
- package/dist/src/Screen/async-screen.js +45 -41
- package/dist/src/Screen/full-screen.d.ts +1 -0
- package/dist/src/Screen/full-screen.js +3 -0
- package/dist/src/Screen/list-screen.d.ts +7 -1
- package/dist/src/Screen/list-screen.js +17 -6
- package/dist/src/Screen/modal-screen.d.ts +6 -0
- package/dist/src/Screen/modal-screen.js +2 -0
- package/dist/src/Screen/screen.d.ts +42 -6
- package/dist/src/Screen/screen.js +2 -2
- package/dist/src/Widget/textarea.js +10 -14
- package/dist/src/Widget/types.d.ts +1 -1
- package/dist/src/launcher.d.ts +6 -0
- package/dist/src/launcher.js +2 -0
- package/dist/src/lib/format.js +1 -1
- package/dist/src/lib/markdown.d.ts +53 -0
- package/dist/src/lib/markdown.js +522 -0
- package/dist/src/lib/refresh-signal.d.ts +56 -0
- package/dist/src/lib/refresh-signal.js +85 -0
- package/dist/src/lib/theme.d.ts +36 -0
- package/dist/src/lib/theme.js +40 -0
- package/dist/src/lib/version.d.ts +5 -0
- package/dist/src/lib/version.js +46 -0
- package/dist/src/lib/vnode.d.ts +86 -63
- package/dist/src/lib/vnode.js +112 -85
- package/dist/src/types.d.ts +32 -0
- package/dist/src/ui.d.ts +2 -0
- package/dist/src/ui.js +1 -0
- package/package.json +4 -3
|
@@ -24,5 +24,19 @@ import type { DropshClient, TicketDetailDocument } from '../../../types.js';
|
|
|
24
24
|
* this include list must stay a superset of the list read's — dropping either
|
|
25
25
|
* `parent_id` or `labels` degrades a rendered row to a raw uuid without failing a
|
|
26
26
|
* fixture-driven test.
|
|
27
|
+
*
|
|
28
|
+
* GAIA-369 D5 added `blocked_by` for the Related tab's Blocked by rows, and the
|
|
29
|
+
* measurement is the reason it can be identities at all: live against the develop
|
|
30
|
+
* control plane under `session`, the `blocked_by` LINKAGE data is in the document
|
|
31
|
+
* with no include at all (`{"data": []}` on GAIA-369), and `include=blocked_by`
|
|
32
|
+
* sideloads `gaia_ticket--gaia_ticket` resources carrying `identifier` (measured
|
|
33
|
+
* on GAIA-23, GAIA-90 and GAIA-92 → GAIA-19, GAIA-97, GAIA-90). So the section
|
|
34
|
+
* renders rows without a second request and without a second read profile.
|
|
35
|
+
*
|
|
36
|
+
* That split is also the failure mode to keep in mind: because the linkage
|
|
37
|
+
* arrives regardless, dropping this include does NOT empty the section — it
|
|
38
|
+
* degrades every row to a raw uuid, silently. Hence the include set is pinned as
|
|
39
|
+
* a string in `tests/Entity/ticket/Data/detail.test.ts` rather than inferred from
|
|
40
|
+
* a rendered section, and this list must stay a superset of the list read's.
|
|
27
41
|
*/
|
|
28
42
|
export declare function loadTicketDetail(client: DropshClient, ticketId: string): Promise<TicketDetailDocument>;
|
|
@@ -23,7 +23,21 @@
|
|
|
23
23
|
* this include list must stay a superset of the list read's — dropping either
|
|
24
24
|
* `parent_id` or `labels` degrades a rendered row to a raw uuid without failing a
|
|
25
25
|
* fixture-driven test.
|
|
26
|
+
*
|
|
27
|
+
* GAIA-369 D5 added `blocked_by` for the Related tab's Blocked by rows, and the
|
|
28
|
+
* measurement is the reason it can be identities at all: live against the develop
|
|
29
|
+
* control plane under `session`, the `blocked_by` LINKAGE data is in the document
|
|
30
|
+
* with no include at all (`{"data": []}` on GAIA-369), and `include=blocked_by`
|
|
31
|
+
* sideloads `gaia_ticket--gaia_ticket` resources carrying `identifier` (measured
|
|
32
|
+
* on GAIA-23, GAIA-90 and GAIA-92 → GAIA-19, GAIA-97, GAIA-90). So the section
|
|
33
|
+
* renders rows without a second request and without a second read profile.
|
|
34
|
+
*
|
|
35
|
+
* That split is also the failure mode to keep in mind: because the linkage
|
|
36
|
+
* arrives regardless, dropping this include does NOT empty the section — it
|
|
37
|
+
* degrades every row to a raw uuid, silently. Hence the include set is pinned as
|
|
38
|
+
* a string in `tests/Entity/ticket/Data/detail.test.ts` rather than inferred from
|
|
39
|
+
* a rendered section, and this list must stay a superset of the list read's.
|
|
26
40
|
*/
|
|
27
41
|
export function loadTicketDetail(client, ticketId) {
|
|
28
|
-
return client.get(`gaia_ticket/gaia_ticket/${ticketId}?include=comments,sub_tickets,runs,referenced_tickets,conductor_id,assignee_user_id,parent_id,labels`);
|
|
42
|
+
return client.get(`gaia_ticket/gaia_ticket/${ticketId}?include=comments,sub_tickets,runs,referenced_tickets,conductor_id,assignee_user_id,parent_id,labels,blocked_by`);
|
|
29
43
|
}
|
|
@@ -55,3 +55,10 @@ export interface TicketQueryOptions {
|
|
|
55
55
|
includes?: boolean;
|
|
56
56
|
}
|
|
57
57
|
export declare function buildTicketQueryParams(projectId: string, criteria: TicketCriteria, options?: TicketQueryOptions): DrupalJsonApiParams;
|
|
58
|
+
/**
|
|
59
|
+
* Build the JSON:API query for one assignee's tickets (GAIA-377 D2).
|
|
60
|
+
*
|
|
61
|
+
* Same paging, fieldset and closed-filter cycle as the project builder; the
|
|
62
|
+
* scope is `assignee_user_id.id` instead of `project_id.id`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function buildUserTicketQueryParams(userId: string, criteria: TicketCriteria, options?: TicketQueryOptions): DrupalJsonApiParams;
|
|
@@ -61,6 +61,22 @@ export function describeCriteria(criteria) {
|
|
|
61
61
|
export function buildTicketQueryParams(projectId, criteria, options = {}) {
|
|
62
62
|
const params = new DrupalJsonApiParams();
|
|
63
63
|
params.addFilter('project_id.id', projectId);
|
|
64
|
+
applyTicketCriteria(params, criteria, options);
|
|
65
|
+
return params;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build the JSON:API query for one assignee's tickets (GAIA-377 D2).
|
|
69
|
+
*
|
|
70
|
+
* Same paging, fieldset and closed-filter cycle as the project builder; the
|
|
71
|
+
* scope is `assignee_user_id.id` instead of `project_id.id`.
|
|
72
|
+
*/
|
|
73
|
+
export function buildUserTicketQueryParams(userId, criteria, options = {}) {
|
|
74
|
+
const params = new DrupalJsonApiParams();
|
|
75
|
+
params.addFilter('assignee_user_id.id', userId);
|
|
76
|
+
applyTicketCriteria(params, criteria, options);
|
|
77
|
+
return params;
|
|
78
|
+
}
|
|
79
|
+
function applyTicketCriteria(params, criteria, options) {
|
|
64
80
|
if (criteria.query) {
|
|
65
81
|
params.addGroup('search', 'OR');
|
|
66
82
|
params.addFilter('identifier', criteria.query, 'CONTAINS', 'search');
|
|
@@ -77,7 +93,8 @@ export function buildTicketQueryParams(projectId, criteria, options = {}) {
|
|
|
77
93
|
if (criteria.parent)
|
|
78
94
|
params.addFilter('parent_id.id', criteria.parent.id);
|
|
79
95
|
// Assigned to this user only; a top-level AND like parent, never part of
|
|
80
|
-
// the search OR group (GAIA-299 AC-4).
|
|
96
|
+
// the search OR group (GAIA-299 AC-4). Skipped when the builder itself is
|
|
97
|
+
// already scoped to an assignee (user ticket list).
|
|
81
98
|
if (criteria.who)
|
|
82
99
|
params.addFilter('assignee_user_id.id', criteria.who.id);
|
|
83
100
|
const { size, index } = options.page ?? { size: PAGE_SIZE, index: 0 };
|
|
@@ -96,5 +113,4 @@ export function buildTicketQueryParams(projectId, criteria, options = {}) {
|
|
|
96
113
|
// Page one carries no offset, so its query is the shortest it can be.
|
|
97
114
|
if (index > 0)
|
|
98
115
|
params.addPageOffset(index * size);
|
|
99
|
-
return params;
|
|
100
116
|
}
|
|
@@ -204,8 +204,32 @@ export declare function mapTicketDocument(document: {
|
|
|
204
204
|
* {@link Page}, whose four members and their meaning are documented there
|
|
205
205
|
* (GAIA-294). */
|
|
206
206
|
export type TicketPage = Page<TicketRow>;
|
|
207
|
+
/**
|
|
208
|
+
* How many tickets the criteria match once the `closed` criterion is DROPPED —
|
|
209
|
+
* GAIA-370.
|
|
210
|
+
*
|
|
211
|
+
* The size of the COLLECTION, never of a page: it is the server's own
|
|
212
|
+
* `meta.count` (GAIA-305), read off a one-row response, so an answer of 285 costs
|
|
213
|
+
* the same single read as an answer of 1 and cannot be a recount of whatever the
|
|
214
|
+
* screen happens to be holding. `null` when the server reports no count, which
|
|
215
|
+
* `readTotal` already treats as the explicit unknown rather than as a zero.
|
|
216
|
+
*
|
|
217
|
+
* It builds the SAME query as {@link loadFilteredTickets} — the same OR group over
|
|
218
|
+
* identifier/title, the same project/state/parent/who ANDs — with exactly one
|
|
219
|
+
* criterion removed. That is what makes the number it returns an answer to "how
|
|
220
|
+
* many is `closed` holding back" and not to some other question; a second,
|
|
221
|
+
* hand-written query here could drift from the one the rows came from, and then
|
|
222
|
+
* the count beside `No tickets.` would describe a different collection than the
|
|
223
|
+
* emptiness it explains.
|
|
224
|
+
*
|
|
225
|
+
* `includes: false` and `page[limit]=1`: no row is mapped, so no sideload is worth
|
|
226
|
+
* requesting.
|
|
227
|
+
*/
|
|
228
|
+
export declare function countTicketsIgnoringClosed(client: DropshClient, projectId: string, criteria: TicketCriteria): Promise<number | null>;
|
|
207
229
|
/** Server-side filtered/searched ticket list for a project — ONE page of it. The
|
|
208
230
|
* query sideloads `parent_id` + `conductor_id`; each row's parent/conductor is
|
|
209
231
|
* resolved from the document's `included` (AC-5). */
|
|
210
232
|
export declare function loadFilteredTickets(client: DropshClient, projectId: string, criteria: TicketCriteria, page?: number): Promise<TicketPage>;
|
|
233
|
+
/** One page of tickets assigned to a user (GAIA-377 D2). */
|
|
234
|
+
export declare function loadUserTickets(client: DropshClient, userId: string, criteria: TicketCriteria, page?: number): Promise<TicketPage>;
|
|
211
235
|
export {};
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { asString } from '../../../lib/format.js';
|
|
6
6
|
import { loadPage, } from '../../collection.js';
|
|
7
7
|
import { LIST_PAGE_SIZE } from '../../cycle.js';
|
|
8
|
-
import { buildTicketQueryParams } from './query.js';
|
|
8
|
+
import { buildTicketQueryParams, buildUserTicketQueryParams, } from './query.js';
|
|
9
9
|
/** Build the {@link IncludedIndex} from a document's `included` array. */
|
|
10
10
|
function indexIncluded(included) {
|
|
11
11
|
const index = new Map();
|
|
@@ -247,6 +247,32 @@ export function mapTicketData(item, included) {
|
|
|
247
247
|
export function mapTicketDocument(document) {
|
|
248
248
|
return mapTicketData(document.data ?? {}, indexIncluded(document.included));
|
|
249
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* How many tickets the criteria match once the `closed` criterion is DROPPED —
|
|
252
|
+
* GAIA-370.
|
|
253
|
+
*
|
|
254
|
+
* The size of the COLLECTION, never of a page: it is the server's own
|
|
255
|
+
* `meta.count` (GAIA-305), read off a one-row response, so an answer of 285 costs
|
|
256
|
+
* the same single read as an answer of 1 and cannot be a recount of whatever the
|
|
257
|
+
* screen happens to be holding. `null` when the server reports no count, which
|
|
258
|
+
* `readTotal` already treats as the explicit unknown rather than as a zero.
|
|
259
|
+
*
|
|
260
|
+
* It builds the SAME query as {@link loadFilteredTickets} — the same OR group over
|
|
261
|
+
* identifier/title, the same project/state/parent/who ANDs — with exactly one
|
|
262
|
+
* criterion removed. That is what makes the number it returns an answer to "how
|
|
263
|
+
* many is `closed` holding back" and not to some other question; a second,
|
|
264
|
+
* hand-written query here could drift from the one the rows came from, and then
|
|
265
|
+
* the count beside `No tickets.` would describe a different collection than the
|
|
266
|
+
* emptiness it explains.
|
|
267
|
+
*
|
|
268
|
+
* `includes: false` and `page[limit]=1`: no row is mapped, so no sideload is worth
|
|
269
|
+
* requesting.
|
|
270
|
+
*/
|
|
271
|
+
export async function countTicketsIgnoringClosed(client, projectId, criteria) {
|
|
272
|
+
const params = buildTicketQueryParams(projectId, { ...criteria, closed: null }, { page: { size: 1, index: 0 }, includes: false });
|
|
273
|
+
const page = await loadPage(client, 'gaia_ticket/gaia_ticket', params, () => [], 0, { offset: 0, limit: 1 });
|
|
274
|
+
return page.total;
|
|
275
|
+
}
|
|
250
276
|
/** Server-side filtered/searched ticket list for a project — ONE page of it. The
|
|
251
277
|
* query sideloads `parent_id` + `conductor_id`; each row's parent/conductor is
|
|
252
278
|
* resolved from the document's `included` (AC-5). */
|
|
@@ -266,3 +292,17 @@ export async function loadFilteredTickets(client, projectId, criteria, page = 0)
|
|
|
266
292
|
return data.map((item) => mapTicketData(item, index));
|
|
267
293
|
}, page, window);
|
|
268
294
|
}
|
|
295
|
+
/** One page of tickets assigned to a user (GAIA-377 D2). */
|
|
296
|
+
export async function loadUserTickets(client, userId, criteria, page = 0) {
|
|
297
|
+
const window = {
|
|
298
|
+
offset: page * LIST_PAGE_SIZE,
|
|
299
|
+
limit: LIST_PAGE_SIZE,
|
|
300
|
+
};
|
|
301
|
+
const params = buildUserTicketQueryParams(userId, criteria, {
|
|
302
|
+
page: { size: window.limit, index: page },
|
|
303
|
+
});
|
|
304
|
+
return loadPage(client, 'gaia_ticket/gaia_ticket', params, (data, included) => {
|
|
305
|
+
const index = indexIncluded(included);
|
|
306
|
+
return data.map((item) => mapTicketData(item, index));
|
|
307
|
+
}, page, window);
|
|
308
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { VNode } from '@opentui/core';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { AsyncModalScreen } from '../../../Screen/async-modal-screen.js';
|
|
3
|
+
import type { ModalChrome } from '../../../Screen/modal-screen.js';
|
|
4
|
+
import type { ScreenAction, ScreenContext } from '../../../Screen/screen.js';
|
|
4
5
|
import { type CreateFormOptions } from '../Form/create.js';
|
|
5
|
-
export declare class CreateTicketFormScreen extends
|
|
6
|
+
export declare class CreateTicketFormScreen extends AsyncModalScreen<CreateFormOptions> {
|
|
6
7
|
readonly modal = true;
|
|
7
8
|
static readonly route: {
|
|
8
9
|
id: string;
|
|
@@ -66,7 +67,12 @@ export declare class CreateTicketFormScreen extends AsyncScreen<CreateFormOption
|
|
|
66
67
|
takeAction(): ScreenAction | undefined;
|
|
67
68
|
handleInput(input: string, context: ScreenContext): boolean;
|
|
68
69
|
reset(): void;
|
|
69
|
-
|
|
70
|
+
protected modalChrome(context: ScreenContext): ModalChrome;
|
|
71
|
+
protected actions(): import("../../../Component/footer-actions/index.js").ActionItem[];
|
|
72
|
+
/** Two status rows so Ctrl+D is not clipped in the modal box (AC-1). */
|
|
73
|
+
protected modalStatusLines(): string[];
|
|
74
|
+
private fieldActions;
|
|
75
|
+
private submitActions;
|
|
70
76
|
/**
|
|
71
77
|
* Write the ticket straight to the control plane (GAIA-309).
|
|
72
78
|
*
|
|
@@ -2,10 +2,10 @@ import { writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { buildCreateLaunch, shortName } from '../../../Agent/launch.js';
|
|
5
|
+
import { actionItem, formatActionLadder, } from '../../../Component/footer-actions/index.js';
|
|
5
6
|
import { serverMessage } from '../../../lib/jsonapi-error.js';
|
|
6
7
|
import { columnNode } from '../../../lib/vnode.js';
|
|
7
|
-
import {
|
|
8
|
-
import { presentation, } from '../../../Screen/screen.js';
|
|
8
|
+
import { AsyncModalScreen } from '../../../Screen/async-modal-screen.js';
|
|
9
9
|
import { createOptionLoader, } from '../../../Widget/option-source.js';
|
|
10
10
|
import { CREATE_FORM_REGION, createFormTree, } from '../Component/create-form/index.js';
|
|
11
11
|
import { buildTicketCreateBody, createTicket } from '../Data/write.js';
|
|
@@ -28,10 +28,7 @@ import { keyToIntent } from '../Form/keys.js';
|
|
|
28
28
|
// is the only clue the label list is togglable — to buy room for the new keys.
|
|
29
29
|
// The modal frame splits a body row on `\n` already (`tui-kernel.ts`), so a
|
|
30
30
|
// second row costs no kernel change; R3 already accepts that the popup grew.
|
|
31
|
-
|
|
32
|
-
const PICK_KEYS = ' ↑↓ field · ←→ suggestion · Enter pick';
|
|
33
|
-
const SUBMIT_KEYS = ' Ctrl+S agent · Ctrl+D direct · Esc cancel ';
|
|
34
|
-
export class CreateTicketFormScreen extends AsyncScreen {
|
|
31
|
+
export class CreateTicketFormScreen extends AsyncModalScreen {
|
|
35
32
|
modal = true;
|
|
36
33
|
static route = {
|
|
37
34
|
id: 'ticket.create',
|
|
@@ -238,21 +235,45 @@ export class CreateTicketFormScreen extends AsyncScreen {
|
|
|
238
235
|
this.sources = {};
|
|
239
236
|
super.reset();
|
|
240
237
|
}
|
|
241
|
-
|
|
242
|
-
return
|
|
238
|
+
modalChrome(context) {
|
|
239
|
+
return {
|
|
243
240
|
title: '⚡ New ticket',
|
|
244
241
|
meta: context.project?.name ??
|
|
245
242
|
context.project?.slug ??
|
|
246
243
|
context.options?.project ??
|
|
247
244
|
'',
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
actions() {
|
|
248
|
+
return [...this.fieldActions(), ...this.submitActions()];
|
|
249
|
+
}
|
|
250
|
+
/** Two status rows so Ctrl+D is not clipped in the modal box (AC-1). */
|
|
251
|
+
modalStatusLines() {
|
|
252
|
+
return [
|
|
253
|
+
formatActionLadder(this.fieldActions()),
|
|
254
|
+
formatActionLadder(this.submitActions()),
|
|
255
|
+
];
|
|
256
|
+
}
|
|
257
|
+
fieldActions() {
|
|
258
|
+
return this.form &&
|
|
259
|
+
CREATE_FORM.activeField(this.form).kind === 'autocomplete'
|
|
260
|
+
? [
|
|
261
|
+
actionItem('create-field', '↑↓', 'field'),
|
|
262
|
+
actionItem('create-suggest', '←→', 'suggestion'),
|
|
263
|
+
actionItem('create-pick', 'Enter', 'pick'),
|
|
264
|
+
]
|
|
265
|
+
: [
|
|
266
|
+
actionItem('create-field', '↑↓', 'field'),
|
|
267
|
+
actionItem('create-change', '←→', 'change'),
|
|
268
|
+
actionItem('create-toggle', 'Space', 'toggle'),
|
|
269
|
+
];
|
|
270
|
+
}
|
|
271
|
+
submitActions() {
|
|
272
|
+
return [
|
|
273
|
+
actionItem('create-agent', 'Ctrl+S', 'agent'),
|
|
274
|
+
actionItem('create-direct', 'Ctrl+D', 'direct'),
|
|
275
|
+
actionItem('create-esc', 'Esc', 'cancel'),
|
|
276
|
+
];
|
|
256
277
|
}
|
|
257
278
|
/**
|
|
258
279
|
* Write the ticket straight to the control plane (GAIA-309).
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { VNode } from '@opentui/core';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { AsyncModalScreen } from '../../../Screen/async-modal-screen.js';
|
|
3
|
+
import type { ModalChrome } from '../../../Screen/modal-screen.js';
|
|
4
|
+
import type { ScreenAction, ScreenContext } from '../../../Screen/screen.js';
|
|
4
5
|
import type { TicketDetailDocument } from '../../../types.js';
|
|
5
6
|
import { type EditFormOptions } from '../Form/edit.js';
|
|
6
7
|
interface EditAssignmentData {
|
|
@@ -11,7 +12,7 @@ interface EditAssignmentInput {
|
|
|
11
12
|
readonly document: TicketDetailDocument;
|
|
12
13
|
}
|
|
13
14
|
export declare const editAssignmentInputContext: import("../../../Routing/context.js").ContextKey<EditAssignmentInput>;
|
|
14
|
-
export declare class EditAssignmentFormScreen extends
|
|
15
|
+
export declare class EditAssignmentFormScreen extends AsyncModalScreen<EditAssignmentData> {
|
|
15
16
|
static result(ticketId: string, assigneeId: string): {
|
|
16
17
|
type: "ticket-assignment-updated";
|
|
17
18
|
ticketId: string;
|
|
@@ -77,6 +78,7 @@ export declare class EditAssignmentFormScreen extends AsyncScreen<EditAssignment
|
|
|
77
78
|
*/
|
|
78
79
|
leave(): void;
|
|
79
80
|
reset(): void;
|
|
80
|
-
|
|
81
|
+
protected modalChrome(context: ScreenContext): ModalChrome;
|
|
82
|
+
protected actions(): import("../../../Component/footer-actions/index.js").ActionItem[];
|
|
81
83
|
}
|
|
82
84
|
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { actionItem } from '../../../Component/footer-actions/index.js';
|
|
1
2
|
import { serverMessage } from '../../../lib/jsonapi-error.js';
|
|
2
3
|
import { columnNode } from '../../../lib/vnode.js';
|
|
3
4
|
import { defineContext } from '../../../Routing/context.js';
|
|
4
|
-
import {
|
|
5
|
-
import { presentation, } from '../../../Screen/screen.js';
|
|
5
|
+
import { AsyncModalScreen } from '../../../Screen/async-modal-screen.js';
|
|
6
6
|
import { createOptionLoader, } from '../../../Widget/option-source.js';
|
|
7
7
|
import { activeRunInfo } from '../../run/Data/active-run.js';
|
|
8
8
|
import { EDIT_FORM_REGION, editFormTree, } from '../Component/edit-form/index.js';
|
|
@@ -18,7 +18,7 @@ const relationshipId = (document, name) => {
|
|
|
18
18
|
return undefined;
|
|
19
19
|
return Array.isArray(data) ? data[0]?.id : data.id;
|
|
20
20
|
};
|
|
21
|
-
export class EditAssignmentFormScreen extends
|
|
21
|
+
export class EditAssignmentFormScreen extends AsyncModalScreen {
|
|
22
22
|
static result(ticketId, assigneeId) {
|
|
23
23
|
return { type: 'ticket-assignment-updated', ticketId, assigneeId };
|
|
24
24
|
}
|
|
@@ -251,21 +251,22 @@ export class EditAssignmentFormScreen extends AsyncScreen {
|
|
|
251
251
|
this.context = undefined;
|
|
252
252
|
super.reset();
|
|
253
253
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
// estimate and the spent time too, so the chrome stops naming one half.
|
|
254
|
+
modalChrome(context) {
|
|
255
|
+
// GAIA-308: chrome names the ticket, not only assignment.
|
|
256
|
+
return {
|
|
258
257
|
title: '✎ Edit ticket',
|
|
259
258
|
meta: String(this.readyValue?.document.data?.attributes?.identifier ??
|
|
260
259
|
context.params?.ticketId ??
|
|
261
260
|
''),
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
actions() {
|
|
264
|
+
// GAIA-360 D12 — Esc:close (not cancel); Cancel ticket is lifecycle.
|
|
265
|
+
return [
|
|
266
|
+
actionItem('edit-field', '↑↓', 'field'),
|
|
267
|
+
actionItem('edit-change', '←→', 'change'),
|
|
268
|
+
actionItem('edit-save', 'Ctrl+S', 'save'),
|
|
269
|
+
actionItem('edit-esc', 'Esc', 'close'),
|
|
270
|
+
];
|
|
270
271
|
}
|
|
271
272
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { VNode } from '@opentui/core';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { AsyncModalScreen } from '../../../Screen/async-modal-screen.js';
|
|
3
|
+
import type { ModalChrome } from '../../../Screen/modal-screen.js';
|
|
4
|
+
import type { ScreenAction, ScreenContext } from '../../../Screen/screen.js';
|
|
4
5
|
import { type ParentFilterOptions } from '../Form/parent-filter.js';
|
|
5
|
-
export declare class ParentFilterScreen extends
|
|
6
|
+
export declare class ParentFilterScreen extends AsyncModalScreen<ParentFilterOptions> {
|
|
6
7
|
readonly modal = true;
|
|
7
8
|
static readonly route: {
|
|
8
9
|
id: string;
|
|
@@ -40,5 +41,6 @@ export declare class ParentFilterScreen extends AsyncScreen<ParentFilterOptions>
|
|
|
40
41
|
takeAction(): ScreenAction | undefined;
|
|
41
42
|
handleInput(input: string, context: ScreenContext): boolean;
|
|
42
43
|
reset(): void;
|
|
43
|
-
|
|
44
|
+
protected modalChrome(context: ScreenContext): ModalChrome;
|
|
45
|
+
protected actions(): import("../../../Component/footer-actions/index.js").ActionItem[];
|
|
44
46
|
}
|
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
// loads through, never a second lookup. The source builds its query with
|
|
10
10
|
// `parent: null` criteria (F7), so an active parent filter can never narrow
|
|
11
11
|
// the picker to that parent's own children.
|
|
12
|
+
import { actionItem } from '../../../Component/footer-actions/index.js';
|
|
12
13
|
import { columnNode } from '../../../lib/vnode.js';
|
|
13
|
-
import {
|
|
14
|
-
import { presentation, } from '../../../Screen/screen.js';
|
|
14
|
+
import { AsyncModalScreen } from '../../../Screen/async-modal-screen.js';
|
|
15
15
|
import { createOptionLoader, primeSources, } from '../../../Widget/option-source.js';
|
|
16
16
|
import { FIELDS_REGION, renderDeclaredFieldsTree, } from '../../../Widget/render.js';
|
|
17
17
|
import { keyToIntent } from '../Form/keys.js';
|
|
18
18
|
import { PARENT_FILTER_FORM, } from '../Form/parent-filter.js';
|
|
19
19
|
import { parentOptionSource } from '../Form/parent-source.js';
|
|
20
|
-
export class ParentFilterScreen extends
|
|
20
|
+
export class ParentFilterScreen extends AsyncModalScreen {
|
|
21
21
|
modal = true;
|
|
22
22
|
static route = {
|
|
23
23
|
id: 'project.tickets.parent-filter',
|
|
@@ -186,15 +186,18 @@ export class ParentFilterScreen extends AsyncScreen {
|
|
|
186
186
|
this.sources = {};
|
|
187
187
|
super.reset();
|
|
188
188
|
}
|
|
189
|
-
|
|
190
|
-
return
|
|
189
|
+
modalChrome(context) {
|
|
190
|
+
return {
|
|
191
191
|
title: '⚡ Filter by parent',
|
|
192
192
|
meta: context.project?.name ?? context.project?.slug ?? '',
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
actions() {
|
|
196
|
+
return [
|
|
197
|
+
actionItem('parent-suggest', '↑↓/←→', 'suggestion'),
|
|
198
|
+
actionItem('parent-pick', 'Enter', 'pick'),
|
|
199
|
+
actionItem('parent-clear', 'Del', 'clear'),
|
|
200
|
+
actionItem('parent-esc', 'Esc', 'cancel'),
|
|
201
|
+
];
|
|
199
202
|
}
|
|
200
203
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { VNode } from '@opentui/core';
|
|
2
2
|
import { AsyncScreen } from '../../../Screen/async-screen.js';
|
|
3
|
-
import {
|
|
3
|
+
import type { FullScreenChrome } from '../../../Screen/full-screen.js';
|
|
4
|
+
import type { ScreenContext } from '../../../Screen/screen.js';
|
|
4
5
|
import type { HostedAgent } from '../../../types.js';
|
|
5
6
|
/**
|
|
6
7
|
* The ticket agents list, declared once (GAIA-345 D2/D10).
|
|
@@ -127,7 +128,9 @@ export declare class TicketAgentsScreen extends AsyncScreen<HostedAgent[]> {
|
|
|
127
128
|
protected load(context: ScreenContext): Promise<HostedAgent[]>;
|
|
128
129
|
protected renderLoading(): string[];
|
|
129
130
|
protected renderReady(rows: HostedAgent[]): VNode;
|
|
130
|
-
|
|
131
|
+
protected chrome(_context: ScreenContext): FullScreenChrome;
|
|
132
|
+
protected actions(): import("../../../Component/footer-actions/index.js").ActionItem[];
|
|
133
|
+
protected notice(): string | undefined;
|
|
131
134
|
focus(index: number, context: ScreenContext): boolean;
|
|
132
135
|
activate(index: number, context: ScreenContext): boolean;
|
|
133
136
|
select(index: number, context: ScreenContext): boolean;
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
// `Agent/Data/` for it to sit in. `PICKER_TABLE` is the precedent for exactly
|
|
19
19
|
// that case, and it sits in `Entity/project/Screen/project-picker.ts` for exactly
|
|
20
20
|
// this reason.
|
|
21
|
+
import { actionItem } from '../../../Component/footer-actions/index.js';
|
|
21
22
|
import { ListCursor } from '../../../lib/cursor.js';
|
|
22
23
|
import { AsyncScreen } from '../../../Screen/async-screen.js';
|
|
23
|
-
import { presentation } from '../../../Screen/screen.js';
|
|
24
24
|
import { listRowIds, listTableTree } from '../../collection.js';
|
|
25
25
|
/**
|
|
26
26
|
* The ticket agents list, declared once (GAIA-345 D2/D10).
|
|
@@ -179,24 +179,24 @@ export class TicketAgentsScreen extends AsyncScreen {
|
|
|
179
179
|
selectedIndex: this.selectedIndex,
|
|
180
180
|
});
|
|
181
181
|
}
|
|
182
|
-
|
|
182
|
+
chrome(_context) {
|
|
183
183
|
const rows = this.rows;
|
|
184
|
-
return
|
|
184
|
+
return {
|
|
185
185
|
selectable: rows.length,
|
|
186
186
|
selectedIndex: this.selectedIndex,
|
|
187
|
-
// Row 0 is the table's own header, so the selected AGENT is one row below
|
|
188
|
-
// its index. Still the row the kernel scrolls into view and nothing more:
|
|
189
|
-
// the highlight is a prop on that row's cells now (GAIA-340 D3).
|
|
190
|
-
// The node a click on each row lands on (GAIA-340 Task 11), read off the
|
|
191
|
-
// SAME configuration that keys the rows (GAIA-345 D2) — which retires the
|
|
192
|
-
// hand-written `agentRowIds`, a second expression of an id the component
|
|
193
|
-
// spelled independently.
|
|
194
187
|
rowIds: listRowIds(AGENT_TABLE, rows),
|
|
195
188
|
title: 'Ticket-Agents',
|
|
196
|
-
meta: `${rows.length} agent(s)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
189
|
+
meta: `${rows.length} agent(s)`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
actions() {
|
|
193
|
+
return [
|
|
194
|
+
actionItem('agents-enter', 'Enter', 'switch'),
|
|
195
|
+
actionItem('agents-tab', 'Tab', 'tabs'),
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
notice() {
|
|
199
|
+
return this.status || undefined;
|
|
200
200
|
}
|
|
201
201
|
focus(index, context) {
|
|
202
202
|
const row = this.readyValue?.[index];
|