Tuck the trajectory replay controls into a compact pill
The wide range scrubber under the map was the loudest element on the live map. Keep replay, speed and 回到最新, drop the slider itself, and shrink the bar to a right aligned pill so the map and the colour legend stay in focus.
This commit is contained in:
@@ -787,8 +787,7 @@ to{transform:translate(-50%,-50%) rotate(calc(var(--construct-drift) + 360deg))}
|
||||
.map-ride-marker{position:absolute;z-index:4;display:grid;place-items:center;width:22px;height:22px;border:1px solid color-mix(in srgb,var(--gold) 70%,var(--line));border-radius:999px;background:color-mix(in srgb,var(--surface-solid) 76%,transparent);color:var(--gold);font:800 9px/1 var(--font-mono);transform:translate(-50%,-50%) scale(var(--map-inverse,1));pointer-events:none}
|
||||
.map-ride-marker img{width:100%;height:100%;object-fit:contain}
|
||||
.map-track-swatch{display:inline-block;width:10px;height:10px;margin-right:6px;border-radius:999px;vertical-align:middle}
|
||||
.map-playback-bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:6px 10px;border:1px solid color-mix(in srgb,var(--line) 70%,transparent);border-radius:10px;background:var(--control-surface)}
|
||||
.map-playback-bar input[type=range]{flex:1 1 240px;min-width:120px;min-height:22px;margin:0;accent-color:var(--accent)}
|
||||
.map-playback-bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;width:max-content;max-width:100%;margin-left:auto;padding:5px 10px;border:1px solid color-mix(in srgb,var(--line) 70%,transparent);border-radius:999px;background:var(--control-surface)}
|
||||
.map-playback-bar .page-status{margin:0}
|
||||
.scum-map-menu{position:relative;flex:0 0 auto}
|
||||
.scum-map-menu>summary{display:inline-flex;align-items:center;min-height:26px;padding:0 9px;font-size:11px;list-style:none;cursor:pointer}
|
||||
|
||||
@@ -721,9 +721,8 @@ function persistMapLayers(view: ViewState, actions: SCUMWorkspaceActions | undef
|
||||
function mapPlaybackBar(e: ReactLike["createElement"], view: ViewState, span: { from: number; to: number }, enabled: boolean) {
|
||||
const cursor = view.mapPlayback.value >= 1 ? Infinity : span.from + (span.to - span.from) * view.mapPlayback.value;
|
||||
return e("div", { className: "map-playback-bar" },
|
||||
e("button", { type: "button", className: view.mapPlayback.playing ? "primary-command" : "icon-command", disabled: !enabled, "aria-label": view.mapPlayback.playing ? "暂停轨迹回放" : "播放轨迹回放", onClick: () => view.setMapPlayback((previous) => ({ ...previous, playing: !previous.playing, value: !previous.playing && previous.value >= 1 ? 0 : previous.value })) }, view.mapPlayback.playing ? "暂停" : "回放"),
|
||||
e("input", { type: "range", min: "0", max: "1000", step: "1", value: String(Math.round(view.mapPlayback.value * 1000)), "aria-label": "轨迹回放进度", disabled: !enabled, onChange: (event: InputEvent) => view.setMapPlayback((previous) => ({ ...previous, playing: false, value: Math.min(1, Math.max(0, Number(inputValue(event)) / 1000)) })) }),
|
||||
e("span", { className: "page-status" }, Number.isFinite(cursor) ? `回放 ${timeLabel(cursor)}` : "回放 全部"),
|
||||
e("button", { type: "button", className: view.mapPlayback.playing ? "primary-command" : "icon-command", disabled: !enabled, "aria-label": view.mapPlayback.playing ? "暂停轨迹回放" : "播放轨迹回放", onClick: () => view.setMapPlayback((previous) => ({ ...previous, playing: !previous.playing, value: !previous.playing && previous.value >= 1 ? 0 : previous.value })) }, view.mapPlayback.playing ? "暂停" : "回放"),
|
||||
labeledField(e, "速度", e("select", { value: String(view.mapPlayback.speed), "aria-label": "轨迹回放速度", onChange: (event: InputEvent) => view.setMapPlayback((previous) => ({ ...previous, speed: Number(inputValue(event)) || 1 })) }, [1, 2, 4].map((speed) => e("option", { key: speed, value: String(speed) }, `${speed}x`)))),
|
||||
e("button", { type: "button", className: "icon-command", disabled: !enabled, onClick: () => view.setMapPlayback(scumMapPlaybackIdle) }, "回到最新")
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"id": "game.scum",
|
||||
"name": "SCUM Server",
|
||||
"description": "First-party SCUM game server operations plugin with platform-mediated lifecycle and plugin-owned RCON data flows.",
|
||||
"version": "0.1.23",
|
||||
"version": "0.1.24",
|
||||
"kind": "game-plugin",
|
||||
"tags": [
|
||||
"scum",
|
||||
|
||||
@@ -357,7 +357,7 @@ describe("plugin manifest validation", () => {
|
||||
const serialized = JSON.stringify(manifest).toLowerCase();
|
||||
|
||||
expect(serialized).not.toContain("local-proof");
|
||||
expect(manifest.version).toBe("0.1.23");
|
||||
expect(manifest.version).toBe("0.1.24");
|
||||
expect(installAction.environment?.SERVER_TEMPLATE).toBe("scum-server");
|
||||
expect(manifest.permissions).toEqual(expect.arrayContaining(["server.game-client.read", "server.game-client.command", "server.game-client.maintenance"]));
|
||||
expect(manifest.gameClientBridge.commands.map((command) => command.type)).toEqual(expect.arrayContaining([
|
||||
|
||||
@@ -306,7 +306,9 @@ describe("SCUM plugin feature module", () => {
|
||||
expect(view.texts.some((text) => /\d+ \/ \d+ 个点 · \d+ 条轨迹/.test(text))).toBe(true);
|
||||
for (const layer of ["地形", "等高线", "夜间", "建筑区"]) expect(view.texts).toContain(layer);
|
||||
expect(view.buttons.map((button) => button.label)).not.toEqual(expect.arrayContaining(["地形", "等高线", "夜间"]));
|
||||
expect(view.inputs.map((input) => input.label)).toEqual(expect.arrayContaining(["轨迹快捷时间", "轨迹开始时间", "轨迹结束时间", "地图底图", "轨迹回放进度"]));
|
||||
expect(view.inputs.map((input) => input.label)).toEqual(expect.arrayContaining(["轨迹快捷时间", "轨迹开始时间", "轨迹结束时间", "地图底图"]));
|
||||
expect(view.inputs.map((input) => input.label)).not.toContain("轨迹回放进度");
|
||||
expect(pageSource).not.toContain('type: "range"');
|
||||
expect(view.inputs.map((input) => input.label)).not.toContain("启用自定义地图");
|
||||
expect(view.texts).not.toContain("地图范围与图层");
|
||||
expect(view.texts).not.toContain("自定义范围");
|
||||
|
||||
Reference in New Issue
Block a user