Clean up world clock module code and add themes. (#885)

This commit is contained in:
Jas Singh
2025-04-04 23:13:18 -07:00
committed by GitHub
parent 1d717e9f2e
commit 18c383b754
54 changed files with 834 additions and 717 deletions

View File

@@ -1,120 +1,82 @@
import { openMenu } from '../../utils/menu';
import options from 'src/options';
import { BarBoxChild } from 'src/lib/types/bar.js';
import { runAsyncCommand, throttledScrollHandler } from 'src/components/bar/utils/helpers.js';
import { inputHandler } from 'src/components/bar/utils/helpers.js';
import { bind, Variable } from 'astal';
import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers';
import { Astal } from 'astal/gtk3';
import { systemTime } from 'src/globals/time';
import { GLib } from 'astal';
import { Module } from '../../shared/Module';
const { format, formatDiffDate, tz, icon, showIcon, showTime, rightClick, middleClick, scrollUp, scrollDown } =
options.bar.customModules.worldclock;
const { style } = options.theme.bar.buttons;
const {
format,
formatDiffDate,
divider,
tz,
icon,
showIcon,
leftClick,
rightClick,
middleClick,
scrollUp,
scrollDown,
} = options.bar.customModules.worldclock;
const time = Variable.derive(
[systemTime, format, formatDiffDate, tz],
(c, f, fdd, tzn) =>
tzn
.map((t) =>
c
.to_timezone(GLib.TimeZone.new(t))
.format(c.to_timezone(GLib.TimeZone.new(t)).get_day_of_year() == c.get_day_of_year() ? f : fdd),
)
.join(' | ') || '',
);
const WorldClock = (): BarBoxChild => {
const ClockTime = (): JSX.Element => <label className={'bar-button-label worldclock bar'} label={bind(time)} />;
const ClockIcon = (): JSX.Element => (
<label className={'bar-button-icon worldclock txt-icon bar'} label={bind(icon)} />
);
const componentClassName = Variable.derive(
[bind(style), bind(showIcon), bind(showTime)],
(btnStyle, shwIcn, shwLbl) => {
const styleMap = {
default: 'style1',
split: 'style2',
wave: 'style3',
wave2: 'style3',
};
return `worldclock-container ${styleMap[btnStyle]} ${!shwLbl ? 'no-label' : ''} ${!shwIcn ? 'no-icon' : ''}`;
},
);
const componentChildren = Variable.derive([bind(showIcon), bind(showTime)], (shIcn, shTm) => {
if (shIcn && !shTm) {
return <ClockIcon />;
} else if (shTm && !shIcn) {
return <ClockTime />;
export const WorldClock = (): BarBoxChild => {
const iconBinding = Variable.derive([bind(icon), bind(showIcon)], (timeIcon, showTimeIcon) => {
if (!showTimeIcon) {
return '';
}
return (
<box>
<ClockIcon />
<ClockTime />
</box>
);
return timeIcon;
});
const component = (
<box
className={componentClassName()}
onDestroy={() => {
componentClassName.drop();
componentChildren.drop();
}}
>
{componentChildren()}
</box>
const timeBinding = Variable.derive(
[systemTime, format, formatDiffDate, tz, divider],
(localSystemTime, timeFormat, differentDayFormat, targetTimeZones, timeDivider) =>
targetTimeZones
.map((timeZoneId) => {
const targetTimezone = GLib.TimeZone.new(timeZoneId);
const timeInTargetZone = localSystemTime.to_timezone(targetTimezone);
if (timeInTargetZone === null) {
return 'Invalid TimeZone';
}
const isTargetZoneSameDay =
timeInTargetZone.get_day_of_year() === localSystemTime.get_day_of_year();
const formatForTimeZone = isTargetZoneSameDay ? timeFormat : differentDayFormat;
return timeInTargetZone.format(formatForTimeZone);
})
.join(timeDivider),
);
return {
component,
isVisible: true,
const microphoneModule = Module({
textIcon: iconBinding(),
label: timeBinding(),
boxClass: 'worldclock',
props: {
setup: (self: Astal.Button): void => {
let disconnectFunctions: (() => void)[] = [];
Variable.derive(
[
bind(rightClick),
bind(middleClick),
bind(scrollUp),
bind(scrollDown),
bind(options.bar.scrollSpeed),
],
() => {
disconnectFunctions.forEach((disconnect) => disconnect());
disconnectFunctions = [];
const throttledHandler = throttledScrollHandler(options.bar.scrollSpeed.get());
disconnectFunctions.push(
onPrimaryClick(self, (clicked, event) => {
openMenu(clicked, event, 'calendarmenu');
}),
);
disconnectFunctions.push(
onSecondaryClick(self, (clicked, event) => {
runAsyncCommand(rightClick.get(), { clicked, event });
}),
);
disconnectFunctions.push(
onMiddleClick(self, (clicked, event) => {
runAsyncCommand(middleClick.get(), { clicked, event });
}),
);
disconnectFunctions.push(onScroll(self, throttledHandler, scrollUp.get(), scrollDown.get()));
setup: (self: Astal.Button) => {
inputHandler(self, {
onPrimaryClick: {
cmd: leftClick,
},
);
onSecondaryClick: {
cmd: rightClick,
},
onMiddleClick: {
cmd: middleClick,
},
onScrollUp: {
cmd: scrollUp,
},
onScrollDown: {
cmd: scrollDown,
},
});
},
},
};
};
});
export { WorldClock };
return microphoneModule;
};

View File

@@ -429,13 +429,15 @@ export const CustomModuleSettings = (): JSX.Element => {
<Option opt={options.bar.customModules.worldclock.icon} title="Icon" type="string" />
<Option opt={options.bar.customModules.worldclock.showIcon} title="Show Icon" type="boolean" />
<Option opt={options.theme.bar.buttons.modules.worldclock.spacing} title="Spacing" type="string" />
<Option opt={options.bar.customModules.worldclock.showTime} title="Show Time" type="boolean" />
<Option opt={options.bar.customModules.worldclock.format} title="Format" type="string" />
<Option
opt={options.bar.customModules.worldclock.formatDiffDate}
title="Format (when date different from local date)"
title="Cross-Day Time Format"
subtitle="Format to use when the timezone is on a different calendar day than the local timezone."
type="string"
/>
<Option opt={options.bar.customModules.worldclock.divider} title="Date Divider" type="string" />
<Option opt={options.bar.customModules.worldclock.leftClick} title="Left Click" type="string" />
<Option opt={options.bar.customModules.worldclock.rightClick} title="Right Click" type="string" />
<Option opt={options.bar.customModules.worldclock.middleClick} title="Middle Click" type="string" />
<Option opt={options.bar.customModules.worldclock.scrollUp} title="Scroll Up" type="string" />

View File

@@ -26,7 +26,10 @@ export const CustomModuleTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.modules.microphone.icon_background}
title="Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.modules.microphone.border} title="Border" type="color" />
@@ -39,7 +42,10 @@ export const CustomModuleTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.modules.ram.icon_background}
title="Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.modules.ram.border} title="Border" type="color" />
@@ -52,7 +58,10 @@ export const CustomModuleTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.modules.cpu.icon_background}
title="Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.modules.cpu.border} title="Border" type="color" />
@@ -69,7 +78,10 @@ export const CustomModuleTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.modules.cpuTemp.icon_background}
title="Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.modules.cpuTemp.border} title="Border" type="color" />
@@ -86,7 +98,10 @@ export const CustomModuleTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.modules.storage.icon_background}
title="Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.modules.storage.border} title="Border" type="color" />
@@ -103,7 +118,10 @@ export const CustomModuleTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.modules.netstat.icon_background}
title="Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.modules.netstat.border} title="Border" type="color" />
@@ -120,7 +138,10 @@ export const CustomModuleTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.modules.kbLayout.icon_background}
title="Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.modules.kbLayout.border} title="Border" type="color" />
@@ -137,7 +158,10 @@ export const CustomModuleTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.modules.updates.icon_background}
title="Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.modules.updates.border} title="Border" type="color" />
@@ -154,7 +178,10 @@ export const CustomModuleTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.modules.submap.icon_background}
title="Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.modules.submap.border} title="Border" type="color" />
@@ -171,7 +198,10 @@ export const CustomModuleTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.modules.weather.icon_background}
title="Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.modules.weather.border} title="Border" type="color" />
@@ -188,7 +218,10 @@ export const CustomModuleTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.modules.hyprsunset.icon_background}
title="Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.modules.hyprsunset.border} title="Border" type="color" />
@@ -205,7 +238,10 @@ export const CustomModuleTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.modules.hypridle.icon_background}
title="Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.modules.hypridle.border} title="Border" type="color" />
@@ -218,14 +254,17 @@ export const CustomModuleTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.modules.cava.icon_background}
title="Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.modules.cava.border} title="Border" type="color" />
{/* World Clock Module Section */}
<Header title="World Clock" />
<Option opt={options.theme.bar.buttons.modules.worldclock.text} title="Bars" type="color" />
<Option opt={options.theme.bar.buttons.modules.worldclock.text} title="Text" type="color" />
<Option opt={options.theme.bar.buttons.modules.worldclock.icon} title="Icon" type="color" />
<Option
opt={options.theme.bar.buttons.modules.worldclock.background}
@@ -235,7 +274,10 @@ export const CustomModuleTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.modules.worldclock.icon_background}
title="Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.modules.worldclock.border} title="Border" type="color" />
@@ -251,7 +293,10 @@ export const CustomModuleTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.modules.power.icon_background}
title="Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.modules.power.border} title="Border" type="color" />

