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{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-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-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{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 input[type=range]{flex:1 1 240px;min-width:120px;min-height:22px;margin:0;accent-color:var(--accent)}
|
|
||||||
.map-playback-bar .page-status{margin:0}
|
.map-playback-bar .page-status{margin:0}
|
||||||
.scum-map-menu{position:relative;flex:0 0 auto}
|
.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}
|
.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) {
|
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;
|
const cursor = view.mapPlayback.value >= 1 ? Infinity : span.from + (span.to - span.from) * view.mapPlayback.value;
|
||||||
return e("div", { className: "map-playback-bar" },
|
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("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`)))),
|
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) }, "回到最新")
|
e("button", { type: "button", className: "icon-command", disabled: !enabled, onClick: () => view.setMapPlayback(scumMapPlaybackIdle) }, "回到最新")
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"id": "game.scum",
|
"id": "game.scum",
|
||||||
"name": "SCUM Server",
|
"name": "SCUM Server",
|
||||||
"description": "First-party SCUM game server operations plugin with platform-mediated lifecycle and plugin-owned RCON data flows.",
|
"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",
|
"kind": "game-plugin",
|
||||||
"tags": [
|
"tags": [
|
||||||
"scum",
|
"scum",
|
||||||
|
|||||||
@@ -357,7 +357,7 @@ describe("plugin manifest validation", () => {
|
|||||||
const serialized = JSON.stringify(manifest).toLowerCase();
|
const serialized = JSON.stringify(manifest).toLowerCase();
|
||||||
|
|
||||||
expect(serialized).not.toContain("local-proof");
|
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(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.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([
|
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);
|
expect(view.texts.some((text) => /\d+ \/ \d+ 个点 · \d+ 条轨迹/.test(text))).toBe(true);
|
||||||
for (const layer of ["地形", "等高线", "夜间", "建筑区"]) expect(view.texts).toContain(layer);
|
for (const layer of ["地形", "等高线", "夜间", "建筑区"]) expect(view.texts).toContain(layer);
|
||||||
expect(view.buttons.map((button) => button.label)).not.toEqual(expect.arrayContaining(["地形", "等高线", "夜间"]));
|
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.inputs.map((input) => input.label)).not.toContain("启用自定义地图");
|
||||||
expect(view.texts).not.toContain("地图范围与图层");
|
expect(view.texts).not.toContain("地图范围与图层");
|
||||||
expect(view.texts).not.toContain("自定义范围");
|
expect(view.texts).not.toContain("自定义范围");
|
||||||
|
|||||||
Reference in New Issue
Block a user