clay-server 4.1.0-beta.9 → 4.1.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/lib/context-usage.js +47 -0
- package/lib/issues-mcp-server.js +1 -1
- package/lib/issues-service.js +3 -1
- package/lib/loop-registry-occurrence.js +1 -0
- package/lib/notes.js +20 -3
- package/lib/os-users.js +6 -0
- package/lib/project-connection.js +14 -3
- package/lib/project-file-path.js +13 -0
- package/lib/project-file-watch.js +19 -8
- package/lib/project-filesystem.js +10 -16
- package/lib/project-http.js +53 -48
- package/lib/project-logs-mcp-server.js +8 -8
- package/lib/project-logs-schema.js +1 -1
- package/lib/project-logs-service.js +46 -13
- package/lib/project-logs-store.js +17 -15
- package/lib/project-logs.js +70 -0
- package/lib/project-loop.js +1 -0
- package/lib/project-notifications.js +1 -1
- package/lib/project-scheduled-result-store.js +131 -0
- package/lib/project-scheduled-task-execution.js +35 -2
- package/lib/project-session-notes.js +11 -4
- package/lib/project-sessions.js +7 -0
- package/lib/project-user-message.js +47 -0
- package/lib/project.js +53 -3
- package/lib/public/app.js +26 -9
- package/lib/public/css/avatar-imprints.css +1 -1
- package/lib/public/css/command-palette.css +20 -0
- package/lib/public/css/git-panel.css +13 -12
- package/lib/public/css/git-placard.css +4 -4
- package/lib/public/css/issues.css +78 -31
- package/lib/public/css/mobile-nav.css +7 -0
- package/lib/public/css/notifications-center.css +3 -3
- package/lib/public/css/project-logs.css +15 -0
- package/lib/public/css/sidebar.css +7 -0
- package/lib/public/css/sticky-notes.css +29 -0
- package/lib/public/modules/app-connection.js +9 -1
- package/lib/public/modules/app-favicon.js +10 -0
- package/lib/public/modules/app-messages.js +43 -12
- package/lib/public/modules/app-notifications.js +2 -0
- package/lib/public/modules/app-panels.js +77 -23
- package/lib/public/modules/app-projects.js +2 -0
- package/lib/public/modules/app-rate-limit.js +148 -61
- package/lib/public/modules/avatar-imprint.js +25 -70
- package/lib/public/modules/avatar.js +12 -4
- package/lib/public/modules/chat-render-runtime.js +20 -0
- package/lib/public/modules/clay-issue-links.js +23 -2
- package/lib/public/modules/clay-log-links.js +11 -1
- package/lib/public/modules/context-view-preference.js +143 -0
- package/lib/public/modules/default-vendor.js +116 -0
- package/lib/public/modules/filebrowser-tabs.js +1 -0
- package/lib/public/modules/filebrowser.js +45 -18
- package/lib/public/modules/issues-render.js +36 -10
- package/lib/public/modules/issues.js +95 -13
- package/lib/public/modules/markdown.js +3 -1
- package/lib/public/modules/pane-bridge.js +14 -8
- package/lib/public/modules/pane-issue-bridge.js +27 -0
- package/lib/public/modules/permission-hints.js +13 -0
- package/lib/public/modules/profile.js +9 -9
- package/lib/public/modules/project-activation.js +49 -0
- package/lib/public/modules/project-logs-render.js +13 -3
- package/lib/public/modules/project-logs.js +45 -15
- package/lib/public/modules/right-workbench.js +44 -0
- package/lib/public/modules/scheduled-result-client.js +33 -0
- package/lib/public/modules/scheduled-result-display.js +50 -0
- package/lib/public/modules/scheduled-result-notification.js +31 -0
- package/lib/public/modules/scheduled-task-presentation.js +15 -0
- package/lib/public/modules/scheduled-tasks.js +9 -21
- package/lib/public/modules/sidebar-mobile.js +51 -3
- package/lib/public/modules/sidebar-sessions.js +65 -5
- package/lib/public/modules/split-session-boundary.js +28 -0
- package/lib/public/modules/split-view.js +42 -7
- package/lib/public/modules/sticky-notes-browser.js +130 -18
- package/lib/public/modules/sticky-notes.js +2 -3
- package/lib/public/modules/terminal.js +4 -4
- package/lib/public/modules/thinking-lifecycle.js +4 -1
- package/lib/public/modules/tools.js +5 -1
- package/lib/scheduled-run-state.js +10 -2
- package/lib/scheduler.js +1 -0
- package/lib/sdk-bridge.js +97 -61
- package/lib/sdk-message-processor.js +44 -13
- package/lib/sdk-skill-discovery.js +6 -28
- package/lib/sdk-turn-queue.js +14 -0
- package/lib/server-context-view.js +53 -0
- package/lib/server-default-vendor.js +60 -0
- package/lib/server.js +23 -6
- package/lib/session-notes-mcp-server.js +3 -5
- package/lib/session-pair-factory.js +2 -2
- package/lib/session-title-generator.js +89 -0
- package/lib/session-title-policy.js +8 -2
- package/lib/sessions.js +26 -1
- package/lib/user-presence.js +6 -0
- package/lib/users-context-view-preferences.js +65 -0
- package/lib/users-default-vendor-preferences.js +80 -0
- package/lib/users.js +12 -0
- package/lib/ws-schema.js +11 -0
- package/lib/yoke/acp-agent-profiles.js +15 -2
- package/lib/yoke/adapters/acp.js +3 -2
- package/lib/yoke/adapters/antigravity.js +2 -2
- package/lib/yoke/adapters/claude-worker.js +11 -12
- package/lib/yoke/adapters/claude.js +75 -22
- package/lib/yoke/adapters/codex.js +216 -29
- package/lib/yoke/adapters/kiro.js +6 -5
- package/lib/yoke/claude-context-usage.js +57 -0
- package/lib/yoke/claude-message-correlation.js +31 -0
- package/lib/yoke/claude-model-switch.js +15 -0
- package/lib/yoke/index.js +5 -1
- package/lib/yoke/mcp-bridge-server.js +18 -5
- package/lib/yoke/session-skill-catalog.js +45 -0
- package/lib/yoke/skill-discovery.js +60 -20
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +5 -7
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Local pixel identities: deterministic SVG avatars derived from a stable seed.
|
|
2
2
|
|
|
3
3
|
var PAPER = "#f1efe8";
|
|
4
4
|
var INK = "#1b1b1a";
|
|
5
|
-
var LIGHT_BASE = "#e7e5de";
|
|
6
|
-
var LIGHT_FIELD = "#333330";
|
|
7
5
|
var MUTED_INDIGO = "#72778f";
|
|
6
|
+
var IDENTICON_PALETTE = ["#333330", MUTED_INDIGO, "#6f8382", "#887b91", "#8a806a"];
|
|
8
7
|
var MATE_MARK_PALETTE = [
|
|
9
8
|
{ background: "#6f7489", foreground: PAPER },
|
|
10
9
|
{ background: "#78816c", foreground: PAPER },
|
|
@@ -24,25 +23,6 @@ function hashString(value) {
|
|
|
24
23
|
return hash >>> 0;
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
function makeRandom(seed) {
|
|
28
|
-
var state = seed >>> 0;
|
|
29
|
-
return function nextRandom() {
|
|
30
|
-
state += 0x6d2b79f5;
|
|
31
|
-
var value = state;
|
|
32
|
-
value = Math.imul(value ^ (value >>> 15), value | 1);
|
|
33
|
-
value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
|
|
34
|
-
return ((value ^ (value >>> 14)) >>> 0) / 4294967296;
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function between(random, min, max) {
|
|
39
|
-
return min + random() * (max - min);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function number(value) {
|
|
43
|
-
return Math.round(value * 100) / 100;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
26
|
function xmlText(value) {
|
|
47
27
|
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
48
28
|
}
|
|
@@ -54,62 +34,37 @@ function legacyIdentity(style, seed) {
|
|
|
54
34
|
return "legacy:" + sourceStyle + ":" + sourceSeed;
|
|
55
35
|
}
|
|
56
36
|
|
|
57
|
-
function curvePath(y, bow, endOffset) {
|
|
58
|
-
return "M -28 " + number(y) +
|
|
59
|
-
" C 17 " + number(y + bow) +
|
|
60
|
-
", 71 " + number(y - bow) +
|
|
61
|
-
", 124 " + number(y + endOffset);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function cutPath(random) {
|
|
65
|
-
var start = between(random, 37, 52);
|
|
66
|
-
var first = between(random, 14, 84);
|
|
67
|
-
var second = between(random, 12, 86);
|
|
68
|
-
var end = between(random, 40, 64);
|
|
69
|
-
return "M -22 " + number(start) + " C 21 " + number(first) + ", 73 " + number(second) + ", 118 " + number(end);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
37
|
export function imprintSvg(options) {
|
|
73
38
|
var settings = options || {};
|
|
74
39
|
var size = Math.max(12, Math.min(256, Number(settings.size) || 64));
|
|
75
40
|
var identity = legacyIdentity(settings.style, settings.seed);
|
|
76
41
|
var hash = hashString(identity);
|
|
77
|
-
var
|
|
78
|
-
var
|
|
79
|
-
var
|
|
80
|
-
var
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
var firstBow = between(random, -34, 34);
|
|
89
|
-
var secondBow = between(random, -32, 32);
|
|
90
|
-
var firstPath = curvePath(firstY, firstBow, 6);
|
|
91
|
-
var secondPath = curvePath(secondY, -secondBow, -5);
|
|
92
|
-
var contours = "";
|
|
42
|
+
var cells = identiconCells(hash);
|
|
43
|
+
var foreground = IDENTICON_PALETTE[hash % IDENTICON_PALETTE.length];
|
|
44
|
+
var pixels = "";
|
|
45
|
+
for (var i = 0; i < cells.length; i++) {
|
|
46
|
+
pixels += '<rect x="' + (cells[i][0] + 1) + '" y="' + (cells[i][1] + 1) + '" width="1" height="1"/>';
|
|
47
|
+
}
|
|
48
|
+
return '<svg xmlns="http://www.w3.org/2000/svg" width="' + size + '" height="' + size +
|
|
49
|
+
'" viewBox="0 0 7 7" shape-rendering="crispEdges"><rect width="7" height="7" fill="' + PAPER +
|
|
50
|
+
'"/><g fill="' + foreground + '">' + pixels + '</g><rect x="0.5" y="0.5" width="6" height="6" fill="none" stroke="' +
|
|
51
|
+
INK + '" stroke-opacity="0.14" stroke-width="0.08"/></svg>';
|
|
52
|
+
}
|
|
93
53
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
54
|
+
// Generate the left three columns and mirror them into a compact 5x5 grid.
|
|
55
|
+
export function identiconCells(hash) {
|
|
56
|
+
var value = hash >>> 0;
|
|
57
|
+
var cells = [];
|
|
58
|
+
for (var row = 0; row < 5; row++) {
|
|
59
|
+
for (var column = 0; column < 3; column++) {
|
|
60
|
+
value = Math.imul(value ^ (value >>> 13), 16777619) >>> 0;
|
|
61
|
+
if ((value & 1) || (row === 2 && column === 1)) {
|
|
62
|
+
cells.push([column, row]);
|
|
63
|
+
if (column < 2) cells.push([4 - column, row]);
|
|
64
|
+
}
|
|
99
65
|
}
|
|
100
66
|
}
|
|
101
|
-
|
|
102
|
-
var clipId = "imprint-" + hash.toString(16);
|
|
103
|
-
return '<svg xmlns="http://www.w3.org/2000/svg" width="' + size + '" height="' + size +
|
|
104
|
-
'" viewBox="0 0 96 96"><defs><clipPath id="' + clipId +
|
|
105
|
-
'"><rect width="96" height="96" rx="24"/></clipPath></defs><g clip-path="url(#' + clipId +
|
|
106
|
-
')"><rect width="96" height="96" fill="' + base + '"/><g transform="rotate(' + rotation +
|
|
107
|
-
' 48 48)"><path d="' + firstPath + '" fill="none" stroke="' + field + '" stroke-width="' +
|
|
108
|
-
number(bandWidth) + '" stroke-linecap="square"/><path d="' + secondPath + '" fill="none" stroke="' +
|
|
109
|
-
accent + '" stroke-width="' + number(secondaryWidth) + '" stroke-linecap="square"/><path d="' +
|
|
110
|
-
cutPath(random) + '" fill="none" stroke="' + base + '" stroke-width="' + number(cutWidth) +
|
|
111
|
-
'" stroke-linecap="square"/>' + contours + '</g></g><rect x="0.5" y="0.5" width="95" height="95" rx="23.5" fill="none" stroke="' +
|
|
112
|
-
(darkBase ? "#000000" : INK) + '" stroke-opacity="0.16"/></svg>';
|
|
67
|
+
return cells;
|
|
113
68
|
}
|
|
114
69
|
|
|
115
70
|
export function imprintDataUrl(options) {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
// Centralized
|
|
1
|
+
// Centralized local avatar URL builder for people and Mates.
|
|
2
2
|
import { imprintDataUrl, mateMarkDataUrl } from './avatar-imprint.js';
|
|
3
3
|
|
|
4
4
|
var imprintCache = new Map();
|
|
5
5
|
var mateMarkCache = new Map();
|
|
6
6
|
|
|
7
7
|
// Legacy style remains part of the identity hash so existing selections map
|
|
8
|
-
// deterministically into the
|
|
8
|
+
// deterministically into the local pixel identity system.
|
|
9
9
|
export function avatarUrl(style, seed, size) {
|
|
10
10
|
var cacheKey = [style || 'imprint', seed || 'anonymous', size || 64].join('|');
|
|
11
11
|
if (imprintCache.has(cacheKey)) return imprintCache.get(cacheKey);
|
|
@@ -15,12 +15,20 @@ export function avatarUrl(style, seed, size) {
|
|
|
15
15
|
return url;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// Use the same bounded account identity for fresh fallbacks across every surface.
|
|
19
|
+
export function userAvatarSeed(user) {
|
|
20
|
+
var profile = user && user.profile ? user.profile : user;
|
|
21
|
+
var savedSeed = profile && profile.avatarSeed || user && user.avatarSeed;
|
|
22
|
+
if (savedSeed) return String(savedSeed);
|
|
23
|
+
var identity = profile && (profile.username || profile.id || profile.userId) || user && (user.username || user.id || user.userId) || 'anonymous';
|
|
24
|
+
return String(identity).substring(0, 30);
|
|
25
|
+
}
|
|
26
|
+
|
|
18
27
|
// Build avatar URL for a user object, preferring custom avatar if set.
|
|
19
28
|
export function userAvatarUrl(user, size) {
|
|
20
29
|
if (user && user.avatarCustom) return user.avatarCustom;
|
|
21
30
|
var style = (user && user.avatarStyle) || 'imprint';
|
|
22
|
-
|
|
23
|
-
return avatarUrl(style, seed, size);
|
|
31
|
+
return avatarUrl(style, userAvatarSeed(user), size);
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
export function mateMarkUrl(seed, size) {
|
|
@@ -145,6 +145,26 @@ export function addToMessages(element) {
|
|
|
145
145
|
var messagesEl = getMessagesEl();
|
|
146
146
|
if (prependAnchor) messagesEl.insertBefore(element, prependAnchor);
|
|
147
147
|
else messagesEl.appendChild(element);
|
|
148
|
+
keepActiveThinkingAtTail();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Thinking is a single accumulating record for the active turn. Other live
|
|
152
|
+
// transcript entries may arrive between reasoning segments, so keep only the
|
|
153
|
+
// currently active record after the tail activity indicator.
|
|
154
|
+
export function keepActiveThinkingAtTail() {
|
|
155
|
+
if (prependAnchor || store.get('replayingHistory')) return;
|
|
156
|
+
var thinkingState = store.get('thinkingState');
|
|
157
|
+
var thinkingEl = thinkingState && thinkingState.active && thinkingState.el;
|
|
158
|
+
var messagesEl = getMessagesEl();
|
|
159
|
+
if (!thinkingEl || !messagesEl || thinkingEl.parentNode !== messagesEl) return;
|
|
160
|
+
if (activityEl && activityEl.parentNode === messagesEl) {
|
|
161
|
+
if (activityEl.nextSibling !== thinkingEl || messagesEl.lastElementChild !== thinkingEl) {
|
|
162
|
+
messagesEl.appendChild(activityEl);
|
|
163
|
+
messagesEl.appendChild(thinkingEl);
|
|
164
|
+
}
|
|
165
|
+
} else if (messagesEl.lastElementChild !== thinkingEl) {
|
|
166
|
+
messagesEl.appendChild(thinkingEl);
|
|
167
|
+
}
|
|
148
168
|
}
|
|
149
169
|
|
|
150
170
|
export function scrollToBottom() {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { store } from './store.js';
|
|
2
2
|
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { forwardPaneIssueReference } from './pane-bridge.js';
|
|
3
4
|
function accountKey(state) { return state.isMultiUserMode ? 'user:' + String(state.myUserId || '') : 'single'; }
|
|
4
5
|
function requestContext(state) { return { accountKey: accountKey(state), projectSlug: state.currentSlug || null, epoch: state.issuesEpoch || 0 }; }
|
|
5
6
|
function sameContext(request, state) {
|
|
@@ -42,12 +43,17 @@ function createIssueLink(ref, labelText) {
|
|
|
42
43
|
button.appendChild(meta);
|
|
43
44
|
}
|
|
44
45
|
var cache = store.get('issuesCache') || {};
|
|
45
|
-
if (cache[ref])
|
|
46
|
+
if (cache[ref]) updateIssueLink(button, cache[ref]);
|
|
46
47
|
setTimeout(function () { resolve(ref); }, 0);
|
|
47
48
|
return button;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
export function enhanceClayIssueLinks(root) {
|
|
52
|
+
root.querySelectorAll('a').forEach(function (anchor) {
|
|
53
|
+
var ref = anchor.getAttribute('data-clay-record-ref') || anchor.getAttribute('href');
|
|
54
|
+
if (anchor.closest('pre, code, button') || !isExactClayIssueReference(ref)) return;
|
|
55
|
+
anchor.replaceWith(createIssueLink(ref.trim(), anchor.textContent.trim()));
|
|
56
|
+
});
|
|
51
57
|
var codeNodes = root.querySelectorAll("code");
|
|
52
58
|
for (var codeIndex = 0; codeIndex < codeNodes.length; codeIndex++) {
|
|
53
59
|
var code = codeNodes[codeIndex];
|
|
@@ -80,6 +86,20 @@ export function enhanceClayIssueLinks(root) {
|
|
|
80
86
|
}
|
|
81
87
|
}
|
|
82
88
|
|
|
89
|
+
export function updateIssueLink(button, target) {
|
|
90
|
+
var status = target.status || 'open';
|
|
91
|
+
var title = target.title || 'Issue';
|
|
92
|
+
button.replaceChildren();
|
|
93
|
+
var label = document.createElement('span');
|
|
94
|
+
label.textContent = 'Issue';
|
|
95
|
+
var meta = document.createElement('small');
|
|
96
|
+
meta.textContent = title;
|
|
97
|
+
button.append(label, meta);
|
|
98
|
+
button.dataset.status = status;
|
|
99
|
+
button.title = title + ' · ' + status.replace(/_/g, ' ');
|
|
100
|
+
button.setAttribute('aria-label', 'Open Project Issue: ' + button.title);
|
|
101
|
+
}
|
|
102
|
+
|
|
83
103
|
function resolve(ref) {
|
|
84
104
|
var ws = getWs();
|
|
85
105
|
var requests = store.get('issueLinkRequests') || {};
|
|
@@ -101,7 +121,7 @@ export function handleIssueReference(msg) {
|
|
|
101
121
|
}
|
|
102
122
|
document.querySelectorAll('[data-issue-ref]').forEach(function (button) {
|
|
103
123
|
if (button.dataset.issueRef !== msg.ref) return;
|
|
104
|
-
if (msg.target)
|
|
124
|
+
if (msg.target) updateIssueLink(button, msg.target);
|
|
105
125
|
else button.title = msg.error || 'Issue unavailable';
|
|
106
126
|
});
|
|
107
127
|
if (store.get('issueLinkOpenRef') === msg.ref) {
|
|
@@ -111,6 +131,7 @@ export function handleIssueReference(msg) {
|
|
|
111
131
|
return true;
|
|
112
132
|
}
|
|
113
133
|
function navigate(target) {
|
|
134
|
+
if (forwardPaneIssueReference(target.ref, target.projectSlug)) return;
|
|
114
135
|
if (target.projectSlug !== store.get('currentSlug')) { window.location.assign('/p/' + encodeURIComponent(target.projectSlug) + '/?issue=' + encodeURIComponent(target.ref)); return; }
|
|
115
136
|
import('./issues.js').then(function (module) { module.openIssues(target.ref); });
|
|
116
137
|
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import { enhanceClayIssueLinks } from './clay-issue-links.js';
|
|
1
|
+
import { enhanceClayIssueLinks, isExactClayIssueReference } from './clay-issue-links.js';
|
|
2
|
+
|
|
3
|
+
export function renderClayRecordLink(href, text) {
|
|
4
|
+
if (!isExactClayLogReference(href) && !isExactClayIssueReference(href)) return null;
|
|
5
|
+
return '<a href="#" data-clay-record-ref="' + href.trim() + '">' + text + '</a>';
|
|
6
|
+
}
|
|
2
7
|
export function parseClayLogReferences(text) {
|
|
3
8
|
var pattern = /\[clayos\/(log:[A-Za-z0-9_-]{24})(?:\s+[\u2014-]\s+([^\]\n]{1,80}))?\]|(^|[^A-Za-z0-9_/:])(log:[A-Za-z0-9_-]{24})(?![A-Za-z0-9_-])/g;
|
|
4
9
|
var results = [];
|
|
@@ -39,6 +44,11 @@ function createLogLink(ref, labelText) {
|
|
|
39
44
|
|
|
40
45
|
export function enhanceClayLogLinks(root) {
|
|
41
46
|
enhanceClayIssueLinks(root);
|
|
47
|
+
root.querySelectorAll('a').forEach(function (anchor) {
|
|
48
|
+
var ref = anchor.getAttribute('data-clay-record-ref') || anchor.getAttribute('href');
|
|
49
|
+
if (anchor.closest('pre, code, button') || !isExactClayLogReference(ref)) return;
|
|
50
|
+
anchor.replaceWith(createLogLink(ref.trim(), anchor.textContent.trim()));
|
|
51
|
+
});
|
|
42
52
|
var codeNodes = root.querySelectorAll("code");
|
|
43
53
|
for (var codeIndex = 0; codeIndex < codeNodes.length; codeIndex++) {
|
|
44
54
|
var code = codeNodes[codeIndex];
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
|
|
4
|
+
var MODES = ["off", "mini", "panel"];
|
|
5
|
+
var sequence = 0;
|
|
6
|
+
|
|
7
|
+
function validMode(mode) { return MODES.indexOf(mode) !== -1; }
|
|
8
|
+
function nextRequestId(prefix) { sequence += 1; return prefix + Date.now() + "-" + sequence; }
|
|
9
|
+
function accountId() { return store.get("myUserId") || "default"; }
|
|
10
|
+
function initialState() {
|
|
11
|
+
return { mode: "off", canonicalMode: "off", preferencePresent: false, loading: false, saving: false, requestId: null, saveRequestId: null, pendingSaves: [], accountId: null, serverEpoch: null, rejectedEpoch: null, canonicalRevision: 0, error: "" };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function send(message) {
|
|
15
|
+
var ws = getWs();
|
|
16
|
+
if (!ws || ws.readyState !== 1) return false;
|
|
17
|
+
ws.send(JSON.stringify(message));
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getContextView() {
|
|
22
|
+
var state = store.get("contextViewPreferenceState") || initialState();
|
|
23
|
+
return validMode(state.mode) ? state.mode : "off";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getEffectiveContextView() {
|
|
27
|
+
if (store.get("paneMode") !== true) return getContextView();
|
|
28
|
+
var override = store.get("contextViewOverride");
|
|
29
|
+
return override === null || override === undefined ? getContextView() : (validMode(override) ? override : getContextView());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function beginContextViewConnection() {
|
|
33
|
+
var state = store.get("contextViewPreferenceState") || initialState();
|
|
34
|
+
store.set({ contextViewPreferenceState: Object.assign({}, state, {
|
|
35
|
+
mode: validMode(state.canonicalMode) ? state.canonicalMode : "off",
|
|
36
|
+
loading: false,
|
|
37
|
+
saving: false,
|
|
38
|
+
requestId: null,
|
|
39
|
+
saveRequestId: null,
|
|
40
|
+
pendingSaves: [],
|
|
41
|
+
rejectedEpoch: state.serverEpoch || null,
|
|
42
|
+
serverEpoch: null,
|
|
43
|
+
canonicalRevision: 0,
|
|
44
|
+
error: "",
|
|
45
|
+
}) });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function requestContextView() {
|
|
49
|
+
var id = nextRequestId("context-view-");
|
|
50
|
+
var state = store.get("contextViewPreferenceState") || initialState();
|
|
51
|
+
store.set({ contextViewPreferenceState: Object.assign({}, state, { loading: true, requestId: id, error: "" }) });
|
|
52
|
+
if (!send({ type: "context_view_get", requestId: id })) {
|
|
53
|
+
store.set({ contextViewPreferenceState: Object.assign({}, store.get("contextViewPreferenceState") || initialState(), { loading: false, requestId: null, error: "Clay is offline. Reconnect and try again." }) });
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function setContextView(mode) {
|
|
60
|
+
if (!validMode(mode)) return false;
|
|
61
|
+
var id = nextRequestId("context-view-save-");
|
|
62
|
+
var state = store.get("contextViewPreferenceState") || initialState();
|
|
63
|
+
var pendingSaves = (state.pendingSaves || []).concat([{ requestId: id, mode: mode }]);
|
|
64
|
+
store.set({ contextViewPreferenceState: Object.assign({}, state, { mode: mode, saving: true, saveRequestId: id, pendingSaves: pendingSaves, error: "" }) });
|
|
65
|
+
if (!send({ type: "context_view_set", requestId: id, mode: mode })) {
|
|
66
|
+
var reverted = store.get("contextViewPreferenceState") || initialState();
|
|
67
|
+
pendingSaves = (reverted.pendingSaves || []).filter(function (item) { return item.requestId !== id; });
|
|
68
|
+
var pendingMode = pendingSaves.length ? pendingSaves[pendingSaves.length - 1].mode : (reverted.canonicalMode || "off");
|
|
69
|
+
store.set({ contextViewPreferenceState: Object.assign({}, reverted, { mode: pendingMode, saving: pendingSaves.length > 0, saveRequestId: pendingSaves.length ? pendingSaves[pendingSaves.length - 1].requestId : null, pendingSaves: pendingSaves, error: "Clay is offline. Reconnect and try again." }) });
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function handleContextViewMessage(msg) {
|
|
76
|
+
if (!msg || msg.type !== "context_view_state") return false;
|
|
77
|
+
var state = store.get("contextViewPreferenceState") || initialState();
|
|
78
|
+
var currentAccount = accountId();
|
|
79
|
+
var isGetReply = !!msg.requestId && msg.requestId === state.requestId;
|
|
80
|
+
var pendingSaves = state.pendingSaves || [];
|
|
81
|
+
var saveIndex = -1;
|
|
82
|
+
for (var i = 0; i < pendingSaves.length; i++) if (pendingSaves[i].requestId === msg.requestId) saveIndex = i;
|
|
83
|
+
var isSaveReply = !!msg.requestId && saveIndex !== -1;
|
|
84
|
+
var isOlderSaveRevision = isSaveReply && typeof msg.canonicalRevision === "number" && msg.canonicalRevision < (state.canonicalRevision || 0);
|
|
85
|
+
var isCorrelatedAccountFailure = (isGetReply || isSaveReply) && msg.accountId === null && msg.accountAvailable === false;
|
|
86
|
+
if ((!isCorrelatedAccountFailure && msg.accountId !== currentAccount) || (msg.accountId === null && !isCorrelatedAccountFailure)) return true;
|
|
87
|
+
if (msg.serverEpoch && state.serverEpoch && msg.serverEpoch !== state.serverEpoch && (!msg.requestId || (!isGetReply && !isSaveReply))) return true;
|
|
88
|
+
if (msg.serverEpoch && state.rejectedEpoch && msg.serverEpoch === state.rejectedEpoch && (!isGetReply && !isSaveReply)) return true;
|
|
89
|
+
if (msg.canonicalRevision !== undefined && msg.canonicalRevision < (state.canonicalRevision || 0) && msg.serverEpoch === state.serverEpoch && !isSaveReply) return true;
|
|
90
|
+
if (msg.requestId && !isGetReply && !isSaveReply) return true;
|
|
91
|
+
if (msg.requestId && !isSaveReply && msg.ready === false && state.saving) return true;
|
|
92
|
+
var next = Object.assign({}, state, {
|
|
93
|
+
accountId: msg.accountId || currentAccount,
|
|
94
|
+
serverEpoch: msg.serverEpoch || state.serverEpoch || null,
|
|
95
|
+
rejectedEpoch: isGetReply || isSaveReply ? null : state.rejectedEpoch || null,
|
|
96
|
+
canonicalRevision: isOlderSaveRevision ? (state.canonicalRevision || 0) : (typeof msg.canonicalRevision === "number" ? msg.canonicalRevision : state.canonicalRevision || 0),
|
|
97
|
+
loading: isGetReply ? false : state.loading,
|
|
98
|
+
saving: state.saving,
|
|
99
|
+
requestId: isGetReply ? null : state.requestId,
|
|
100
|
+
saveRequestId: state.saveRequestId,
|
|
101
|
+
error: msg.error || "",
|
|
102
|
+
});
|
|
103
|
+
if (Object.prototype.hasOwnProperty.call(msg, "mode") && validMode(msg.mode) && msg.ready !== false && !isCorrelatedAccountFailure) {
|
|
104
|
+
if (isSaveReply) {
|
|
105
|
+
if (!isOlderSaveRevision && msg.ready !== false) {
|
|
106
|
+
pendingSaves = pendingSaves.slice(saveIndex + 1);
|
|
107
|
+
} else {
|
|
108
|
+
pendingSaves = pendingSaves.filter(function (item) { return item.requestId !== msg.requestId; });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!isOlderSaveRevision) {
|
|
112
|
+
next.canonicalMode = msg.mode;
|
|
113
|
+
next.preferencePresent = msg.preferencePresent === true;
|
|
114
|
+
}
|
|
115
|
+
if (pendingSaves.length) {
|
|
116
|
+
next.mode = pendingSaves[pendingSaves.length - 1].mode;
|
|
117
|
+
next.saving = true;
|
|
118
|
+
next.saveRequestId = pendingSaves[pendingSaves.length - 1].requestId;
|
|
119
|
+
} else {
|
|
120
|
+
next.mode = next.canonicalMode;
|
|
121
|
+
next.saving = false;
|
|
122
|
+
next.saveRequestId = null;
|
|
123
|
+
}
|
|
124
|
+
next.pendingSaves = pendingSaves;
|
|
125
|
+
} else if (isSaveReply && (msg.ready === false || isCorrelatedAccountFailure)) {
|
|
126
|
+
pendingSaves = pendingSaves.filter(function (item) { return item.requestId !== msg.requestId; });
|
|
127
|
+
next.pendingSaves = pendingSaves;
|
|
128
|
+
next.mode = pendingSaves.length ? pendingSaves[pendingSaves.length - 1].mode : (state.canonicalMode || "off");
|
|
129
|
+
next.saving = pendingSaves.length > 0;
|
|
130
|
+
next.saveRequestId = pendingSaves.length ? pendingSaves[pendingSaves.length - 1].requestId : null;
|
|
131
|
+
}
|
|
132
|
+
store.set({ contextViewPreferenceState: next });
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
store.subscribe(function (state, previous) {
|
|
137
|
+
if (state.myUserId !== previous.myUserId || state.currentSlug !== previous.currentSlug) {
|
|
138
|
+
store.set({ contextViewPreferenceState: Object.assign({}, initialState(), { accountId: accountId() }), contextViewOverride: null });
|
|
139
|
+
if (state.connected) requestContextView();
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
export { validMode };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { firstInstalledVendor } from './vendor-priority.js';
|
|
4
|
+
|
|
5
|
+
function send(message) {
|
|
6
|
+
var ws = getWs();
|
|
7
|
+
if (!ws || ws.readyState !== 1) return false;
|
|
8
|
+
ws.send(JSON.stringify(message));
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
var sequence = 0;
|
|
13
|
+
|
|
14
|
+
function nextRequestId(prefix) {
|
|
15
|
+
sequence += 1;
|
|
16
|
+
return prefix + Date.now() + "-" + sequence;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function emptyState(accountId, projectSlug) {
|
|
20
|
+
return { loading: false, saving: false, getRequestId: null, saveRequestId: null, preference: null, preferencePresent: false, installedVendors: [], accountId: accountId || null, projectSlug: projectSlug || null, serverEpoch: null, canonicalRevision: 0, error: "" };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function requestDefaultVendor() {
|
|
24
|
+
var id = nextRequestId("default-vendor-");
|
|
25
|
+
var state = store.get("defaultVendorState") || {};
|
|
26
|
+
store.set({ defaultVendorState: Object.assign({}, state, { loading: true, getRequestId: id, error: "" }) });
|
|
27
|
+
if (!send({ type: "default_vendor_get", requestId: id })) {
|
|
28
|
+
store.set({ defaultVendorState: Object.assign({}, store.get("defaultVendorState") || {}, { loading: false, getRequestId: null, error: "Clay is offline. Reconnect and try again." }) });
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function beginDefaultVendorConnection() {
|
|
35
|
+
var state = store.get("defaultVendorState") || {};
|
|
36
|
+
store.set({ defaultVendorState: Object.assign({}, state, {
|
|
37
|
+
loading: false,
|
|
38
|
+
saving: false,
|
|
39
|
+
getRequestId: null,
|
|
40
|
+
saveRequestId: null,
|
|
41
|
+
preference: null,
|
|
42
|
+
preferencePresent: false,
|
|
43
|
+
installedVendors: [],
|
|
44
|
+
accountId: store.get("myUserId") || "default",
|
|
45
|
+
projectSlug: store.get("currentSlug") || null,
|
|
46
|
+
serverEpoch: null,
|
|
47
|
+
canonicalRevision: 0,
|
|
48
|
+
error: "",
|
|
49
|
+
}) });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function saveDefaultVendor(vendor) {
|
|
53
|
+
var state = store.get("defaultVendorState") || {};
|
|
54
|
+
if (vendor !== "" && (state.installedVendors || []).indexOf(vendor) === -1) return false;
|
|
55
|
+
var id = nextRequestId("default-vendor-save-");
|
|
56
|
+
store.set({ defaultVendorState: Object.assign({}, state, { saving: true, saveRequestId: id, error: "" }) });
|
|
57
|
+
if (!send({ type: "default_vendor_set", requestId: id, vendor: vendor })) {
|
|
58
|
+
store.set({ defaultVendorState: Object.assign({}, store.get("defaultVendorState") || {}, { saving: false, saveRequestId: null, error: "Clay is offline. Reconnect and try again." }) });
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function handleDefaultVendorMessage(msg) {
|
|
65
|
+
if (!msg || msg.type !== "default_vendor_state") return false;
|
|
66
|
+
var state = store.get("defaultVendorState") || {};
|
|
67
|
+
var accountId = store.get("myUserId") || "default";
|
|
68
|
+
var projectSlug = store.get("currentSlug") || null;
|
|
69
|
+
var isGetReply = !!msg.requestId && msg.requestId === state.getRequestId;
|
|
70
|
+
var isSaveReply = !!msg.requestId && msg.requestId === state.saveRequestId;
|
|
71
|
+
var isCorrelatedAccountFailure = (isGetReply || isSaveReply) && msg.accountId === null && msg.accountAvailable === false;
|
|
72
|
+
if ((!isCorrelatedAccountFailure && msg.accountId !== accountId) || (msg.accountId === null && !isCorrelatedAccountFailure)) return true;
|
|
73
|
+
if (Object.prototype.hasOwnProperty.call(msg, "projectSlug") && msg.projectSlug !== projectSlug) return true;
|
|
74
|
+
if (msg.serverEpoch && state.serverEpoch && msg.serverEpoch !== state.serverEpoch && (!msg.requestId || (msg.requestId !== state.getRequestId && msg.requestId !== state.saveRequestId))) return true;
|
|
75
|
+
if (msg.canonicalRevision !== undefined && msg.canonicalRevision < (state.canonicalRevision || 0) && msg.serverEpoch === state.serverEpoch) return true;
|
|
76
|
+
if (msg.requestId && !isGetReply && !isSaveReply) return true;
|
|
77
|
+
if (msg.requestId && !isSaveReply && msg.ready === false && state.saving) return true;
|
|
78
|
+
var next = Object.assign({}, state, {
|
|
79
|
+
accountId: msg.accountId || accountId,
|
|
80
|
+
projectSlug: projectSlug,
|
|
81
|
+
serverEpoch: msg.serverEpoch || state.serverEpoch || null,
|
|
82
|
+
canonicalRevision: typeof msg.canonicalRevision === "number" ? msg.canonicalRevision : state.canonicalRevision || 0,
|
|
83
|
+
installedVendors: Array.isArray(msg.installedVendors) ? msg.installedVendors : state.installedVendors || [],
|
|
84
|
+
loading: isGetReply ? false : state.loading,
|
|
85
|
+
saving: isSaveReply ? false : state.saving,
|
|
86
|
+
getRequestId: isGetReply ? null : state.getRequestId,
|
|
87
|
+
saveRequestId: isSaveReply ? null : state.saveRequestId,
|
|
88
|
+
error: msg.error || ""
|
|
89
|
+
});
|
|
90
|
+
if (Object.prototype.hasOwnProperty.call(msg, "preference")) {
|
|
91
|
+
next.preference = msg.preference || null;
|
|
92
|
+
next.preferencePresent = msg.preferencePresent === true;
|
|
93
|
+
}
|
|
94
|
+
store.set({ defaultVendorState: next });
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
store.subscribe(function (state, previous) {
|
|
99
|
+
if (state.myUserId !== previous.myUserId || state.currentSlug !== previous.currentSlug) {
|
|
100
|
+
var accountId = state.myUserId || "default";
|
|
101
|
+
store.set({ defaultVendorState: emptyState(accountId, state.currentSlug || null) });
|
|
102
|
+
if (state.connected) requestDefaultVendor();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
export function resolvePreferredVendor(installedVendors) {
|
|
107
|
+
var installed = Array.isArray(installedVendors) ? installedVendors : [];
|
|
108
|
+
var state = store.get("defaultVendorState") || {};
|
|
109
|
+
if (state.preference && installed.indexOf(state.preference) !== -1) return state.preference;
|
|
110
|
+
return firstInstalledVendor(installed);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function defaultVendorUnavailable(installedVendors) {
|
|
114
|
+
var state = store.get("defaultVendorState") || {};
|
|
115
|
+
return !!state.preference && (installedVendors || []).indexOf(state.preference) === -1;
|
|
116
|
+
}
|
|
@@ -101,4 +101,5 @@ export function closeFileViewerTab(path) {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
export function focusedFileViewerTab() { return focusedPath; }
|
|
104
|
+
export function focusedFileViewerTabData() { return focusedPath && tabs.has(focusedPath) ? tabs.get(focusedPath) : null; }
|
|
104
105
|
export function clearFileViewerTabs() { tabs.clear(); focusedPath = null; previewPath = null; render(); }
|