View File

@@ -313,7 +313,10 @@ export const BarSettings = (): JSX.Element => {
<Option
opt={options.bar.windowtitle.title_map}
title="Window Title Mappings"
subtitle="Requires Custom Title.\nWiki: https://hyprpanel.com/configuration/panel.html#window-title-mappings"
subtitle={
'Requires Custom Title.\n' +
'Wiki: https://hyprpanel.com/configuration/panel.html#window-title-mappings'
}
type="object"
subtitleLink="https://hyprpanel.com/configuration/panel.html#window-title-mappings"
/>
@@ -423,14 +426,17 @@ export const BarSettings = (): JSX.Element => {
<Option
opt={options.bar.systray.ignore}
title="Ignore List"
subtitle="Apps to ignore\nWiki: https://hyprpanel.com/configuration/panel.html#system-tray"
subtitle={'Apps to ignore\n' + 'Wiki: https://hyprpanel.com/configuration/panel.html#system-tray'}
subtitleLink="https://hyprpanel.com/configuration/panel.html#system-tray"
type="object"
/>
<Option
opt={options.bar.systray.customIcons}
title="Custom Systray Icons"
subtitle="Define custom icons for systray.\nWiki: https://hyprpanel.com/configuration/panel.html#custom-systray-icons"
subtitle={
'Define custom icons for systray.\n' +
'Wiki: https://hyprpanel.com/configuration/panel.html#custom-systray-icons'
}
subtitleLink="https://hyprpanel.com/configuration/panel.html#custom-systray-icons"
type="object"
/>

View File

@@ -70,7 +70,10 @@ export const BarTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.icon_background}
title="Button Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
@@ -115,7 +118,10 @@ export const BarTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.windowtitle.icon_background}
title="Button Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.windowtitle.border} title="Border" type="color" />
@@ -128,7 +134,10 @@ export const BarTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.media.icon_background}
title="Button Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.media.border} title="Border" type="color" />
@@ -141,7 +150,10 @@ export const BarTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.volume.icon_background}
title="Button Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.volume.border} title="Border" type="color" />
@@ -154,7 +166,10 @@ export const BarTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.network.icon_background}
title="Button Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.network.border} title="Border" type="color" />
@@ -167,7 +182,10 @@ export const BarTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.bluetooth.icon_background}
title="Button Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.bluetooth.border} title="Border" type="color" />
@@ -186,7 +204,10 @@ export const BarTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.battery.icon_background}
title="Button Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.battery.border} title="Border" type="color" />
@@ -199,7 +220,10 @@ export const BarTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.clock.icon_background}
title="Button Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.clock.border} title="Border" type="color" />
@@ -212,7 +236,10 @@ export const BarTheme = (): JSX.Element => {
<Option
opt={options.theme.bar.buttons.notifications.icon_background}
title="Button Icon Background"
subtitle="Applies a background color to the icon section of the button.\nRequires 'split' button styling."
subtitle={
'Applies a background color to the icon section of the button.\n' +
"Requires 'split' button styling."
}
type="color"
/>
<Option opt={options.theme.bar.buttons.notifications.border} title="Border" type="color" />

View File

@@ -1247,9 +1247,10 @@ const options = mkOptions({
worldclock: {
icon: opt('󱉊'),
showIcon: opt(true),
showTime: opt(true),
format: opt('%I:%M:%S %p %Z'),
formatDiffDate: opt('%a %b %d %I:%M:%S %p %Z'),
divider: opt('  '),
leftClick: opt('menu:calendar'),
rightClick: opt(''),
middleClick: opt(''),
scrollUp: opt(''),

View File

@@ -58,69 +58,3 @@
0em
);
}
.bar-button-label.worldclock {
color: if($bar-buttons-monochrome, $bar-buttons-text, $bar-buttons-modules-worldclock-text);
margin-left: $bar-buttons-modules-worldclock-spacing;
}
.bar-button-icon.worldclock {
font-size: 1.2em;
color: if($bar-buttons-monochrome, $bar-buttons-icon, $bar-buttons-modules-worldclock-icon);
}
.style2 {
.bar-button-icon.worldclock {
border-top-left-radius: $bar-buttons-radius;
border-bottom-left-radius: $bar-buttons-radius;
background: if(
$bar-buttons-monochrome,
$bar-buttons-icon_background,
$bar-buttons-modules-worldclock-icon_background
);
padding: $bar-buttons-padding_y 0em;
padding-left: $bar-buttons-padding_x;
padding-right: $bar-buttons-modules-worldclock-spacing;
border-top-left-radius: if(
$bar-buttons-modules-worldclock-enableBorder or $bar-buttons-enableBorders,
$bar-buttons-radius * $bar-buttons-innerRadiusMultiplier,
$bar-buttons-radius
);
border-bottom-left-radius: if(
$bar-buttons-modules-worldclock-enableBorder or $bar-buttons-enableBorders,
$bar-buttons-radius * $bar-buttons-innerRadiusMultiplier,
$bar-buttons-radius
);
color: if($bar-buttons-monochrome, $bar-buttons-icon, $bar-buttons-modules-worldclock-icon);
}
.bar-button-label.worldclock {
padding: $bar-buttons-padding_y 0em;
padding-right: $bar-buttons-padding_x;
padding-left: $bar-buttons-modules-worldclock-spacing;
margin-left: 0em;
}
&.no-label.worldclock-container {
.bar-button-icon.worldclock {
border-top-right-radius: if(
$bar-buttons-modules-worldclock-enableBorder or $bar-buttons-enableBorders,
$bar-buttons-radius * $bar-buttons-innerRadiusMultiplier,
$bar-buttons-radius
);
border-bottom-right-radius: if(
$bar-buttons-modules-worldclock-enableBorder or $bar-buttons-enableBorders,
$bar-buttons-radius * $bar-buttons-innerRadiusMultiplier,
$bar-buttons-radius
);
}
}
}
.bar_item_box_visible.worldclock {
border: if(
$bar-buttons-modules-worldclock-enableBorder or $bar-buttons-enableBorders,
$bar-buttons-borderSize solid
if($bar-buttons-monochrome, $bar-buttons-borderColor, $bar-buttons-modules-worldclock-border),
0em
);
}