@el4cteo/rbx-studio-mcp 0.3.1 → 0.3.6
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 +20 -26
- package/dist/index.js +1 -1
- package/dist/lib/format.js +18 -2
- package/dist/lib/format.js.map +1 -1
- package/dist/tools/api.js +20 -1
- package/dist/tools/api.js.map +1 -1
- package/dist/tools/debug.js +18 -8
- package/dist/tools/debug.js.map +1 -1
- package/dist/tools/discover.js +5 -0
- package/dist/tools/discover.js.map +1 -1
- package/dist/tools/input.js +38 -5
- package/dist/tools/input.js.map +1 -1
- package/dist/tools/instances.js +49 -2
- package/dist/tools/instances.js.map +1 -1
- package/dist/tools/perf.js +9 -8
- package/dist/tools/perf.js.map +1 -1
- package/dist/tools/scripts.js +10 -1
- package/dist/tools/scripts.js.map +1 -1
- package/package.json +1 -1
- package/plugin/src/Config.luau +1 -1
- package/plugin/src/Console.luau +301 -117
- package/plugin/src/Mirror.luau +126 -0
- package/plugin/src/Serialize.luau +43 -3
- package/plugin/src/ThemePicker.luau +458 -0
- package/plugin/src/Themes/Aurora.luau +161 -0
- package/plugin/src/Themes/Blueprint.luau +213 -0
- package/plugin/src/Themes/Draw.luau +177 -0
- package/plugin/src/Themes/Lattice.luau +304 -0
- package/plugin/src/Themes/Nebula.luau +182 -0
- package/plugin/src/Themes/Observatory.luau +200 -0
- package/plugin/src/Themes/Orbit.luau +268 -0
- package/plugin/src/Themes/Phosphor.luau +200 -0
- package/plugin/src/Themes/Theme.luau +121 -0
- package/plugin/src/Themes/Void.luau +266 -0
- package/plugin/src/Themes/init.luau +116 -0
- package/plugin/src/Undo.luau +29 -0
- package/plugin/src/Visuals.luau +838 -907
- package/plugin/src/handlers/Device.luau +21 -1
- package/plugin/src/handlers/Discover.luau +75 -1
- package/plugin/src/handlers/Exec.luau +17 -5
- package/plugin/src/handlers/Input.luau +583 -493
- package/plugin/src/handlers/Instances.luau +35 -3
- package/plugin/src/init.server.luau +209 -11
package/plugin/src/Console.luau
CHANGED
|
@@ -18,39 +18,87 @@
|
|
|
18
18
|
Instances, and colour spans do the same job in a fraction of the code.
|
|
19
19
|
]]
|
|
20
20
|
|
|
21
|
+
local ThemePicker = require(script.Parent.ThemePicker)
|
|
22
|
+
local Themes = require(script.Parent.Themes)
|
|
21
23
|
local Visuals = require(script.Parent.Visuals)
|
|
22
24
|
|
|
23
25
|
local Console = {}
|
|
24
26
|
|
|
25
|
-
--
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
27
|
+
--[[
|
|
28
|
+
Ring buffer bound, counted in logged rows rather than in rendered lines.
|
|
29
|
+
|
|
30
|
+
It used to bound the rendered lines, which meant a burst of failures -- each
|
|
31
|
+
writing a message plus two wrapped lines of explanation -- evicted three
|
|
32
|
+
times as much history as a burst of successes. Bounding the records makes
|
|
33
|
+
"the last three hundred things that happened" mean what it says.
|
|
34
|
+
]]
|
|
35
|
+
local MAX_RECORDS = 300
|
|
36
|
+
|
|
37
|
+
--[[
|
|
38
|
+
The active preset's colours, rebound rather than re-read.
|
|
39
|
+
|
|
40
|
+
Every use site in this file is a plain `PALETTE.dim`, and there are about
|
|
41
|
+
forty of them. Turning each into a function call to satisfy theming would
|
|
42
|
+
have been forty edits to working code for no gain: the palette changes only
|
|
43
|
+
when the user picks a different preset, so it is simply reassigned there and
|
|
44
|
+
the reads stay as they were.
|
|
45
|
+
|
|
46
|
+
The catch is that anything which COPIES a colour out of here keeps the old
|
|
47
|
+
one. Instances built in `mount` do exactly that, which is why `applyTheme`
|
|
48
|
+
has to walk them by hand.
|
|
49
|
+
]]
|
|
50
|
+
local PALETTE: Themes.Palette = Themes.palette()
|
|
51
|
+
|
|
52
|
+
--[[
|
|
53
|
+
An optional listener on everything this console is told.
|
|
54
|
+
|
|
55
|
+
Exists for `Mirror`, which relays a playtest server's activity to the client
|
|
56
|
+
view that cannot reach the bridge itself. Kept as one hook rather than as
|
|
57
|
+
calls sprinkled through the file so there is a single place where "what the
|
|
58
|
+
console was told" is defined, and so the client half -- which sets no
|
|
59
|
+
observer -- cannot echo what it replays back into the channel.
|
|
60
|
+
]]
|
|
61
|
+
local observer: ((string, { any }) -> ())? = nil
|
|
62
|
+
|
|
63
|
+
function Console.setObserver(fn: ((string, { any }) -> ())?)
|
|
64
|
+
observer = fn
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
local function notify(kind: string, arguments: { any })
|
|
68
|
+
local listener = observer
|
|
69
|
+
if listener ~= nil then
|
|
70
|
+
task.spawn(listener, kind, arguments)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
--[[
|
|
75
|
+
Set on the console that is REPLAYING someone else's events.
|
|
76
|
+
|
|
77
|
+
A replayed call must not grow its own consequences. `recordCall` schedules
|
|
78
|
+
the "agent idle" summary, so a mirrored session scheduled one of its own and
|
|
79
|
+
then received the original over the channel too -- the same line twice, a
|
|
80
|
+
few pixels apart, which is exactly the sort of thing that makes a panel look
|
|
81
|
+
broken. The origin is the only session entitled to decide the burst ended.
|
|
82
|
+
]]
|
|
83
|
+
local mirroring = false
|
|
84
|
+
|
|
85
|
+
function Console.setMirroring(value: boolean)
|
|
86
|
+
mirroring = value
|
|
87
|
+
end
|
|
43
88
|
|
|
44
89
|
-- Sigil plus colour per level. The sigil is what makes the log scannable at a
|
|
45
90
|
-- glance; colour alone fails for anyone who cannot separate red from green.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
91
|
+
-- Named by palette key rather than by colour, because this table is built once
|
|
92
|
+
-- at load and a colour captured then belongs to whichever preset happened to
|
|
93
|
+
-- be active at the time.
|
|
94
|
+
local LEVELS: { [string]: { sigil: string, key: string } } = {
|
|
95
|
+
ok = { sigil = "\u{25C6}", key = "green" },
|
|
96
|
+
error = { sigil = "\u{2715}", key = "red" },
|
|
97
|
+
warn = { sigil = "\u{25B2}", key = "amber" },
|
|
98
|
+
info = { sigil = "\u{25C6}", key = "violet" },
|
|
99
|
+
dim = { sigil = "\u{00B7}", key = "dim" },
|
|
100
|
+
call = { sigil = "\u{25B8}", key = "cyan" },
|
|
101
|
+
reply = { sigil = "\u{25C2}", key = "violet" },
|
|
54
102
|
}
|
|
55
103
|
|
|
56
104
|
export type Level = "ok" | "error" | "warn" | "info" | "dim" | "call" | "reply"
|
|
@@ -105,8 +153,28 @@ local function wrapDetail(detail: string): { string }
|
|
|
105
153
|
return lines
|
|
106
154
|
end
|
|
107
155
|
|
|
156
|
+
--[[
|
|
157
|
+
One logged row, before it is coloured.
|
|
158
|
+
|
|
159
|
+
Holding the parts rather than the finished string is what lets a theme
|
|
160
|
+
switch repaint history: see `renderRecord`.
|
|
161
|
+
]]
|
|
162
|
+
type Record = {
|
|
163
|
+
level: Level,
|
|
164
|
+
message: string,
|
|
165
|
+
detail: string?,
|
|
166
|
+
stamp: string,
|
|
167
|
+
}
|
|
168
|
+
|
|
108
169
|
type State = {
|
|
109
|
-
|
|
170
|
+
records: { Record },
|
|
171
|
+
-- The last status reported, replayed after a theme switch. Without it the
|
|
172
|
+
-- header has no way to know what colour it should be wearing.
|
|
173
|
+
status: string,
|
|
174
|
+
statusMeta: string,
|
|
175
|
+
-- The accent rule's gradient. A ColorSequence rather than a Color3, so it
|
|
176
|
+
-- cannot ride the generic role list.
|
|
177
|
+
ruleFade: UIGradient?,
|
|
110
178
|
--[[
|
|
111
179
|
Rows written, as opposed to lines rendered.
|
|
112
180
|
|
|
@@ -144,7 +212,10 @@ type State = {
|
|
|
144
212
|
}
|
|
145
213
|
|
|
146
214
|
local state: State = {
|
|
147
|
-
|
|
215
|
+
records = {},
|
|
216
|
+
ruleFade = nil,
|
|
217
|
+
status = "disconnected",
|
|
218
|
+
statusMeta = "",
|
|
148
219
|
entries = 0,
|
|
149
220
|
label = nil,
|
|
150
221
|
scroller = nil,
|
|
@@ -189,13 +260,89 @@ local function span(color: Color3, value: string): string
|
|
|
189
260
|
return string.format('<font color="%s">%s</font>', hex(color), escape(value))
|
|
190
261
|
end
|
|
191
262
|
|
|
263
|
+
--[[
|
|
264
|
+
Turns one record into the lines it occupies.
|
|
265
|
+
|
|
266
|
+
Rendering is deferred to here rather than done when the row is logged, which
|
|
267
|
+
is the change that made theming possible at all: a line that has already had
|
|
268
|
+
`#A78BFA` baked into it cannot be recoloured, so switching preset used to
|
|
269
|
+
leave the whole session's history in the previous theme's palette while new
|
|
270
|
+
rows arrived in the new one. Now nothing is coloured until it is painted.
|
|
271
|
+
]]
|
|
272
|
+
local function renderRecord(record: Record): { string }
|
|
273
|
+
local spec = LEVELS[record.level] or LEVELS.info
|
|
274
|
+
local detail = record.detail
|
|
275
|
+
|
|
276
|
+
--[[
|
|
277
|
+
Measured in characters, not bytes.
|
|
278
|
+
|
|
279
|
+
`#body` counts bytes, and every sigil in this console is a 3-byte UTF-8
|
|
280
|
+
glyph, so it over-counted each line by two and pushed the latency column
|
|
281
|
+
two places left on exactly the lines that had a latency. The column was
|
|
282
|
+
never straight, and the cause was invisible until two different sigils
|
|
283
|
+
sat next to each other.
|
|
284
|
+
]]
|
|
285
|
+
--[[
|
|
286
|
+
Only a detail that will actually sit in the column costs the message any
|
|
287
|
+
of its width. Cutting the message to make room for a detail that then
|
|
288
|
+
goes on its own line below would shorten it for nothing.
|
|
289
|
+
]]
|
|
290
|
+
local detailWidth = if detail then (utf8.len(detail) or #detail) else 0
|
|
291
|
+
local inlineDetail = detail ~= nil
|
|
292
|
+
and (utf8.len(record.message) or #record.message) + detailWidth + 3
|
|
293
|
+
<= DETAIL_COLUMN + INLINE_DETAIL
|
|
294
|
+
|
|
295
|
+
local trimmed = record.message
|
|
296
|
+
local budget = DETAIL_COLUMN - 3
|
|
297
|
+
if inlineDetail and (utf8.len(trimmed) or #trimmed) > budget then
|
|
298
|
+
-- Cut rather than wrap. A wrapped line destroys the alignment and
|
|
299
|
+
-- carries the detail off the end of the visible width as well.
|
|
300
|
+
local offset = utf8.offset(trimmed, budget) or budget
|
|
301
|
+
trimmed = string.sub(trimmed, 1, offset - 1) .. utf8.char(0x2026)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
local body = string.format("%s %s", spec.sigil, trimmed)
|
|
305
|
+
local bodyWidth = utf8.len(body) or #body
|
|
306
|
+
local lines = { span(PALETTE.dim, record.stamp) .. " " .. span(PALETTE[spec.key], body) }
|
|
307
|
+
|
|
308
|
+
--[[
|
|
309
|
+
Short details ride the right-hand column; long ones get their own lines.
|
|
310
|
+
|
|
311
|
+
The column exists for latencies -- "12ms" stacking into something
|
|
312
|
+
readable -- and it was applied to every detail regardless of length. A
|
|
313
|
+
sentence of prose therefore started at column 52, ran off the widget, and
|
|
314
|
+
wrapped back to column 0, so the explanation of a standby session came
|
|
315
|
+
out as a ragged block that began in the middle of the screen and ended at
|
|
316
|
+
the left edge. Anything that will not fit beside the message is better
|
|
317
|
+
off beneath it.
|
|
318
|
+
]]
|
|
319
|
+
if detail then
|
|
320
|
+
if inlineDetail then
|
|
321
|
+
local padding = math.max(1, DETAIL_COLUMN - bodyWidth)
|
|
322
|
+
lines[1] ..= span(PALETTE.dim, string.rep(" ", padding) .. detail)
|
|
323
|
+
else
|
|
324
|
+
for _, wrapped in wrapDetail(detail) do
|
|
325
|
+
table.insert(lines, span(PALETTE.dim, DETAIL_INDENT .. wrapped))
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
return lines
|
|
330
|
+
end
|
|
331
|
+
|
|
192
332
|
local function paint()
|
|
193
333
|
state.dirty = false
|
|
194
334
|
local label = state.label
|
|
195
335
|
if not label then
|
|
196
336
|
return
|
|
197
337
|
end
|
|
198
|
-
|
|
338
|
+
|
|
339
|
+
local lines: { string } = {}
|
|
340
|
+
for _, record in state.records do
|
|
341
|
+
for _, line in renderRecord(record) do
|
|
342
|
+
table.insert(lines, line)
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
label.Text = table.concat(lines, "\n")
|
|
199
346
|
|
|
200
347
|
-- Only follow the tail when the user has not scrolled up to read history.
|
|
201
348
|
local scroller = state.scroller
|
|
@@ -261,66 +408,21 @@ end
|
|
|
261
408
|
ragged offset.
|
|
262
409
|
]]
|
|
263
410
|
function Console.log(level: Level, message: string, detail: string?)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
sat next to each other.
|
|
274
|
-
]]
|
|
275
|
-
--[[
|
|
276
|
-
Only a detail that will actually sit in the column costs the message any
|
|
277
|
-
of its width. Cutting the message to make room for a detail that then
|
|
278
|
-
goes on its own line below would shorten it for nothing.
|
|
279
|
-
]]
|
|
280
|
-
local detailWidth = if detail then (utf8.len(detail) or #detail) else 0
|
|
281
|
-
local inlineDetail = detail ~= nil
|
|
282
|
-
and (utf8.len(message) or #message) + detailWidth + 3 <= DETAIL_COLUMN + INLINE_DETAIL
|
|
283
|
-
|
|
284
|
-
local trimmed = message
|
|
285
|
-
local budget = DETAIL_COLUMN - 3
|
|
286
|
-
if inlineDetail and (utf8.len(trimmed) or #trimmed) > budget then
|
|
287
|
-
-- Cut rather than wrap. A wrapped line destroys the alignment and
|
|
288
|
-
-- carries the detail off the end of the visible width as well.
|
|
289
|
-
local offset = utf8.offset(trimmed, budget) or budget
|
|
290
|
-
trimmed = string.sub(trimmed, 1, offset - 1) .. utf8.char(0x2026)
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
local body = string.format("%s %s", spec.sigil, trimmed)
|
|
294
|
-
local bodyWidth = utf8.len(body) or #body
|
|
295
|
-
local line = span(PALETTE.dim, os.date("%H:%M:%S") :: string) .. " " .. span(spec.color, body)
|
|
296
|
-
|
|
297
|
-
--[[
|
|
298
|
-
Short details ride the right-hand column; long ones get their own lines.
|
|
299
|
-
|
|
300
|
-
The column exists for latencies -- "12ms" stacking into something
|
|
301
|
-
readable -- and it was applied to every detail regardless of length. A
|
|
302
|
-
sentence of prose therefore started at column 52, ran off the widget, and
|
|
303
|
-
wrapped back to column 0, so the explanation of a standby session came
|
|
304
|
-
out as a ragged block that began in the middle of the screen and ended at
|
|
305
|
-
the left edge. Anything that will not fit beside the message is better
|
|
306
|
-
off beneath it.
|
|
307
|
-
]]
|
|
308
|
-
table.insert(state.lines, line)
|
|
411
|
+
table.insert(state.records, {
|
|
412
|
+
level = level,
|
|
413
|
+
message = message,
|
|
414
|
+
detail = detail,
|
|
415
|
+
-- Stamped when the row happened, not when it is painted. A repaint after
|
|
416
|
+
-- a theme switch re-renders every line, and re-reading the clock there
|
|
417
|
+
-- would restamp the whole session to the moment the user changed colour.
|
|
418
|
+
stamp = os.date("%H:%M:%S") :: string,
|
|
419
|
+
})
|
|
309
420
|
state.entries += 1
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
local padding = math.max(1, DETAIL_COLUMN - bodyWidth)
|
|
313
|
-
state.lines[#state.lines] ..= span(PALETTE.dim, string.rep(" ", padding) .. detail)
|
|
314
|
-
else
|
|
315
|
-
for _, wrapped in wrapDetail(detail) do
|
|
316
|
-
table.insert(state.lines, span(PALETTE.dim, DETAIL_INDENT .. wrapped))
|
|
317
|
-
end
|
|
318
|
-
end
|
|
319
|
-
end
|
|
320
|
-
while #state.lines > MAX_LINES do
|
|
321
|
-
table.remove(state.lines, 1)
|
|
421
|
+
while #state.records > MAX_RECORDS do
|
|
422
|
+
table.remove(state.records, 1)
|
|
322
423
|
end
|
|
323
424
|
redraw()
|
|
425
|
+
notify("log", { level, message, detail })
|
|
324
426
|
end
|
|
325
427
|
|
|
326
428
|
--[[
|
|
@@ -344,11 +446,11 @@ end
|
|
|
344
446
|
and heavier still. Urgency drives both the spin rate and how strongly the
|
|
345
447
|
colour takes over, so the two never disagree.
|
|
346
448
|
]]
|
|
347
|
-
local KIND_LOOK: { [string]: {
|
|
348
|
-
read = {
|
|
349
|
-
write = {
|
|
350
|
-
run = {
|
|
351
|
-
debug = {
|
|
449
|
+
local KIND_LOOK: { [string]: { key: string, urgency: number } } = {
|
|
450
|
+
read = { key = "cyan", urgency = 0.3 },
|
|
451
|
+
write = { key = "violet", urgency = 0.7 },
|
|
452
|
+
run = { key = "green", urgency = 0.9 },
|
|
453
|
+
debug = { key = "amber", urgency = 0.5 },
|
|
352
454
|
}
|
|
353
455
|
|
|
354
456
|
function Console.beginCall(title: string, kind: string)
|
|
@@ -356,8 +458,9 @@ function Console.beginCall(title: string, kind: string)
|
|
|
356
458
|
state.generation += 1
|
|
357
459
|
local look = KIND_LOOK[kind] or KIND_LOOK.read
|
|
358
460
|
Visuals.setCaption(title)
|
|
359
|
-
Visuals.setKind(look.
|
|
461
|
+
Visuals.setKind(PALETTE[look.key], look.urgency)
|
|
360
462
|
refreshCounters()
|
|
463
|
+
notify("beginCall", { title, kind })
|
|
361
464
|
end
|
|
362
465
|
|
|
363
466
|
--[[
|
|
@@ -382,6 +485,7 @@ function Console.recordCall(ok: boolean, milliseconds: number)
|
|
|
382
485
|
-- The band plots it: bar height is how long it took, colour is whether it
|
|
383
486
|
-- worked, and a failure knocks the solid off its axis as well.
|
|
384
487
|
Visuals.recordCall(milliseconds, ok, title)
|
|
488
|
+
notify("recordCall", { ok, milliseconds, title })
|
|
385
489
|
|
|
386
490
|
--[[
|
|
387
491
|
Says so when the session goes quiet, once per burst.
|
|
@@ -395,6 +499,9 @@ function Console.recordCall(ok: boolean, milliseconds: number)
|
|
|
395
499
|
]]
|
|
396
500
|
state.generation += 1
|
|
397
501
|
local mine = state.generation
|
|
502
|
+
if mirroring then
|
|
503
|
+
return
|
|
504
|
+
end
|
|
398
505
|
task.delay(QUIET_SECONDS, function()
|
|
399
506
|
if state.generation ~= mine or state.calls == 0 then
|
|
400
507
|
return
|
|
@@ -499,7 +606,7 @@ function Console.clear()
|
|
|
499
606
|
the one that went.
|
|
500
607
|
]]
|
|
501
608
|
local cleared = state.entries
|
|
502
|
-
table.clear(state.
|
|
609
|
+
table.clear(state.records)
|
|
503
610
|
state.entries = 0
|
|
504
611
|
state.calls = 0
|
|
505
612
|
state.errors = 0
|
|
@@ -524,6 +631,9 @@ end
|
|
|
524
631
|
visible without scrolling, however long the session has run.
|
|
525
632
|
]]
|
|
526
633
|
function Console.setStatus(status: string, meta: string)
|
|
634
|
+
state.status = status
|
|
635
|
+
state.statusMeta = meta
|
|
636
|
+
|
|
527
637
|
local dot = state.statusDot
|
|
528
638
|
local text = state.statusText
|
|
529
639
|
local metaLabel = state.metaText
|
|
@@ -537,7 +647,7 @@ function Console.setStatus(status: string, meta: string)
|
|
|
537
647
|
local color = if status == "connected"
|
|
538
648
|
then PALETTE.green
|
|
539
649
|
elseif status == "connecting" then PALETTE.amber
|
|
540
|
-
elseif status == "standby" then PALETTE.dim
|
|
650
|
+
elseif status == "standby" or status == "mirroring" then PALETTE.dim
|
|
541
651
|
else PALETTE.red
|
|
542
652
|
|
|
543
653
|
dot.TextColor3 = color
|
|
@@ -550,14 +660,37 @@ function Console.setStatus(status: string, meta: string)
|
|
|
550
660
|
Visuals.setTint(color)
|
|
551
661
|
end
|
|
552
662
|
|
|
663
|
+
--[[
|
|
664
|
+
Records which palette colour an Instance is wearing, and puts it on.
|
|
665
|
+
|
|
666
|
+
The console builds about thirty Instances and copies a colour into each. A
|
|
667
|
+
copy does not follow the palette when the user picks a different preset, so
|
|
668
|
+
the panel would keep the old theme's header, rule, scrollbar and buttons
|
|
669
|
+
while the log underneath repainted -- which looks less like a theme than
|
|
670
|
+
like a half-finished render.
|
|
671
|
+
|
|
672
|
+
Rather than keep thirty named references and re-set thirty properties by
|
|
673
|
+
hand, each one declares the property and palette key it is wearing when it
|
|
674
|
+
is built, and `Console.applyTheme` walks the list. Adding a widget therefore
|
|
675
|
+
cannot forget to theme it: the same call that colours it registers it.
|
|
676
|
+
]]
|
|
677
|
+
type Role = { instance: Instance, property: string, key: string }
|
|
678
|
+
local roles: { Role } = {}
|
|
679
|
+
|
|
680
|
+
local function themed<T>(instance: T & Instance, property: string, key: string): T
|
|
681
|
+
table.insert(roles, { instance = instance, property = property, key = key })
|
|
682
|
+
;(instance :: any)[property] = PALETTE[key]
|
|
683
|
+
return instance
|
|
684
|
+
end
|
|
685
|
+
|
|
553
686
|
local function makeButton(parent: Instance, text: string, order: number): TextButton
|
|
554
687
|
local button = Instance.new("TextButton")
|
|
555
688
|
button.Name = text
|
|
556
689
|
button.Text = text
|
|
557
690
|
button.Font = Enum.Font.Code
|
|
558
691
|
button.TextSize = 11
|
|
559
|
-
button
|
|
560
|
-
button
|
|
692
|
+
themed(button, "TextColor3", "dim")
|
|
693
|
+
themed(button, "BackgroundColor3", "background")
|
|
561
694
|
button.AutoButtonColor = false
|
|
562
695
|
button.BorderSizePixel = 0
|
|
563
696
|
button.Size = UDim2.new(0, 76, 0, 20)
|
|
@@ -569,7 +702,7 @@ local function makeButton(parent: Instance, text: string, order: number): TextBu
|
|
|
569
702
|
corner.Parent = button
|
|
570
703
|
|
|
571
704
|
local stroke = Instance.new("UIStroke")
|
|
572
|
-
stroke
|
|
705
|
+
themed(stroke, "Color", "dim")
|
|
573
706
|
stroke.Transparency = 0.6
|
|
574
707
|
stroke.Parent = button
|
|
575
708
|
|
|
@@ -590,6 +723,10 @@ end
|
|
|
590
723
|
export type Handlers = {
|
|
591
724
|
onReconnect: () -> (),
|
|
592
725
|
onClear: () -> (),
|
|
726
|
+
-- Called after a preset switch has already been applied, so the caller's
|
|
727
|
+
-- only job is to remember it. Persistence lives there because
|
|
728
|
+
-- `plugin:SetSetting` is not reachable from a ModuleScript.
|
|
729
|
+
onTheme: (string) -> (),
|
|
593
730
|
}
|
|
594
731
|
|
|
595
732
|
--[[
|
|
@@ -599,14 +736,14 @@ export type Handlers = {
|
|
|
599
736
|
function Console.mount(parent: Instance, handlers: Handlers)
|
|
600
737
|
local root = Instance.new("Frame")
|
|
601
738
|
root.Size = UDim2.fromScale(1, 1)
|
|
602
|
-
root
|
|
739
|
+
themed(root, "BackgroundColor3", "background")
|
|
603
740
|
root.BorderSizePixel = 0
|
|
604
741
|
root.Parent = parent
|
|
605
742
|
|
|
606
743
|
-- Header ---------------------------------------------------------------
|
|
607
744
|
local header = Instance.new("Frame")
|
|
608
745
|
header.Size = UDim2.new(1, 0, 0, 32)
|
|
609
|
-
header
|
|
746
|
+
themed(header, "BackgroundColor3", "surface")
|
|
610
747
|
header.BorderSizePixel = 0
|
|
611
748
|
header.Parent = root
|
|
612
749
|
|
|
@@ -619,6 +756,10 @@ function Console.mount(parent: Instance, handlers: Handlers)
|
|
|
619
756
|
dot.Text = "\u{25CF}"
|
|
620
757
|
dot.Font = Enum.Font.Code
|
|
621
758
|
dot.TextSize = 13
|
|
759
|
+
-- Deliberately NOT registered with `themed`. Red here is the value it
|
|
760
|
+
-- STARTS at, not the colour it wears: what it should be is whatever
|
|
761
|
+
-- `setStatus` last reported. Registering it meant every theme switch
|
|
762
|
+
-- repainted a connected session red.
|
|
622
763
|
dot.TextColor3 = PALETTE.red
|
|
623
764
|
dot.BackgroundTransparency = 1
|
|
624
765
|
dot.Size = UDim2.new(0, 12, 1, 0)
|
|
@@ -641,7 +782,7 @@ function Console.mount(parent: Instance, handlers: Handlers)
|
|
|
641
782
|
meta.Text = ""
|
|
642
783
|
meta.Font = Enum.Font.Code
|
|
643
784
|
meta.TextSize = 11
|
|
644
|
-
meta
|
|
785
|
+
themed(meta, "TextColor3", "dim")
|
|
645
786
|
meta.TextXAlignment = Enum.TextXAlignment.Left
|
|
646
787
|
meta.TextTruncate = Enum.TextTruncate.AtEnd
|
|
647
788
|
meta.BackgroundTransparency = 1
|
|
@@ -663,11 +804,11 @@ function Console.mount(parent: Instance, handlers: Handlers)
|
|
|
663
804
|
clientsChip.AnchorPoint = Vector2.new(1, 0.5)
|
|
664
805
|
clientsChip.Position = UDim2.new(1, -252, 0.5, 0)
|
|
665
806
|
clientsChip.Size = UDim2.new(0, 84, 0, 18)
|
|
666
|
-
clientsChip
|
|
807
|
+
themed(clientsChip, "BackgroundColor3", "background")
|
|
667
808
|
clientsChip.BorderSizePixel = 0
|
|
668
809
|
clientsChip.Font = Enum.Font.Code
|
|
669
810
|
clientsChip.TextSize = 11
|
|
670
|
-
clientsChip
|
|
811
|
+
themed(clientsChip, "TextColor3", "cyan")
|
|
671
812
|
clientsChip.Text = ""
|
|
672
813
|
clientsChip.Visible = false
|
|
673
814
|
clientsChip.Parent = header
|
|
@@ -678,7 +819,7 @@ function Console.mount(parent: Instance, handlers: Handlers)
|
|
|
678
819
|
chipCorner.Parent = clientsChip
|
|
679
820
|
|
|
680
821
|
local chipStroke = Instance.new("UIStroke")
|
|
681
|
-
chipStroke
|
|
822
|
+
themed(chipStroke, "Color", "cyan")
|
|
682
823
|
chipStroke.Transparency = 0.6
|
|
683
824
|
chipStroke.Parent = clientsChip
|
|
684
825
|
|
|
@@ -706,12 +847,13 @@ function Console.mount(parent: Instance, handlers: Handlers)
|
|
|
706
847
|
local rule = Instance.new("Frame")
|
|
707
848
|
rule.Position = UDim2.new(0, 0, 0, 32)
|
|
708
849
|
rule.Size = UDim2.new(1, 0, 0, 1)
|
|
709
|
-
rule
|
|
850
|
+
themed(rule, "BackgroundColor3", "rule")
|
|
710
851
|
rule.BorderSizePixel = 0
|
|
711
852
|
rule.Parent = root
|
|
712
853
|
|
|
713
854
|
local ruleFade = Instance.new("UIGradient")
|
|
714
855
|
ruleFade.Color = ColorSequence.new(PALETTE.rule)
|
|
856
|
+
state.ruleFade = ruleFade
|
|
715
857
|
ruleFade.Transparency = NumberSequence.new({
|
|
716
858
|
NumberSequenceKeypoint.new(0, 0.15),
|
|
717
859
|
NumberSequenceKeypoint.new(1, 0.85),
|
|
@@ -725,7 +867,7 @@ function Console.mount(parent: Instance, handlers: Handlers)
|
|
|
725
867
|
scroller.BackgroundTransparency = 1
|
|
726
868
|
scroller.BorderSizePixel = 0
|
|
727
869
|
scroller.ScrollBarThickness = 5
|
|
728
|
-
scroller
|
|
870
|
+
themed(scroller, "ScrollBarImageColor3", "violet")
|
|
729
871
|
scroller.ScrollBarImageTransparency = 0.5
|
|
730
872
|
scroller.CanvasSize = UDim2.new()
|
|
731
873
|
scroller.AutomaticCanvasSize = Enum.AutomaticSize.Y
|
|
@@ -747,7 +889,7 @@ function Console.mount(parent: Instance, handlers: Handlers)
|
|
|
747
889
|
label.Font = Enum.Font.Code
|
|
748
890
|
label.TextSize = 12
|
|
749
891
|
label.LineHeight = 1.25
|
|
750
|
-
label
|
|
892
|
+
themed(label, "TextColor3", "text")
|
|
751
893
|
label.RichText = true
|
|
752
894
|
label.TextWrapped = true
|
|
753
895
|
label.TextXAlignment = Enum.TextXAlignment.Left
|
|
@@ -761,7 +903,7 @@ function Console.mount(parent: Instance, handlers: Handlers)
|
|
|
761
903
|
footer.AnchorPoint = Vector2.new(0, 1)
|
|
762
904
|
footer.Position = UDim2.fromScale(0, 1)
|
|
763
905
|
footer.Size = UDim2.new(1, 0, 0, 22)
|
|
764
|
-
footer
|
|
906
|
+
themed(footer, "BackgroundColor3", "surface")
|
|
765
907
|
footer.BorderSizePixel = 0
|
|
766
908
|
footer.Parent = root
|
|
767
909
|
|
|
@@ -774,7 +916,7 @@ function Console.mount(parent: Instance, handlers: Handlers)
|
|
|
774
916
|
prompt.Text = "rbx\u{00B7}studio"
|
|
775
917
|
prompt.Font = Enum.Font.Code
|
|
776
918
|
prompt.TextSize = 11
|
|
777
|
-
prompt
|
|
919
|
+
themed(prompt, "TextColor3", "violet")
|
|
778
920
|
prompt.TextXAlignment = Enum.TextXAlignment.Left
|
|
779
921
|
prompt.BackgroundTransparency = 1
|
|
780
922
|
prompt.Size = UDim2.new(0, 70, 1, 0)
|
|
@@ -793,7 +935,7 @@ function Console.mount(parent: Instance, handlers: Handlers)
|
|
|
793
935
|
counters.Text = "idle"
|
|
794
936
|
counters.Font = Enum.Font.Code
|
|
795
937
|
counters.TextSize = 11
|
|
796
|
-
counters
|
|
938
|
+
themed(counters, "TextColor3", "dim")
|
|
797
939
|
counters.TextXAlignment = Enum.TextXAlignment.Right
|
|
798
940
|
counters.BackgroundTransparency = 1
|
|
799
941
|
counters.AnchorPoint = Vector2.new(1, 0)
|
|
@@ -810,14 +952,7 @@ function Console.mount(parent: Instance, handlers: Handlers)
|
|
|
810
952
|
costs legibility is a bad trade however good it looks, and this is a
|
|
811
953
|
console before it is anything else.
|
|
812
954
|
]]
|
|
813
|
-
Visuals.mount(root
|
|
814
|
-
background = PALETTE.background,
|
|
815
|
-
surface = PALETTE.surface,
|
|
816
|
-
text = PALETTE.text,
|
|
817
|
-
dim = PALETTE.dim,
|
|
818
|
-
violet = PALETTE.violet,
|
|
819
|
-
red = PALETTE.red,
|
|
820
|
-
})
|
|
955
|
+
Visuals.mount(root)
|
|
821
956
|
local band = root:FindFirstChild("ActivityBand") :: Frame
|
|
822
957
|
band.Position = UDim2.new(0, 0, 0, 33)
|
|
823
958
|
band.Size = UDim2.new(1, 0, 0, Visuals.BAND_HEIGHT)
|
|
@@ -846,6 +981,20 @@ function Console.mount(parent: Instance, handlers: Handlers)
|
|
|
846
981
|
visualsButton.Text = if Visuals.isVisible() then "\u{25C6} visuals" else "visuals"
|
|
847
982
|
end)
|
|
848
983
|
|
|
984
|
+
--[[
|
|
985
|
+
The preset drawer, mounted last so its tab sits over everything.
|
|
986
|
+
|
|
987
|
+
It has to be the last child of `root` as well as the highest ZIndex: the
|
|
988
|
+
band, the log and the footer are all built before it and a drawer that
|
|
989
|
+
opens behind the log is a drawer nobody can click.
|
|
990
|
+
]]
|
|
991
|
+
ThemePicker.mount(root, function(id)
|
|
992
|
+
Console.applyTheme()
|
|
993
|
+
ThemePicker.applyTheme()
|
|
994
|
+
layoutLog()
|
|
995
|
+
handlers.onTheme(id)
|
|
996
|
+
end)
|
|
997
|
+
|
|
849
998
|
-- Treat "scrolled away from the bottom" as the user reading history, and
|
|
850
999
|
-- stop yanking the view down under them until they scroll back.
|
|
851
1000
|
scroller:GetPropertyChangedSignal("CanvasPosition"):Connect(function()
|
|
@@ -854,4 +1003,39 @@ function Console.mount(parent: Instance, handlers: Handlers)
|
|
|
854
1003
|
end)
|
|
855
1004
|
end
|
|
856
1005
|
|
|
1006
|
+
--[[
|
|
1007
|
+
Switches the whole panel to the active preset.
|
|
1008
|
+
|
|
1009
|
+
Three things have to move together or the switch looks broken: the palette
|
|
1010
|
+
this file reads, the Instances that copied a colour out of it, and the log,
|
|
1011
|
+
whose rows are re-rendered from records rather than recoloured in place. The
|
|
1012
|
+
band goes last because remounting a preset is the expensive part and there
|
|
1013
|
+
is no reason to make the text wait for it.
|
|
1014
|
+
]]
|
|
1015
|
+
function Console.applyTheme()
|
|
1016
|
+
PALETTE = Themes.palette()
|
|
1017
|
+
|
|
1018
|
+
for _, role in roles do
|
|
1019
|
+
-- A widget can outlive its registration if the panel is rebuilt, and
|
|
1020
|
+
-- writing to a destroyed Instance throws.
|
|
1021
|
+
if role.instance.Parent ~= nil or role.instance:IsA("UIStroke") then
|
|
1022
|
+
pcall(function()
|
|
1023
|
+
(role.instance :: any)[role.property] = PALETTE[role.key]
|
|
1024
|
+
end)
|
|
1025
|
+
end
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1028
|
+
local ruleFade = state.ruleFade
|
|
1029
|
+
if ruleFade then
|
|
1030
|
+
ruleFade.Color = ColorSequence.new(PALETTE.rule)
|
|
1031
|
+
end
|
|
1032
|
+
|
|
1033
|
+
refreshCounters()
|
|
1034
|
+
-- Replayed rather than recoloured: the status light's colour is a fact about
|
|
1035
|
+
-- the connection, and the only thing that knows it is `setStatus`.
|
|
1036
|
+
Console.setStatus(state.status, state.statusMeta)
|
|
1037
|
+
redraw()
|
|
1038
|
+
Visuals.applyTheme()
|
|
1039
|
+
end
|
|
1040
|
+
|
|
857
1041
|
return Console
|