Seconds per clock, and type that scales with the bar height
Seconds move from one bar-wide switch to a per-clock setting. A clock's own `showSeconds` wins, the top-level one is the default for clocks with no opinion, and the centre clock gets `local.showSeconds` since it has no entry in `clocks[]`. An explicit false on a clock beats a true default, so the check is for the property's presence rather than its truthiness. The tick rate follows what is actually displayed: `anyShowsSeconds()` decides between a per-second and a per-minute SystemClock, so a bar showing no seconds does not wake the shell sixty times as often to redraw an identical minute. `barSize` is now editable from the popup, and everything drawn on the bar is sized against the theme's own bar height rather than pinned to the theme's 12px type. A taller bar carries proportionally larger text, gaps and rule; at the theme's own height the scale is exactly 1, so nothing moves. Heights are clamped to 12-120 rather than rejected, so a value the editor can produce can never be one the config layer silently discards. Fixes a latent bug this surfaced: updateRow() rebuilt a draft row from a fixed pair of fields, so editing a clock's label would have thrown away its own seconds setting. It now copies every key the row had. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MhxtL7WzL5XmuTd8GHbcTY
This commit is contained in:
+30
-3
@@ -84,6 +84,29 @@ function isWorkingHours(wall, hours) {
|
|||||||
return hour >= start || hour < end
|
return hour >= start || hour < end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Seconds are decided per clock, falling back to the config-wide default.
|
||||||
|
// An explicit `false` on a clock has to beat a `true` default, so the check is
|
||||||
|
// for the property's presence, not its truthiness.
|
||||||
|
function showsSeconds(entry, fallback) {
|
||||||
|
if (entry && typeof entry.showSeconds === "boolean") return entry.showSeconds
|
||||||
|
return fallback === true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether anything on the bar is showing seconds, which is what decides
|
||||||
|
// between a per-second and a per-minute tick. Ticking every second when
|
||||||
|
// nothing displays them wakes the whole shell sixty times as often for no
|
||||||
|
// visible change.
|
||||||
|
function anyShowsSeconds(config) {
|
||||||
|
var cfg = config || {}
|
||||||
|
var fallback = cfg.showSeconds === true
|
||||||
|
if (showsSeconds(cfg.local, fallback)) return true
|
||||||
|
var entries = Array.isArray(cfg.clocks) ? cfg.clocks : []
|
||||||
|
for (var i = 0; i < entries.length; i++) {
|
||||||
|
if (showsSeconds(entries[i], fallback)) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
function isValidLabel(label) {
|
function isValidLabel(label) {
|
||||||
return typeof label === "string" && label.replace(/^\s+|\s+$/g, "").length > 0
|
return typeof label === "string" && label.replace(/^\s+|\s+$/g, "").length > 0
|
||||||
}
|
}
|
||||||
@@ -93,6 +116,7 @@ function isValidLabel(label) {
|
|||||||
// show the label struck through instead of quietly lying about the time.
|
// show the label struck through instead of quietly lying about the time.
|
||||||
function buildClock(entry, offsets, utcMs, localOffset, options) {
|
function buildClock(entry, offsets, utcMs, localOffset, options) {
|
||||||
var opts = options || {}
|
var opts = options || {}
|
||||||
|
var withSeconds = showsSeconds(entry, opts.showSeconds)
|
||||||
var timezone = String(entry && entry.timezone ? entry.timezone : "")
|
var timezone = String(entry && entry.timezone ? entry.timezone : "")
|
||||||
var label = isValidLabel(entry && entry.label) ? String(entry.label).replace(/^\s+|\s+$/g, "") : timezone
|
var label = isValidLabel(entry && entry.label) ? String(entry.label).replace(/^\s+|\s+$/g, "") : timezone
|
||||||
var offset = parseOffset(offsets ? offsets[timezone] : null)
|
var offset = parseOffset(offsets ? offsets[timezone] : null)
|
||||||
@@ -100,7 +124,8 @@ function buildClock(entry, offsets, utcMs, localOffset, options) {
|
|||||||
if (offset === null) {
|
if (offset === null) {
|
||||||
return {
|
return {
|
||||||
label: label, timezone: timezone, known: false,
|
label: label, timezone: timezone, known: false,
|
||||||
time: "--:--", marker: "", delta: 0, relativeMinutes: 0, working: false
|
time: withSeconds ? "--:--:--" : "--:--",
|
||||||
|
marker: "", delta: 0, relativeMinutes: 0, working: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +137,7 @@ function buildClock(entry, offsets, utcMs, localOffset, options) {
|
|||||||
label: label,
|
label: label,
|
||||||
timezone: timezone,
|
timezone: timezone,
|
||||||
known: true,
|
known: true,
|
||||||
time: formatTime(wall, opts.showSeconds === true),
|
time: formatTime(wall, withSeconds),
|
||||||
marker: dayMarker(delta),
|
marker: dayMarker(delta),
|
||||||
delta: delta,
|
delta: delta,
|
||||||
relativeMinutes: offset - localOffset,
|
relativeMinutes: offset - localOffset,
|
||||||
@@ -165,7 +190,7 @@ function buildBar(config, offsets, utcMs, localOffsetRaw) {
|
|||||||
// of the trailing group where they are visible but never mis-ordered.
|
// of the trailing group where they are visible but never mis-ordered.
|
||||||
right: right.concat(unknown),
|
right: right.concat(unknown),
|
||||||
center: {
|
center: {
|
||||||
time: formatTime(localWall, options.showSeconds),
|
time: formatTime(localWall, showsSeconds(cfg.local, options.showSeconds)),
|
||||||
date: localWall.getUTCFullYear() + "-" + pad2(localWall.getUTCMonth() + 1) + "-" + pad2(localWall.getUTCDate()),
|
date: localWall.getUTCFullYear() + "-" + pad2(localWall.getUTCMonth() + 1) + "-" + pad2(localWall.getUTCDate()),
|
||||||
offsetMinutes: localOffset
|
offsetMinutes: localOffset
|
||||||
}
|
}
|
||||||
@@ -211,6 +236,8 @@ if (typeof module !== "undefined" && module.exports) {
|
|||||||
dayDelta: dayDelta,
|
dayDelta: dayDelta,
|
||||||
dayMarker: dayMarker,
|
dayMarker: dayMarker,
|
||||||
isWorkingHours: isWorkingHours,
|
isWorkingHours: isWorkingHours,
|
||||||
|
showsSeconds: showsSeconds,
|
||||||
|
anyShowsSeconds: anyShowsSeconds,
|
||||||
buildClock: buildClock,
|
buildClock: buildClock,
|
||||||
buildBar: buildBar,
|
buildBar: buildBar,
|
||||||
zonesToProbe: zonesToProbe,
|
zonesToProbe: zonesToProbe,
|
||||||
|
|||||||
+26
-13
@@ -33,15 +33,28 @@ Item {
|
|||||||
// stay the same weight when the theme or font size changes.
|
// stay the same weight when the theme or font size changes.
|
||||||
readonly property int barSize: config && config.barSize > 0 ? config.barSize : Style.bar.sizeHorizontal
|
readonly property int barSize: config && config.barSize > 0 ? config.barSize : Style.bar.sizeHorizontal
|
||||||
|
|
||||||
|
// Everything drawn on the bar is sized against the theme's own bar height,
|
||||||
|
// so a taller bar carries proportionally larger type and gaps rather than
|
||||||
|
// the theme's 26px-sized text marooned in the middle of it. At the default
|
||||||
|
// height this is exactly 1, which leaves the theme's own tokens untouched.
|
||||||
|
readonly property real barScale: Style.bar.sizeHorizontal > 0
|
||||||
|
? barSize / Style.bar.sizeHorizontal
|
||||||
|
: 1
|
||||||
|
|
||||||
|
function scaled(px) { return Math.max(1, Math.round(px * root.barScale)) }
|
||||||
|
|
||||||
|
readonly property int timeFontSize: scaled(Style.font.body)
|
||||||
|
readonly property int labelFontSize: scaled(Style.font.caption)
|
||||||
|
|
||||||
readonly property color background: Color.bar.background
|
readonly property color background: Color.bar.background
|
||||||
readonly property color foreground: Color.bar.text
|
readonly property color foreground: Color.bar.text
|
||||||
readonly property color muted: Color.muted
|
readonly property color muted: Color.muted
|
||||||
readonly property color accent: Color.accent
|
readonly property color accent: Color.accent
|
||||||
readonly property color urgent: Color.urgent
|
readonly property color urgent: Color.urgent
|
||||||
|
|
||||||
readonly property int edgePadding: Style.space(12)
|
readonly property int edgePadding: scaled(Style.space(12))
|
||||||
readonly property int clockGap: Style.space(16)
|
readonly property int clockGap: scaled(Style.space(16))
|
||||||
readonly property int labelGap: Style.space(6)
|
readonly property int labelGap: scaled(Style.space(6))
|
||||||
|
|
||||||
Config { id: configFile }
|
Config { id: configFile }
|
||||||
|
|
||||||
@@ -50,12 +63,12 @@ Item {
|
|||||||
zones: Model.zonesToProbe(root.config)
|
zones: Model.zonesToProbe(root.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minute precision is all the display needs; seconds are opt-in because
|
// Seconds are opt-in per clock, so the tick only drops to per-second when
|
||||||
// ticking every second wakes the whole shell sixty times as often for a
|
// something on the bar is actually displaying them — otherwise this wakes
|
||||||
// readout nobody reads that closely.
|
// the whole shell sixty times as often to redraw an identical minute.
|
||||||
SystemClock {
|
SystemClock {
|
||||||
id: clock
|
id: clock
|
||||||
precision: root.config && root.config.showSeconds ? SystemClock.Seconds : SystemClock.Minutes
|
precision: Model.anyShowsSeconds(root.config) ? SystemClock.Seconds : SystemClock.Minutes
|
||||||
onDateChanged: {
|
onDateChanged: {
|
||||||
root.nowMs = date.getTime()
|
root.nowMs = date.getTime()
|
||||||
// Offsets only move at a transition, and transitions land on the hour.
|
// Offsets only move at a transition, and transitions land on the hour.
|
||||||
@@ -117,7 +130,7 @@ Item {
|
|||||||
// step down the scale — present for identification, not competing with
|
// step down the scale — present for identification, not competing with
|
||||||
// the time for the glance.
|
// the time for the glance.
|
||||||
font.family: Style.font.family
|
font.family: Style.font.family
|
||||||
font.pixelSize: Style.font.caption
|
font.pixelSize: root.labelFontSize
|
||||||
// Held back from the time it captions, but only once: a dimmed clock is
|
// Held back from the time it captions, but only once: a dimmed clock is
|
||||||
// already carrying the muted tint, and compounding the two put its label
|
// already carrying the muted tint, and compounding the two put its label
|
||||||
// under the legibility floor.
|
// under the legibility floor.
|
||||||
@@ -129,7 +142,7 @@ Item {
|
|||||||
text: entry.clock.time
|
text: entry.clock.time
|
||||||
color: entry.tint
|
color: entry.tint
|
||||||
font.family: Style.font.family
|
font.family: Style.font.family
|
||||||
font.pixelSize: Style.font.body
|
font.pixelSize: root.timeFontSize
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
@@ -142,7 +155,7 @@ Item {
|
|||||||
text: entry.clock.marker
|
text: entry.clock.marker
|
||||||
color: entry.tint
|
color: entry.tint
|
||||||
font.family: Style.font.family
|
font.family: Style.font.family
|
||||||
font.pixelSize: Style.font.caption
|
font.pixelSize: root.labelFontSize
|
||||||
opacity: 0.7
|
opacity: 0.7
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,7 +197,7 @@ Item {
|
|||||||
// that cannot be overlapped by a long list of clocks.
|
// that cannot be overlapped by a long list of clocks.
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors { top: parent.top; left: parent.left; right: parent.right }
|
anchors { top: parent.top; left: parent.left; right: parent.right }
|
||||||
height: configFile.parseError !== "" ? 2 : 1
|
height: configFile.parseError !== "" ? root.scaled(2) : Math.max(1, root.scaled(1))
|
||||||
color: configFile.parseError !== "" ? root.urgent : root.foreground
|
color: configFile.parseError !== "" ? root.urgent : root.foreground
|
||||||
opacity: configFile.parseError !== "" ? 0.9 : 0.12
|
opacity: configFile.parseError !== "" ? 0.9 : 0.12
|
||||||
}
|
}
|
||||||
@@ -215,7 +228,7 @@ Item {
|
|||||||
text: root.bar.center.time
|
text: root.bar.center.time
|
||||||
color: root.accent
|
color: root.accent
|
||||||
font.family: Style.font.family
|
font.family: Style.font.family
|
||||||
font.pixelSize: Style.font.body
|
font.pixelSize: root.timeFontSize
|
||||||
font.bold: true
|
font.bold: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,7 +241,7 @@ Item {
|
|||||||
text: "config error"
|
text: "config error"
|
||||||
color: root.urgent
|
color: root.urgent
|
||||||
font.family: Style.font.family
|
font.family: Style.font.family
|
||||||
font.pixelSize: Style.font.caption
|
font.pixelSize: root.labelFontSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-2
@@ -38,7 +38,10 @@ QtObject {
|
|||||||
],
|
],
|
||||||
workingHours: { start: 8, end: 18, workdays: [1, 2, 3, 4, 5] },
|
workingHours: { start: 8, end: 18, workdays: [1, 2, 3, 4, 5] },
|
||||||
dimOutsideWorkingHours: true,
|
dimOutsideWorkingHours: true,
|
||||||
|
// The bar-wide default for seconds. Any clock, and the centre clock via
|
||||||
|
// `local`, may override it.
|
||||||
showSeconds: false,
|
showSeconds: false,
|
||||||
|
local: { showSeconds: false },
|
||||||
barSize: 0
|
barSize: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,7 +61,12 @@ QtObject {
|
|||||||
var tz = String(entry.timezone || "").replace(/^\s+|\s+$/g, "")
|
var tz = String(entry.timezone || "").replace(/^\s+|\s+$/g, "")
|
||||||
if (!tz) continue
|
if (!tz) continue
|
||||||
var label = String(entry.label === undefined || entry.label === null ? "" : entry.label).replace(/^\s+|\s+$/g, "")
|
var label = String(entry.label === undefined || entry.label === null ? "" : entry.label).replace(/^\s+|\s+$/g, "")
|
||||||
clocks.push({ label: label || tz, timezone: tz })
|
var clock = { label: label || tz, timezone: tz }
|
||||||
|
// Carried through only when the clock actually sets it, so a clock
|
||||||
|
// with no opinion keeps inheriting the bar-wide default rather than
|
||||||
|
// being frozen to whatever it happened to be when this was written.
|
||||||
|
if (typeof entry.showSeconds === "boolean") clock.showSeconds = entry.showSeconds
|
||||||
|
clocks.push(clock)
|
||||||
}
|
}
|
||||||
out.clocks = clocks
|
out.clocks = clocks
|
||||||
}
|
}
|
||||||
@@ -81,8 +89,15 @@ QtObject {
|
|||||||
|
|
||||||
if (typeof raw.dimOutsideWorkingHours === "boolean") out.dimOutsideWorkingHours = raw.dimOutsideWorkingHours
|
if (typeof raw.dimOutsideWorkingHours === "boolean") out.dimOutsideWorkingHours = raw.dimOutsideWorkingHours
|
||||||
if (typeof raw.showSeconds === "boolean") out.showSeconds = raw.showSeconds
|
if (typeof raw.showSeconds === "boolean") out.showSeconds = raw.showSeconds
|
||||||
|
if (raw.local && typeof raw.local === "object" && typeof raw.local.showSeconds === "boolean")
|
||||||
|
out.local.showSeconds = raw.local.showSeconds
|
||||||
|
|
||||||
|
// 0 means "follow the theme". The ceiling is a guard against a typo
|
||||||
|
// turning the bar into a full-screen panel with no way back to the
|
||||||
|
// editor that would undo it.
|
||||||
var size = Number(raw.barSize)
|
var size = Number(raw.barSize)
|
||||||
if (isFinite(size) && size >= 0 && size <= 200) out.barSize = size
|
if (isFinite(size) && size > 0) out.barSize = Math.min(120, Math.max(12, Math.round(size)))
|
||||||
|
else if (isFinite(size)) out.barSize = 0
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|||||||
+93
-7
@@ -24,6 +24,9 @@ Item {
|
|||||||
property int workStart: 8
|
property int workStart: 8
|
||||||
property int workEnd: 18
|
property int workEnd: 18
|
||||||
property bool dimOutside: true
|
property bool dimOutside: true
|
||||||
|
property bool defaultSeconds: false
|
||||||
|
property bool localSeconds: false
|
||||||
|
property int barSize: 0
|
||||||
|
|
||||||
readonly property color background: Color.popups.background
|
readonly property color background: Color.popups.background
|
||||||
readonly property color foreground: Color.popups.text
|
readonly property color foreground: Color.popups.text
|
||||||
@@ -56,24 +59,38 @@ Item {
|
|||||||
var cfg = root.configFile ? root.configFile.config : null
|
var cfg = root.configFile ? root.configFile.config : null
|
||||||
var clocks = cfg && Array.isArray(cfg.clocks) ? cfg.clocks : []
|
var clocks = cfg && Array.isArray(cfg.clocks) ? cfg.clocks : []
|
||||||
var copy = []
|
var copy = []
|
||||||
for (var i = 0; i < clocks.length; i++)
|
for (var i = 0; i < clocks.length; i++) {
|
||||||
copy.push({ label: String(clocks[i].label || ""), timezone: String(clocks[i].timezone || "") })
|
var row = { label: String(clocks[i].label || ""), timezone: String(clocks[i].timezone || "") }
|
||||||
|
if (typeof clocks[i].showSeconds === "boolean") row.showSeconds = clocks[i].showSeconds
|
||||||
|
copy.push(row)
|
||||||
|
}
|
||||||
root.draft = copy
|
root.draft = copy
|
||||||
if (cfg && cfg.workingHours) {
|
if (cfg && cfg.workingHours) {
|
||||||
root.workStart = Number(cfg.workingHours.start)
|
root.workStart = Number(cfg.workingHours.start)
|
||||||
root.workEnd = Number(cfg.workingHours.end)
|
root.workEnd = Number(cfg.workingHours.end)
|
||||||
}
|
}
|
||||||
root.dimOutside = cfg ? cfg.dimOutsideWorkingHours !== false : true
|
root.dimOutside = cfg ? cfg.dimOutsideWorkingHours !== false : true
|
||||||
|
root.defaultSeconds = cfg ? cfg.showSeconds === true : false
|
||||||
|
root.localSeconds = Model.showsSeconds(cfg ? cfg.local : null, root.defaultSeconds)
|
||||||
|
root.barSize = cfg && cfg.barSize > 0 ? cfg.barSize : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// What a row's toggle shows: its own setting when it has one, otherwise the
|
||||||
|
// bar-wide default it is inheriting.
|
||||||
|
function rowShowsSeconds(row) { return Model.showsSeconds(row, root.defaultSeconds) }
|
||||||
|
|
||||||
// QML only re-renders a var array when the reference changes, so every
|
// QML only re-renders a var array when the reference changes, so every
|
||||||
// mutation rebuilds it rather than editing in place.
|
// mutation rebuilds it rather than editing in place.
|
||||||
function replaceDraft(next) { root.draft = next }
|
function replaceDraft(next) { root.draft = next }
|
||||||
|
|
||||||
|
// Copies every key the row already had. Rebuilding it from a fixed pair of
|
||||||
|
// fields silently dropped anything else on it — editing a label would have
|
||||||
|
// thrown away that clock's own seconds setting.
|
||||||
function updateRow(index, key, value) {
|
function updateRow(index, key, value) {
|
||||||
var next = root.draft.slice()
|
var next = root.draft.slice()
|
||||||
if (!next[index]) return
|
if (!next[index]) return
|
||||||
var row = { label: next[index].label, timezone: next[index].timezone }
|
var row = {}
|
||||||
|
for (var k in next[index]) row[k] = next[index][k]
|
||||||
row[key] = value
|
row[key] = value
|
||||||
next[index] = row
|
next[index] = row
|
||||||
replaceDraft(next)
|
replaceDraft(next)
|
||||||
@@ -99,7 +116,11 @@ Item {
|
|||||||
var tz = String(root.draft[i].timezone || "").replace(/^\s+|\s+$/g, "")
|
var tz = String(root.draft[i].timezone || "").replace(/^\s+|\s+$/g, "")
|
||||||
if (!tz) continue
|
if (!tz) continue
|
||||||
var label = String(root.draft[i].label || "").replace(/^\s+|\s+$/g, "")
|
var label = String(root.draft[i].label || "").replace(/^\s+|\s+$/g, "")
|
||||||
out.push({ label: label || tz.split("/").pop().replace(/_/g, " "), timezone: tz })
|
var row = { label: label || tz.split("/").pop().replace(/_/g, " "), timezone: tz }
|
||||||
|
// Only clocks the user actually set keep an explicit flag; the rest go
|
||||||
|
// on inheriting the bar-wide default.
|
||||||
|
if (typeof root.draft[i].showSeconds === "boolean") row.showSeconds = root.draft[i].showSeconds
|
||||||
|
out.push(row)
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
@@ -116,8 +137,9 @@ Item {
|
|||||||
workdays: cfg && cfg.workingHours ? cfg.workingHours.workdays : [1, 2, 3, 4, 5]
|
workdays: cfg && cfg.workingHours ? cfg.workingHours.workdays : [1, 2, 3, 4, 5]
|
||||||
},
|
},
|
||||||
dimOutsideWorkingHours: root.dimOutside,
|
dimOutsideWorkingHours: root.dimOutside,
|
||||||
showSeconds: cfg ? cfg.showSeconds === true : false,
|
showSeconds: root.defaultSeconds,
|
||||||
barSize: cfg ? cfg.barSize : 0
|
local: { showSeconds: root.localSeconds },
|
||||||
|
barSize: root.barSize
|
||||||
})
|
})
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
@@ -211,7 +233,7 @@ Item {
|
|||||||
spacing: Style.space(8)
|
spacing: Style.space(8)
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
Layout.preferredWidth: Style.space(74)
|
Layout.preferredWidth: Style.space(66)
|
||||||
text: row.modelData.label
|
text: row.modelData.label
|
||||||
placeholderText: "Label"
|
placeholderText: "Label"
|
||||||
foreground: root.foreground
|
foreground: root.foreground
|
||||||
@@ -245,6 +267,18 @@ Item {
|
|||||||
font.pixelSize: Style.font.caption
|
font.pixelSize: Style.font.caption
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "SS"
|
||||||
|
bordered: true
|
||||||
|
selected: root.rowShowsSeconds(row.modelData)
|
||||||
|
tooltipText: root.rowShowsSeconds(row.modelData)
|
||||||
|
? "Showing seconds — click for HH:MM"
|
||||||
|
: "Show seconds on this clock"
|
||||||
|
foreground: root.foreground
|
||||||
|
accent: root.accent
|
||||||
|
onClicked: root.updateRow(row.index, "showSeconds", !root.rowShowsSeconds(row.modelData))
|
||||||
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
iconText: ""
|
iconText: ""
|
||||||
tooltipText: "Remove this clock"
|
tooltipText: "Remove this clock"
|
||||||
@@ -346,6 +380,58 @@ Item {
|
|||||||
|
|
||||||
PanelSeparator { Layout.fillWidth: true }
|
PanelSeparator { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
PanelSectionHeader {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: "Bar"
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Style.space(8)
|
||||||
|
|
||||||
|
Text {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: "Seconds on your local clock"
|
||||||
|
color: root.foreground
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.family: Style.font.family
|
||||||
|
font.pixelSize: Style.font.body
|
||||||
|
}
|
||||||
|
|
||||||
|
ToggleSwitch {
|
||||||
|
checked: root.localSeconds
|
||||||
|
foreground: root.foreground
|
||||||
|
accent: root.accent
|
||||||
|
onToggled: root.localSeconds = !root.localSeconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Style.space(8)
|
||||||
|
|
||||||
|
Text {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: "Bar height — 0 follows the theme. Text scales with it."
|
||||||
|
color: root.muted
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.family: Style.font.family
|
||||||
|
font.pixelSize: Style.font.caption
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberField {
|
||||||
|
Layout.preferredWidth: Style.spacing.numberFieldWidth
|
||||||
|
value: root.barSize
|
||||||
|
from: 0
|
||||||
|
to: 120
|
||||||
|
foreground: root.foreground
|
||||||
|
accent: root.accent
|
||||||
|
onModified: function (v) { root.barSize = v }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PanelSeparator { Layout.fillWidth: true }
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: Style.space(8)
|
spacing: Style.space(8)
|
||||||
|
|||||||
@@ -21,8 +21,12 @@ tiles windows above it exactly as it does for the top bar.
|
|||||||
carries no label.
|
carries no label.
|
||||||
- **Dimmed outside working hours**, evaluated in each clock's *own* timezone
|
- **Dimmed outside working hours**, evaluated in each clock's *own* timezone
|
||||||
(weekends included), so a glance tells you who is actually around.
|
(weekends included), so a glance tells you who is actually around.
|
||||||
|
- **Seconds per clock.** Any clock can show `HH:MM:SS` on its own, including
|
||||||
|
your local one. The bar only drops to a per-second tick when something is
|
||||||
|
actually displaying seconds.
|
||||||
- **Themed.** Colours, fonts, spacing and bar height all come from the active
|
- **Themed.** Colours, fonts, spacing and bar height all come from the active
|
||||||
Omarchy theme; it follows a theme switch with no restart.
|
Omarchy theme; it follows a theme switch with no restart. Set your own bar
|
||||||
|
height and the type and spacing scale with it.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
@@ -65,11 +69,12 @@ shows that zone's current offset so you can see the effect before saving.
|
|||||||
{ "label": "NYC", "timezone": "America/New_York" },
|
{ "label": "NYC", "timezone": "America/New_York" },
|
||||||
{ "label": "SG", "timezone": "Asia/Singapore" },
|
{ "label": "SG", "timezone": "Asia/Singapore" },
|
||||||
{ "label": "AUS", "timezone": "Australia/Sydney" },
|
{ "label": "AUS", "timezone": "Australia/Sydney" },
|
||||||
{ "label": "NZ", "timezone": "Pacific/Auckland" }
|
{ "label": "NZ", "timezone": "Pacific/Auckland", "showSeconds": true }
|
||||||
],
|
],
|
||||||
"workingHours": { "start": 8, "end": 18, "workdays": [1, 2, 3, 4, 5] },
|
"workingHours": { "start": 8, "end": 18, "workdays": [1, 2, 3, 4, 5] },
|
||||||
"dimOutsideWorkingHours": true,
|
"dimOutsideWorkingHours": true,
|
||||||
"showSeconds": false,
|
"showSeconds": false,
|
||||||
|
"local": { "showSeconds": false },
|
||||||
"barSize": 0
|
"barSize": 0
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -78,11 +83,18 @@ shows that zone's current offset so you can see the effect before saving.
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `clocks[].label` | Shown left of the time. Defaults to the last part of the zone name. |
|
| `clocks[].label` | Shown left of the time. Defaults to the last part of the zone name. |
|
||||||
| `clocks[].timezone` | Any IANA zone (`timedatectl list-timezones`). |
|
| `clocks[].timezone` | Any IANA zone (`timedatectl list-timezones`). |
|
||||||
|
| `clocks[].showSeconds` | Seconds on this clock. Omit it to inherit the bar-wide default below. |
|
||||||
| `workingHours.start` / `.end` | Hours, 0–24, in each clock's own zone. A window that wraps midnight (`22`–`6`) is understood. |
|
| `workingHours.start` / `.end` | Hours, 0–24, in each clock's own zone. A window that wraps midnight (`22`–`6`) is understood. |
|
||||||
| `workingHours.workdays` | Days counted as working. `0` = Sunday. |
|
| `workingHours.workdays` | Days counted as working. `0` = Sunday. |
|
||||||
| `dimOutsideWorkingHours` | Mute clocks outside that window. |
|
| `dimOutsideWorkingHours` | Mute clocks outside that window. |
|
||||||
| `showSeconds` | `HH:MM:SS` and a per-second tick, instead of `HH:MM`. |
|
| `showSeconds` | The bar-wide default for seconds, for clocks that do not set their own. |
|
||||||
| `barSize` | Bar height in px. `0` follows the theme's bar height. |
|
| `local.showSeconds` | Seconds on your local clock, which has no entry in `clocks[]`. |
|
||||||
|
| `barSize` | Bar height in px. `0` follows the theme's. Otherwise 12–120; text and spacing scale with it. |
|
||||||
|
|
||||||
|
Seconds resolve per clock: a clock's own `showSeconds` wins, otherwise the
|
||||||
|
bar-wide default applies. An explicit `false` on a clock beats a `true`
|
||||||
|
default. The bar ticks once a minute unless something on it is showing
|
||||||
|
seconds — nothing pays for a per-second redraw it does not use.
|
||||||
|
|
||||||
There is no `left` / `right` setting: which side a clock lands on is a fact
|
There is no `left` / `right` setting: which side a clock lands on is a fact
|
||||||
about its offset, not a preference.
|
about its offset, not a preference.
|
||||||
|
|||||||
@@ -182,4 +182,71 @@ test("an empty config renders an empty bar, not a crash", function () {
|
|||||||
assert.deepStrictEqual(M.buildBar(null, null, NOW, null).center.time, "15:24")
|
assert.deepStrictEqual(M.buildBar(null, null, NOW, null).center.time, "15:24")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("seconds are per clock, with a config-wide default", function () {
|
||||||
|
var cfg = {
|
||||||
|
showSeconds: false,
|
||||||
|
clocks: [
|
||||||
|
{ label: "LA", timezone: "America/Los_Angeles", showSeconds: true },
|
||||||
|
{ label: "NYC", timezone: "America/New_York" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
var bar = M.buildBar(cfg, OFFSETS, NOW, SAST)
|
||||||
|
var byLabel = {}
|
||||||
|
bar.left.forEach(function (c) { byLabel[c.label] = c })
|
||||||
|
assert.strictEqual(byLabel.LA.time, "08:24:00", "opted in")
|
||||||
|
assert.strictEqual(byLabel.NYC.time, "11:24", "inherits the default")
|
||||||
|
assert.strictEqual(bar.center.time, "17:24", "centre inherits the default")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("an explicit false on a clock beats a true default", function () {
|
||||||
|
var cfg = {
|
||||||
|
showSeconds: true,
|
||||||
|
clocks: [
|
||||||
|
{ label: "LA", timezone: "America/Los_Angeles", showSeconds: false },
|
||||||
|
{ label: "NYC", timezone: "America/New_York" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
var bar = M.buildBar(cfg, OFFSETS, NOW, SAST)
|
||||||
|
var byLabel = {}
|
||||||
|
bar.left.forEach(function (c) { byLabel[c.label] = c })
|
||||||
|
assert.strictEqual(byLabel.LA.time, "08:24", "opted out")
|
||||||
|
assert.strictEqual(byLabel.NYC.time, "11:24:00")
|
||||||
|
assert.strictEqual(bar.center.time, "17:24:00")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("the centre clock carries its own seconds override", function () {
|
||||||
|
var cfg = {
|
||||||
|
showSeconds: false,
|
||||||
|
local: { showSeconds: true },
|
||||||
|
clocks: [{ label: "LA", timezone: "America/Los_Angeles" }]
|
||||||
|
}
|
||||||
|
var bar = M.buildBar(cfg, OFFSETS, NOW, SAST)
|
||||||
|
assert.strictEqual(bar.center.time, "17:24:00")
|
||||||
|
assert.strictEqual(bar.left[0].time, "08:24", "clocks are unaffected by it")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("anyShowsSeconds drives the tick rate", function () {
|
||||||
|
assert.strictEqual(M.anyShowsSeconds({ clocks: [{ label: "A", timezone: "X" }] }), false)
|
||||||
|
assert.strictEqual(M.anyShowsSeconds({ showSeconds: true, clocks: [] }), true, "the default alone counts")
|
||||||
|
assert.strictEqual(M.anyShowsSeconds({ local: { showSeconds: true }, clocks: [] }), true, "the centre alone counts")
|
||||||
|
assert.strictEqual(M.anyShowsSeconds({
|
||||||
|
clocks: [{ label: "A", timezone: "X" }, { label: "B", timezone: "Y", showSeconds: true }]
|
||||||
|
}), true, "one clock is enough")
|
||||||
|
assert.strictEqual(M.anyShowsSeconds({
|
||||||
|
showSeconds: true,
|
||||||
|
local: { showSeconds: false },
|
||||||
|
clocks: [{ label: "A", timezone: "X", showSeconds: false }]
|
||||||
|
}), false, "everything opted out")
|
||||||
|
assert.strictEqual(M.anyShowsSeconds(null), false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("an unresolved clock keeps the width of the format it asked for", function () {
|
||||||
|
var bar = M.buildBar({ clocks: [
|
||||||
|
{ label: "A", timezone: "Not/AZone" },
|
||||||
|
{ label: "B", timezone: "Not/AZone", showSeconds: true }
|
||||||
|
] }, {}, NOW, SAST)
|
||||||
|
assert.strictEqual(bar.right[0].time, "--:--")
|
||||||
|
assert.strictEqual(bar.right[1].time, "--:--:--")
|
||||||
|
})
|
||||||
|
|
||||||
console.log(passed + " passed" + (process.exitCode ? ", some FAILED" : ""))
|
console.log(passed + " passed" + (process.exitCode ? ", some FAILED" : ""))
|
||||||
|
|||||||
Reference in New Issue
Block a user