@typetorch/framework 0.5.6 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/out/devtools/admin-ui.d.ts +22 -0
- package/out/devtools/admin-ui.luau +108 -1
- package/out/devtools/build-actions.d.ts +14 -0
- package/out/devtools/build-actions.luau +25 -0
- package/out/devtools/claude-ui.d.ts +5 -0
- package/out/devtools/claude-ui.luau +37 -7
- package/out/devtools/client.luau +144 -14
- package/out/devtools/commands.d.ts +115 -0
- package/out/devtools/commands.luau +504 -0
- package/out/devtools/layout.d.ts +11 -0
- package/out/devtools/layout.luau +22 -0
- package/out/devtools/palette-commands.d.ts +55 -0
- package/out/devtools/palette-commands.luau +667 -0
- package/out/devtools/palette-rules.d.ts +57 -0
- package/out/devtools/palette-rules.luau +103 -0
- package/out/devtools/palette.d.ts +26 -0
- package/out/devtools/palette.luau +469 -0
- package/out/devtools/panes.d.ts +21 -1
- package/out/devtools/panes.luau +111 -14
- package/out/version.d.ts +1 -1
- package/out/version.luau +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -185,6 +185,16 @@ return the generation's stop function, which the kernel calls on a swap.
|
|
|
185
185
|
build picker (Server > Branch) and a Claude prompt. The sidebar, top to bottom: Artifact, Server, Network, Modules,
|
|
186
186
|
Manage, Logs, Dex, Claude. The window can be dragged by its header and resized from its corner (double-tap the
|
|
187
187
|
header to reset).
|
|
188
|
+
- **Command palette (`Ctrl+K`, `Cmd+K` on a Mac, or the search button in the menu header):** opens the dev menu if it
|
|
189
|
+
is closed and searches every command the dev may run now (fuzzy: `swb` finds *Switch branch*). Up / Down pick,
|
|
190
|
+
Enter runs, Shift+Enter opens a page in a split, Escape closes; recent commands come first on an empty query. Never
|
|
191
|
+
fires while typing in chat or a text box. Commands: open any page; split right / down, open the pane in a new window,
|
|
192
|
+
close it, dock all windows; Reload, Roll back, switch branch, load a build here, join a server on a branch or build
|
|
193
|
+
(the Branch tab's rules and words, `devtools/build-actions.ts`), New server and Join a listed server (owners); find a
|
|
194
|
+
player by name, display name or UserId and open them in Manage > Players or their Kick / Ban card (owners); copy the
|
|
195
|
+
job id, artifact id, commit, branch or place version (the copy popup); "Ask Claude: ..." puts the query in the Claude
|
|
196
|
+
draft. Anything that swaps what runs on the server takes a second Enter or tap to confirm. The kernel and the server
|
|
197
|
+
re-check every request. Core: `devtools/commands.ts` (pure, Lune-tested by `scripts/test-commands.luau`).
|
|
188
198
|
- **Server TPS (framework 0.4.2, kernel 0.4.2):** Server > Status has a *TPS* line next to Memory and Lua heap: the
|
|
189
199
|
last minute's average, its slowest second and the physics FPS (`59.9 avg, 52.0 min, physics 60`; the warning colour
|
|
190
200
|
under 50 on average). Older kernels show `needs kernel 0.4.2`. The same numbers ride on fleet heartbeats (the
|
|
@@ -15,6 +15,28 @@ export interface AdminDeps {
|
|
|
15
15
|
ok: boolean,
|
|
16
16
|
result: unknown
|
|
17
17
|
];
|
|
18
|
+
/** The command palette's player lookup (client.ts): who Manage > Players shows next. */
|
|
19
|
+
playerFocus?: PlayerFocus;
|
|
20
|
+
}
|
|
21
|
+
/** What the command palette asked Manage > Players for: show this player, and open Kick or Ban for them. */
|
|
22
|
+
export interface PlayerFocusRequest {
|
|
23
|
+
userId: number;
|
|
24
|
+
action?: "kick" | "ban";
|
|
25
|
+
at: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The command palette's way into Manage > Players (one per client generation, client.ts): `request` remembers a player
|
|
29
|
+
* and tells an open Players tab; a tab that mounts later takes it after its first load. The Kick and Ban cards are the
|
|
30
|
+
* tab's own (reason, duration, armed button); the server re-checks every action.
|
|
31
|
+
*/
|
|
32
|
+
export declare class PlayerFocus {
|
|
33
|
+
private pending?;
|
|
34
|
+
private readonly listeners;
|
|
35
|
+
request(userId: number, action?: "kick" | "ban"): void;
|
|
36
|
+
/** The waiting request (dropped after FOCUS_WAIT s), taken once. */
|
|
37
|
+
take(): PlayerFocusRequest | undefined;
|
|
38
|
+
waiting(): boolean;
|
|
39
|
+
listen(trove: Trove, listener: () => void): void;
|
|
18
40
|
}
|
|
19
41
|
/** The Manage sub-tabs, keyed like client.ts RENDER ("Tab/Sub"). */
|
|
20
42
|
export declare function adminTabs(deps: AdminDeps): Record<string, (tab: AdminTab) => void>;
|
|
@@ -115,6 +115,64 @@ local ERRORS = {
|
|
|
115
115
|
publish_failed = "Couldn't send",
|
|
116
116
|
["not a dev"] = "Not a dev",
|
|
117
117
|
}
|
|
118
|
+
--* How long a palette request waits for Manage > Players to load (seconds).
|
|
119
|
+
local FOCUS_WAIT = 15
|
|
120
|
+
--* What the command palette asked Manage > Players for: show this player, and open Kick or Ban for them.
|
|
121
|
+
--[[
|
|
122
|
+
*
|
|
123
|
+
* The command palette's way into Manage > Players (one per client generation, client.ts): `request` remembers a player
|
|
124
|
+
* and tells an open Players tab; a tab that mounts later takes it after its first load. The Kick and Ban cards are the
|
|
125
|
+
* tab's own (reason, duration, armed button); the server re-checks every action.
|
|
126
|
+
|
|
127
|
+
]]
|
|
128
|
+
local PlayerFocus
|
|
129
|
+
do
|
|
130
|
+
PlayerFocus = setmetatable({}, {
|
|
131
|
+
__tostring = function()
|
|
132
|
+
return "PlayerFocus"
|
|
133
|
+
end,
|
|
134
|
+
})
|
|
135
|
+
PlayerFocus.__index = PlayerFocus
|
|
136
|
+
function PlayerFocus.new(...)
|
|
137
|
+
local self = setmetatable({}, PlayerFocus)
|
|
138
|
+
return self:constructor(...) or self
|
|
139
|
+
end
|
|
140
|
+
function PlayerFocus:constructor()
|
|
141
|
+
self.listeners = {}
|
|
142
|
+
end
|
|
143
|
+
function PlayerFocus:request(userId, action)
|
|
144
|
+
self.pending = {
|
|
145
|
+
userId = userId,
|
|
146
|
+
action = action,
|
|
147
|
+
at = os.clock(),
|
|
148
|
+
}
|
|
149
|
+
for listener in self.listeners do
|
|
150
|
+
task.spawn(listener)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
function PlayerFocus:take()
|
|
154
|
+
local pending = self.pending
|
|
155
|
+
self.pending = nil
|
|
156
|
+
return if pending ~= nil and os.clock() - pending.at <= FOCUS_WAIT then pending else nil
|
|
157
|
+
end
|
|
158
|
+
function PlayerFocus:waiting()
|
|
159
|
+
return self.pending ~= nil
|
|
160
|
+
end
|
|
161
|
+
function PlayerFocus:listen(trove, listener)
|
|
162
|
+
local _listeners = self.listeners
|
|
163
|
+
local _listener = listener
|
|
164
|
+
_listeners[_listener] = true
|
|
165
|
+
trove:add(function()
|
|
166
|
+
local _listeners_1 = self.listeners
|
|
167
|
+
local _listener_1 = listener
|
|
168
|
+
-- ▼ Set.delete ▼
|
|
169
|
+
local _valueExisted = _listeners_1[_listener_1] ~= nil
|
|
170
|
+
_listeners_1[_listener_1] = nil
|
|
171
|
+
-- ▲ Set.delete ▲
|
|
172
|
+
return _valueExisted
|
|
173
|
+
end)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
118
176
|
--* The Manage sub-tabs, keyed like client.ts RENDER ("Tab/Sub").
|
|
119
177
|
local renderPlayers, renderServers, renderBans
|
|
120
178
|
local function adminTabs(deps)
|
|
@@ -140,7 +198,7 @@ local function every(trove, interval, callback)
|
|
|
140
198
|
while true do
|
|
141
199
|
local ok, err = pcall(callback)
|
|
142
200
|
if not ok then
|
|
143
|
-
warn("[src/devtools/admin-ui.ts:
|
|
201
|
+
warn("[src/devtools/admin-ui.ts:205]", `[devtools] manage refresh failed: {err}`)
|
|
144
202
|
end
|
|
145
203
|
task.wait(interval)
|
|
146
204
|
end
|
|
@@ -786,6 +844,7 @@ function renderPlayers(tab, deps)
|
|
|
786
844
|
local list = page:group(4)
|
|
787
845
|
local rows = {}
|
|
788
846
|
local busy = false
|
|
847
|
+
local loaded = false
|
|
789
848
|
local act = function(label, op, userId, doneText)
|
|
790
849
|
if busy then
|
|
791
850
|
return nil
|
|
@@ -1072,6 +1131,47 @@ function renderPlayers(tab, deps)
|
|
|
1072
1131
|
frame.Parent = list.frame
|
|
1073
1132
|
return row
|
|
1074
1133
|
end
|
|
1134
|
+
-- The command palette's player lookup: scroll to the player and flash their row, or open Kick / Ban for them.
|
|
1135
|
+
local applyFocus = function()
|
|
1136
|
+
local focus = deps.playerFocus
|
|
1137
|
+
if not focus or not loaded or not focus:waiting() then
|
|
1138
|
+
return nil
|
|
1139
|
+
end
|
|
1140
|
+
local want = focus:take()
|
|
1141
|
+
if not want then
|
|
1142
|
+
return nil
|
|
1143
|
+
end
|
|
1144
|
+
local _userId = want.userId
|
|
1145
|
+
local row = rows[_userId]
|
|
1146
|
+
if not row then
|
|
1147
|
+
setResult("Not in this server", COLORS.warn)
|
|
1148
|
+
return nil
|
|
1149
|
+
end
|
|
1150
|
+
local content = tab.content
|
|
1151
|
+
local top = row.frame.AbsolutePosition.Y - content.AbsolutePosition.Y + content.CanvasPosition.Y
|
|
1152
|
+
content.CanvasPosition = Vector2.new(0, math.max(0, top - 8))
|
|
1153
|
+
row.frame.BackgroundColor3 = COLORS.button
|
|
1154
|
+
bump(row.frame)
|
|
1155
|
+
trove:add(task.delay(1.5, function()
|
|
1156
|
+
if row.frame.Parent then
|
|
1157
|
+
row.frame.BackgroundColor3 = COLORS.row
|
|
1158
|
+
end
|
|
1159
|
+
end))
|
|
1160
|
+
local data = row.data
|
|
1161
|
+
if want.action == "kick" then
|
|
1162
|
+
if data.can.kick then
|
|
1163
|
+
openKick(data)
|
|
1164
|
+
else
|
|
1165
|
+
setResult(`Can't kick {data.displayName}`, COLORS.bad)
|
|
1166
|
+
end
|
|
1167
|
+
elseif want.action == "ban" then
|
|
1168
|
+
if data.can.ban then
|
|
1169
|
+
openBan(data)
|
|
1170
|
+
else
|
|
1171
|
+
setResult(`Can't ban {data.displayName}`, COLORS.bad)
|
|
1172
|
+
end
|
|
1173
|
+
end
|
|
1174
|
+
end
|
|
1075
1175
|
local refresh = function()
|
|
1076
1176
|
local _binding_1 = deps.call("admin.players")
|
|
1077
1177
|
local ok = _binding_1[1]
|
|
@@ -1137,6 +1237,12 @@ function renderPlayers(tab, deps)
|
|
|
1137
1237
|
row.frame:Destroy()
|
|
1138
1238
|
rows[userId] = nil
|
|
1139
1239
|
end
|
|
1240
|
+
loaded = true
|
|
1241
|
+
applyFocus()
|
|
1242
|
+
end
|
|
1243
|
+
local _result = deps.playerFocus
|
|
1244
|
+
if _result ~= nil then
|
|
1245
|
+
_result:listen(trove, applyFocus)
|
|
1140
1246
|
end
|
|
1141
1247
|
every(trove, PLAYERS_REFRESH, refresh)
|
|
1142
1248
|
end
|
|
@@ -2656,4 +2762,5 @@ end
|
|
|
2656
2762
|
return {
|
|
2657
2763
|
adminTabs = adminTabs,
|
|
2658
2764
|
migrateControl = migrateControl,
|
|
2765
|
+
PlayerFocus = PlayerFocus,
|
|
2659
2766
|
}
|
|
@@ -38,6 +38,20 @@ export declare function buildAction(ctx: PickerContext, entry: {
|
|
|
38
38
|
channel: string;
|
|
39
39
|
}): BuildAction;
|
|
40
40
|
export declare function buildLabel(action: BuildAction): string;
|
|
41
|
+
/**
|
|
42
|
+
* The "pin" op's payload for a build the viewer may pin (`buildAction` "pin"): an A/B experiment pin when an owner pins
|
|
43
|
+
* on an unsigned public server whose kernel has experiments (0.2.3+), else the plain asset id. The Branch tab and the
|
|
44
|
+
* command palette both send this.
|
|
45
|
+
*/
|
|
46
|
+
export declare function pinPayload(ctx: PickerContext, assetId: number): number | {
|
|
47
|
+
assetId: number;
|
|
48
|
+
experiment: true;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Whether an action swaps what runs on THIS server (every player on it): the owner's switch or load ("here"), the stored
|
|
52
|
+
* switch and the dev pin. "join" moves only you. The command palette makes all of them two-step.
|
|
53
|
+
*/
|
|
54
|
+
export declare function swapsServer(action: BranchAction | BuildAction): boolean;
|
|
41
55
|
/** Kernels before 0.3.4 on prod servers: in-game loads can't reach them, only `typetorch pin`. */
|
|
42
56
|
export declare function showCliNote(ctx: PickerContext): boolean;
|
|
43
57
|
/** Whether this client's player is an owner (cosmetic: the kernel re-checks). An older kernel's "admin" is a dev. */
|
|
@@ -63,6 +63,29 @@ end
|
|
|
63
63
|
local function buildLabel(action)
|
|
64
64
|
return if action == "join" then "Join" else "Load here"
|
|
65
65
|
end
|
|
66
|
+
--[[
|
|
67
|
+
*
|
|
68
|
+
* The "pin" op's payload for a build the viewer may pin (`buildAction` "pin"): an A/B experiment pin when an owner pins
|
|
69
|
+
* on an unsigned public server whose kernel has experiments (0.2.3+), else the plain asset id. The Branch tab and the
|
|
70
|
+
* command palette both send this.
|
|
71
|
+
|
|
72
|
+
]]
|
|
73
|
+
local function pinPayload(ctx, assetId)
|
|
74
|
+
local abInPlace = ctx.serverType == "public" and ctx.isOwner and ctx.experiments and not ctx.signedOnly
|
|
75
|
+
return if abInPlace then {
|
|
76
|
+
assetId = assetId,
|
|
77
|
+
experiment = true,
|
|
78
|
+
} else assetId
|
|
79
|
+
end
|
|
80
|
+
--[[
|
|
81
|
+
*
|
|
82
|
+
* Whether an action swaps what runs on THIS server (every player on it): the owner's switch or load ("here"), the stored
|
|
83
|
+
* switch and the dev pin. "join" moves only you. The command palette makes all of them two-step.
|
|
84
|
+
|
|
85
|
+
]]
|
|
86
|
+
local function swapsServer(action)
|
|
87
|
+
return action == "here" or action == "stored" or action == "pin"
|
|
88
|
+
end
|
|
66
89
|
--* Kernels before 0.3.4 on prod servers: in-game loads can't reach them, only `typetorch pin`.
|
|
67
90
|
local function showCliNote(ctx)
|
|
68
91
|
return not ctx.switchKernel and ctx.signedOnly and (ctx.isOwner or ctx.serverType ~= "public")
|
|
@@ -255,6 +278,8 @@ return {
|
|
|
255
278
|
branchLabel = branchLabel,
|
|
256
279
|
buildAction = buildAction,
|
|
257
280
|
buildLabel = buildLabel,
|
|
281
|
+
pinPayload = pinPayload,
|
|
282
|
+
swapsServer = swapsServer,
|
|
258
283
|
showCliNote = showCliNote,
|
|
259
284
|
viewerIsOwner = viewerIsOwner,
|
|
260
285
|
switchOffered = switchOffered,
|
|
@@ -45,5 +45,10 @@ type Block = {
|
|
|
45
45
|
export declare function parseMarkdown(text: string): Block[];
|
|
46
46
|
/** Inline Markdown → RichText: escapes &, < and > first, then `code`, links, **bold** and *italic*. */
|
|
47
47
|
export declare function inline(text: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* The command palette's "Ask Claude": replaces the draft with `text` (kept in the persist store like typing) and, when
|
|
50
|
+
* the Claude tab is open, shows it in its message box with the caret at the end. Nothing is sent.
|
|
51
|
+
*/
|
|
52
|
+
export declare function setClaudeDraft(kernel: ClientKernel, text: string): void;
|
|
48
53
|
export declare function renderClaudeChat(tab: ClaudeChatTab, deps: ClaudeChatDeps): void;
|
|
49
54
|
export {};
|
|
@@ -1065,19 +1065,37 @@ local function readableDetail(text)
|
|
|
1065
1065
|
return if #out > DETAIL_CHARS then `{string.sub(out, 1, DETAIL_CHARS)}\n...` else out
|
|
1066
1066
|
end
|
|
1067
1067
|
-- The tab -------------------------------------------------------------------------------------------------------------
|
|
1068
|
-
local
|
|
1069
|
-
|
|
1070
|
-
local _binding = deps
|
|
1071
|
-
local kernel = _binding.kernel
|
|
1072
|
-
local call = _binding.call
|
|
1073
|
-
local trove = tab.trove
|
|
1074
|
-
local state = kernel:persist(PERSIST_KEY, function()
|
|
1068
|
+
local function chatState(kernel)
|
|
1069
|
+
return kernel:persist(PERSIST_KEY, function()
|
|
1075
1070
|
return {
|
|
1076
1071
|
draft = "",
|
|
1077
1072
|
attachPath = false,
|
|
1078
1073
|
attachErrors = false,
|
|
1079
1074
|
}
|
|
1080
1075
|
end)
|
|
1076
|
+
end
|
|
1077
|
+
--* The open Claude tab's message box (one at most: Claude is a single page), for setClaudeDraft.
|
|
1078
|
+
local showDraft
|
|
1079
|
+
--[[
|
|
1080
|
+
*
|
|
1081
|
+
* The command palette's "Ask Claude": replaces the draft with `text` (kept in the persist store like typing) and, when
|
|
1082
|
+
* the Claude tab is open, shows it in its message box with the caret at the end. Nothing is sent.
|
|
1083
|
+
|
|
1084
|
+
]]
|
|
1085
|
+
local function setClaudeDraft(kernel, text)
|
|
1086
|
+
chatState(kernel).draft = text
|
|
1087
|
+
local _result = showDraft
|
|
1088
|
+
if _result ~= nil then
|
|
1089
|
+
_result(text)
|
|
1090
|
+
end
|
|
1091
|
+
end
|
|
1092
|
+
local buildClaudeChat
|
|
1093
|
+
local function renderClaudeChat(tab, deps)
|
|
1094
|
+
local _binding = deps
|
|
1095
|
+
local kernel = _binding.kernel
|
|
1096
|
+
local call = _binding.call
|
|
1097
|
+
local trove = tab.trove
|
|
1098
|
+
local state = chatState(kernel)
|
|
1081
1099
|
-- The chat takes the content's place in the window body (like the explorer).
|
|
1082
1100
|
local content = tab.content
|
|
1083
1101
|
local host = trove:add(make("Frame", {
|
|
@@ -1549,6 +1567,17 @@ function buildClaudeChat(tab, deps, host, state)
|
|
|
1549
1567
|
state.draft = text
|
|
1550
1568
|
end)
|
|
1551
1569
|
fitBox()
|
|
1570
|
+
local myShowDraft = function(text)
|
|
1571
|
+
box.Text = text
|
|
1572
|
+
box:CaptureFocus()
|
|
1573
|
+
box.CursorPosition = #text + 1
|
|
1574
|
+
end
|
|
1575
|
+
showDraft = myShowDraft
|
|
1576
|
+
trove:add(function()
|
|
1577
|
+
if showDraft == myShowDraft then
|
|
1578
|
+
showDraft = nil
|
|
1579
|
+
end
|
|
1580
|
+
end)
|
|
1552
1581
|
-- The actions row draws above the message box (the "+" menu hangs over it).
|
|
1553
1582
|
local actions = make("Frame", {
|
|
1554
1583
|
BackgroundTransparency = 1,
|
|
@@ -4595,5 +4624,6 @@ return {
|
|
|
4595
4624
|
stripEmoji = stripEmoji,
|
|
4596
4625
|
parseMarkdown = parseMarkdown,
|
|
4597
4626
|
inline = inline,
|
|
4627
|
+
setClaudeDraft = setClaudeDraft,
|
|
4598
4628
|
renderClaudeChat = renderClaudeChat,
|
|
4599
4629
|
}
|
package/out/devtools/client.luau
CHANGED
|
@@ -17,6 +17,7 @@ local branchLabel = _build_actions.branchLabel
|
|
|
17
17
|
local buildAction = _build_actions.buildAction
|
|
18
18
|
local buildLabel = _build_actions.buildLabel
|
|
19
19
|
local buildName = _build_actions.buildName
|
|
20
|
+
local pinPayload = _build_actions.pinPayload
|
|
20
21
|
local requestSwitch = _build_actions.requestSwitch
|
|
21
22
|
local showCliNote = _build_actions.showCliNote
|
|
22
23
|
local switchedText = _build_actions.switchedText
|
|
@@ -52,6 +53,12 @@ local renderClaudeChat = TS.import(script, script.Parent, "claude-ui").renderCla
|
|
|
52
53
|
local _admin_ui = TS.import(script, script.Parent, "admin-ui")
|
|
53
54
|
local adminTabs = _admin_ui.adminTabs
|
|
54
55
|
local migrateControl = _admin_ui.migrateControl
|
|
56
|
+
local PlayerFocus = _admin_ui.PlayerFocus
|
|
57
|
+
local _commands = TS.import(script, script.Parent, "commands")
|
|
58
|
+
local isPaletteHotkey = _commands.isPaletteHotkey
|
|
59
|
+
local sanitizeRecent = _commands.sanitizeRecent
|
|
60
|
+
local createPalette = TS.import(script, script.Parent, "palette").createPalette
|
|
61
|
+
local paletteCommands = TS.import(script, script.Parent, "palette-commands").paletteCommands
|
|
55
62
|
local renderNetworkInspector = TS.import(script, script.Parent, "network-inspector").renderNetworkInspector
|
|
56
63
|
local renderAssetsTab = TS.import(script, script.Parent, "assets-ui").renderAssetsTab
|
|
57
64
|
local renderStateTab = TS.import(script, script.Parent, "state-ui").renderStateTab
|
|
@@ -426,7 +433,7 @@ local function every(trove, interval, callback)
|
|
|
426
433
|
while true do
|
|
427
434
|
local ok, err = pcall(callback)
|
|
428
435
|
if not ok then
|
|
429
|
-
warn("[src/devtools/client.ts:
|
|
436
|
+
warn("[src/devtools/client.ts:374]", `[devtools] refresh failed: {err}`)
|
|
430
437
|
end
|
|
431
438
|
task.wait(interval)
|
|
432
439
|
end
|
|
@@ -2109,7 +2116,6 @@ local function startDevtoolsClient(kernel, dispatcher, trove)
|
|
|
2109
2116
|
if showCliNote(ctx) then
|
|
2110
2117
|
body:text("Use the CLI: typetorch pin", COLORS.dim)
|
|
2111
2118
|
end
|
|
2112
|
-
local abInPlace = ctx.serverType == "public" and isOwner and ctx.experiments and not ctx.signedOnly
|
|
2113
2119
|
if ctx.serverType == "public" and isOwner and not ctx.experiments and not ctx.signedOnly and not switchKernel then
|
|
2114
2120
|
body:text("A/B needs kernel 0.2.3", COLORS.dim)
|
|
2115
2121
|
end
|
|
@@ -2240,10 +2246,7 @@ local function startDevtoolsClient(kernel, dispatcher, trove)
|
|
|
2240
2246
|
end
|
|
2241
2247
|
return nil
|
|
2242
2248
|
end
|
|
2243
|
-
act(`Loading {short}`, "pin",
|
|
2244
|
-
assetId = entry.assetId,
|
|
2245
|
-
experiment = true,
|
|
2246
|
-
} else entry.assetId)
|
|
2249
|
+
act(`Loading {short}`, "pin", pinPayload(ctx, entry.assetId))
|
|
2247
2250
|
end,
|
|
2248
2251
|
})
|
|
2249
2252
|
expandable(loadRow, entry.assetId)
|
|
@@ -2321,6 +2324,8 @@ local function startDevtoolsClient(kernel, dispatcher, trove)
|
|
|
2321
2324
|
imageInbox = imageInbox,
|
|
2322
2325
|
})
|
|
2323
2326
|
end
|
|
2327
|
+
--* The command palette's player lookup into Manage > Players (admin-ui.ts).
|
|
2328
|
+
local playerFocus = PlayerFocus.new()
|
|
2324
2329
|
--* Keys: a tab name, or "Tab/Sub" for tabs with sub-tabs.
|
|
2325
2330
|
local _object_1 = {
|
|
2326
2331
|
Artifact = renderArtifact,
|
|
@@ -2348,6 +2353,7 @@ local function startDevtoolsClient(kernel, dispatcher, trove)
|
|
|
2348
2353
|
for _k, _v in adminTabs({
|
|
2349
2354
|
kernel = kernel,
|
|
2350
2355
|
call = call,
|
|
2356
|
+
playerFocus = playerFocus,
|
|
2351
2357
|
}) do
|
|
2352
2358
|
_object_1[_k] = _v
|
|
2353
2359
|
end
|
|
@@ -2369,6 +2375,8 @@ local function startDevtoolsClient(kernel, dispatcher, trove)
|
|
|
2369
2375
|
-- Window ----------------------------------------------------------------------------------------------------------
|
|
2370
2376
|
local ui
|
|
2371
2377
|
local wm
|
|
2378
|
+
--* The command palette (Ctrl+K, palette.ts), built with the menu.
|
|
2379
|
+
local palette
|
|
2372
2380
|
local isOpen = false
|
|
2373
2381
|
local dev = false
|
|
2374
2382
|
--* This player is an owner (framework 0.3.2: the Manage group shows only for owners).
|
|
@@ -2522,7 +2530,7 @@ local function startDevtoolsClient(kernel, dispatcher, trove)
|
|
|
2522
2530
|
})
|
|
2523
2531
|
end)
|
|
2524
2532
|
if not ok then
|
|
2525
|
-
warn("[src/devtools/client.ts:
|
|
2533
|
+
warn("[src/devtools/client.ts:1758]", `[devtools] {key} tab failed: {err}`)
|
|
2526
2534
|
page:text(`This tab failed: {err}`, COLORS.bad)
|
|
2527
2535
|
end
|
|
2528
2536
|
end
|
|
@@ -2577,20 +2585,55 @@ local function startDevtoolsClient(kernel, dispatcher, trove)
|
|
|
2577
2585
|
end
|
|
2578
2586
|
isOpen = false
|
|
2579
2587
|
state.open = false
|
|
2580
|
-
local _result =
|
|
2588
|
+
local _result = palette
|
|
2581
2589
|
if _result ~= nil then
|
|
2582
|
-
_result.
|
|
2590
|
+
_result.close()
|
|
2591
|
+
end
|
|
2592
|
+
local _result_1 = wm
|
|
2593
|
+
if _result_1 ~= nil then
|
|
2594
|
+
_result_1.fadeOut()
|
|
2583
2595
|
end
|
|
2584
2596
|
popOut(ui.window, function()
|
|
2585
2597
|
if not isOpen then
|
|
2586
|
-
local
|
|
2587
|
-
if
|
|
2588
|
-
|
|
2598
|
+
local _result_2 = wm
|
|
2599
|
+
if _result_2 ~= nil then
|
|
2600
|
+
_result_2.unmount()
|
|
2589
2601
|
end
|
|
2590
2602
|
end
|
|
2591
2603
|
end)
|
|
2592
2604
|
end
|
|
2593
2605
|
local open
|
|
2606
|
+
local openPalette
|
|
2607
|
+
local commands = paletteCommands({
|
|
2608
|
+
kernel = kernel,
|
|
2609
|
+
call = call,
|
|
2610
|
+
wm = function()
|
|
2611
|
+
return wm
|
|
2612
|
+
end,
|
|
2613
|
+
pages = function()
|
|
2614
|
+
return paneHost.picks()
|
|
2615
|
+
end,
|
|
2616
|
+
owner = function()
|
|
2617
|
+
return owner
|
|
2618
|
+
end,
|
|
2619
|
+
playerFocus = playerFocus,
|
|
2620
|
+
swapText = swapText,
|
|
2621
|
+
serverText = serverText,
|
|
2622
|
+
report = function(text, color)
|
|
2623
|
+
local _result = palette
|
|
2624
|
+
if _result ~= nil then
|
|
2625
|
+
_result = _result.report(text, color)
|
|
2626
|
+
end
|
|
2627
|
+
return _result
|
|
2628
|
+
end,
|
|
2629
|
+
refresh = function()
|
|
2630
|
+
local _result = palette
|
|
2631
|
+
if _result ~= nil then
|
|
2632
|
+
_result = _result.refresh()
|
|
2633
|
+
end
|
|
2634
|
+
return _result
|
|
2635
|
+
end,
|
|
2636
|
+
})
|
|
2594
2637
|
local build = function()
|
|
2595
2638
|
local playerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
|
|
2596
2639
|
sweepStaleMenus(playerGui, kernel.generation)
|
|
@@ -2656,7 +2699,7 @@ local function startDevtoolsClient(kernel, dispatcher, trove)
|
|
|
2656
2699
|
}, header), `{kernel.artifact.id} #{kernel.generation}`, 14, COLORS.dim, Enum.Font.Code)
|
|
2657
2700
|
subtitle.TextWrapped = false
|
|
2658
2701
|
subtitle.TextTruncate = Enum.TextTruncate.AtEnd
|
|
2659
|
-
subtitle.Size = UDim2.new(1, -
|
|
2702
|
+
subtitle.Size = UDim2.new(1, -212, 1, 0)
|
|
2660
2703
|
subtitle.Position = UDim2.fromOffset(114, 0)
|
|
2661
2704
|
local closeButton = style(make("TextButton", {
|
|
2662
2705
|
AutoButtonColor = true,
|
|
@@ -2669,6 +2712,40 @@ local function startDevtoolsClient(kernel, dispatcher, trove)
|
|
|
2669
2712
|
closeButton.Size = UDim2.fromOffset(34, 34)
|
|
2670
2713
|
corner(closeButton, 6)
|
|
2671
2714
|
closeButton.Activated:Connect(close)
|
|
2715
|
+
-- The command palette's button (touch has no Ctrl+K): a magnifier drawn from Frames (no glyphs).
|
|
2716
|
+
local searchButton = make("TextButton", {
|
|
2717
|
+
Name = "Search",
|
|
2718
|
+
Text = "",
|
|
2719
|
+
AutoButtonColor = true,
|
|
2720
|
+
BackgroundColor3 = COLORS.button,
|
|
2721
|
+
BorderSizePixel = 0,
|
|
2722
|
+
AnchorPoint = Vector2.new(1, 0.5),
|
|
2723
|
+
Position = UDim2.new(1, -50, 0.5, 0),
|
|
2724
|
+
Size = UDim2.fromOffset(34, 34),
|
|
2725
|
+
}, header)
|
|
2726
|
+
corner(searchButton, 6)
|
|
2727
|
+
local lens = make("Frame", {
|
|
2728
|
+
BackgroundTransparency = 1,
|
|
2729
|
+
Position = UDim2.fromOffset(9, 8),
|
|
2730
|
+
Size = UDim2.fromOffset(12, 12),
|
|
2731
|
+
}, searchButton)
|
|
2732
|
+
corner(lens, 6)
|
|
2733
|
+
make("UIStroke", {
|
|
2734
|
+
Color = COLORS.text,
|
|
2735
|
+
Thickness = 2,
|
|
2736
|
+
ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
|
|
2737
|
+
}, lens)
|
|
2738
|
+
make("Frame", {
|
|
2739
|
+
BackgroundColor3 = COLORS.text,
|
|
2740
|
+
BorderSizePixel = 0,
|
|
2741
|
+
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
2742
|
+
Position = UDim2.fromOffset(23, 23),
|
|
2743
|
+
Size = UDim2.fromOffset(8, 2),
|
|
2744
|
+
Rotation = 45,
|
|
2745
|
+
}, searchButton)
|
|
2746
|
+
searchButton.Activated:Connect(function()
|
|
2747
|
+
return openPalette()
|
|
2748
|
+
end)
|
|
2672
2749
|
-- Sidebar (script-free ScrollingFrame): tabs, and for tabs with sub-tabs an indented group of children right
|
|
2673
2750
|
-- under them (visible while that tab is selected). Clicking a group header opens its first child.
|
|
2674
2751
|
local tabs = scrolling(window, {
|
|
@@ -2824,6 +2901,26 @@ local function startDevtoolsClient(kernel, dispatcher, trove)
|
|
|
2824
2901
|
return layout
|
|
2825
2902
|
end,
|
|
2826
2903
|
host = paneHost,
|
|
2904
|
+
covered = function()
|
|
2905
|
+
local _result = palette
|
|
2906
|
+
if _result ~= nil then
|
|
2907
|
+
_result = _result.isOpen()
|
|
2908
|
+
end
|
|
2909
|
+
return _result == true
|
|
2910
|
+
end,
|
|
2911
|
+
})
|
|
2912
|
+
palette = createPalette({
|
|
2913
|
+
gui = gui,
|
|
2914
|
+
trove = trove,
|
|
2915
|
+
registry = commands.registry,
|
|
2916
|
+
context = commands.context,
|
|
2917
|
+
recent = function()
|
|
2918
|
+
return sanitizeRecent(state.recentCommands)
|
|
2919
|
+
end,
|
|
2920
|
+
setRecent = function(list)
|
|
2921
|
+
state.recentCommands = list
|
|
2922
|
+
end,
|
|
2923
|
+
opened = commands.load,
|
|
2827
2924
|
})
|
|
2828
2925
|
-- Placement: drag by the header, resize by the grip, double-tap the header to reset. Saved across swaps.
|
|
2829
2926
|
local viewport = function()
|
|
@@ -2998,6 +3095,20 @@ local function startDevtoolsClient(kernel, dispatcher, trove)
|
|
|
2998
3095
|
_result.show()
|
|
2999
3096
|
end
|
|
3000
3097
|
end
|
|
3098
|
+
--* Ctrl+K / Cmd+K and the header's search button: the command palette (opens the menu first).
|
|
3099
|
+
openPalette = function()
|
|
3100
|
+
if not dev then
|
|
3101
|
+
return nil
|
|
3102
|
+
end
|
|
3103
|
+
open()
|
|
3104
|
+
if not isOpen then
|
|
3105
|
+
return nil
|
|
3106
|
+
end
|
|
3107
|
+
local _result = palette
|
|
3108
|
+
if _result ~= nil then
|
|
3109
|
+
_result.open()
|
|
3110
|
+
end
|
|
3111
|
+
end
|
|
3001
3112
|
local setDev = function(value)
|
|
3002
3113
|
if value == dev then
|
|
3003
3114
|
return nil
|
|
@@ -3121,6 +3232,25 @@ local function startDevtoolsClient(kernel, dispatcher, trove)
|
|
|
3121
3232
|
open()
|
|
3122
3233
|
end
|
|
3123
3234
|
end)
|
|
3235
|
+
-- Ctrl+K (Cmd+K on a Mac: Super / Meta). Never while typing in chat or a TextBox (`processed`).
|
|
3236
|
+
trove:connect(UserInputService.InputBegan, function(input, processed)
|
|
3237
|
+
if not dev then
|
|
3238
|
+
return nil
|
|
3239
|
+
end
|
|
3240
|
+
local held = UserInputService:IsKeyDown(Enum.KeyCode.LeftControl) or UserInputService:IsKeyDown(Enum.KeyCode.RightControl) or UserInputService:IsKeyDown(Enum.KeyCode.LeftSuper) or UserInputService:IsKeyDown(Enum.KeyCode.RightSuper) or UserInputService:IsKeyDown(Enum.KeyCode.LeftMeta) or UserInputService:IsKeyDown(Enum.KeyCode.RightMeta)
|
|
3241
|
+
if not isPaletteHotkey(input.KeyCode.Name, held, processed) then
|
|
3242
|
+
return nil
|
|
3243
|
+
end
|
|
3244
|
+
local _result = palette
|
|
3245
|
+
if _result ~= nil then
|
|
3246
|
+
_result = _result.isOpen()
|
|
3247
|
+
end
|
|
3248
|
+
if _result then
|
|
3249
|
+
palette.close()
|
|
3250
|
+
else
|
|
3251
|
+
openPalette()
|
|
3252
|
+
end
|
|
3253
|
+
end)
|
|
3124
3254
|
return {
|
|
3125
3255
|
started = function()
|
|
3126
3256
|
-- Its own thread: off dev rules it waits for the server's answer (op "access").
|
|
@@ -3129,7 +3259,7 @@ local function startDevtoolsClient(kernel, dispatcher, trove)
|
|
|
3129
3259
|
return playReloadSound()
|
|
3130
3260
|
end)
|
|
3131
3261
|
if not ok then
|
|
3132
|
-
warn("[src/devtools/client.ts:
|
|
3262
|
+
warn("[src/devtools/client.ts:2270]", `[devtools] reload sound failed: {err}`)
|
|
3133
3263
|
end
|
|
3134
3264
|
end))
|
|
3135
3265
|
end,
|