Minor: Refactor the code-base for better organization and compartmentalization. (#934)
* Clean up unused code * Fix media player formatting issue for labels with new line characteres. * Refactor the media player handlers into a class. * More code cleanup and organize shared weather utils into distinct classes. * Flatten some nesting. * Move weather manager in dedicated class and build HTTP Utility class for Rest API calling. * Remove logs * Rebase master merge * Reorg code (WIP) * More reorg * Delete utility scripts * Reorg options * Finish moving all options over * Fix typescript issues * Update options imports to default * missed update * Screw barrel files honestly, work of the devil. * Only initialize power profiles if power-profiles-daemon is running. * Fix window positioning and weather service naming * style dir * More organization * Restructure types to be closer to their source * Remove lib types and constants * Update basic weather object to be saner with extensibility. * Service updates * Fix initialization strategy for services. * Fix Config Manager to only emit changed objects and added missing temp converters. * Update storage service to handle unit changes. * Added cpu temp sensor auto-discovery * Added missing JSDocs to services * remove unused * Migrate to network service. * Fix network password issue. * Move out password input into helper * Rename password mask constant to be less double-negativey. * Dropdown menu rename * Added a component to edit JSON in the settings dialog (rough/WIP) * Align settings * Add and style JSON Editor. * Adjust padding * perf(shortcuts): ⚡ avoid unnecessary polling when shortcuts are disabled Stops the recording poller when shortcuts are disabled, preventing redundant polling and reducing resource usage. * Fix types and return value if shortcut not enabled. * Move the swww daemon checking process outside of the wallpaper service into a dedicated deamon lifecyle processor. * Add more string formatters and use title case for weather status (as it was). * Fix startup errors. * Rgba fix * Remove zod from dependencies --------- Co-authored-by: KernelDiego <gonzalezdiego.contact@gmail.com>
This commit is contained in:
@@ -2,7 +2,7 @@ import { Gtk } from 'astal/gtk3';
|
||||
import { ActiveDevices } from './devices/index.js';
|
||||
import { ActivePlaybacks } from './playbacks/index.js';
|
||||
import { bind, Variable } from 'astal';
|
||||
import { isPrimaryClick } from 'src/lib/utils.js';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
|
||||
export enum ActiveDeviceMenu {
|
||||
DEVICES = 'devices',
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { bind } from 'astal';
|
||||
import { Gdk, Gtk } from 'astal/gtk3';
|
||||
import AstalWp from 'gi://AstalWp?version=0.1';
|
||||
import { capitalizeFirstLetter, isScrollDown, isScrollUp } from 'src/lib/utils';
|
||||
import options from 'src/options';
|
||||
import options from 'src/configuration';
|
||||
import { isScrollUp, isScrollDown } from 'src/lib/events/mouse';
|
||||
import { capitalizeFirstLetter } from 'src/lib/string/formatters';
|
||||
|
||||
const { raiseMaximumVolume } = options.menus.volume;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { bind, Variable } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import { getIcon } from '../../utils';
|
||||
import AstalWp from 'gi://AstalWp?version=0.1';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
|
||||
export const SliderIcon = ({ type, device }: SliderIconProps): JSX.Element => {
|
||||
const iconBinding = Variable.derive([bind(device, 'volume'), bind(device, 'mute')], (volume, isMuted) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import AstalWp from 'gi://AstalWp?version=0.1';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import { bind } from 'astal';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
|
||||
const DeviceIcon = ({ device, type, icon }: AudioDeviceProps): JSX.Element => {
|
||||
return (
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import DropdownMenu from '../shared/dropdown/index.js';
|
||||
import { VolumeSliders } from './active/index.js';
|
||||
import options from 'src/options.js';
|
||||
import { bind } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { AvailableDevices } from './available/index.js';
|
||||
import { RevealerTransitionMap } from 'src/lib/constants/options.js';
|
||||
import { RevealerTransitionMap } from 'src/components/settings/constants.js';
|
||||
import options from 'src/configuration';
|
||||
|
||||
export default (): JSX.Element => {
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { bind } from 'astal';
|
||||
import { ActionButton } from './ActionButton';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import AstalBluetooth from 'gi://AstalBluetooth?version=0.1';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
|
||||
export const ConnectButton = ({ device }: ConnectButtonProps): JSX.Element => {
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ActionButton } from './ActionButton';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import AstalBluetooth from 'gi://AstalBluetooth?version=0.1';
|
||||
import { forgetBluetoothDevice } from '../helpers';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
|
||||
export const ForgetButton = ({ device }: ForgetButtonProps): JSX.Element => {
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { bind } from 'astal';
|
||||
import { ActionButton } from './ActionButton';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import AstalBluetooth from 'gi://AstalBluetooth?version=0.1';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
|
||||
export const PairButton = ({ device }: PairButtonProps): JSX.Element => {
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { bind } from 'astal';
|
||||
import { ActionButton } from './ActionButton';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import AstalBluetooth from 'gi://AstalBluetooth?version=0.1';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
|
||||
export const TrustButton = ({ device }: TrustButtonProps): JSX.Element => {
|
||||
return (
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import AstalBluetooth from 'gi://AstalBluetooth?version=0.1';
|
||||
import Spinner from 'src/components/shared/Spinner';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import { bind } from 'astal';
|
||||
import { DeviceIcon } from './DeviceIcon';
|
||||
import { DeviceName } from './DeviceName';
|
||||
import { DeviceStatus } from './DeviceStatus';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
|
||||
export const BluetoothDevice = ({ device, connectedDevices }: BluetoothDeviceProps): JSX.Element => {
|
||||
const IsConnectingSpinner = (): JSX.Element => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import { bind, timeout } from 'astal';
|
||||
import { isDiscovering } from './helper';
|
||||
import AstalBluetooth from 'gi://AstalBluetooth?version=0.1';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
|
||||
const bluetoothService = AstalBluetooth.get_default();
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import options from 'src/configuration';
|
||||
import DropdownMenu from '../shared/dropdown/index.js';
|
||||
import { BluetoothDevices } from './devices/index.js';
|
||||
import { Header } from './header/index.js';
|
||||
import options from 'src/options.js';
|
||||
import { bind } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { RevealerTransitionMap } from 'src/lib/constants/options.js';
|
||||
import { RevealerTransitionMap } from 'src/components/settings/constants.js';
|
||||
|
||||
export default (): JSX.Element => {
|
||||
return (
|
||||
|
||||
@@ -2,9 +2,9 @@ import DropdownMenu from '../shared/dropdown/index.js';
|
||||
import { TimeWidget } from './time/index';
|
||||
import { CalendarWidget } from './CalendarWidget.js';
|
||||
import { WeatherWidget } from './weather/index';
|
||||
import options from 'src/options';
|
||||
import { bind } from 'astal';
|
||||
import { RevealerTransitionMap } from 'src/lib/constants/options.js';
|
||||
import { RevealerTransitionMap } from 'src/components/settings/constants.js';
|
||||
import options from 'src/configuration';
|
||||
|
||||
const { transition } = options.menus;
|
||||
const { enabled: weatherEnabled } = options.menus.clock.weather;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import options from 'src/options';
|
||||
import { bind, Variable } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { systemTime } from 'src/shared/time';
|
||||
import options from 'src/configuration';
|
||||
import { systemTime } from 'src/lib/units/time';
|
||||
|
||||
const { military, hideSeconds } = options.menus.clock.time;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import options from 'src/options';
|
||||
import { bind, GLib, Variable } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { systemTime } from 'src/shared/time';
|
||||
import options from 'src/configuration';
|
||||
import { systemTime } from 'src/lib/units/time';
|
||||
|
||||
const { military, hideSeconds } = options.menus.clock.time;
|
||||
|
||||
|
||||
@@ -1,64 +1,51 @@
|
||||
import { Weather, WeatherIconTitle } from 'src/lib/types/weather.types';
|
||||
import { isValidWeatherIconTitle } from 'src/shared/weather';
|
||||
import { Weather, WeatherIcon, WeatherStatus } from 'src/services/weather/types';
|
||||
|
||||
/**
|
||||
* Retrieves the next epoch time for weather data.
|
||||
* Calculates the target hour for weather data lookup
|
||||
*
|
||||
* This function calculates the next epoch time based on the current weather data and the specified number of hours from now.
|
||||
* It ensures that the prediction remains within the current day by rewinding the time if necessary.
|
||||
*
|
||||
* @param wthr The current weather data.
|
||||
* @param hoursFromNow The number of hours from now to calculate the next epoch time.
|
||||
*
|
||||
* @returns The next epoch time as a number.
|
||||
* @param baseTime - The base time to calculate from
|
||||
* @param hoursFromNow - Number of hours to add
|
||||
* @returns A Date object set to the start of the target hour
|
||||
*/
|
||||
export const getNextEpoch = (wthr: Weather, hoursFromNow: number): number => {
|
||||
const currentEpoch = wthr.location.localtime_epoch;
|
||||
const epochAtHourStart = currentEpoch - (currentEpoch % 3600);
|
||||
let nextEpoch = 3600 * hoursFromNow + epochAtHourStart;
|
||||
export const getTargetHour = (baseTime: Date, hoursFromNow: number): Date => {
|
||||
const targetTime = new Date(baseTime);
|
||||
const newHour = targetTime.getHours() + hoursFromNow;
|
||||
targetTime.setHours(newHour);
|
||||
targetTime.setMinutes(0, 0, 0);
|
||||
|
||||
const curHour = new Date(currentEpoch * 1000).getHours();
|
||||
|
||||
/*
|
||||
* NOTE: Since the API is only capable of showing the current day; if
|
||||
* the hours left in the day are less than 4 (aka spilling into the next day),
|
||||
* then rewind to contain the prediction within the current day.
|
||||
*/
|
||||
if (curHour > 19) {
|
||||
const hoursToRewind = curHour - 19;
|
||||
nextEpoch = 3600 * hoursFromNow + epochAtHourStart - hoursToRewind * 3600;
|
||||
const currentHour = baseTime.getHours();
|
||||
if (currentHour > 19) {
|
||||
const hoursToRewind = currentHour - 19;
|
||||
targetTime.setHours(targetTime.getHours() - hoursToRewind);
|
||||
}
|
||||
return nextEpoch;
|
||||
|
||||
return targetTime;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the weather icon query for a specific time in the future.
|
||||
* Retrieves the weather icon for a specific hour in the future
|
||||
*
|
||||
* This function calculates the next epoch time and retrieves the corresponding weather data.
|
||||
* It then generates a weather icon query based on the weather condition and time of day.
|
||||
*
|
||||
* @param weather The current weather data.
|
||||
* @param hoursFromNow The number of hours from now to calculate the weather icon query.
|
||||
*
|
||||
* @returns The weather icon query as a string.
|
||||
* @param weather - The current weather data
|
||||
* @param hoursFromNow - Number of hours from now to get the icon for
|
||||
* @returns The appropriate weather icon
|
||||
*/
|
||||
export const getIconQuery = (weather: Weather, hoursFromNow: number): WeatherIconTitle => {
|
||||
const nextEpoch = getNextEpoch(weather, hoursFromNow);
|
||||
const weatherAtEpoch = weather.forecast.forecastday[0].hour.find((h) => h.time_epoch === nextEpoch);
|
||||
|
||||
if (weatherAtEpoch === undefined) {
|
||||
return 'warning';
|
||||
export const getHourlyWeatherIcon = (weather: Weather, hoursFromNow: number): WeatherIcon => {
|
||||
if (!weather?.forecast?.[0]?.hourly) {
|
||||
return WeatherIcon.WARNING;
|
||||
}
|
||||
|
||||
let iconQuery = weatherAtEpoch.condition.text.trim().toLowerCase().replaceAll(' ', '_');
|
||||
const targetHour = getTargetHour(weather.lastUpdated, hoursFromNow);
|
||||
const targetTime = targetHour.getTime();
|
||||
|
||||
if (!weatherAtEpoch?.is_day && iconQuery === 'partly_cloudy') {
|
||||
iconQuery = 'partly_cloudy_night';
|
||||
const weatherAtHour = weather.forecast[0].hourly.find((hour) => {
|
||||
const hourTime = hour.time.getTime();
|
||||
return hourTime === targetTime;
|
||||
});
|
||||
|
||||
if (!weatherAtHour) {
|
||||
return WeatherIcon.WARNING;
|
||||
}
|
||||
|
||||
if (isValidWeatherIconTitle(iconQuery)) {
|
||||
return iconQuery;
|
||||
} else {
|
||||
return 'warning';
|
||||
}
|
||||
const iconQuery: WeatherStatus = weatherAtHour.condition?.text ?? 'WARNING';
|
||||
return WeatherIcon[iconQuery];
|
||||
};
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { bind } from 'astal';
|
||||
import { globalWeatherVar } from 'src/shared/weather';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { weatherIcons } from 'src/lib/icons/weather.js';
|
||||
import { getIconQuery } from '../helpers';
|
||||
import WeatherService from 'src/services/weather';
|
||||
import { getHourlyWeatherIcon } from '../helpers';
|
||||
|
||||
const weatherService = WeatherService.getInstance();
|
||||
|
||||
export const HourlyIcon = ({ hoursFromNow }: HourlyIconProps): JSX.Element => {
|
||||
return (
|
||||
<box halign={Gtk.Align.CENTER}>
|
||||
<label
|
||||
className={'hourly-weather-icon txt-icon'}
|
||||
label={bind(globalWeatherVar).as((weather) => {
|
||||
const iconQuery = getIconQuery(weather, hoursFromNow);
|
||||
const weatherIcn = weatherIcons[iconQuery] || weatherIcons['warning'];
|
||||
return weatherIcn;
|
||||
label={bind(weatherService.weatherData).as((weather) => {
|
||||
const weatherIcon = getHourlyWeatherIcon(weather, hoursFromNow);
|
||||
return weatherIcon;
|
||||
})}
|
||||
halign={Gtk.Align.CENTER}
|
||||
/>
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
import options from 'src/options';
|
||||
import { globalWeatherVar } from 'src/shared/weather';
|
||||
import { getNextEpoch } from '../helpers';
|
||||
import { bind, Variable } from 'astal';
|
||||
import options from 'src/configuration';
|
||||
import WeatherService from 'src/services/weather';
|
||||
import { getTargetHour } from '../helpers';
|
||||
import { TemperatureConverter } from 'src/lib/units/temperature';
|
||||
|
||||
const weatherService = WeatherService.getInstance();
|
||||
|
||||
const { unit } = options.menus.clock.weather;
|
||||
|
||||
export const HourlyTemp = ({ hoursFromNow }: HourlyTempProps): JSX.Element => {
|
||||
const weatherBinding = Variable.derive([bind(globalWeatherVar), bind(unit)], (weather, unitType) => {
|
||||
if (!Object.keys(weather).length) {
|
||||
return '-';
|
||||
}
|
||||
const weatherBinding = Variable.derive(
|
||||
[bind(weatherService.weatherData), bind(unit)],
|
||||
(weather, unitType) => {
|
||||
if (!Object.keys(weather).length || !weather?.forecast?.[0]?.hourly) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
const nextEpoch = getNextEpoch(weather, hoursFromNow);
|
||||
const weatherAtEpoch = weather.forecast.forecastday[0].hour.find((h) => h.time_epoch === nextEpoch);
|
||||
const targetHour = getTargetHour(new Date(), hoursFromNow);
|
||||
const weatherAtTargetHour = weather.forecast[0].hourly.find(
|
||||
(h) => h.time.getTime() === targetHour.getTime(),
|
||||
);
|
||||
const temperatureAtTargetHour = weatherAtTargetHour?.temperature ?? 0;
|
||||
|
||||
if (unitType === 'imperial') {
|
||||
return `${weatherAtEpoch ? Math.ceil(weatherAtEpoch.temp_f) : '-'}° F`;
|
||||
}
|
||||
return `${weatherAtEpoch ? Math.ceil(weatherAtEpoch.temp_c) : '-'}° C`;
|
||||
});
|
||||
const tempConverter = TemperatureConverter.fromCelsius(temperatureAtTargetHour);
|
||||
const isImperial = unitType === 'imperial';
|
||||
|
||||
return isImperial ? tempConverter.formatFahrenheit() : tempConverter.formatCelsius();
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<label
|
||||
|
||||
@@ -1,29 +1,32 @@
|
||||
import options from 'src/options';
|
||||
import { globalWeatherVar } from 'src/shared/weather';
|
||||
import { getNextEpoch } from '../helpers';
|
||||
import { bind, Variable } from 'astal';
|
||||
import options from 'src/configuration';
|
||||
import WeatherService from 'src/services/weather';
|
||||
import { getTargetHour } from '../helpers';
|
||||
|
||||
const weatherService = WeatherService.getInstance();
|
||||
const { military } = options.menus.clock.time;
|
||||
|
||||
export const HourlyTime = ({ hoursFromNow }: HourlyTimeProps): JSX.Element => {
|
||||
const weatherBinding = Variable.derive([bind(globalWeatherVar), bind(military)], (weather, military) => {
|
||||
if (!Object.keys(weather).length) {
|
||||
return '-';
|
||||
}
|
||||
const weatherBinding = Variable.derive(
|
||||
[bind(weatherService.weatherData), bind(military)],
|
||||
(weather, military) => {
|
||||
if (!Object.keys(weather).length) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
const nextEpoch = getNextEpoch(weather, hoursFromNow);
|
||||
const dateAtEpoch = new Date(nextEpoch * 1000);
|
||||
const targetHour = getTargetHour(new Date(), hoursFromNow);
|
||||
|
||||
let hours = dateAtEpoch.getHours();
|
||||
let hours = targetHour.getHours();
|
||||
|
||||
if (military) {
|
||||
return `${hours}:00`;
|
||||
}
|
||||
if (military) {
|
||||
return `${hours}:00`;
|
||||
}
|
||||
|
||||
const ampm = hours >= 12 ? 'PM' : 'AM';
|
||||
hours = hours % 12 || 12;
|
||||
return `${hours}${ampm}`;
|
||||
});
|
||||
const ampm = hours >= 12 ? 'PM' : 'AM';
|
||||
hours = hours % 12 || 12;
|
||||
return `${hours}${ampm}`;
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<label
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { bind } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { getWeatherStatusTextIcon, globalWeatherVar } from 'src/shared/weather';
|
||||
import WeatherService from 'src/services/weather';
|
||||
|
||||
const weatherService = WeatherService.getInstance();
|
||||
|
||||
export const TodayIcon = (): JSX.Element => {
|
||||
return (
|
||||
@@ -11,7 +13,7 @@ export const TodayIcon = (): JSX.Element => {
|
||||
>
|
||||
<label
|
||||
className={'calendar-menu-weather today icon txt-icon'}
|
||||
label={bind(globalWeatherVar).as((weather) => getWeatherStatusTextIcon(weather))}
|
||||
label={bind(weatherService.statusIcon)}
|
||||
/>
|
||||
</box>
|
||||
);
|
||||
|
||||
@@ -1,31 +1,27 @@
|
||||
import { getTemperature, globalWeatherVar } from 'src/shared/weather';
|
||||
import options from 'src/options';
|
||||
import { getRainChance } from 'src/shared/weather';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { bind, Variable } from 'astal';
|
||||
import { bind } from 'astal';
|
||||
import WeatherService from 'src/services/weather';
|
||||
|
||||
const { unit } = options.menus.clock.weather;
|
||||
const weatherService = WeatherService.getInstance();
|
||||
|
||||
export const TodayStats = (): JSX.Element => {
|
||||
const temperatureBinding = Variable.derive([bind(globalWeatherVar), bind(unit)], getTemperature);
|
||||
|
||||
return (
|
||||
<box
|
||||
className={'calendar-menu-weather today stats container'}
|
||||
halign={Gtk.Align.END}
|
||||
valign={Gtk.Align.CENTER}
|
||||
vertical
|
||||
onDestroy={() => {
|
||||
temperatureBinding.drop();
|
||||
}}
|
||||
>
|
||||
<box className={'weather wind'}>
|
||||
<label className={'weather wind icon txt-icon'} label={''} />
|
||||
<label className={'weather wind label'} label={temperatureBinding()} />
|
||||
<label className={'weather wind label'} label={bind(weatherService.windCondition)} />
|
||||
</box>
|
||||
<box className={'weather precip'}>
|
||||
<label className={'weather precip icon txt-icon'} label={''} />
|
||||
<label className={'weather precip label'} label={bind(globalWeatherVar).as(getRainChance)} />
|
||||
<label
|
||||
className={'weather precip label'}
|
||||
label={bind(weatherService.rainChance).as((chanceOfRain) => `${chanceOfRain}%`)}
|
||||
/>
|
||||
</box>
|
||||
</box>
|
||||
);
|
||||
|
||||
@@ -1,43 +1,48 @@
|
||||
import options from 'src/options';
|
||||
import { globalWeatherVar } from 'src/shared/weather';
|
||||
import { getTemperature, getWeatherIcon } from 'src/shared/weather';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { bind, Variable } from 'astal';
|
||||
import { bind } from 'astal';
|
||||
import WeatherService from 'src/services/weather';
|
||||
import options from 'src/configuration';
|
||||
import { toTitleCase } from 'src/lib/string/formatters';
|
||||
|
||||
const { unit } = options.menus.clock.weather;
|
||||
const weatherService = WeatherService.getInstance();
|
||||
|
||||
unit.subscribe((unitType) => (weatherService.unit = unitType));
|
||||
|
||||
const WeatherStatus = (): JSX.Element => {
|
||||
return (
|
||||
<box halign={Gtk.Align.CENTER}>
|
||||
<label
|
||||
className={bind(globalWeatherVar).as(
|
||||
(weather) =>
|
||||
`calendar-menu-weather today condition label ${getWeatherIcon(Math.ceil(weather.current.temp_f)).color}`,
|
||||
className={bind(weatherService.gaugeIcon).as(
|
||||
(gauge) => `calendar-menu-weather today condition label ${gauge.color}`,
|
||||
)}
|
||||
label={bind(weatherService.weatherData).as((weather) =>
|
||||
toTitleCase(weather.current.condition.text),
|
||||
)}
|
||||
label={bind(globalWeatherVar).as((weather) => weather.current.condition.text)}
|
||||
truncate
|
||||
tooltipText={bind(globalWeatherVar).as((weather) => weather.current.condition.text)}
|
||||
tooltipText={bind(weatherService.weatherData).as((weather) => weather.current.condition.text)}
|
||||
/>
|
||||
</box>
|
||||
);
|
||||
};
|
||||
|
||||
const Temperature = (): JSX.Element => {
|
||||
const labelBinding = Variable.derive([bind(globalWeatherVar), bind(unit)], getTemperature);
|
||||
|
||||
const TemperatureLabel = (): JSX.Element => {
|
||||
return <label className={'calendar-menu-weather today temp label'} label={labelBinding()} />;
|
||||
return (
|
||||
<label
|
||||
className={'calendar-menu-weather today temp label'}
|
||||
label={bind(weatherService.temperature)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ThermometerIcon = (): JSX.Element => {
|
||||
return (
|
||||
<label
|
||||
className={bind(globalWeatherVar).as(
|
||||
(weather) =>
|
||||
`calendar-menu-weather today temp label icon txt-icon ${getWeatherIcon(Math.ceil(weather.current.temp_f)).color}`,
|
||||
)}
|
||||
label={bind(globalWeatherVar).as(
|
||||
(weather) => getWeatherIcon(Math.ceil(weather.current.temp_f)).icon,
|
||||
className={bind(weatherService.gaugeIcon).as(
|
||||
(gauge) => `calendar-menu-weather today temp label icon txt-icon ${gauge.color}`,
|
||||
)}
|
||||
label={bind(weatherService.gaugeIcon).as((gauge) => gauge.icon)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -47,9 +52,6 @@ const Temperature = (): JSX.Element => {
|
||||
className={'calendar-menu-weather today temp container'}
|
||||
valign={Gtk.Align.CENTER}
|
||||
vertical={false}
|
||||
onDestroy={() => {
|
||||
labelBinding.drop();
|
||||
}}
|
||||
hexpand
|
||||
>
|
||||
<box halign={Gtk.Align.CENTER} hexpand>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { bind } from 'astal';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import { isWifiEnabled } from './helpers';
|
||||
import AstalNotifd from 'gi://AstalNotifd?version=0.1';
|
||||
import AstalBluetooth from 'gi://AstalBluetooth?version=0.1';
|
||||
import AstalNetwork from 'gi://AstalNetwork?version=0.1';
|
||||
import AstalWp from 'gi://AstalWp?version=0.1';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
|
||||
const wireplumber = AstalWp.get_default() as AstalWp.Wp;
|
||||
const audioService = wireplumber.audio;
|
||||
|
||||
@@ -6,8 +6,9 @@ import {
|
||||
PlaybackButton,
|
||||
WifiButton,
|
||||
} from './ControlButtons';
|
||||
import { JSXElement } from 'src/core/types';
|
||||
|
||||
export const Controls = ({ isEnabled }: ControlsProps): JSX.Element => {
|
||||
export const Controls = ({ isEnabled }: ControlsProps): JSXElement => {
|
||||
if (!isEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { bind, execAsync, Variable } from 'astal';
|
||||
import { App, Gtk, Widget } from 'astal/gtk3';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import options from 'src/options';
|
||||
import options from 'src/configuration';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
|
||||
const { left, right } = options.menus.dashboard.directories;
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { LeftSection, RightSection } from './Sections';
|
||||
import { LeftLink1, LeftLink2, LeftLink3, RightLink1, RightLink2, RightLink3 } from './DirectoryLinks';
|
||||
import { JSXElement } from 'src/core/types';
|
||||
|
||||
export const Directories = ({ isEnabled }: DirectoriesProps): JSX.Element => {
|
||||
export const Directories = ({ isEnabled }: DirectoriesProps): JSXElement => {
|
||||
if (!isEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import { Shortcuts } from './shortcuts/index.js';
|
||||
import { Controls } from './controls/index.js';
|
||||
import { Stats } from './stats/index.js';
|
||||
import { Directories } from './directories/index.js';
|
||||
import options from 'src/options.js';
|
||||
import { bind, Variable } from 'astal';
|
||||
import { RevealerTransitionMap } from 'src/lib/constants/options.js';
|
||||
import { RevealerTransitionMap } from 'src/components/settings/constants.js';
|
||||
import options from 'src/configuration';
|
||||
|
||||
const { controls, shortcuts, stats, directories } = options.menus.dashboard;
|
||||
const { transition } = options.menus;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
import { handleClick } from './helpers';
|
||||
import { PowerOptions } from 'src/lib/options/options.types';
|
||||
import { PowerOptions } from 'src/lib/options/types';
|
||||
|
||||
const PowerActionButton = (icon: string, tooltip: string, action: PowerOptions): JSX.Element => {
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { bind, GLib } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import options from 'src/options.js';
|
||||
import { normalizePath, isAnImage } from 'src/lib/utils.js';
|
||||
import options from 'src/configuration';
|
||||
import { normalizeToAbsolutePath } from 'src/lib/path/helpers';
|
||||
import { isAnImage } from 'src/lib/validation/images';
|
||||
|
||||
const { image, name } = options.menus.dashboard.powermenu.avatar;
|
||||
|
||||
@@ -12,7 +13,7 @@ const ProfilePicture = (): JSX.Element => {
|
||||
halign={Gtk.Align.CENTER}
|
||||
css={bind(image).as((img) => {
|
||||
if (isAnImage(img)) {
|
||||
return `background-image: url("${normalizePath(img)}")`;
|
||||
return `background-image: url("${normalizeToAbsolutePath(img)}")`;
|
||||
}
|
||||
|
||||
return `background-image: url("${SRC_DIR}/assets/hyprpanel.png")`;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { App } from 'astal/gtk3';
|
||||
import powermenu from '../../power/helpers/actions.js';
|
||||
import { execAsync } from 'astal';
|
||||
import options from 'src/options';
|
||||
import { PowerOptions } from 'src/lib/options/options.types.js';
|
||||
import { PowerOptions } from 'src/lib/options/types.js';
|
||||
import options from 'src/configuration';
|
||||
|
||||
const { confirmation, shutdown, logout, sleep, reboot } = options.menus.dashboard.powermenu;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Widget } from 'astal/gtk3';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import { handleClick, hasCommand } from '../helpers';
|
||||
import options from 'src/options';
|
||||
import { ShortcutVariable } from 'src/lib/types/dashboard.types';
|
||||
import options from 'src/configuration';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
import { ShortcutVariable } from '../types';
|
||||
|
||||
const { left, right } = options.menus.dashboard.shortcuts;
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { bind, execAsync, timeout, Variable } from 'astal';
|
||||
import { App } from 'astal/gtk3';
|
||||
import options from 'src/configuration';
|
||||
import { BashPoller } from 'src/lib/poller/BashPoller';
|
||||
import { ShortcutVariable } from 'src/lib/types/dashboard.types';
|
||||
import options from 'src/options';
|
||||
import { ShortcutVariable } from './types';
|
||||
|
||||
const { left } = options.menus.dashboard.shortcuts;
|
||||
|
||||
/**
|
||||
* A variable representing the polling interval in milliseconds.
|
||||
*/
|
||||
const pollingInterval = Variable(1000);
|
||||
|
||||
/**
|
||||
* Retrieves the latest recording path from options.
|
||||
*
|
||||
@@ -27,19 +32,6 @@ export const executeCommand = async (command: string): Promise<void> => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the recorder status based on the command output.
|
||||
*
|
||||
* This function checks if the command output indicates that recording is in progress.
|
||||
*
|
||||
* @param commandOutput The output of the command to check.
|
||||
*
|
||||
* @returns True if the command output is 'recording', false otherwise.
|
||||
*/
|
||||
export const handleRecorder = (commandOutput: string): boolean => {
|
||||
return commandOutput === 'recording';
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the click action for a shortcut.
|
||||
*
|
||||
@@ -82,11 +74,6 @@ export const leftCardHidden = Variable(
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* A variable representing the polling interval in milliseconds.
|
||||
*/
|
||||
export const pollingInterval = Variable(1000);
|
||||
|
||||
/**
|
||||
* A variable indicating whether recording is in progress.
|
||||
*/
|
||||
@@ -105,3 +92,16 @@ export const recordingPoller = new BashPoller<boolean, []>(
|
||||
`${SRC_DIR}/scripts/screen_record.sh status`,
|
||||
handleRecorder,
|
||||
);
|
||||
|
||||
/**
|
||||
* Handles the recorder status based on the command output.
|
||||
*
|
||||
* This function checks if the command output indicates that recording is in progress.
|
||||
*
|
||||
* @param commandOutput The output of the command to check.
|
||||
*
|
||||
* @returns True if the command output is 'recording', false otherwise.
|
||||
*/
|
||||
function handleRecorder(commandOutput: string): boolean {
|
||||
return commandOutput === 'recording';
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { LeftShortcuts, RightShortcuts } from './sections/Section';
|
||||
import { recordingPoller } from './helpers';
|
||||
import { JSXElement } from 'src/core/types';
|
||||
|
||||
export const Shortcuts = ({ isEnabled }: ShortcutsProps): JSX.Element => {
|
||||
recordingPoller.initialize();
|
||||
|
||||
export const Shortcuts = ({ isEnabled }: ShortcutsProps): JSXElement => {
|
||||
if (!isEnabled) {
|
||||
recordingPoller.stop();
|
||||
return null;
|
||||
}
|
||||
recordingPoller.initialize();
|
||||
|
||||
return (
|
||||
<box className={'shortcuts-container'} halign={Gtk.Align.FILL} hexpand>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { bind, Variable } from 'astal';
|
||||
import options from 'src/options';
|
||||
import { hasCommand, isRecording, leftCardHidden } from '../helpers';
|
||||
import {
|
||||
LeftShortcut1,
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
import { LeftColumn, RightColumn } from './Column';
|
||||
import { SettingsButton } from '../buttons/SettingsButton';
|
||||
import { RecordingButton } from '../buttons/RecordingButton';
|
||||
import options from 'src/configuration';
|
||||
|
||||
const { left, right } = options.menus.dashboard.shortcuts;
|
||||
|
||||
|
||||
8
src/components/menus/dashboard/shortcuts/types.ts
Normal file
8
src/components/menus/dashboard/shortcuts/types.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Variable } from 'astal';
|
||||
|
||||
export type ShortcutVariable = {
|
||||
tooltip: Variable<string>;
|
||||
command: Variable<string>;
|
||||
icon: Variable<string>;
|
||||
configurable?: true;
|
||||
};
|
||||
@@ -1,11 +1,10 @@
|
||||
import { bind } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import options from 'src/options';
|
||||
import { handleClick } from './helpers';
|
||||
import { cpuService, gpuService, handleClick, ramService, storageService } from './helpers';
|
||||
import { Binding } from 'astal';
|
||||
import { cpuService, gpuService, ramService, storageService } from '.';
|
||||
import { renderResourceLabel } from 'src/components/bar/utils/helpers';
|
||||
import { renderResourceLabel } from 'src/components/bar/utils/systemResource';
|
||||
import options from 'src/configuration';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
|
||||
const { enable_gpu } = options.menus.dashboard.stats;
|
||||
|
||||
@@ -41,12 +40,14 @@ export const GpuStat = (): JSX.Element => {
|
||||
return <box />;
|
||||
}
|
||||
|
||||
gpuService.initialize();
|
||||
|
||||
return (
|
||||
<StatBar
|
||||
icon={''}
|
||||
stat={'gpu'}
|
||||
value={bind(gpuService.gpuUsage)}
|
||||
label={bind(gpuService.gpuUsage).as((gpuUsage) => `${Math.floor(gpuUsage * 100)}%`)}
|
||||
value={bind(gpuService.gpu)}
|
||||
label={bind(gpuService.gpu).as((gpuUsage) => `${Math.floor(gpuUsage * 100)}%`)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -55,6 +56,8 @@ export const GpuStat = (): JSX.Element => {
|
||||
};
|
||||
|
||||
export const CpuStat = (): JSX.Element => {
|
||||
cpuService.initialize();
|
||||
|
||||
return (
|
||||
<StatBar
|
||||
icon={''}
|
||||
@@ -66,6 +69,8 @@ export const CpuStat = (): JSX.Element => {
|
||||
};
|
||||
|
||||
export const RamStat = (): JSX.Element => {
|
||||
ramService.initialize();
|
||||
|
||||
return (
|
||||
<StatBar
|
||||
icon={''}
|
||||
@@ -79,6 +84,8 @@ export const RamStat = (): JSX.Element => {
|
||||
};
|
||||
|
||||
export const StorageStat = (): JSX.Element => {
|
||||
storageService.initialize();
|
||||
|
||||
return (
|
||||
<StatBar
|
||||
icon={''}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import { execAsync } from 'astal';
|
||||
import { App } from 'astal/gtk3';
|
||||
import options from 'src/options';
|
||||
import Cpu from 'src/services/Cpu';
|
||||
import Gpu from 'src/services/Gpu';
|
||||
import Ram from 'src/services/Ram';
|
||||
import Storage from 'src/services/Storage';
|
||||
import options from 'src/configuration';
|
||||
import CpuUsageService from 'src/services/system/cpuUsage';
|
||||
import GpuUsageService from 'src/services/system/gpuUsage';
|
||||
import RamUsageService from 'src/services/system/ramUsage';
|
||||
import StorageService from 'src/services/system/storage';
|
||||
|
||||
const { terminal } = options;
|
||||
const { interval, enabled, enable_gpu } = options.menus.dashboard.stats;
|
||||
const { paths } = options.bar.customModules.storage;
|
||||
|
||||
export const gpuService = new GpuUsageService();
|
||||
export const cpuService = new CpuUsageService();
|
||||
export const ramService = new RamUsageService();
|
||||
export const storageService = new StorageService({ pathsToMonitor: paths });
|
||||
|
||||
/**
|
||||
* Handles the click event for the dashboard menu.
|
||||
@@ -24,16 +30,12 @@ export const handleClick = (): void => {
|
||||
* Monitors the interval for updating CPU, RAM, and storage services.
|
||||
*
|
||||
* This function subscribes to the interval setting and updates the timers for the CPU, RAM, and storage services accordingly.
|
||||
*
|
||||
* @param cpuService The CPU service instance.
|
||||
* @param ramService The RAM service instance.
|
||||
* @param storageService The storage service instance.
|
||||
*/
|
||||
const monitorInterval = (cpuService: Cpu, ramService: Ram, storageService: Storage): void => {
|
||||
const monitorInterval = (): void => {
|
||||
interval.subscribe(() => {
|
||||
ramService.updateTimer(interval.get());
|
||||
cpuService.updateTimer(interval.get());
|
||||
storageService.updateTimer(interval.get());
|
||||
storageService.frequency = interval.get();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -41,18 +43,8 @@ const monitorInterval = (cpuService: Cpu, ramService: Ram, storageService: Stora
|
||||
* Monitors the enabled state for CPU, RAM, GPU, and storage services.
|
||||
*
|
||||
* This function subscribes to the enabled setting and starts or stops the pollers for the CPU, RAM, GPU, and storage services based on the enabled state.
|
||||
*
|
||||
* @param cpuService The CPU service instance.
|
||||
* @param ramService The RAM service instance.
|
||||
* @param gpuService The GPU service instance.
|
||||
* @param storageService The storage service instance.
|
||||
*/
|
||||
const monitorStatsEnabled = (
|
||||
cpuService: Cpu,
|
||||
ramService: Ram,
|
||||
gpuService: Gpu,
|
||||
storageService: Storage,
|
||||
): void => {
|
||||
const monitorStatsEnabled = (): void => {
|
||||
enabled.subscribe(() => {
|
||||
if (!enabled.get()) {
|
||||
ramService.stopPoller();
|
||||
@@ -76,10 +68,8 @@ const monitorStatsEnabled = (
|
||||
* Monitors the GPU tracking enabled state.
|
||||
*
|
||||
* This function subscribes to the GPU tracking enabled setting and starts or stops the GPU poller based on the enabled state.
|
||||
*
|
||||
* @param gpuService The GPU service instance.
|
||||
*/
|
||||
const monitorGpuTrackingEnabled = (gpuService: Gpu): void => {
|
||||
const monitorGpuTrackingEnabled = (): void => {
|
||||
enable_gpu.subscribe((gpuEnabled) => {
|
||||
if (gpuEnabled) {
|
||||
return gpuService.startPoller();
|
||||
@@ -90,24 +80,12 @@ const monitorGpuTrackingEnabled = (gpuService: Gpu): void => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the pollers for CPU, RAM, GPU, and storage services.
|
||||
* Sets up dashboard monitoring for CPU, RAM, GPU, and storage services.
|
||||
*
|
||||
* This function sets up the initial state for the CPU, RAM, GPU, and storage services, including starting the pollers if enabled.
|
||||
* It also sets up monitoring for interval changes, enabled state changes, and GPU tracking enabled state.
|
||||
*
|
||||
* @param cpuService The CPU service instance.
|
||||
* @param ramService The RAM service instance.
|
||||
* @param gpuService The GPU service instance.
|
||||
* @param storageService The storage service instance.
|
||||
* This function sets up the initial state for the services and monitoring for interval changes, enabled state changes, and GPU tracking enabled state.
|
||||
*/
|
||||
export const initializePollers = (
|
||||
cpuService: Cpu,
|
||||
ramService: Ram,
|
||||
gpuService: Gpu,
|
||||
storageService: Storage,
|
||||
): void => {
|
||||
ramService.setShouldRound(true);
|
||||
storageService.setShouldRound(true);
|
||||
export const setupDashboardMonitoring = (): void => {
|
||||
storageService.round = true;
|
||||
|
||||
if (enabled.get()) {
|
||||
ramService.startPoller();
|
||||
@@ -121,7 +99,7 @@ export const initializePollers = (
|
||||
gpuService.stopPoller();
|
||||
}
|
||||
|
||||
monitorInterval(cpuService, ramService, storageService);
|
||||
monitorStatsEnabled(cpuService, ramService, gpuService, storageService);
|
||||
monitorGpuTrackingEnabled(gpuService);
|
||||
monitorInterval();
|
||||
monitorStatsEnabled();
|
||||
monitorGpuTrackingEnabled();
|
||||
};
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { CpuStat, GpuStat, RamStat, StorageStat } from './StatBars';
|
||||
import { initializePollers } from './helpers';
|
||||
import Gpu from 'src/services/Gpu';
|
||||
import Ram from 'src/services/Ram';
|
||||
import Cpu from 'src/services/Cpu';
|
||||
import Storage from 'src/services/Storage';
|
||||
import { setupDashboardMonitoring } from './helpers';
|
||||
import { JSXElement } from 'src/core/types';
|
||||
|
||||
export const ramService = new Ram();
|
||||
export const cpuService = new Cpu();
|
||||
export const storageService = new Storage();
|
||||
export const gpuService = new Gpu();
|
||||
|
||||
initializePollers(cpuService, ramService, gpuService, storageService);
|
||||
|
||||
export const Stats = ({ isEnabled }: StatsProps): JSX.Element => {
|
||||
export const Stats = ({ isEnabled }: StatsProps): JSXElement => {
|
||||
if (!isEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
setupDashboardMonitoring();
|
||||
|
||||
return (
|
||||
<box
|
||||
className={'dashboard-card stats-container'}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { bind } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import Brightness from 'src/services/Brightness';
|
||||
import BrightnessService from 'src/services/system/brightness';
|
||||
|
||||
const brightnessService = Brightness.get_default();
|
||||
const brightnessService = BrightnessService.getInstance();
|
||||
|
||||
export const BrightnessPercentage = (): JSX.Element => {
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { bind } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import Brightness from 'src/services/Brightness';
|
||||
import BrightnessService from 'src/services/system/brightness';
|
||||
|
||||
const brightnessService = Brightness.get_default();
|
||||
const brightnessService = BrightnessService.getInstance();
|
||||
|
||||
export const BrightnessSlider = (): JSX.Element => {
|
||||
return (
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import DropdownMenu from '../shared/dropdown/index.js';
|
||||
import { EnergyProfiles } from './profiles/index.js';
|
||||
import { Brightness } from './brightness/index.js';
|
||||
import options from 'src/options.js';
|
||||
import { bind } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { RevealerTransitionMap } from 'src/lib/constants/options.js';
|
||||
import { RevealerTransitionMap } from 'src/components/settings/constants.js';
|
||||
import options from 'src/configuration';
|
||||
|
||||
const { transition } = options.menus;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { bind } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { uptime } from 'src/lib/variables.js';
|
||||
import { renderUptime } from './helpers';
|
||||
import { uptime } from 'src/services/system/uptime';
|
||||
|
||||
export const PowerProfileHeader = (): JSX.Element => {
|
||||
return (
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { bind } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import AstalPowerProfiles from 'gi://AstalPowerProfiles?version=0.1';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
import icons from 'src/lib/icons/icons';
|
||||
import { ProfileType } from 'src/lib/types/powerprofiles.types';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
|
||||
const powerProfilesService = AstalPowerProfiles.get_default();
|
||||
import { ProfileType } from './types';
|
||||
|
||||
export const PowerProfiles = (): JSX.Element => {
|
||||
const powerProfilesService = AstalPowerProfiles.get_default();
|
||||
const powerProfiles = powerProfilesService.get_profiles();
|
||||
|
||||
return (
|
||||
|
||||
1
src/components/menus/energy/profiles/types.ts
Normal file
1
src/components/menus/energy/profiles/types.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type ProfileType = 'balanced' | 'power-saver' | 'performance';
|
||||
@@ -1,12 +1,10 @@
|
||||
import icons from 'src/lib/icons/icons';
|
||||
import { Astal, Gtk, Widget } from 'astal/gtk3';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import { bind } from 'astal';
|
||||
import { isLoopActive, isShuffleActive, loopIconMap, loopTooltipMap } from './helpers';
|
||||
import AstalMpris from 'gi://AstalMpris?version=0.1';
|
||||
import { activePlayer, loopStatus, shuffleStatus } from 'src/shared/media';
|
||||
|
||||
export type LoopStatus = 'none' | 'track' | 'playlist';
|
||||
import { loopStatus, activePlayer, shuffleStatus } from 'src/services/media';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
|
||||
export const Loop = (): JSX.Element => {
|
||||
const className = bind(loopStatus).as((status) => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Astal, Gtk, Widget } from 'astal/gtk3';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import { bind } from 'astal';
|
||||
import { getPlaybackIcon } from './helpers';
|
||||
import AstalMpris from 'gi://AstalMpris?version=0.1';
|
||||
import { activePlayer, canPlay, playbackStatus } from 'src/shared/media';
|
||||
import { canPlay, playbackStatus, activePlayer } from 'src/services/media';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
|
||||
export const PlayPause = (): JSX.Element => {
|
||||
const className = bind(canPlay).as((canPlay) => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { bind } from 'astal';
|
||||
import { Astal, Gtk, Widget } from 'astal/gtk3';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import { getNextPlayer, getPreviousPlayer } from './helpers';
|
||||
import AstalMpris from 'gi://AstalMpris?version=0.1';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
|
||||
const mprisService = AstalMpris.get_default();
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import icons from 'src/lib/icons/icons';
|
||||
import { Astal, Gtk, Widget } from 'astal/gtk3';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import { bind } from 'astal';
|
||||
import { activePlayer, canGoNext, canGoPrevious } from 'src/shared/media';
|
||||
import { canGoNext, activePlayer, canGoPrevious } from 'src/services/media';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
|
||||
export const NextTrack = (): JSX.Element => {
|
||||
const className = bind(canGoNext).as((skippable) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import AstalMpris from 'gi://AstalMpris?version=0.1';
|
||||
import { activePlayer } from 'src/shared/media';
|
||||
import icons2 from 'src/lib/icons/icons2';
|
||||
import { PlaybackIconMap } from 'src/lib/types/mpris.types';
|
||||
import icons2 from 'src/lib/icons/icons';
|
||||
import { activePlayer } from 'src/services/media';
|
||||
import { PlaybackIconMap } from './types';
|
||||
|
||||
const mprisService = AstalMpris.get_default();
|
||||
|
||||
|
||||
5
src/components/menus/media/components/controls/types.ts
Normal file
5
src/components/menus/media/components/controls/types.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import AstalMpris from 'gi://AstalMpris';
|
||||
|
||||
export type PlaybackIconMap = {
|
||||
[key in AstalMpris.PlaybackStatus]: string;
|
||||
};
|
||||
@@ -1,13 +1,23 @@
|
||||
import { Binding } from 'astal';
|
||||
import { bind, Variable } from 'astal';
|
||||
import AstalMpris from 'gi://AstalMpris?version=0.1';
|
||||
import { mediaArtUrl } from 'src/shared/media';
|
||||
import options from 'src/options';
|
||||
import options from 'src/configuration';
|
||||
import { mediaArtUrl } from 'src/services/media';
|
||||
|
||||
const mprisService = AstalMpris.get_default();
|
||||
const { tint, color } = options.theme.bar.menus.menu.media.card;
|
||||
|
||||
const curPlayer = Variable('');
|
||||
/**
|
||||
* Retrieves the background binding for the media card.
|
||||
*
|
||||
* This function sets up a derived variable that updates the background CSS for the media card
|
||||
* based on the current theme settings for color, tint, and media art URL.
|
||||
*
|
||||
* @returns A Binding<string> representing the background CSS for the media card.
|
||||
*/
|
||||
export const getBackground = (): Binding<string> => {
|
||||
return Variable.derive([bind(color), bind(tint), bind(mediaArtUrl)], (_, __, artUrl) => {
|
||||
return generateAlbumArt(artUrl);
|
||||
})();
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates CSS for album art with a tinted background.
|
||||
@@ -19,7 +29,7 @@ const curPlayer = Variable('');
|
||||
*
|
||||
* @returns A CSS string for the album art background.
|
||||
*/
|
||||
export const generateAlbumArt = (imageUrl: string): string => {
|
||||
function generateAlbumArt(imageUrl: string): string {
|
||||
const userTint = tint.get();
|
||||
const userHexColor = color.get();
|
||||
|
||||
@@ -36,50 +46,4 @@ export const generateAlbumArt = (imageUrl: string): string => {
|
||||
), url("${imageUrl}");`;
|
||||
|
||||
return css;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the active player hook.
|
||||
*
|
||||
* This function sets up a listener for changes in the MPRIS service.
|
||||
* It updates the current player based on the playback status and the order of players.
|
||||
*/
|
||||
export const initializeActivePlayerHook = (): void => {
|
||||
mprisService.connect('changed', () => {
|
||||
const statusOrder = {
|
||||
[AstalMpris.PlaybackStatus.PLAYING]: 1,
|
||||
[AstalMpris.PlaybackStatus.PAUSED]: 2,
|
||||
[AstalMpris.PlaybackStatus.STOPPED]: 3,
|
||||
};
|
||||
|
||||
const isPlaying = mprisService
|
||||
.get_players()
|
||||
.find((p) => p['playbackStatus'] === AstalMpris.PlaybackStatus.PLAYING);
|
||||
|
||||
const playerStillExists = mprisService
|
||||
.get_players()
|
||||
.some((player) => curPlayer.get() === player.busName);
|
||||
|
||||
const nextPlayerUp = mprisService
|
||||
.get_players()
|
||||
.sort((a, b) => statusOrder[a.playbackStatus] - statusOrder[b.playbackStatus])[0].bus_name;
|
||||
|
||||
if (isPlaying || !playerStillExists) {
|
||||
curPlayer.set(nextPlayerUp);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the background binding for the media card.
|
||||
*
|
||||
* This function sets up a derived variable that updates the background CSS for the media card
|
||||
* based on the current theme settings for color, tint, and media art URL.
|
||||
*
|
||||
* @returns A Binding<string> representing the background CSS for the media card.
|
||||
*/
|
||||
export const getBackground = (): Binding<string> => {
|
||||
return Variable.derive([bind(color), bind(tint), bind(mediaArtUrl)], (_, __, artUrl) => {
|
||||
return generateAlbumArt(artUrl);
|
||||
})();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export const getTimeStamp = (position: number, totalLength: number): string => {
|
||||
*
|
||||
* @returns A formatted time string in the format "HH:MM:SS" or "MM:SS".
|
||||
*/
|
||||
export const getFormattedTime = (time: number): string => {
|
||||
function getFormattedTime(time: number): string {
|
||||
const curHour = Math.floor(time / 3600);
|
||||
const curMin = Math.floor((time % 3600) / 60);
|
||||
const curSec = Math.floor(time % 60);
|
||||
@@ -41,4 +41,4 @@ export const getFormattedTime = (time: number): string => {
|
||||
};
|
||||
|
||||
return `${formatHour(curHour)}${formatTime(curMin)}:${formatTime(curSec)}`;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import options from 'src/options';
|
||||
import { bind, Variable } from 'astal';
|
||||
import { Widget } from 'astal/gtk3';
|
||||
import { activePlayer, currentPosition, timeStamp } from 'src/shared/media';
|
||||
import options from 'src/configuration';
|
||||
import { activePlayer, currentPosition, timeStamp } from 'src/services/media';
|
||||
|
||||
const { displayTimeTooltip } = options.menus.media;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import options from 'src/options';
|
||||
import { bind } from 'astal';
|
||||
import { timeStamp } from 'src/shared/media';
|
||||
import options from 'src/configuration';
|
||||
import { timeStamp } from 'src/services/media';
|
||||
|
||||
const { displayTime } = options.menus.media;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import options from 'src/options';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { bind } from 'astal';
|
||||
import { mediaAlbum } from 'src/shared/media';
|
||||
import { mediaAlbum } from 'src/services/media';
|
||||
import options from 'src/configuration';
|
||||
|
||||
const { hideAlbum } = options.menus.media;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import options from 'src/options';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { bind } from 'astal';
|
||||
import { mediaArtist } from 'src/shared/media';
|
||||
import { mediaArtist } from 'src/services/media';
|
||||
import options from 'src/configuration';
|
||||
|
||||
const { hideAuthor } = options.menus.media;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { bind } from 'astal';
|
||||
import { mediaTitle } from 'src/shared/media';
|
||||
import { mediaTitle } from 'src/services/media';
|
||||
|
||||
export const SongName = (): JSX.Element => {
|
||||
return (
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { bind } from 'astal';
|
||||
import DropdownMenu from '../shared/dropdown/index.js';
|
||||
import options from 'src/options.js';
|
||||
import options from 'src/configuration';
|
||||
import { MediaContainer } from './components/MediaContainer.js';
|
||||
import { MediaInfo } from './components/title/index.js';
|
||||
import { MediaControls } from './components/controls/index.js';
|
||||
import { MediaSlider } from './components/timebar/index.js';
|
||||
import { MediaTimeStamp } from './components/timelabel/index.js';
|
||||
import { RevealerTransitionMap } from 'src/lib/constants/options.js';
|
||||
import { RevealerTransitionMap } from 'src/components/settings/constants.js';
|
||||
|
||||
const { transition } = options.menus;
|
||||
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
import { bind, Variable } from 'astal';
|
||||
import AstalNetwork from 'gi://AstalNetwork?version=0.1';
|
||||
|
||||
const networkService = AstalNetwork.get_default();
|
||||
|
||||
/*******************************************
|
||||
* Values *
|
||||
*******************************************/
|
||||
export const wiredState: Variable<AstalNetwork.DeviceState> = Variable(AstalNetwork.DeviceState.UNKNOWN);
|
||||
export const wiredInternet: Variable<AstalNetwork.Internet> = Variable(AstalNetwork.Internet.DISCONNECTED);
|
||||
export const wiredIcon: Variable<string> = Variable('');
|
||||
export const wiredSpeed: Variable<number> = Variable(0);
|
||||
|
||||
/*******************************************
|
||||
* Bindings *
|
||||
*******************************************/
|
||||
let wiredStateBinding: Variable<void> | undefined;
|
||||
let wiredInternetBinding: Variable<void> | undefined;
|
||||
let wiredIconBinding: Variable<void> | undefined;
|
||||
let wiredSpeedBinding: Variable<void> | undefined;
|
||||
|
||||
/**
|
||||
* Retrieves the current state of the wired network.
|
||||
*
|
||||
* This function sets up a binding to the `state` property of the wired network service.
|
||||
* If the wired network service is available, it updates the `wiredState` variable with the current state.
|
||||
*/
|
||||
const getWiredState = (): void => {
|
||||
wiredStateBinding?.drop();
|
||||
wiredStateBinding = undefined;
|
||||
|
||||
if (networkService.wired === null) {
|
||||
wiredState.set(AstalNetwork.DeviceState.UNAVAILABLE);
|
||||
return;
|
||||
}
|
||||
|
||||
wiredStateBinding = Variable.derive([bind(networkService.wired, 'state')], (state) => {
|
||||
wiredState.set(state);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the current internet status of the wired network.
|
||||
*
|
||||
* This function sets up a binding to the `internet` property of the wired network service.
|
||||
* If the wired network service is available, it updates the `wiredInternet` variable with the current internet status.
|
||||
*/
|
||||
const getWiredInternet = (): void => {
|
||||
wiredInternetBinding?.drop();
|
||||
wiredInternetBinding = undefined;
|
||||
|
||||
if (networkService.wired === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
wiredInternetBinding = Variable.derive([bind(networkService.wired, 'internet')], (internet) => {
|
||||
wiredInternet.set(internet);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the current icon for the wired network.
|
||||
*
|
||||
* This function sets up a binding to the `iconName` property of the wired network service.
|
||||
* If the wired network service is available, it updates the `wiredIcon` variable with the current icon name.
|
||||
*/
|
||||
const getWiredIcon = (): void => {
|
||||
wiredIconBinding?.drop();
|
||||
wiredIconBinding = undefined;
|
||||
|
||||
if (networkService.wired === null) {
|
||||
wiredIcon.set('network-wired-symbolic');
|
||||
return;
|
||||
}
|
||||
|
||||
wiredIconBinding = Variable.derive([bind(networkService.wired, 'iconName')], (icon) => {
|
||||
wiredIcon.set(icon);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the current speed of the wired network.
|
||||
*
|
||||
* This function sets up a binding to the `speed` property of the wired network service.
|
||||
* If the wired network service is available, it updates the `wiredSpeed` variable with the current speed.
|
||||
*/
|
||||
const getWiredSpeed = (): void => {
|
||||
wiredSpeedBinding?.drop();
|
||||
wiredSpeedBinding = undefined;
|
||||
|
||||
if (networkService.wired === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
wiredSpeedBinding = Variable.derive([bind(networkService.wired, 'speed')], (speed) => {
|
||||
wiredSpeed.set(speed);
|
||||
});
|
||||
};
|
||||
|
||||
Variable.derive([bind(networkService, 'wired')], () => {
|
||||
getWiredState();
|
||||
getWiredInternet();
|
||||
getWiredIcon();
|
||||
getWiredSpeed();
|
||||
});
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { bind } from 'astal/binding';
|
||||
import AstalNetwork from 'gi://AstalNetwork?version=0.1';
|
||||
import { DEVICE_STATES } from 'src/lib/constants/network';
|
||||
import { wiredIcon, wiredInternet, wiredSpeed, wiredState } from './helpers';
|
||||
import { DEVICE_STATES } from 'src/services/network/types';
|
||||
import { NetworkService } from 'src/services/network';
|
||||
|
||||
const networkService = NetworkService.getInstance();
|
||||
|
||||
export const Ethernet = (): JSX.Element => {
|
||||
return (
|
||||
@@ -15,13 +17,13 @@ export const Ethernet = (): JSX.Element => {
|
||||
<box className={'network-element-item'}>
|
||||
<box halign={Gtk.Align.START}>
|
||||
<icon
|
||||
className={bind(wiredState).as((state) => {
|
||||
className={bind(networkService.ethernet.wiredState).as((state) => {
|
||||
return `network-icon ethernet ${state === AstalNetwork.DeviceState.ACTIVATED ? 'active' : ''}`;
|
||||
})}
|
||||
tooltipText={bind(wiredInternet).as((internet) => {
|
||||
tooltipText={bind(networkService.ethernet.wiredInternet).as((internet) => {
|
||||
return internet.toString();
|
||||
})}
|
||||
icon={bind(wiredIcon)}
|
||||
icon={bind(networkService.ethernet.wiredIcon)}
|
||||
/>
|
||||
<box className={'connection-container'} vertical>
|
||||
<label
|
||||
@@ -29,7 +31,7 @@ export const Ethernet = (): JSX.Element => {
|
||||
halign={Gtk.Align.START}
|
||||
truncate
|
||||
wrap
|
||||
label={bind(wiredSpeed).as((speed) => {
|
||||
label={bind(networkService.ethernet.wiredSpeed).as((speed) => {
|
||||
return `Ethernet Connection (${speed} Mbps)`;
|
||||
})}
|
||||
/>
|
||||
@@ -38,7 +40,7 @@ export const Ethernet = (): JSX.Element => {
|
||||
halign={Gtk.Align.START}
|
||||
truncate
|
||||
wrap
|
||||
label={bind(wiredState).as((state) => {
|
||||
label={bind(networkService.ethernet.wiredState).as((state) => {
|
||||
return DEVICE_STATES[state];
|
||||
})}
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import DropdownMenu from '../shared/dropdown/index.js';
|
||||
import { Ethernet } from './ethernet/index.js';
|
||||
import { Wifi } from './wifi/index.js';
|
||||
import options from 'src/options.js';
|
||||
import { bind } from 'astal';
|
||||
import { NoWifi } from './wifi/WirelessAPs/NoWifi.js';
|
||||
import { RevealerTransitionMap } from 'src/lib/constants/options.js';
|
||||
import { RevealerTransitionMap } from 'src/components/settings/constants.js';
|
||||
import AstalNetwork from 'gi://AstalNetwork?version=0.1';
|
||||
import options from 'src/configuration';
|
||||
|
||||
const networkService = AstalNetwork.get_default();
|
||||
|
||||
|
||||
1
src/components/menus/network/types.ts
Normal file
1
src/components/menus/network/types.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type WifiIcon = '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '';
|
||||
@@ -1,36 +0,0 @@
|
||||
import { WifiIcon } from 'src/lib/types/network.types';
|
||||
|
||||
/**
|
||||
* Retrieves the appropriate WiFi icon based on the provided icon name.
|
||||
*
|
||||
* This function returns a WiFi icon based on the given icon name. If the icon name is not provided,
|
||||
* it returns a default icon. It uses a predefined mapping of device icon names to WiFi icons.
|
||||
*
|
||||
* @param iconName The name of the icon to look up. If not provided, a default icon is returned.
|
||||
*
|
||||
* @returns The corresponding WiFi icon as a string.
|
||||
*/
|
||||
const getWifiIcon = (iconName?: string): WifiIcon => {
|
||||
if (iconName === undefined) {
|
||||
return '' as WifiIcon;
|
||||
}
|
||||
const deviceIconMap: [string, WifiIcon][] = [
|
||||
['network-wireless-acquiring', ''],
|
||||
['network-wireless-connected', ''],
|
||||
['network-wireless-encrypted', ''],
|
||||
['network-wireless-hotspot', ''],
|
||||
['network-wireless-no-route', ''],
|
||||
['network-wireless-offline', ''],
|
||||
['network-wireless-signal-excellent', ''],
|
||||
['network-wireless-signal-good', ''],
|
||||
['network-wireless-signal-ok', ''],
|
||||
['network-wireless-signal-weak', ''],
|
||||
['network-wireless-signal-none', ''],
|
||||
];
|
||||
|
||||
const foundMatch = deviceIconMap.find((icon) => RegExp(icon[0]).test(iconName.toLowerCase()));
|
||||
|
||||
return foundMatch ? foundMatch[1] : '';
|
||||
};
|
||||
|
||||
export { getWifiIcon };
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Gdk, Gtk } from 'astal/gtk3';
|
||||
import { isPrimaryClick, Notify } from 'src/lib/utils';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import AstalNetwork from 'gi://AstalNetwork?version=0.1';
|
||||
import { execAsync, Variable } from 'astal';
|
||||
import { Variable } from 'astal';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
import { handlePasswordInput } from './helpers';
|
||||
|
||||
export const PasswordInput = ({ connecting, staging }: PasswordInputProps): JSX.Element => {
|
||||
const shouldMaskPassword = true;
|
||||
const showPassword = true;
|
||||
|
||||
return (
|
||||
<box className="network-password-input-container" halign={Gtk.Align.FILL} hexpand>
|
||||
@@ -12,33 +13,13 @@ export const PasswordInput = ({ connecting, staging }: PasswordInputProps): JSX.
|
||||
className="network-password-input"
|
||||
hexpand
|
||||
halign={Gtk.Align.START}
|
||||
visibility={shouldMaskPassword}
|
||||
visibility={!showPassword}
|
||||
placeholderText="Enter Password"
|
||||
onKeyPressEvent={(self, event) => {
|
||||
const keyPressed = event.get_keyval()[1];
|
||||
|
||||
if (keyPressed === Gdk.KEY_Return) {
|
||||
connecting.set(staging.get()?.bssid ?? '');
|
||||
|
||||
const connectCommand = `nmcli device wifi connect "${staging.get()?.ssid}" password "${self.text}"`;
|
||||
|
||||
execAsync(connectCommand)
|
||||
.catch((err) => {
|
||||
connecting.set('');
|
||||
|
||||
Notify({
|
||||
summary: 'Network',
|
||||
body: err.message,
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
connecting.set('');
|
||||
|
||||
staging.set({} as AstalNetwork.AccessPoint);
|
||||
});
|
||||
|
||||
self.text = '';
|
||||
}
|
||||
handlePasswordInput(self, event, staging);
|
||||
}}
|
||||
setup={(self) => {
|
||||
setTimeout(() => self.grab_focus(), 100);
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
@@ -47,7 +28,7 @@ export const PasswordInput = ({ connecting, staging }: PasswordInputProps): JSX.
|
||||
onClick={(_, event) => {
|
||||
if (isPrimaryClick(event)) {
|
||||
connecting.set('');
|
||||
staging.set({} as AstalNetwork.AccessPoint);
|
||||
staging.set(undefined);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
35
src/components/menus/network/wifi/APStaging/helpers/index.ts
Normal file
35
src/components/menus/network/wifi/APStaging/helpers/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Variable } from 'astal';
|
||||
import { Gdk } from 'astal/gtk3';
|
||||
import { Entry } from 'astal/gtk3/widget';
|
||||
import AstalNetwork from 'gi://AstalNetwork?version=0.1';
|
||||
import { NetworkService } from 'src/services/network';
|
||||
import { SystemUtilities } from 'src/core/system/SystemUtilities';
|
||||
|
||||
const networkService = NetworkService.getInstance();
|
||||
|
||||
export function handlePasswordInput(
|
||||
self: Entry,
|
||||
event: Gdk.Event,
|
||||
staging: Variable<AstalNetwork.AccessPoint | undefined>,
|
||||
): void {
|
||||
const keyPressed = event.get_keyval()[1];
|
||||
const accessPoint = staging.get();
|
||||
const password = self.text;
|
||||
|
||||
if (keyPressed !== Gdk.KEY_Return || password.length === 0 || !accessPoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
networkService.wifi.connectToAPWithPassword(accessPoint, password).catch((err) => {
|
||||
if (self.is_visible() && self.get_realized()) {
|
||||
self.grab_focus();
|
||||
}
|
||||
|
||||
SystemUtilities.notify({
|
||||
summary: 'Network',
|
||||
body: err.message,
|
||||
});
|
||||
|
||||
self.text = '';
|
||||
});
|
||||
}
|
||||
@@ -2,24 +2,34 @@ import { bind } from 'astal/binding';
|
||||
import { Variable } from 'astal';
|
||||
import { AccessPoint } from './AccessPoint';
|
||||
import { PasswordInput } from './PasswordInput';
|
||||
import { connecting, staging } from '../WirelessAPs/helpers';
|
||||
import AstalNetwork from 'gi://AstalNetwork?version=0.1';
|
||||
import { NetworkService } from 'src/services/network';
|
||||
|
||||
const networkService = AstalNetwork.get_default();
|
||||
const networkService = NetworkService.getInstance();
|
||||
const astalNetwork = AstalNetwork.get_default();
|
||||
|
||||
export const APStaging = (): JSX.Element => {
|
||||
const stagingBinding = Variable.derive([bind(networkService, 'wifi'), bind(staging)], () => {
|
||||
if (staging.get()?.ssid === undefined) {
|
||||
return <box />;
|
||||
}
|
||||
const stagingBinding = Variable.derive(
|
||||
[bind(astalNetwork, 'wifi'), bind(networkService.wifi.staging)],
|
||||
() => {
|
||||
if (networkService.wifi.staging.get()?.ssid === undefined) {
|
||||
return <box />;
|
||||
}
|
||||
|
||||
return (
|
||||
<box className="network-element-item staging" vertical>
|
||||
<AccessPoint connecting={connecting} staging={staging} />
|
||||
<PasswordInput connecting={connecting} staging={staging} />
|
||||
</box>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<box className="network-element-item staging" vertical>
|
||||
<AccessPoint
|
||||
connecting={networkService.wifi.connecting}
|
||||
staging={networkService.wifi.staging}
|
||||
/>
|
||||
<PasswordInput
|
||||
connecting={networkService.wifi.connecting}
|
||||
staging={networkService.wifi.staging}
|
||||
/>
|
||||
</box>
|
||||
);
|
||||
},
|
||||
);
|
||||
return (
|
||||
<box
|
||||
className="wap-staging"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { bind } from 'astal';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import { isScanning } from './helpers';
|
||||
import AstalNetwork from 'gi://AstalNetwork?version=0.1';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
import { NetworkService } from 'src/services/network';
|
||||
|
||||
const networkService = AstalNetwork.get_default();
|
||||
const networkService = NetworkService.getInstance();
|
||||
const astalNetwork = AstalNetwork.get_default();
|
||||
|
||||
export const RefreshButton = (): JSX.Element => {
|
||||
return (
|
||||
@@ -14,12 +15,14 @@ export const RefreshButton = (): JSX.Element => {
|
||||
halign={Gtk.Align.END}
|
||||
onClick={(_, event) => {
|
||||
if (isPrimaryClick(event)) {
|
||||
networkService.wifi?.scan();
|
||||
astalNetwork.wifi?.scan();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<icon
|
||||
className={bind(isScanning).as((scanning) => (scanning ? 'spinning-icon' : ''))}
|
||||
className={bind(networkService.wifi.isScanning).as((scanning) =>
|
||||
scanning ? 'spinning-icon' : '',
|
||||
)}
|
||||
icon="view-refresh-symbolic"
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { bind, Variable } from 'astal';
|
||||
import AstalNetwork from 'gi://AstalNetwork?version=0.1';
|
||||
|
||||
const networkService = AstalNetwork.get_default();
|
||||
|
||||
export const isScanning: Variable<boolean> = Variable(false);
|
||||
let scanningBinding: Variable<void> | undefined;
|
||||
|
||||
Variable.derive([bind(networkService, 'wifi')], () => {
|
||||
scanningBinding?.drop();
|
||||
scanningBinding = undefined;
|
||||
|
||||
if (networkService.wifi === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scanningBinding = Variable.derive([bind(networkService.wifi, 'scanning')], (scanning) => {
|
||||
isScanning.set(scanning);
|
||||
});
|
||||
});
|
||||
@@ -1,19 +1,19 @@
|
||||
import { Variable } from 'astal';
|
||||
import AstalNetwork from 'gi://AstalNetwork?version=0.1';
|
||||
import { getWifiIcon } from '../../utils';
|
||||
import { connectToAP, getWifiStatus, isDisconnecting, isApEnabled, isApActive } from './helpers';
|
||||
import { NetworkService } from 'src/services/network';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import Spinner from 'src/components/shared/Spinner';
|
||||
|
||||
const networkService = AstalNetwork.get_default();
|
||||
const networkService = NetworkService.getInstance();
|
||||
const astalNetwork = AstalNetwork.get_default();
|
||||
|
||||
export const AccessPoint = ({ connecting, accessPoint }: AccessPointProps): JSX.Element => {
|
||||
const ConnectionIcon = (): JSX.Element => {
|
||||
return (
|
||||
<label
|
||||
valign={Gtk.Align.START}
|
||||
className={`network-icon wifi ${isApActive(accessPoint) ? 'active' : ''} txt-icon`}
|
||||
label={getWifiIcon(accessPoint.iconName)}
|
||||
className={`network-icon wifi ${networkService.wifi.isApActive(accessPoint) ? 'active' : ''} txt-icon`}
|
||||
label={networkService.getWifiIcon(accessPoint.iconName)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -28,11 +28,16 @@ export const AccessPoint = ({ connecting, accessPoint }: AccessPointProps): JSX.
|
||||
wrap
|
||||
label={accessPoint.ssid ?? ''}
|
||||
/>
|
||||
<revealer revealChild={isApActive(accessPoint) && isApEnabled(networkService.wifi?.state)}>
|
||||
<revealer
|
||||
revealChild={
|
||||
networkService.wifi.isApActive(accessPoint) &&
|
||||
networkService.wifi.isApEnabled(astalNetwork.wifi?.state)
|
||||
}
|
||||
>
|
||||
<label
|
||||
className="connection-status dim"
|
||||
halign={Gtk.Align.START}
|
||||
label={getWifiStatus()}
|
||||
label={networkService.wifi.getWifiStatus()}
|
||||
/>
|
||||
</revealer>
|
||||
</box>
|
||||
@@ -44,7 +49,9 @@ export const AccessPoint = ({ connecting, accessPoint }: AccessPointProps): JSX.
|
||||
<revealer
|
||||
halign={Gtk.Align.END}
|
||||
valign={Gtk.Align.CENTER}
|
||||
revealChild={accessPoint.bssid === connecting.get() || isDisconnecting(accessPoint)}
|
||||
revealChild={
|
||||
accessPoint.bssid === connecting.get() || networkService.wifi.isDisconnecting(accessPoint)
|
||||
}
|
||||
>
|
||||
<Spinner
|
||||
className="spinner wap"
|
||||
@@ -62,7 +69,7 @@ export const AccessPoint = ({ connecting, accessPoint }: AccessPointProps): JSX.
|
||||
<button
|
||||
className="network-element-item"
|
||||
onClick={(_, event) => {
|
||||
connectToAP(accessPoint, event);
|
||||
networkService.wifi.connectToAP(accessPoint, event);
|
||||
}}
|
||||
>
|
||||
<box hexpand>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Variable } from 'astal';
|
||||
import { disconnectFromAP, forgetAP, isApActive } from './helpers';
|
||||
import AstalNetwork from 'gi://AstalNetwork?version=0.1';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { NetworkService } from 'src/services/network';
|
||||
|
||||
const networkService = NetworkService.getInstance();
|
||||
|
||||
export const Controls = ({ connecting, accessPoint }: ControlsProps): JSX.Element => {
|
||||
const DisconnectButton = (): JSX.Element => {
|
||||
@@ -9,7 +11,7 @@ export const Controls = ({ connecting, accessPoint }: ControlsProps): JSX.Elemen
|
||||
<button
|
||||
className="menu-icon-button network disconnect"
|
||||
onClick={(_, event) => {
|
||||
disconnectFromAP(accessPoint, event);
|
||||
networkService.wifi.disconnectFromAP(accessPoint, event);
|
||||
}}
|
||||
>
|
||||
<label
|
||||
@@ -27,7 +29,7 @@ export const Controls = ({ connecting, accessPoint }: ControlsProps): JSX.Elemen
|
||||
className="menu-icon-button network disconnect"
|
||||
tooltipText="Delete/Forget Network"
|
||||
onClick={(_, event) => {
|
||||
forgetAP(accessPoint, event);
|
||||
networkService.wifi.forgetAP(accessPoint, event);
|
||||
}}
|
||||
>
|
||||
<label className="txt-icon delete-network" label="" />
|
||||
@@ -37,7 +39,9 @@ export const Controls = ({ connecting, accessPoint }: ControlsProps): JSX.Elemen
|
||||
|
||||
return (
|
||||
<revealer
|
||||
revealChild={accessPoint.bssid !== connecting.get() && isApActive(accessPoint)}
|
||||
revealChild={
|
||||
accessPoint.bssid !== connecting.get() && networkService.wifi.isApActive(accessPoint)
|
||||
}
|
||||
valign={Gtk.Align.START}
|
||||
>
|
||||
<box className={'network-element-controls-container'}>
|
||||
|
||||
@@ -1,318 +0,0 @@
|
||||
import { bind, execAsync, Variable } from 'astal';
|
||||
import { Astal } from 'astal/gtk3';
|
||||
import AstalNetwork from 'gi://AstalNetwork?version=0.1';
|
||||
import { DEVICE_STATES } from 'src/lib/constants/network';
|
||||
import { isPrimaryClick, Notify } from 'src/lib/utils';
|
||||
|
||||
const networkService = AstalNetwork.get_default();
|
||||
|
||||
export const isWifiEnabled: Variable<boolean> = Variable(false);
|
||||
export const wifiAccessPoints: Variable<AstalNetwork.AccessPoint[]> = Variable([]);
|
||||
|
||||
let wifiEnabledBinding: Variable<void> | undefined;
|
||||
let accessPointBinding: Variable<void> | undefined;
|
||||
|
||||
export const staging = Variable<AstalNetwork.AccessPoint | undefined>(undefined);
|
||||
export const connecting = Variable<string>('');
|
||||
|
||||
/**
|
||||
* Checks if WiFi is enabled and updates the `isWifiEnabled` variable.
|
||||
*
|
||||
* This function sets up a binding to the `enabled` property of the WiFi service.
|
||||
* If the WiFi service is available, it updates the `isWifiEnabled` variable based on the enabled state.
|
||||
*/
|
||||
const wifiEnabled = (): void => {
|
||||
wifiEnabledBinding?.drop();
|
||||
wifiEnabledBinding = undefined;
|
||||
|
||||
if (networkService.wifi === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
wifiEnabledBinding = Variable.derive([bind(networkService.wifi, 'enabled')], (isEnabled) => {
|
||||
isWifiEnabled.set(isEnabled);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the list of WiFi access points.
|
||||
*
|
||||
* This function sets up a binding to the `accessPoints` property of the WiFi service.
|
||||
* If the WiFi service is available, it updates the `wifiAccessPoints` variable with the list of access points.
|
||||
*/
|
||||
const accessPoints = (): void => {
|
||||
accessPointBinding?.drop();
|
||||
accessPointBinding = undefined;
|
||||
|
||||
if (networkService.wifi === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Variable.derive([bind(networkService.wifi, 'accessPoints')], (axsPoints) => {
|
||||
wifiAccessPoints.set(axsPoints);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes duplicate access points based on their SSID.
|
||||
*
|
||||
* This function iterates through the list of access points and removes duplicates based on their SSID.
|
||||
* It returns an array of deduplicated access points.
|
||||
*
|
||||
* @returns An array of deduplicated access points.
|
||||
*/
|
||||
const dedupeWAPs = (): AstalNetwork.AccessPoint[] => {
|
||||
if (networkService.wifi === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const WAPs = networkService.wifi.get_access_points();
|
||||
const dedupMap: Record<string, AstalNetwork.AccessPoint> = {};
|
||||
|
||||
WAPs.forEach((item: AstalNetwork.AccessPoint) => {
|
||||
if (item.ssid !== null && !Object.prototype.hasOwnProperty.call(dedupMap, item.ssid)) {
|
||||
dedupMap[item.ssid] = item;
|
||||
}
|
||||
});
|
||||
|
||||
return Object.keys(dedupMap).map((itm) => dedupMap[itm]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if a given access point is currently in the staging area.
|
||||
*
|
||||
* This function checks if the provided access point is in the staging area by comparing its BSSID with the BSSID of the staged access point.
|
||||
*
|
||||
* @param wap The access point to check.
|
||||
*
|
||||
* @returns True if the access point is in staging; otherwise, false.
|
||||
*/
|
||||
const isInStaging = (wap: AstalNetwork.AccessPoint): boolean => {
|
||||
const wapInStaging = staging.get();
|
||||
if (wapInStaging === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return wap.bssid === wapInStaging.bssid;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves a list of filtered wireless access points by removing duplicates and excluding specific entries.
|
||||
*
|
||||
* This function filters the list of access points by removing duplicates and excluding access points with the SSID 'Unknown' or those in the staging area.
|
||||
* It also sorts the access points by their signal strength and active status.
|
||||
*
|
||||
* @returns A filtered array of wireless access points.
|
||||
*/
|
||||
export const getFilteredWirelessAPs = (): AstalNetwork.AccessPoint[] => {
|
||||
const dedupedWAPs = dedupeWAPs();
|
||||
|
||||
const filteredWAPs = dedupedWAPs
|
||||
.filter((ap: AstalNetwork.AccessPoint) => {
|
||||
return ap.ssid !== 'Unknown' && !isInStaging(ap);
|
||||
})
|
||||
.sort((a: AstalNetwork.AccessPoint, b: AstalNetwork.AccessPoint) => {
|
||||
if (isApActive(a)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (isApActive(b)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return b.strength - a.strength;
|
||||
});
|
||||
|
||||
return filteredWAPs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines whether the device is in an active state.
|
||||
*
|
||||
* This function checks if the provided device state is active by comparing it with the disconnected, unavailable, and failed states.
|
||||
*
|
||||
* @param state The current state of the device.
|
||||
*
|
||||
* @returns True if the device is in an active state; otherwise, false.
|
||||
*/
|
||||
export const isApEnabled = (state: AstalNetwork.DeviceState | undefined): boolean => {
|
||||
if (state == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !(
|
||||
state === AstalNetwork.DeviceState.DISCONNECTED ||
|
||||
state === AstalNetwork.DeviceState.UNAVAILABLE ||
|
||||
state === AstalNetwork.DeviceState.FAILED
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the given access point is the currently active one.
|
||||
*
|
||||
* This function compares the SSID of the provided access point with the SSID of the active access point in the WiFi service.
|
||||
*
|
||||
* @param accessPoint The access point to check.
|
||||
*
|
||||
* @returns True if the access point is active; otherwise, false.
|
||||
*/
|
||||
export const isApActive = (accessPoint: AstalNetwork.AccessPoint): boolean => {
|
||||
return accessPoint.ssid === networkService.wifi?.activeAccessPoint?.ssid;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the specified access point is in the process of disconnecting.
|
||||
*
|
||||
* This function checks if the provided access point is the active one and if the WiFi service state is deactivating.
|
||||
*
|
||||
* @param accessPoint The access point to check.
|
||||
*
|
||||
* @returns True if the access point is disconnecting; otherwise, false.
|
||||
*/
|
||||
export const isDisconnecting = (accessPoint: AstalNetwork.AccessPoint): boolean => {
|
||||
if (isApActive(accessPoint)) {
|
||||
return networkService.wifi?.state === AstalNetwork.DeviceState.DEACTIVATING;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts the connection ID associated with a given SSID from the `nmcli` command output.
|
||||
*
|
||||
* This function parses the output of the `nmcli` command to find the connection ID associated with the provided SSID.
|
||||
*
|
||||
* @param ssid The SSID of the network.
|
||||
* @param nmcliOutput The output string from the `nmcli` command.
|
||||
*
|
||||
* @returns The connection ID if found; otherwise, undefined.
|
||||
*/
|
||||
export const getIdFromSsid = (ssid: string, nmcliOutput: string): string | undefined => {
|
||||
const lines = nmcliOutput.trim().split('\n');
|
||||
for (const line of lines) {
|
||||
const columns = line.trim().split(/\s{2,}/);
|
||||
if (columns[0].includes(ssid)) {
|
||||
return columns[1];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the current Wi-Fi status based on the network service state.
|
||||
*
|
||||
* This function returns a string representing the current Wi-Fi status based on the state of the WiFi service.
|
||||
*
|
||||
* @returns A string representing the current Wi-Fi status.
|
||||
*/
|
||||
export const getWifiStatus = (): string => {
|
||||
const wifiState = networkService.wifi?.state;
|
||||
|
||||
if (wifiState !== null) {
|
||||
return DEVICE_STATES[wifiState];
|
||||
}
|
||||
return DEVICE_STATES[AstalNetwork.DeviceState.UNKNOWN];
|
||||
};
|
||||
|
||||
/**
|
||||
* Initiates a connection to the specified access point.
|
||||
*
|
||||
* This function attempts to connect to the provided access point using the `nmcli` command.
|
||||
* It handles connection attempts, updates the `connecting` variable, and manages errors.
|
||||
*
|
||||
* @param accessPoint The access point to connect to.
|
||||
* @param event The click event triggering the connection.
|
||||
*/
|
||||
export const connectToAP = (accessPoint: AstalNetwork.AccessPoint, event: Astal.ClickEvent): void => {
|
||||
if (accessPoint.bssid === connecting.get() || isApActive(accessPoint) || !isPrimaryClick(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
connecting.set(accessPoint.bssid || '');
|
||||
execAsync(`nmcli device wifi connect ${accessPoint.bssid}`)
|
||||
.then(() => {
|
||||
connecting.set('');
|
||||
staging.set({} as AstalNetwork.AccessPoint);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
connecting.set('');
|
||||
if (err.message.toLowerCase().includes('secrets were required, but not provided')) {
|
||||
staging.set(accessPoint);
|
||||
} else {
|
||||
Notify({
|
||||
summary: 'Network',
|
||||
body: err.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnects from the specified access point.
|
||||
*
|
||||
* This function attempts to disconnect from the provided access point using the `nmcli` command.
|
||||
* It handles disconnection attempts, updates the `connecting` variable, and manages errors.
|
||||
*
|
||||
* @param accessPoint The access point to disconnect from.
|
||||
* @param event The click event triggering the disconnection.
|
||||
*/
|
||||
export const disconnectFromAP = (accessPoint: AstalNetwork.AccessPoint, event: Astal.ClickEvent): void => {
|
||||
if (!isPrimaryClick(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
connecting.set(accessPoint.bssid || '');
|
||||
execAsync('nmcli connection show --active').then((res: string) => {
|
||||
const connectionId = getIdFromSsid(accessPoint.ssid || '', res);
|
||||
|
||||
if (connectionId === undefined) {
|
||||
console.error(`Error while disconnecting "${accessPoint.ssid}": Connection ID not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
execAsync(`nmcli connection down ${connectionId} "${accessPoint.ssid}"`)
|
||||
.then(() => {
|
||||
connecting.set('');
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
connecting.set('');
|
||||
console.error(`Error while disconnecting "${accessPoint.ssid}": ${err}`);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Forgets the specified access point by deleting its connection.
|
||||
*
|
||||
* This function attempts to forget the provided access point by deleting its connection using the `nmcli` command.
|
||||
* It handles the forget action, updates the `connecting` variable, and manages errors.
|
||||
*
|
||||
* @param accessPoint The access point to forget.
|
||||
* @param event The click event triggering the forget action.
|
||||
*/
|
||||
export const forgetAP = (accessPoint: AstalNetwork.AccessPoint, event: Astal.ClickEvent): void => {
|
||||
if (!isPrimaryClick(event)) {
|
||||
return;
|
||||
}
|
||||
connecting.set(accessPoint.bssid || '');
|
||||
execAsync('nmcli connection show --active').then((res: string) => {
|
||||
const connectionId = getIdFromSsid(accessPoint.ssid || '', res);
|
||||
|
||||
if (connectionId === undefined) {
|
||||
console.error(`Error while forgetting "${accessPoint.ssid}": Connection ID not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
execAsync(`nmcli connection delete ${connectionId} "${accessPoint.ssid}"`)
|
||||
.then(() => {
|
||||
connecting.set('');
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
connecting.set('');
|
||||
console.error(`Error while forgetting "${accessPoint.ssid}": ${err}`);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Variable.derive([bind(networkService, 'wifi')], () => {
|
||||
wifiEnabled();
|
||||
accessPoints();
|
||||
});
|
||||
@@ -1,18 +1,25 @@
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { bind } from 'astal/binding';
|
||||
import AstalNetwork from 'gi://AstalNetwork?version=0.1';
|
||||
import { connecting, getFilteredWirelessAPs, isWifiEnabled, staging, wifiAccessPoints } from './helpers';
|
||||
import { AccessPoint } from './AccessPoint';
|
||||
import { Controls } from './Controls';
|
||||
import { Variable } from 'astal';
|
||||
import { NetworkService } from 'src/services/network';
|
||||
|
||||
const networkService = NetworkService.getInstance();
|
||||
|
||||
export const WirelessAPs = (): JSX.Element => {
|
||||
const wapBinding = Variable.derive(
|
||||
[bind(staging), bind(connecting), bind(wifiAccessPoints), bind(isWifiEnabled)],
|
||||
[
|
||||
bind(networkService.wifi.staging),
|
||||
bind(networkService.wifi.connecting),
|
||||
bind(networkService.wifi.wifiAccessPoints),
|
||||
bind(networkService.wifi.isWifiEnabled),
|
||||
],
|
||||
() => {
|
||||
const filteredWAPs = getFilteredWirelessAPs();
|
||||
const filteredWAPs = networkService.wifi.getFilteredWirelessAPs();
|
||||
|
||||
if (filteredWAPs.length <= 0 && staging.get() === undefined) {
|
||||
if (filteredWAPs.length <= 0 && networkService.wifi.staging.get() === undefined) {
|
||||
return (
|
||||
<label
|
||||
className={'waps-not-found dim'}
|
||||
@@ -30,8 +37,11 @@ export const WirelessAPs = (): JSX.Element => {
|
||||
{filteredWAPs.map((ap: AstalNetwork.AccessPoint) => {
|
||||
return (
|
||||
<box className={'network-element-item'}>
|
||||
<AccessPoint connecting={connecting} accessPoint={ap} />
|
||||
<Controls connecting={connecting} accessPoint={ap} />
|
||||
<AccessPoint
|
||||
connecting={networkService.wifi.connecting}
|
||||
accessPoint={ap}
|
||||
/>
|
||||
<Controls connecting={networkService.wifi.connecting} accessPoint={ap} />
|
||||
</box>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { bind } from 'astal';
|
||||
import AstalNotifd from 'gi://AstalNotifd?version=0.1';
|
||||
import { clearNotifications, removingNotifications } from 'src/shared/notification';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import options from 'src/options';
|
||||
import options from 'src/configuration';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
import { clearNotifications, removingNotifications } from 'src/lib/shared/notifications';
|
||||
|
||||
const notifdService = AstalNotifd.get_default();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { bind, Variable } from 'astal';
|
||||
import AstalNotifd from 'gi://AstalNotifd?version=0.1';
|
||||
import options from 'src/options';
|
||||
import options from 'src/configuration';
|
||||
|
||||
const { displayedTotal } = options.notifications;
|
||||
const notifdService = AstalNotifd.get_default();
|
||||
|
||||
@@ -2,10 +2,10 @@ import DropdownMenu from '../shared/dropdown/index.js';
|
||||
import { Controls } from './controls/index.js';
|
||||
import { NotificationsContainer } from './notification/index.js';
|
||||
import { NotificationPager } from './pager/index.js';
|
||||
import options from 'src/options.js';
|
||||
import { handlePageBoundaries } from './helpers.js';
|
||||
import { bind, Variable } from 'astal';
|
||||
import { RevealerTransitionMap } from 'src/lib/constants/options.js';
|
||||
import { RevealerTransitionMap } from 'src/components/settings/constants.js';
|
||||
import options from 'src/configuration';
|
||||
|
||||
const { transition } = options.menus;
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import options from 'src/options.js';
|
||||
import { filterNotifications } from 'src/lib/shared/notifications.js';
|
||||
import AstalNotifd from 'gi://AstalNotifd?version=0.1';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { bind, Variable } from 'astal';
|
||||
import { NotificationCard } from 'src/components/notifications/Notification.js';
|
||||
import { Placeholder } from './Placeholder';
|
||||
import { NotificationCard } from 'src/components/notifications/Notification';
|
||||
import options from 'src/configuration';
|
||||
import { filterNotifications } from 'src/lib/shared/notifications';
|
||||
|
||||
const notifdService = AstalNotifd.get_default();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Variable } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import AstalNotifd from 'gi://AstalNotifd?version=0.1';
|
||||
import { isPrimaryClick } from 'src/lib/utils';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
|
||||
export const FirstPageButton = ({ curPage, currentPage }: FirstPageButtonProps): JSX.Element => {
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { bind, Variable } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import AstalNotifd from 'gi://AstalNotifd?version=0.1';
|
||||
import options from 'src/options';
|
||||
import { FirstPageButton, LastPageButton, NextPageButton, PreviousPageButton } from './Buttons';
|
||||
import options from 'src/configuration';
|
||||
|
||||
const notifdService = AstalNotifd.get_default();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import options from 'src/options';
|
||||
import { execAsync, GObject, property, register } from 'astal';
|
||||
import { App } from 'astal/gtk3';
|
||||
import { Action } from 'src/lib/types/power.types';
|
||||
import options from 'src/configuration';
|
||||
import { Action } from '../types';
|
||||
const { sleep, reboot, logout, shutdown } = options.menus.dashboard.powermenu;
|
||||
|
||||
@register({ GTypeName: 'PowerMenu' })
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import PopupWindow from '../shared/popup/index.js';
|
||||
import powermenu from './helpers/actions.js';
|
||||
import options from 'src/options.js';
|
||||
import { isPrimaryClick } from 'src/lib/utils.js';
|
||||
import icons from 'src/lib/icons/icons.js';
|
||||
import { bind } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { RevealerTransitionMap } from 'src/lib/constants/options.js';
|
||||
import { Action } from 'src/lib/types/power.types.js';
|
||||
import { RevealerTransitionMap } from 'src/components/settings/constants.js';
|
||||
import options from 'src/configuration';
|
||||
import { isPrimaryClick } from 'src/lib/events/mouse';
|
||||
import { Action } from './types';
|
||||
|
||||
const { transition } = options.menus;
|
||||
|
||||
|
||||
1
src/components/menus/power/types.ts
Normal file
1
src/components/menus/power/types.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type Action = 'sleep' | 'reboot' | 'logout' | 'shutdown';
|
||||
@@ -1,9 +1,9 @@
|
||||
import { capitalizeFirstLetter } from 'src/lib/utils';
|
||||
import options from 'src/options';
|
||||
import options from 'src/configuration';
|
||||
import powermenu from '../power/helpers/actions';
|
||||
import { App, Gtk } from 'astal/gtk3';
|
||||
import { bind, execAsync } from 'astal';
|
||||
import { PowerOptions } from 'src/lib/options/options.types';
|
||||
import { PowerOptions } from 'src/lib/options/types';
|
||||
import { capitalizeFirstLetter } from 'src/lib/string/formatters';
|
||||
|
||||
const { confirmation, shutdown, logout, sleep, reboot, showLabel } = options.menus.power;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { bind } from 'astal';
|
||||
import DropdownMenu from '../shared/dropdown/index.js';
|
||||
import { PowerButton } from './button.js';
|
||||
import options from 'src/options.js';
|
||||
import { RevealerTransitionMap } from 'src/lib/constants/options.js';
|
||||
import options from 'src/configuration';
|
||||
import { RevealerTransitionMap } from 'src/components/settings/constants.js';
|
||||
|
||||
export default (): JSX.Element => {
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { App } from 'astal/gtk3';
|
||||
import { BarEventMarginsProps, EventBoxPaddingProps } from '../types';
|
||||
import { BarEventMarginsProps, EventBoxPaddingProps } from './types';
|
||||
|
||||
const EventBoxPadding = ({ className, windowName }: EventBoxPaddingProps): JSX.Element => {
|
||||
return (
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DropdownMenuList } from 'src/lib/options/options.types';
|
||||
import { calculateMenuPosition } from './locationHandler';
|
||||
import { DropdownMenuList } from 'src/lib/options/types';
|
||||
import { App, Gtk } from 'astal/gtk3';
|
||||
import { calculateMenuPosition } from './locationHandler';
|
||||
|
||||
/**
|
||||
* Handles the realization of a dropdown menu.
|
||||
@@ -1,5 +1,5 @@
|
||||
import options from 'src/options';
|
||||
import { globalEventBoxes } from 'src/shared/dropdown';
|
||||
import options from 'src/configuration';
|
||||
import { globalEventBoxes } from 'src/lib/events/dropdown';
|
||||
import { GLib } from 'astal';
|
||||
import { EventBox } from 'astal/gtk3/widget';
|
||||
import AstalHyprland from 'gi://AstalHyprland?version=0.1';
|
||||
11
src/components/menus/shared/dropdown/helpers/types.ts
Normal file
11
src/components/menus/shared/dropdown/helpers/types.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { BarLocation } from 'src/lib/options/types';
|
||||
|
||||
export type EventBoxPaddingProps = {
|
||||
className: string;
|
||||
windowName: string;
|
||||
};
|
||||
|
||||
export type BarEventMarginsProps = {
|
||||
windowName: string;
|
||||
location?: BarLocation;
|
||||
};
|
||||
@@ -1,11 +1,10 @@
|
||||
import options from 'src/options';
|
||||
import { BarEventMargins } from './eventBoxes/index';
|
||||
import { globalEventBoxes } from 'src/shared/dropdown';
|
||||
import options from 'src/configuration';
|
||||
import { BarEventMargins } from './helpers/eventBoxes';
|
||||
import { globalEventBoxes } from 'src/lib/events/dropdown';
|
||||
import { bind } from 'astal';
|
||||
import { App, Astal, Gdk, Gtk } from 'astal/gtk3';
|
||||
import { Revealer } from 'astal/gtk3/widget';
|
||||
import { locationMap } from 'src/lib/types/defaults/bar.types';
|
||||
import { DropdownMenuProps } from 'src/lib/types/dropdownmenu.types';
|
||||
import { DropdownMenuProps, LocationMap } from './types';
|
||||
|
||||
const { location } = options.theme.bar;
|
||||
|
||||
@@ -16,6 +15,11 @@ export default ({
|
||||
exclusivity = Astal.Exclusivity.IGNORE,
|
||||
...props
|
||||
}: DropdownMenuProps): JSX.Element => {
|
||||
const locationMap: LocationMap = {
|
||||
top: Astal.WindowAnchor.TOP,
|
||||
bottom: Astal.WindowAnchor.BOTTOM,
|
||||
};
|
||||
|
||||
return (
|
||||
<window
|
||||
name={name}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { BarLocation } from 'src/lib/options/options.types';
|
||||
import { Astal, Gtk } from 'astal/gtk3';
|
||||
import { Binding } from 'astal';
|
||||
import { BarLocation } from 'src/lib/options/types';
|
||||
|
||||
export type EventBoxPaddingProps = {
|
||||
className: string;
|
||||
windowName: string;
|
||||
};
|
||||
export interface DropdownMenuProps {
|
||||
name: string;
|
||||
child?: JSX.Element | JSX.Element[] | Binding<JSX.Element | undefined>;
|
||||
layout?: string;
|
||||
transition?: Gtk.RevealerTransitionType | Binding<Gtk.RevealerTransitionType>;
|
||||
exclusivity?: Astal.Exclusivity;
|
||||
fixed?: boolean;
|
||||
onDestroy?: () => void;
|
||||
}
|
||||
|
||||
export type BarEventMarginsProps = {
|
||||
windowName: string;
|
||||
location?: BarLocation;
|
||||
export type LocationMap = {
|
||||
[key in BarLocation]: Astal.WindowAnchor;
|
||||
};
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import { App, Astal, Gdk, Gtk } from 'astal/gtk3';
|
||||
import { WINDOW_LAYOUTS } from 'src/shared/window';
|
||||
import { POPUP_LAYOUTS } from 'src/components/menus/shared/popup/layouts';
|
||||
import { EventBox, Revealer } from 'astal/gtk3/widget';
|
||||
import {
|
||||
PaddingProps,
|
||||
PopupRevealerProps,
|
||||
LayoutFunction,
|
||||
PopupWindowProps,
|
||||
} from 'src/lib/types/popupwindow.types';
|
||||
import { Layouts } from 'src/lib/types/widget.types';
|
||||
import { LayoutFunction, Layouts, PaddingProps, PopupRevealerProps, PopupWindowProps } from './types';
|
||||
|
||||
export const Padding = ({ name, opts }: PaddingProps): JSX.Element => (
|
||||
const Padding = ({ name, opts }: PaddingProps): JSX.Element => (
|
||||
<eventbox
|
||||
className={opts?.className ?? ''}
|
||||
hexpand
|
||||
@@ -119,7 +113,7 @@ const Layout: LayoutFunction = (name, child, transition) => ({
|
||||
});
|
||||
|
||||
const isValidLayout = (layout: string): layout is Layouts => {
|
||||
return WINDOW_LAYOUTS.includes(layout);
|
||||
return POPUP_LAYOUTS.includes(layout);
|
||||
};
|
||||
|
||||
export default ({
|
||||
|
||||
10
src/components/menus/shared/popup/layouts.ts
Normal file
10
src/components/menus/shared/popup/layouts.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export const POPUP_LAYOUTS: string[] = [
|
||||
'center',
|
||||
'top',
|
||||
'top-right',
|
||||
'top-center',
|
||||
'top-left',
|
||||
'bottom-left',
|
||||
'bottom-center',
|
||||
'bottom-right',
|
||||
];
|
||||
52
src/components/menus/shared/popup/types.ts
Normal file
52
src/components/menus/shared/popup/types.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Binding } from 'astal';
|
||||
import { Gtk, Astal } from 'astal/gtk3';
|
||||
import { WindowProps } from 'astal/gtk3/widget';
|
||||
|
||||
export type Layouts =
|
||||
| 'center'
|
||||
| 'top'
|
||||
| 'top-right'
|
||||
| 'top-center'
|
||||
| 'top-left'
|
||||
| 'bottom-left'
|
||||
| 'bottom-center'
|
||||
| 'bottom-right';
|
||||
|
||||
export interface PopupWindowProps extends WindowProps {
|
||||
name: string;
|
||||
child?: JSX.Element;
|
||||
layout?: Layouts;
|
||||
transition?: Gtk.RevealerTransitionType | Binding<Gtk.RevealerTransitionType>;
|
||||
exclusivity?: Astal.Exclusivity;
|
||||
}
|
||||
|
||||
export type LayoutFunction = (
|
||||
name: string,
|
||||
child: JSX.Element,
|
||||
transition: Gtk.RevealerTransitionType | Binding<Gtk.RevealerTransitionType>,
|
||||
) => {
|
||||
center: () => JSX.Element;
|
||||
top: () => JSX.Element;
|
||||
'top-right': () => JSX.Element;
|
||||
'top-center': () => JSX.Element;
|
||||
'top-left': () => JSX.Element;
|
||||
'bottom-left': () => JSX.Element;
|
||||
'bottom-center': () => JSX.Element;
|
||||
'bottom-right': () => JSX.Element;
|
||||
};
|
||||
|
||||
type Opts = {
|
||||
className: string;
|
||||
vexpand: boolean;
|
||||
};
|
||||
|
||||
export type PaddingProps = {
|
||||
name: string;
|
||||
opts?: Opts;
|
||||
};
|
||||
|
||||
export type PopupRevealerProps = {
|
||||
name: string;
|
||||
child: JSX.Element;
|
||||
transition: Gtk.RevealerTransitionType | Binding<Gtk.RevealerTransitionType>;
|
||||
};
|
||||
Reference in New Issue
Block a user