@reticlehq/browser 2.14.0 → 3.2.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/actions/actions-dom.d.ts +9 -0
- package/dist/actions/actions-dom.js +81 -28
- package/dist/actions/actions-press.d.ts +4 -0
- package/dist/actions/actions-press.js +46 -0
- package/dist/actions/actions.d.ts +1 -0
- package/dist/actions/actions.js +50 -13
- package/dist/actions/appeared-text.js +6 -0
- package/dist/actions/scroll.d.ts +8 -1
- package/dist/actions/scroll.js +28 -10
- package/dist/commands/commands.js +10 -3
- package/dist/dom/a11y.js +2 -2
- package/dist/dom/desktop-capture.js +1 -1
- package/dist/dom/dom-ignore.js +1 -1
- package/dist/dom/occlusion.js +31 -5
- package/dist/dom/paint-context.d.ts +6 -0
- package/dist/dom/paint-context.js +38 -0
- package/dist/dom/query.js +24 -7
- package/dist/dom/snapshot.js +1 -1
- package/dist/edit-epoch.d.ts +1 -1
- package/dist/edit-epoch.js +1 -1
- package/dist/index.d.ts +4 -5
- package/dist/index.js +3 -5
- package/dist/observers/animation.js +1 -1
- package/dist/observers/blind-spots.js +1 -1
- package/dist/observers/departure.js +16 -8
- package/dist/observers/dialogs.js +1 -1
- package/dist/observers/dom.js +28 -8
- package/dist/observers/download.js +1 -1
- package/dist/observers/health.js +1 -1
- package/dist/observers/{network-body.js → net-detail/network-body.js} +2 -2
- package/dist/observers/network-stream.js +2 -2
- package/dist/observers/network.d.ts +1 -1
- package/dist/observers/network.js +51 -10
- package/dist/observers/scroll.js +2 -2
- package/dist/presenter/chrome/layers.d.ts +2 -0
- package/dist/presenter/chrome/layers.js +2 -0
- package/dist/presenter/chrome/log-kinds.d.ts +12 -0
- package/dist/presenter/chrome/log-kinds.js +7 -0
- package/dist/presenter/{presenter-effects.js → chrome/presenter-effects.js} +1 -1
- package/dist/presenter/{presenter-log.d.ts → chrome/presenter-log.d.ts} +3 -12
- package/dist/presenter/{presenter-log.js → chrome/presenter-log.js} +4 -9
- package/dist/presenter/chrome/presenter-safe-html.d.ts +2 -0
- package/dist/presenter/chrome/presenter-safe-html.js +16 -0
- package/dist/presenter/{presenter-tally.js → chrome/presenter-tally.js} +1 -1
- package/dist/presenter/{presenter-heroicons-data.d.ts → icons/presenter-heroicons-data.d.ts} +1 -0
- package/dist/presenter/{presenter-heroicons-data.js → icons/presenter-heroicons-data.js} +1 -0
- package/dist/presenter/{presenter-icons.d.ts → icons/presenter-icons.d.ts} +1 -0
- package/dist/presenter/{presenter-icons.js → icons/presenter-icons.js} +1 -0
- package/dist/presenter/presenter-account-styles.d.ts +1 -0
- package/dist/presenter/presenter-account-styles.js +99 -0
- package/dist/presenter/presenter-account.d.ts +40 -0
- package/dist/presenter/presenter-account.js +178 -0
- package/dist/presenter/presenter-config.d.ts +9 -2
- package/dist/presenter/presenter-config.js +3 -1
- package/dist/presenter/presenter-controls.d.ts +1 -6
- package/dist/presenter/presenter-controls.js +7 -2
- package/dist/presenter/presenter-dock-layout.js +1 -1
- package/dist/presenter/presenter-drag.d.ts +1 -19
- package/dist/presenter/presenter-drag.js +3 -50
- package/dist/presenter/presenter-glow.js +1 -1
- package/dist/presenter/presenter-hud-position.d.ts +18 -0
- package/dist/presenter/presenter-hud-position.js +50 -0
- package/dist/presenter/presenter-offer.d.ts +25 -0
- package/dist/presenter/presenter-offer.js +87 -0
- package/dist/presenter/presenter-report-styles.js +20 -1
- package/dist/presenter/presenter-report.d.ts +2 -1
- package/dist/presenter/presenter-report.js +31 -23
- package/dist/presenter/presenter-run-state.d.ts +1 -1
- package/dist/presenter/presenter-run-state.js +1 -1
- package/dist/presenter/presenter-settings-styles.js +2 -1
- package/dist/presenter/presenter-settings.d.ts +8 -0
- package/dist/presenter/presenter-settings.js +58 -3
- package/dist/presenter/presenter-shell-styles.js +33 -15
- package/dist/presenter/presenter-shell.d.ts +8 -0
- package/dist/presenter/presenter-shell.js +47 -4
- package/dist/presenter/presenter-styles.d.ts +2 -1
- package/dist/presenter/presenter-styles.js +8 -3
- package/dist/presenter/presenter-workspace.d.ts +4 -0
- package/dist/presenter/presenter-workspace.js +29 -2
- package/dist/presenter/presenter.d.ts +3 -3
- package/dist/presenter/presenter.js +28 -14
- package/dist/presenter/tour/tour-view.d.ts +33 -0
- package/dist/presenter/tour/tour-view.js +192 -0
- package/dist/presenter/tour/tour.d.ts +25 -0
- package/dist/presenter/tour/tour.js +264 -0
- package/dist/recorder/recorder.js +6 -0
- package/dist/registry/declared-channels.d.ts +2 -0
- package/dist/registry/declared-channels.js +14 -0
- package/dist/registry/declared-platform.d.ts +4 -0
- package/dist/registry/declared-platform.js +34 -0
- package/dist/registry/emitter.js +1 -1
- package/dist/registry/store-adapters.js +1 -1
- package/dist/registry/stores.js +1 -1
- package/dist/reticle-presenter-helpers.js +10 -6
- package/dist/reticle-singleton.d.ts +2 -0
- package/dist/reticle-singleton.js +3 -0
- package/dist/reticle.js +99 -35
- package/dist/review/annotator-styles.js +1 -1
- package/dist/review/annotator.js +2 -2
- package/dist/review/mark-anchor.js +3 -3
- package/dist/transport/transport.js +2 -2
- package/package.json +5 -4
- package/dist/presenter/presenter-test-helpers.d.ts +0 -14
- package/dist/presenter/presenter-test-helpers.js +0 -39
- /package/dist/actions/{synthetic-input.d.ts → synthetic/synthetic-input.d.ts} +0 -0
- /package/dist/actions/{synthetic-input.js → synthetic/synthetic-input.js} +0 -0
- /package/dist/dom/{auto-anchor.d.ts → addressing/auto-anchor.d.ts} +0 -0
- /package/dist/dom/{auto-anchor.js → addressing/auto-anchor.js} +0 -0
- /package/dist/dom/{refs.d.ts → addressing/refs.d.ts} +0 -0
- /package/dist/dom/{refs.js → addressing/refs.js} +0 -0
- /package/dist/dom/{source.d.ts → addressing/source.d.ts} +0 -0
- /package/dist/dom/{source.js → addressing/source.js} +0 -0
- /package/dist/observers/{network-body.d.ts → net-detail/network-body.d.ts} +0 -0
- /package/dist/observers/{network-redact.d.ts → net-detail/network-redact.d.ts} +0 -0
- /package/dist/observers/{network-redact.js → net-detail/network-redact.js} +0 -0
- /package/dist/presenter/{overlay.d.ts → chrome/overlay.d.ts} +0 -0
- /package/dist/presenter/{overlay.js → chrome/overlay.js} +0 -0
- /package/dist/presenter/{presenter-brand.d.ts → chrome/presenter-brand.d.ts} +0 -0
- /package/dist/presenter/{presenter-brand.js → chrome/presenter-brand.js} +0 -0
- /package/dist/presenter/{presenter-effects.d.ts → chrome/presenter-effects.d.ts} +0 -0
- /package/dist/presenter/{presenter-hud-chrome.d.ts → chrome/presenter-hud-chrome.d.ts} +0 -0
- /package/dist/presenter/{presenter-hud-chrome.js → chrome/presenter-hud-chrome.js} +0 -0
- /package/dist/presenter/{presenter-report-copy.d.ts → chrome/presenter-report-copy.d.ts} +0 -0
- /package/dist/presenter/{presenter-report-copy.js → chrome/presenter-report-copy.js} +0 -0
- /package/dist/presenter/{presenter-tally.d.ts → chrome/presenter-tally.d.ts} +0 -0
- /package/dist/presenter/{presenter-verbs.d.ts → chrome/presenter-verbs.d.ts} +0 -0
- /package/dist/presenter/{presenter-verbs.js → chrome/presenter-verbs.js} +0 -0
- /package/dist/registry/{adapters.d.ts → stores/adapters.d.ts} +0 -0
- /package/dist/registry/{adapters.js → stores/adapters.js} +0 -0
- /package/dist/timers/{native-console.d.ts → console/native-console.d.ts} +0 -0
- /package/dist/timers/{native-console.js → console/native-console.js} +0 -0
- /package/dist/timers/{native-timers.d.ts → native/native-timers.d.ts} +0 -0
- /package/dist/timers/{native-timers.js → native/native-timers.js} +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { HUD_DROP_SHADOW, HUD_SURFACE_PAINT } from './chrome/presenter-hud-chrome.js';
|
|
2
|
+
const AVATAR = 20;
|
|
3
|
+
const AVATAR_LG = 28;
|
|
4
|
+
export const ACCOUNT_CSS = `
|
|
5
|
+
/* The control establishes its own stacking context, and a high one.
|
|
6
|
+
Its menu is absolutely positioned inside a panel whose other children (the log well, the act strip)
|
|
7
|
+
are themselves positioned with their own z-index, so a z-index on the MENU alone was being compared
|
|
8
|
+
inside the wrong context and lost to a sibling with a lower number. Raising it here, on the thing
|
|
9
|
+
that contains the menu, is what actually puts the menu above them. 9 clears the workspace menu's 8,
|
|
10
|
+
which is the highest in-panel chrome. */
|
|
11
|
+
/* The header row has to out-stack the log well, or nothing inside it can.
|
|
12
|
+
.reticle-chat-head is position:relative with z-index:2, so it IS a stacking context and the number
|
|
13
|
+
below is scoped inside it -- while the log well is a SIBLING, also at z-index:2, and later in DOM
|
|
14
|
+
order. Equal z, later sibling wins, so the well painted over the whole header and took the open
|
|
15
|
+
menu with it. The menu measured correctly and was invisible, which is the shape of bug that reads
|
|
16
|
+
as "the dropdown does not work". Raising the header is what lets its children escape; the two only
|
|
17
|
+
overlap while a menu is open, so nothing else moves. */
|
|
18
|
+
.reticle-chat-head{z-index:3;}
|
|
19
|
+
.reticle-account{position:relative;z-index:9;display:inline-flex;align-items:center;gap:6px;flex:none;}
|
|
20
|
+
.reticle-account-avatar{
|
|
21
|
+
display:inline-flex;align-items:center;justify-content:center;
|
|
22
|
+
width:${String(AVATAR)}px;height:${String(AVATAR)}px;border-radius:999px;
|
|
23
|
+
font-size:9px;font-weight:600;letter-spacing:.02em;
|
|
24
|
+
background:rgba(255,255,255,.12);color:rgba(255,255,255,.92);user-select:none;}
|
|
25
|
+
.reticle-account-avatar--lg{
|
|
26
|
+
width:${String(AVATAR_LG)}px;height:${String(AVATAR_LG)}px;font-size:11px;}
|
|
27
|
+
|
|
28
|
+
/* The trigger is the avatar plus a hit area, never a second visual element: an avatar that looks
|
|
29
|
+
like a button reads as an action, and this one opens a menu about a state. */
|
|
30
|
+
.reticle-account-trigger{
|
|
31
|
+
display:inline-flex;align-items:center;justify-content:center;
|
|
32
|
+
padding:0;border:none;background:transparent;cursor:pointer;line-height:0;border-radius:999px;
|
|
33
|
+
transition:box-shadow .12s,opacity .12s;}
|
|
34
|
+
.reticle-account-trigger:hover{opacity:.85;}
|
|
35
|
+
.reticle-account-trigger:focus-visible{outline:none;box-shadow:0 0 0 2px rgba(255,255,255,.35);}
|
|
36
|
+
.reticle-account-trigger[aria-expanded="true"]{box-shadow:0 0 0 2px rgba(255,255,255,.28);}
|
|
37
|
+
|
|
38
|
+
.reticle-account-signin{
|
|
39
|
+
border:none;border-radius:6px;padding:2px 9px;cursor:pointer;font:inherit;font-size:10.5px;
|
|
40
|
+
background:rgba(255,255,255,.06);color:rgba(255,255,255,.75);transition:background .12s,color .12s;}
|
|
41
|
+
.reticle-account-signin:hover{background:rgba(255,255,255,.12);color:#fff;}
|
|
42
|
+
|
|
43
|
+
/* Anchored to the control and opening DOWNWARD.
|
|
44
|
+
Not upward, which is what it looked like it wanted: all three surfaces mount this near the TOP of
|
|
45
|
+
a panel whose own overflow is hidden for its rounded corners, so a menu opening upward is laid
|
|
46
|
+
out correctly and then clipped away entirely -- present to the probe, invisible to the eye. Panels
|
|
47
|
+
grow downward and have the height, so down is the direction that stays inside the box. */
|
|
48
|
+
.reticle-account-menu{
|
|
49
|
+
position:absolute;right:0;top:calc(100% + 8px);z-index:6;
|
|
50
|
+
min-width:212px;max-width:280px;padding:10px;box-sizing:border-box;
|
|
51
|
+
display:flex;flex-direction:column;gap:8px;
|
|
52
|
+
border-radius:10px;
|
|
53
|
+
/* The HUD's own surface tokens, not a colour invented here. A menu with its own near-black is a
|
|
54
|
+
second design language in the same panel, and a translucent one lets the panel's empty-state text
|
|
55
|
+
read straight through the rows -- which is what the first version did. */
|
|
56
|
+
${HUD_SURFACE_PAINT}
|
|
57
|
+
box-shadow:inset 0 1px 0 rgba(255,255,255,.09),${HUD_DROP_SHADOW};
|
|
58
|
+
font-size:11px;color:rgba(255,255,255,.78);text-align:left;}
|
|
59
|
+
.reticle-account-menu[hidden]{display:none;}
|
|
60
|
+
|
|
61
|
+
.reticle-account-head{display:flex;align-items:center;gap:8px;min-width:0;}
|
|
62
|
+
.reticle-account-who{display:flex;flex-direction:column;min-width:0;gap:1px;}
|
|
63
|
+
.reticle-account-org{
|
|
64
|
+
font-size:11.5px;font-weight:600;color:rgba(255,255,255,.95);
|
|
65
|
+
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
66
|
+
.reticle-account-email{
|
|
67
|
+
font-size:10.5px;color:rgba(255,255,255,.55);
|
|
68
|
+
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
69
|
+
|
|
70
|
+
.reticle-account-rows{
|
|
71
|
+
display:flex;flex-direction:column;gap:4px;
|
|
72
|
+
padding-top:8px;border-top:1px solid rgba(255,255,255,.08);}
|
|
73
|
+
.reticle-account-row{display:flex;align-items:baseline;justify-content:space-between;gap:10px;}
|
|
74
|
+
.reticle-account-k{color:rgba(255,255,255,.5);flex:none;}
|
|
75
|
+
.reticle-account-v{
|
|
76
|
+
color:rgba(255,255,255,.88);min-width:0;
|
|
77
|
+
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
|
|
78
|
+
font-variant-numeric:tabular-nums;}
|
|
79
|
+
|
|
80
|
+
.reticle-account-actions{
|
|
81
|
+
display:flex;flex-direction:column;gap:2px;
|
|
82
|
+
padding-top:8px;border-top:1px solid rgba(255,255,255,.08);}
|
|
83
|
+
.reticle-account-action{
|
|
84
|
+
display:flex;align-items:center;gap:6px;width:100%;box-sizing:border-box;
|
|
85
|
+
padding:5px 6px;border:none;border-radius:6px;cursor:pointer;
|
|
86
|
+
font:inherit;font-size:11px;text-align:left;text-decoration:none;
|
|
87
|
+
background:transparent;color:rgba(255,255,255,.8);transition:background .12s,color .12s;}
|
|
88
|
+
.reticle-account-action:hover{background:rgba(255,255,255,.08);color:#fff;}
|
|
89
|
+
.reticle-account-action:focus-visible{outline:none;background:rgba(255,255,255,.1);color:#fff;}
|
|
90
|
+
/* The unlinked hint is a sentence, not a verb: it wraps rather than truncating, because a clipped
|
|
91
|
+
instruction is worse than a tall menu. */
|
|
92
|
+
.reticle-account-action--hint{
|
|
93
|
+
color:rgba(255,255,255,.5);font-size:10.5px;line-height:1.35;white-space:normal;cursor:pointer;}
|
|
94
|
+
|
|
95
|
+
.reticle-account-dashboard{
|
|
96
|
+
display:inline-flex;align-items:center;justify-content:center;
|
|
97
|
+
border:none;background:transparent;color:rgba(255,255,255,.6);cursor:pointer;line-height:0;padding:2px;}
|
|
98
|
+
.reticle-account-dashboard:hover{color:#fff;}
|
|
99
|
+
`;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { AccountState } from '@reticlehq/core';
|
|
2
|
+
export declare const ACCOUNT_ROOT_ATTR = "data-reticle-account";
|
|
3
|
+
export declare const ACCOUNT_SIGNIN_ATTR = "data-reticle-account-signin";
|
|
4
|
+
export declare const SYNC_BTN_ATTR = "data-reticle-sync-now";
|
|
5
|
+
export declare const ACCOUNT_TRIGGER_ATTR = "data-reticle-account-trigger";
|
|
6
|
+
export declare const ACCOUNT_MENU_ATTR = "data-reticle-account-menu";
|
|
7
|
+
export declare const ACCOUNT_SIGNOUT_ATTR = "data-reticle-account-signout";
|
|
8
|
+
export declare const ACCOUNT_TEXT: {
|
|
9
|
+
readonly SIGNED_OUT: "Sign in";
|
|
10
|
+
readonly SIGNIN_TITLE: "Run `reticle login` in your terminal — click to copy the command";
|
|
11
|
+
readonly SIGNIN_COMMAND: "reticle login";
|
|
12
|
+
readonly DASHBOARD_TITLE: "Open this project on the dashboard";
|
|
13
|
+
readonly SYNC_TITLE: "Push to the dashboard now, instead of waiting for the next sync";
|
|
14
|
+
readonly COPIED: "Copied";
|
|
15
|
+
readonly AVATAR_FALLBACK: "•";
|
|
16
|
+
readonly MENU_LABEL: "Reticle account";
|
|
17
|
+
readonly TRIGGER_TITLE: "Your Reticle account";
|
|
18
|
+
readonly SIGNED_IN_AS: "Signed in";
|
|
19
|
+
readonly DASHBOARD_ACTION: "Open dashboard";
|
|
20
|
+
readonly LINK_HINT: "Not linked — run `reticle link` to keep a record on the dashboard";
|
|
21
|
+
readonly LINK_COMMAND: "reticle link";
|
|
22
|
+
readonly SIGNOUT_ACTION: "Sign out";
|
|
23
|
+
readonly SIGNOUT_TITLE: "Run `reticle logout` in your terminal — click to copy the command";
|
|
24
|
+
readonly SIGNOUT_COMMAND: "reticle logout";
|
|
25
|
+
readonly PROJECT_LABEL: "Project";
|
|
26
|
+
readonly HOST_LABEL: "Host";
|
|
27
|
+
readonly VERDICTS_LABEL: "Verdicts";
|
|
28
|
+
readonly DEFECTS_LABEL: "Defects found";
|
|
29
|
+
};
|
|
30
|
+
export interface AccountDetails {
|
|
31
|
+
projectName?: string | undefined;
|
|
32
|
+
dashboardUrl?: string | undefined;
|
|
33
|
+
verdicts?: number | undefined;
|
|
34
|
+
defects?: number | undefined;
|
|
35
|
+
}
|
|
36
|
+
export declare function accountInitials(org: string | undefined): string;
|
|
37
|
+
export declare function accountControlHtml(account: AccountState | undefined, details?: AccountDetails, offerSignIn?: boolean): string;
|
|
38
|
+
export declare function accountCapsuleHtml(account: AccountState | undefined, dashboardUrl?: string, offerSignIn?: boolean): string;
|
|
39
|
+
export declare function syncButtonHtml(dashboardUrl: string | undefined): string;
|
|
40
|
+
export declare function mountAccountControl(root: HTMLElement): () => void;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { hiIconHtml, PRESENTER_ICON_SIZE, PresenterIcon } from './icons/presenter-icons.js';
|
|
2
|
+
import { esc, isSafeDashboardUrl } from './chrome/presenter-safe-html.js';
|
|
3
|
+
export const ACCOUNT_ROOT_ATTR = 'data-reticle-account';
|
|
4
|
+
export const ACCOUNT_SIGNIN_ATTR = 'data-reticle-account-signin';
|
|
5
|
+
export const SYNC_BTN_ATTR = 'data-reticle-sync-now';
|
|
6
|
+
export const ACCOUNT_TRIGGER_ATTR = 'data-reticle-account-trigger';
|
|
7
|
+
export const ACCOUNT_MENU_ATTR = 'data-reticle-account-menu';
|
|
8
|
+
export const ACCOUNT_SIGNOUT_ATTR = 'data-reticle-account-signout';
|
|
9
|
+
export const ACCOUNT_TEXT = {
|
|
10
|
+
SIGNED_OUT: 'Sign in',
|
|
11
|
+
SIGNIN_TITLE: 'Run `reticle login` in your terminal — click to copy the command',
|
|
12
|
+
SIGNIN_COMMAND: 'reticle login',
|
|
13
|
+
DASHBOARD_TITLE: 'Open this project on the dashboard',
|
|
14
|
+
SYNC_TITLE: 'Push to the dashboard now, instead of waiting for the next sync',
|
|
15
|
+
COPIED: 'Copied',
|
|
16
|
+
AVATAR_FALLBACK: '•',
|
|
17
|
+
MENU_LABEL: 'Reticle account',
|
|
18
|
+
TRIGGER_TITLE: 'Your Reticle account',
|
|
19
|
+
SIGNED_IN_AS: 'Signed in',
|
|
20
|
+
DASHBOARD_ACTION: 'Open dashboard',
|
|
21
|
+
LINK_HINT: 'Not linked — run `reticle link` to keep a record on the dashboard',
|
|
22
|
+
LINK_COMMAND: 'reticle link',
|
|
23
|
+
SIGNOUT_ACTION: 'Sign out',
|
|
24
|
+
SIGNOUT_TITLE: 'Run `reticle logout` in your terminal — click to copy the command',
|
|
25
|
+
SIGNOUT_COMMAND: 'reticle logout',
|
|
26
|
+
PROJECT_LABEL: 'Project',
|
|
27
|
+
HOST_LABEL: 'Host',
|
|
28
|
+
VERDICTS_LABEL: 'Verdicts',
|
|
29
|
+
DEFECTS_LABEL: 'Defects found',
|
|
30
|
+
};
|
|
31
|
+
const ACCOUNT_COPIED_MS = 1200;
|
|
32
|
+
export function accountInitials(org) {
|
|
33
|
+
const words = (org ?? '')
|
|
34
|
+
.trim()
|
|
35
|
+
.split(/[\s._-]+/)
|
|
36
|
+
.filter((word) => word.length > 0);
|
|
37
|
+
const letters = words
|
|
38
|
+
.slice(0, 2)
|
|
39
|
+
.map((word) => [...word][0] ?? '')
|
|
40
|
+
.join('');
|
|
41
|
+
return letters.length > 0 ? letters.toUpperCase() : ACCOUNT_TEXT.AVATAR_FALLBACK;
|
|
42
|
+
}
|
|
43
|
+
export function accountControlHtml(account, details = {}, offerSignIn = false) {
|
|
44
|
+
if (account === undefined)
|
|
45
|
+
return '';
|
|
46
|
+
if (!account.signedIn) {
|
|
47
|
+
if (!offerSignIn)
|
|
48
|
+
return '';
|
|
49
|
+
return `<div class="reticle-account" ${ACCOUNT_ROOT_ATTR}><button type="button" class="reticle-account-signin" ${ACCOUNT_SIGNIN_ATTR} title="${ACCOUNT_TEXT.SIGNIN_TITLE}" aria-label="${ACCOUNT_TEXT.SIGNIN_TITLE}">${ACCOUNT_TEXT.SIGNED_OUT}</button></div>`;
|
|
50
|
+
}
|
|
51
|
+
const org = account.org ?? '';
|
|
52
|
+
const label = org.length > 0 ? esc(org) : ACCOUNT_TEXT.SIGNED_IN_AS;
|
|
53
|
+
const avatar = `<span class="reticle-account-avatar" aria-hidden="true">${esc(accountInitials(account.org))}</span>`;
|
|
54
|
+
const trigger = `<button type="button" class="reticle-account-trigger" ${ACCOUNT_TRIGGER_ATTR}` +
|
|
55
|
+
` aria-haspopup="true" aria-expanded="false" title="${ACCOUNT_TEXT.TRIGGER_TITLE}"` +
|
|
56
|
+
` aria-label="${ACCOUNT_TEXT.TRIGGER_TITLE}: ${label}">${avatar}</button>`;
|
|
57
|
+
return `<div class="reticle-account" ${ACCOUNT_ROOT_ATTR}>${trigger}${accountMenuHtml(account, details)}</div>`;
|
|
58
|
+
}
|
|
59
|
+
function detailRow(key, value) {
|
|
60
|
+
if (value === undefined || 0 === value.length)
|
|
61
|
+
return '';
|
|
62
|
+
return `<div class="reticle-account-row"><span class="reticle-account-k">${esc(key)}</span><span class="reticle-account-v" title="${esc(value)}">${esc(value)}</span></div>`;
|
|
63
|
+
}
|
|
64
|
+
function accountMenuHtml(account, details) {
|
|
65
|
+
const org = account.org ?? '';
|
|
66
|
+
const identity = `<div class="reticle-account-head">` +
|
|
67
|
+
`<span class="reticle-account-avatar reticle-account-avatar--lg" aria-hidden="true">${esc(accountInitials(account.org))}</span>` +
|
|
68
|
+
`<span class="reticle-account-who">` +
|
|
69
|
+
`<span class="reticle-account-org">${org.length > 0 ? esc(org) : ACCOUNT_TEXT.SIGNED_IN_AS}</span>` +
|
|
70
|
+
(account.email !== undefined && account.email.length > 0
|
|
71
|
+
? `<span class="reticle-account-email" title="${esc(account.email)}">${esc(account.email)}</span>`
|
|
72
|
+
: `<span class="reticle-account-email">${ACCOUNT_TEXT.SIGNED_IN_AS}</span>`) +
|
|
73
|
+
`</span></div>`;
|
|
74
|
+
const rows = detailRow(ACCOUNT_TEXT.HOST_LABEL, account.host) +
|
|
75
|
+
detailRow(ACCOUNT_TEXT.PROJECT_LABEL, details.projectName) +
|
|
76
|
+
detailRow(ACCOUNT_TEXT.VERDICTS_LABEL, details.verdicts !== undefined ? String(details.verdicts) : undefined) +
|
|
77
|
+
detailRow(ACCOUNT_TEXT.DEFECTS_LABEL, details.defects !== undefined ? String(details.defects) : undefined);
|
|
78
|
+
const linked = details.dashboardUrl !== undefined && isSafeDashboardUrl(details.dashboardUrl);
|
|
79
|
+
const dashboard = linked
|
|
80
|
+
? `<a class="reticle-account-action" href="${esc(details.dashboardUrl ?? '')}" target="_blank" rel="noreferrer noopener" title="${ACCOUNT_TEXT.DASHBOARD_TITLE}">${hiIconHtml(PresenterIcon.VIEW, PRESENTER_ICON_SIZE.HELP)}<span>${ACCOUNT_TEXT.DASHBOARD_ACTION}</span></a>`
|
|
81
|
+
:
|
|
82
|
+
`<button type="button" class="reticle-account-action reticle-account-action--hint" ${ACCOUNT_SIGNIN_ATTR} data-reticle-copy="${ACCOUNT_TEXT.LINK_COMMAND}" title="${ACCOUNT_TEXT.LINK_HINT}"><span>${ACCOUNT_TEXT.LINK_HINT}</span></button>`;
|
|
83
|
+
const signOut = `<button type="button" class="reticle-account-action" ${ACCOUNT_SIGNOUT_ATTR} data-reticle-copy="${ACCOUNT_TEXT.SIGNOUT_COMMAND}" title="${ACCOUNT_TEXT.SIGNOUT_TITLE}"><span>${ACCOUNT_TEXT.SIGNOUT_ACTION}</span></button>`;
|
|
84
|
+
return (`<div class="reticle-account-menu" ${ACCOUNT_MENU_ATTR} role="menu" aria-label="${ACCOUNT_TEXT.MENU_LABEL}" aria-hidden="true" hidden>` +
|
|
85
|
+
identity +
|
|
86
|
+
(rows.length > 0 ? `<div class="reticle-account-rows">${rows}</div>` : '') +
|
|
87
|
+
`<div class="reticle-account-actions">${dashboard}${signOut}</div>` +
|
|
88
|
+
`</div>`);
|
|
89
|
+
}
|
|
90
|
+
export function accountCapsuleHtml(account, dashboardUrl, offerSignIn = false) {
|
|
91
|
+
return accountControlHtml(account, { dashboardUrl }, offerSignIn);
|
|
92
|
+
}
|
|
93
|
+
export function syncButtonHtml(dashboardUrl) {
|
|
94
|
+
if (dashboardUrl === undefined || !isSafeDashboardUrl(dashboardUrl))
|
|
95
|
+
return '';
|
|
96
|
+
return `<button type="button" class="reticle-sync-now" ${SYNC_BTN_ATTR} title="${ACCOUNT_TEXT.SYNC_TITLE}" aria-label="${ACCOUNT_TEXT.SYNC_TITLE}">${hiIconHtml(PresenterIcon.SYNC, PRESENTER_ICON_SIZE.HELP)}</button>`;
|
|
97
|
+
}
|
|
98
|
+
export function mountAccountControl(root) {
|
|
99
|
+
const controller = new AbortController();
|
|
100
|
+
const { signal } = controller;
|
|
101
|
+
const closeAll = (except) => {
|
|
102
|
+
for (const menu of root.querySelectorAll(`[${ACCOUNT_MENU_ATTR}]`)) {
|
|
103
|
+
if (menu === except)
|
|
104
|
+
continue;
|
|
105
|
+
menu.setAttribute('hidden', '');
|
|
106
|
+
menu.setAttribute('aria-hidden', 'true');
|
|
107
|
+
}
|
|
108
|
+
for (const trigger of root.querySelectorAll(`[${ACCOUNT_TRIGGER_ATTR}]`)) {
|
|
109
|
+
if (except !== undefined && trigger.parentElement === except.parentElement)
|
|
110
|
+
continue;
|
|
111
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const flashCopied = (el) => {
|
|
115
|
+
const label = el.querySelector('span') ?? el;
|
|
116
|
+
const previous = label.textContent;
|
|
117
|
+
label.textContent = ACCOUNT_TEXT.COPIED;
|
|
118
|
+
setTimeout(() => {
|
|
119
|
+
label.textContent = previous;
|
|
120
|
+
}, ACCOUNT_COPIED_MS);
|
|
121
|
+
};
|
|
122
|
+
root.addEventListener('click', (event) => {
|
|
123
|
+
const target = event.target;
|
|
124
|
+
if (!(target instanceof Element))
|
|
125
|
+
return;
|
|
126
|
+
const trigger = target.closest(`[${ACCOUNT_TRIGGER_ATTR}]`);
|
|
127
|
+
if (trigger instanceof HTMLElement) {
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
event.stopPropagation();
|
|
130
|
+
const menu = trigger.parentElement?.querySelector(`[${ACCOUNT_MENU_ATTR}]`);
|
|
131
|
+
if (!(menu instanceof HTMLElement))
|
|
132
|
+
return;
|
|
133
|
+
const wasOpen = !menu.hasAttribute('hidden');
|
|
134
|
+
closeAll();
|
|
135
|
+
if (wasOpen)
|
|
136
|
+
return;
|
|
137
|
+
menu.removeAttribute('hidden');
|
|
138
|
+
menu.setAttribute('aria-hidden', 'false');
|
|
139
|
+
trigger.setAttribute('aria-expanded', 'true');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const copier = target.closest('[data-reticle-copy]');
|
|
143
|
+
if (copier instanceof HTMLElement) {
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
event.stopPropagation();
|
|
146
|
+
const command = copier.getAttribute('data-reticle-copy') ?? '';
|
|
147
|
+
if (command.length > 0)
|
|
148
|
+
void navigator.clipboard?.writeText(command).catch(() => undefined);
|
|
149
|
+
flashCopied(copier);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const signin = target.closest(`[${ACCOUNT_SIGNIN_ATTR}]`);
|
|
153
|
+
if (signin instanceof HTMLElement) {
|
|
154
|
+
event.preventDefault();
|
|
155
|
+
event.stopPropagation();
|
|
156
|
+
void navigator.clipboard?.writeText(ACCOUNT_TEXT.SIGNIN_COMMAND).catch(() => undefined);
|
|
157
|
+
flashCopied(signin);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
closeAll();
|
|
161
|
+
}, { signal });
|
|
162
|
+
document.addEventListener('pointerdown', (event) => {
|
|
163
|
+
const target = event.target;
|
|
164
|
+
if (target instanceof Node && root.contains(target))
|
|
165
|
+
return;
|
|
166
|
+
closeAll();
|
|
167
|
+
}, { signal });
|
|
168
|
+
document.addEventListener('keydown', (event) => {
|
|
169
|
+
if ('Escape' !== event.key)
|
|
170
|
+
return;
|
|
171
|
+
const open = root.querySelector(`[${ACCOUNT_MENU_ATTR}]:not([hidden])`);
|
|
172
|
+
if (null === open)
|
|
173
|
+
return;
|
|
174
|
+
event.stopPropagation();
|
|
175
|
+
closeAll();
|
|
176
|
+
}, { signal });
|
|
177
|
+
return () => controller.abort();
|
|
178
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HumanControlKind } from '@reticlehq/core';
|
|
2
2
|
export declare const BorderMode: {
|
|
3
3
|
readonly SESSION: "session";
|
|
4
4
|
readonly BUSY: "busy";
|
|
@@ -36,7 +36,9 @@ export declare const HEARTBEAT_MS = 1000;
|
|
|
36
36
|
export declare const IDLE_NOTICE_MS = 4000;
|
|
37
37
|
export declare const ACT_STRIP: {
|
|
38
38
|
readonly READY: "Ready";
|
|
39
|
-
readonly
|
|
39
|
+
readonly PLANNING_PREFIX: "planning next action · ";
|
|
40
|
+
readonly STOPPED: "stopped";
|
|
41
|
+
readonly STOPPED_PREFIX: "stopped · ";
|
|
40
42
|
readonly NOW: "now";
|
|
41
43
|
readonly SINCE_LAST: " since last action";
|
|
42
44
|
};
|
|
@@ -92,3 +94,8 @@ export declare const HUD_DOCK_MARGIN_PX = 8;
|
|
|
92
94
|
export declare const HUD_DRAG_IGNORE_SEL: string;
|
|
93
95
|
export declare const THROTTLED_ATTR = "data-reticle-throttled";
|
|
94
96
|
export declare const Z_HUD_TOOLTIP = 100;
|
|
97
|
+
export interface ControlIntent {
|
|
98
|
+
kind: HumanControlKind;
|
|
99
|
+
text?: string;
|
|
100
|
+
}
|
|
101
|
+
export type ControlHandler = (intent: ControlIntent) => void;
|
|
@@ -19,7 +19,9 @@ export const HEARTBEAT_MS = 1000;
|
|
|
19
19
|
export const IDLE_NOTICE_MS = 4000;
|
|
20
20
|
export const ACT_STRIP = {
|
|
21
21
|
READY: 'Ready',
|
|
22
|
-
|
|
22
|
+
PLANNING_PREFIX: 'planning next action · ',
|
|
23
|
+
STOPPED: 'stopped',
|
|
24
|
+
STOPPED_PREFIX: 'stopped · ',
|
|
23
25
|
NOW: 'now',
|
|
24
26
|
SINCE_LAST: ' since last action',
|
|
25
27
|
};
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { HumanControlKind, PresenterTone, SessionState } from '@reticlehq/core';
|
|
2
|
+
export type { ControlIntent, ControlHandler } from './presenter-config.js';
|
|
2
3
|
export declare const ENDED_FADE_MS = 4000;
|
|
3
|
-
export interface ControlIntent {
|
|
4
|
-
kind: HumanControlKind;
|
|
5
|
-
text?: string;
|
|
6
|
-
}
|
|
7
|
-
export type ControlHandler = (intent: ControlIntent) => void;
|
|
8
4
|
export declare const CONTROLS_CSS: string;
|
|
9
5
|
export declare const CONTROLS_TOOLBAR_HTML: string;
|
|
10
6
|
export declare const CONTROLS_BANNER_HTML = "<div data-reticle-banner class=\"reticle-banner\">Session ended</div>";
|
|
@@ -28,4 +24,3 @@ export declare class ControlPanel {
|
|
|
28
24
|
refilterFlows(): void;
|
|
29
25
|
setState(state: SessionState, text?: string, tone?: PresenterTone): void;
|
|
30
26
|
}
|
|
31
|
-
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { HumanControlKind, PresenterTone, SessionState } from '@reticlehq/core';
|
|
2
|
-
import { nativeSetTimeout, nativeClearTimeout } from '../timers/native-timers.js';
|
|
2
|
+
import { nativeSetTimeout, nativeClearTimeout } from '../timers/native/native-timers.js';
|
|
3
3
|
import { CHAT_TOGGLE_ATTR, CLEAR_MARKS_ATTR, MARKERS_BTN_ATTR } from './presenter-config.js';
|
|
4
|
-
import { PresenterIcon, PRESENTER_ICON_SIZE, hiIcon, hiIconHtml, hiToggleIconHtml, } from './presenter-icons.js';
|
|
4
|
+
import { PresenterIcon, PRESENTER_ICON_SIZE, hiIcon, hiIconHtml, hiToggleIconHtml, } from './icons/presenter-icons.js';
|
|
5
5
|
import { getPresenterSettings } from './presenter-settings.js';
|
|
6
6
|
import { mountWorkspaceSelector, workspaceRowHtml } from './presenter-workspace.js';
|
|
7
7
|
const DATA_RETICLE_STATE = 'data-reticle-state';
|
|
@@ -57,6 +57,11 @@ export const CONTROLS_CSS = `
|
|
|
57
57
|
background:rgba(255,255,255,.06);color:rgba(255,255,255,.7);cursor:pointer;line-height:0;transition:background .12s,color .12s;}
|
|
58
58
|
[data-reticle-chat-panel] .reticle-workspace-copy:hover:not(:disabled){background:rgba(255,255,255,.12);color:#fff;}
|
|
59
59
|
[data-reticle-chat-panel] .reticle-workspace-copy:disabled{opacity:.35;cursor:not-allowed;}
|
|
60
|
+
/* The account capsule, to the right of the menu title. Matches the local idiom in this block:
|
|
61
|
+
the workspace menu predates the design tokens used in the report panel and states its own
|
|
62
|
+
greys, so a token here would be the only one and would not match its neighbours. */
|
|
63
|
+
[data-reticle-chat-panel] .reticle-workspace-menu-actions{
|
|
64
|
+
display:flex;align-items:center;gap:6px;}
|
|
60
65
|
[data-reticle-chat-panel] .reticle-workspace-menu-row{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding:4px 0;}
|
|
61
66
|
[data-reticle-chat-panel] .reticle-workspace-menu-k{flex:none;color:rgba(255,255,255,.42);}
|
|
62
67
|
[data-reticle-chat-panel] .reticle-workspace-menu-v{min-width:0;text-align:right;color:rgba(255,255,255,.9);font-weight:500;word-break:break-all;}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CHAT_ATTR, CHAT_PANEL_ATTR, CHAT_PLACEMENT_ATTR, DOCK_ALIGN_ATTR, DOCK_ATTR, HUD_DOCK_MARGIN_PX, Placement, SETTINGS_ATTR, SETTINGS_PANEL_ATTR, SETTINGS_PLACEMENT_ATTR, } from './presenter-config.js';
|
|
2
|
-
import { applyHudPosition, isHudDragged, readHudPosition, relayoutHudPosition, } from './presenter-
|
|
2
|
+
import { applyHudPosition, isHudDragged, readHudPosition, relayoutHudPosition, } from './presenter-hud-position.js';
|
|
3
3
|
const HUD_ATTR = 'data-reticle-hud';
|
|
4
4
|
const DOCK_PANEL_GAP_PX = 8;
|
|
5
5
|
const DOCK_SETTINGS_GAP_PX = 12;
|
|
@@ -1,26 +1,8 @@
|
|
|
1
|
+
export { applyHudPosition, isHudDragged, readHudPosition, relayoutHudPosition, clampHudPosition, hudLayoutBox, resetHudDockPosition, } from './presenter-hud-position.js';
|
|
1
2
|
interface HudDragCallbacks {
|
|
2
3
|
onDragMove?: () => void;
|
|
3
4
|
onDragEnd?: (moved: boolean) => void;
|
|
4
5
|
}
|
|
5
|
-
export declare function isHudDragged(hud: HTMLElement): boolean;
|
|
6
|
-
export declare function clampHudPosition(left: number, top: number, width: number, height: number, viewportWidth: number, viewportHeight: number, margin?: number): {
|
|
7
|
-
left: number;
|
|
8
|
-
top: number;
|
|
9
|
-
};
|
|
10
|
-
export declare function applyHudPosition(hud: HTMLElement, left: number, top: number): void;
|
|
11
|
-
export declare function resetHudDockPosition(hud: HTMLElement): void;
|
|
12
|
-
export declare function hudLayoutBox(hud: HTMLElement): {
|
|
13
|
-
left: number;
|
|
14
|
-
top: number;
|
|
15
|
-
width: number;
|
|
16
|
-
height: number;
|
|
17
|
-
};
|
|
18
|
-
export declare function readHudPosition(hud: HTMLElement): {
|
|
19
|
-
left: number;
|
|
20
|
-
top: number;
|
|
21
|
-
};
|
|
22
|
-
export declare function relayoutHudPosition(hud: HTMLElement): void;
|
|
23
6
|
export declare function installHudPositionGuards(hud: HTMLElement, overlay: HTMLElement): () => void;
|
|
24
7
|
export declare function installHudDrag(hud: HTMLElement, head: HTMLElement, callbacks?: HudDragCallbacks): () => void;
|
|
25
8
|
export declare function installHudDragHandles(hud: HTMLElement, handles: HTMLElement[], callbacks?: HudDragCallbacks): () => void;
|
|
26
|
-
export {};
|
|
@@ -1,55 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HUD_DRAG_IGNORE_SEL, HUD_DRAG_THRESHOLD_PX, MIN_ATTR, CHAT_ATTR, SETTINGS_ATTR, } from './presenter-config.js';
|
|
2
2
|
import { scheduleSyncDockLayout } from './presenter-dock-layout.js';
|
|
3
|
+
import { applyHudPosition, clampHudPosition, hudLayoutBox, relayoutHudPosition, } from './presenter-hud-position.js';
|
|
4
|
+
export { applyHudPosition, isHudDragged, readHudPosition, relayoutHudPosition, clampHudPosition, hudLayoutBox, resetHudDockPosition, } from './presenter-hud-position.js';
|
|
3
5
|
const DRAG_HANDLE_DRAGGING_CLASS = 'reticle-drag-handle--dragging';
|
|
4
|
-
export function isHudDragged(hud) {
|
|
5
|
-
return '1' === hud.getAttribute(HUD_DRAGGED_ATTR);
|
|
6
|
-
}
|
|
7
|
-
export function clampHudPosition(left, top, width, height, viewportWidth, viewportHeight, margin = HUD_DOCK_MARGIN_PX) {
|
|
8
|
-
const minLeft = margin;
|
|
9
|
-
const minTop = margin;
|
|
10
|
-
const maxLeft = Math.max(minLeft, viewportWidth - width - margin);
|
|
11
|
-
const maxTop = Math.max(minTop, viewportHeight - height - margin);
|
|
12
|
-
return {
|
|
13
|
-
left: Math.max(minLeft, Math.min(left, maxLeft)),
|
|
14
|
-
top: Math.max(minTop, Math.min(top, maxTop)),
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
export function applyHudPosition(hud, left, top) {
|
|
18
|
-
hud.setAttribute(HUD_DRAGGED_ATTR, '1');
|
|
19
|
-
hud.style.setProperty(HUD_POS_X_VAR, `${String(left)}px`);
|
|
20
|
-
hud.style.setProperty(HUD_POS_Y_VAR, `${String(top)}px`);
|
|
21
|
-
}
|
|
22
|
-
export function resetHudDockPosition(hud) {
|
|
23
|
-
hud.removeAttribute(HUD_DRAGGED_ATTR);
|
|
24
|
-
hud.style.removeProperty(HUD_POS_X_VAR);
|
|
25
|
-
hud.style.removeProperty(HUD_POS_Y_VAR);
|
|
26
|
-
}
|
|
27
|
-
export function hudLayoutBox(hud) {
|
|
28
|
-
const rect = hud.getBoundingClientRect();
|
|
29
|
-
return {
|
|
30
|
-
left: rect.left,
|
|
31
|
-
top: rect.top,
|
|
32
|
-
width: rect.width,
|
|
33
|
-
height: rect.height,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
export function readHudPosition(hud) {
|
|
37
|
-
const x = hud.style.getPropertyValue(HUD_POS_X_VAR);
|
|
38
|
-
const y = hud.style.getPropertyValue(HUD_POS_Y_VAR);
|
|
39
|
-
if (x !== '' && y !== '') {
|
|
40
|
-
return { left: Number.parseFloat(x), top: Number.parseFloat(y) };
|
|
41
|
-
}
|
|
42
|
-
const rect = hud.getBoundingClientRect();
|
|
43
|
-
return { left: rect.left, top: rect.top };
|
|
44
|
-
}
|
|
45
|
-
export function relayoutHudPosition(hud) {
|
|
46
|
-
if (!isHudDragged(hud))
|
|
47
|
-
return;
|
|
48
|
-
const { left, top } = readHudPosition(hud);
|
|
49
|
-
const { width, height } = hudLayoutBox(hud);
|
|
50
|
-
const next = clampHudPosition(left, top, width, height, window.innerWidth, window.innerHeight);
|
|
51
|
-
applyHudPosition(hud, next.left, next.top);
|
|
52
|
-
}
|
|
53
6
|
export function installHudPositionGuards(hud, overlay) {
|
|
54
7
|
const scheduleRelayout = () => {
|
|
55
8
|
scheduleSyncDockLayout(hud, overlay);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PresenterMode } from '@reticlehq/core';
|
|
2
|
-
import { nativeSetTimeout, nativeClearTimeout } from '../timers/native-timers.js';
|
|
2
|
+
import { nativeSetTimeout, nativeClearTimeout } from '../timers/native/native-timers.js';
|
|
3
3
|
import { GlowPhase, BorderMode, DATA_BUSY, BUSY_ON, BUSY_OFF, GLOW_ON, GLOW_OFF, DATA_ON, } from './presenter-config.js';
|
|
4
4
|
export class GlowController {
|
|
5
5
|
#phase = GlowPhase.IDLE;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare function isHudDragged(hud: HTMLElement): boolean;
|
|
2
|
+
export declare function clampHudPosition(left: number, top: number, width: number, height: number, viewportWidth: number, viewportHeight: number, margin?: number): {
|
|
3
|
+
left: number;
|
|
4
|
+
top: number;
|
|
5
|
+
};
|
|
6
|
+
export declare function applyHudPosition(hud: HTMLElement, left: number, top: number): void;
|
|
7
|
+
export declare function resetHudDockPosition(hud: HTMLElement): void;
|
|
8
|
+
export declare function hudLayoutBox(hud: HTMLElement): {
|
|
9
|
+
left: number;
|
|
10
|
+
top: number;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
};
|
|
14
|
+
export declare function readHudPosition(hud: HTMLElement): {
|
|
15
|
+
left: number;
|
|
16
|
+
top: number;
|
|
17
|
+
};
|
|
18
|
+
export declare function relayoutHudPosition(hud: HTMLElement): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { HUD_DOCK_MARGIN_PX, HUD_DRAGGED_ATTR, HUD_POS_X_VAR, HUD_POS_Y_VAR, } from './presenter-config.js';
|
|
2
|
+
export function isHudDragged(hud) {
|
|
3
|
+
return '1' === hud.getAttribute(HUD_DRAGGED_ATTR);
|
|
4
|
+
}
|
|
5
|
+
export function clampHudPosition(left, top, width, height, viewportWidth, viewportHeight, margin = HUD_DOCK_MARGIN_PX) {
|
|
6
|
+
const minLeft = margin;
|
|
7
|
+
const minTop = margin;
|
|
8
|
+
const maxLeft = Math.max(minLeft, viewportWidth - width - margin);
|
|
9
|
+
const maxTop = Math.max(minTop, viewportHeight - height - margin);
|
|
10
|
+
return {
|
|
11
|
+
left: Math.max(minLeft, Math.min(left, maxLeft)),
|
|
12
|
+
top: Math.max(minTop, Math.min(top, maxTop)),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function applyHudPosition(hud, left, top) {
|
|
16
|
+
hud.setAttribute(HUD_DRAGGED_ATTR, '1');
|
|
17
|
+
hud.style.setProperty(HUD_POS_X_VAR, `${String(left)}px`);
|
|
18
|
+
hud.style.setProperty(HUD_POS_Y_VAR, `${String(top)}px`);
|
|
19
|
+
}
|
|
20
|
+
export function resetHudDockPosition(hud) {
|
|
21
|
+
hud.removeAttribute(HUD_DRAGGED_ATTR);
|
|
22
|
+
hud.style.removeProperty(HUD_POS_X_VAR);
|
|
23
|
+
hud.style.removeProperty(HUD_POS_Y_VAR);
|
|
24
|
+
}
|
|
25
|
+
export function hudLayoutBox(hud) {
|
|
26
|
+
const rect = hud.getBoundingClientRect();
|
|
27
|
+
return {
|
|
28
|
+
left: rect.left,
|
|
29
|
+
top: rect.top,
|
|
30
|
+
width: rect.width,
|
|
31
|
+
height: rect.height,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function readHudPosition(hud) {
|
|
35
|
+
const x = hud.style.getPropertyValue(HUD_POS_X_VAR);
|
|
36
|
+
const y = hud.style.getPropertyValue(HUD_POS_Y_VAR);
|
|
37
|
+
if (x !== '' && y !== '') {
|
|
38
|
+
return { left: Number.parseFloat(x), top: Number.parseFloat(y) };
|
|
39
|
+
}
|
|
40
|
+
const rect = hud.getBoundingClientRect();
|
|
41
|
+
return { left: rect.left, top: rect.top };
|
|
42
|
+
}
|
|
43
|
+
export function relayoutHudPosition(hud) {
|
|
44
|
+
if (!isHudDragged(hud))
|
|
45
|
+
return;
|
|
46
|
+
const { left, top } = readHudPosition(hud);
|
|
47
|
+
const { width, height } = hudLayoutBox(hud);
|
|
48
|
+
const next = clampHudPosition(left, top, width, height, window.innerWidth, window.innerHeight);
|
|
49
|
+
applyHudPosition(hud, next.left, next.top);
|
|
50
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare const OFFER_ATTR = "data-reticle-offer";
|
|
2
|
+
export declare const OFFER_SLOT_ATTR = "data-reticle-offer-slot";
|
|
3
|
+
export declare const OFFER_CLAIM_ATTR = "data-reticle-offer-claim";
|
|
4
|
+
export declare const OFFER_DISMISS_ATTR = "data-reticle-offer-dismiss";
|
|
5
|
+
export declare const OFFER_DISMISSED_KEY = "reticle.harnessOffer.dismissed";
|
|
6
|
+
export declare const OFFER_TEXT: {
|
|
7
|
+
readonly HEADLINE: "Reticle drives your app for you";
|
|
8
|
+
readonly BODY: "The harness explores your app, proves what works, and saves every journey as a test that replays for free. Built on Jev — no model API key needed.";
|
|
9
|
+
readonly FREE: "Free for 3 months";
|
|
10
|
+
readonly CLAIM: "Claim now";
|
|
11
|
+
readonly DISMISS: "Not now";
|
|
12
|
+
readonly claimed: (days: number) => string;
|
|
13
|
+
readonly ending: (days: number) => string;
|
|
14
|
+
};
|
|
15
|
+
export declare function offerDismissed(storage: Pick<Storage, 'getItem'> | undefined): boolean;
|
|
16
|
+
export declare function dismissOffer(storage: Pick<Storage, 'setItem'> | undefined): void;
|
|
17
|
+
export interface OfferState {
|
|
18
|
+
claimed: boolean;
|
|
19
|
+
daysRemaining?: number | undefined;
|
|
20
|
+
claimUrl?: string | undefined;
|
|
21
|
+
eligible?: boolean | undefined;
|
|
22
|
+
}
|
|
23
|
+
export declare function offerHtml(offer: OfferState | undefined, dismissed: boolean): string;
|
|
24
|
+
export declare function paintOffer(root: ParentNode, offer: OfferState | undefined, storage: Pick<Storage, 'getItem' | 'setItem'> | undefined): void;
|
|
25
|
+
export declare const OFFER_CSS = "\n.reticle-offer{margin:8px 10px;padding:10px 12px;border:1px solid var(--reticle-line,#2a2f3a);border-radius:10px;background:var(--reticle-surface-inset,rgba(255,255,255,.03));}\n.reticle-offer-head{display:flex;align-items:center;justify-content:space-between;gap:8px;}\n.reticle-offer-title{font-weight:600;font-size:12px;}\n.reticle-offer-free{font-size:10px;padding:2px 6px;border-radius:999px;background:var(--reticle-accent,#4f7cff);color:#fff;white-space:nowrap;}\n.reticle-offer-body{margin:6px 0 8px;font-size:11px;line-height:1.45;opacity:.8;}\n.reticle-offer-actions{display:flex;align-items:center;gap:8px;}\n.reticle-offer-claim{font-size:11px;font-weight:600;padding:4px 10px;border-radius:6px;background:var(--reticle-accent,#4f7cff);color:#fff;text-decoration:none;}\n.reticle-offer-dismiss{font-size:11px;padding:4px 8px;border-radius:6px;background:none;border:0;color:inherit;opacity:.6;cursor:pointer;}\n.reticle-offer-dismiss:hover{opacity:1;}\n.reticle-offer-claimed{margin:6px 10px;font-size:10px;opacity:.6;}\n";
|