diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000..ff96a83 --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,130 @@ +env: + es2022: true +extends: + - "eslint:recommended" + - "plugin:@typescript-eslint/recommended" +parser: "@typescript-eslint/parser" +parserOptions: + ecmaVersion: 2022 + sourceType: "module" + project: "./tsconfig.json" + warnOnUnsupportedTypeScriptVersion: false +root: true +ignorePatterns: + - types/ +plugins: + - "@typescript-eslint" +rules: + "@typescript-eslint/ban-ts-comment": + - "off" + "@typescript-eslint/no-non-null-assertion": + - "off" + # "@typescript-eslint/no-explicit-any": + # - "off" + "@typescript-eslint/no-unused-vars": + - error + - varsIgnorePattern: (^unused|_$) + argsIgnorePattern: ^(unused|_) + "@typescript-eslint/no-empty-interface": + - "off" + + arrow-parens: + - error + - as-needed + comma-dangle: + - error + - always-multiline + comma-spacing: + - error + - before: false + after: true + comma-style: + - error + - last + curly: + - error + - multi-or-nest + - consistent + dot-location: + - error + - property + eol-last: + - error + eqeqeq: + - error + - always + indent: + - error + - 4 + - SwitchCase: 1 + keyword-spacing: + - error + - before: true + lines-between-class-members: + - error + - always + - exceptAfterSingleLine: true + padded-blocks: + - error + - never + - allowSingleLineBlocks: false + prefer-const: + - error + quotes: + - error + - double + - avoidEscape: true + semi: + - error + - never + nonblock-statement-body-position: + - error + - below + no-trailing-spaces: + - error + no-useless-escape: + - off + max-len: + - error + - code: 100 + func-call-spacing: + - error + array-bracket-spacing: + - error + space-before-function-paren: + - error + - anonymous: never + named: never + asyncArrow: ignore + space-before-blocks: + - error + key-spacing: + - error + object-curly-spacing: + - error + - always +globals: + Widget: readonly + Utils: readonly + App: readonly + Variable: readonly + Service: readonly + pkg: readonly + ARGV: readonly + Debugger: readonly + GIRepositoryGType: readonly + globalThis: readonly + imports: readonly + Intl: readonly + log: readonly + logError: readonly + print: readonly + printerr: readonly + window: readonly + TextEncoder: readonly + TextDecoder: readonly + console: readonly + setTimeout: readonly + setInterval: readonly + clearTimeout: readonly + clearInterval: readonly diff --git a/directoryMonitorService.js b/directoryMonitorService.js deleted file mode 100644 index 1447a8f..0000000 --- a/directoryMonitorService.js +++ /dev/null @@ -1,41 +0,0 @@ -import Service from "resource:///com/github/Aylur/ags/service.js"; -import App from "resource:///com/github/Aylur/ags/app.js"; -import { monitorFile } from "resource:///com/github/Aylur/ags/utils.js"; -import Gio from "gi://Gio"; - -class DirectoryMonitorService extends Service { - static { - Service.register(this, {}, {}); - } - - constructor() { - super(); - this.recursiveDirectoryMonitor(`${App.configDir}/scss`); - } - - recursiveDirectoryMonitor(directoryPath) { - monitorFile(directoryPath, (_, eventType) => { - if (eventType === Gio.FileMonitorEvent.CHANGES_DONE_HINT) { - this.emit("changed"); - } - }); - - const directory = Gio.File.new_for_path(directoryPath); - const enumerator = directory.enumerate_children( - "standard::*", - Gio.FileQueryInfoFlags.NONE, - null, - ); - - let fileInfo; - while ((fileInfo = enumerator.next_file(null)) !== null) { - const childPath = directoryPath + "/" + fileInfo.get_name(); - if (fileInfo.get_file_type() === Gio.FileType.DIRECTORY) { - this.recursiveDirectoryMonitor(childPath); - } - } - } -} - -const service = new DirectoryMonitorService(); -export default service; diff --git a/directoryMonitorService.ts b/directoryMonitorService.ts new file mode 100644 index 0000000..911dc69 --- /dev/null +++ b/directoryMonitorService.ts @@ -0,0 +1,42 @@ +import Service from "resource:///com/github/Aylur/ags/service.js"; +import App from "resource:///com/github/Aylur/ags/app.js"; +import { monitorFile } from "resource:///com/github/Aylur/ags/utils.js"; +import Gio from "gi://Gio"; +import { FileInfo } from "types/@girs/gio-2.0/gio-2.0.cjs"; + +class DirectoryMonitorService extends Service { + static { + Service.register(this, {}, {}); + } + + constructor() { + super(); + this.recursiveDirectoryMonitor(`${App.configDir}/scss`); + } + + recursiveDirectoryMonitor(directoryPath: string) { + monitorFile(directoryPath, (_, eventType) => { + if (eventType === Gio.FileMonitorEvent.CHANGES_DONE_HINT) { + this.emit("changed"); + } + }); + + const directory = Gio.File.new_for_path(directoryPath); + const enumerator = directory.enumerate_children( + "standard::*", + Gio.FileQueryInfoFlags.NONE, + null, + ); + + let fileInfo: FileInfo; + while ((fileInfo = enumerator.next_file(null) as FileInfo) !== null) { + const childPath = directoryPath + "/" + fileInfo.get_name(); + if (fileInfo.get_file_type() === Gio.FileType.DIRECTORY) { + this.recursiveDirectoryMonitor(childPath); + } + } + } +} + +const service = new DirectoryMonitorService(); +export default service; diff --git a/globals.js b/globals.ts similarity index 64% rename from globals.js rename to globals.ts index b706cdb..913a682 100644 --- a/globals.js +++ b/globals.ts @@ -1,4 +1,4 @@ -const globalMousePos = Variable([]); +const globalMousePos = Variable([0, 0]); globalThis["globalMousePos"] = globalMousePos; diff --git a/lib/types/defaults/weather.ts b/lib/types/defaults/weather.ts new file mode 100644 index 0000000..4e9d54f --- /dev/null +++ b/lib/types/defaults/weather.ts @@ -0,0 +1,1053 @@ +export const DEFAULT_WEATHER = { + "location": { + "name": "Tahiti", + "region": "Somewhere", + "country": "United States of America", + "lat": 0, + "lon": 0, + "tz_id": "Tahiti", + "localtime_epoch": 1721981457, + "localtime": "2024-07-26 1:10" + }, + "current": { + "last_updated_epoch": 1721980800, + "last_updated": "2024-07-26 01:00", + "temp_c": 30.7, + "temp_f": 87.3, + "is_day": 0, + "condition": { + "text": "Clear", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.2, + "wind_kph": 21.2, + "wind_degree": 311, + "wind_dir": "NW", + "pressure_mb": 1010.0, + "pressure_in": 29.84, + "precip_mm": 0.0, + "precip_in": 0.0, + "humidity": 17, + "cloud": 5, + "feelslike_c": 28.6, + "feelslike_f": 83.5, + "windchill_c": 30.7, + "windchill_f": 87.3, + "heatindex_c": 28.6, + "heatindex_f": 83.5, + "dewpoint_c": 2.9, + "dewpoint_f": 37.2, + "vis_km": 10.0, + "vis_miles": 6.0, + "uv": 1.0, + "gust_mph": 18.9, + "gust_kph": 30.4 + }, + "forecast": { + "forecastday": [ + { + "date": "2024-07-26", + "date_epoch": 1721952000, + "day": { + "maxtemp_c": 42.7, + "maxtemp_f": 108.9, + "mintemp_c": 26.3, + "mintemp_f": 79.3, + "avgtemp_c": 33.1, + "avgtemp_f": 91.5, + "maxwind_mph": 22.1, + "maxwind_kph": 35.6, + "totalprecip_mm": 0.0, + "totalprecip_in": 0.0, + "totalsnow_cm": 0.0, + "avgvis_km": 10.0, + "avgvis_miles": 6.0, + "avghumidity": 15, + "daily_will_it_rain": 0, + "daily_chance_of_rain": 0, + "daily_will_it_snow": 0, + "daily_chance_of_snow": 0, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "uv": 11.0 + }, + "astro": { + "sunrise": "06:01 AM", + "sunset": "08:10 PM", + "moonrise": "11:32 PM", + "moonset": "12:01 PM", + "moon_phase": "Waning Gibbous", + "moon_illumination": 74, + "is_moon_up": 0, + "is_sun_up": 1 + }, + "hour": [ + { + "time_epoch": 1721977200, + "time": "2024-07-26 00:00", + "temp_c": 32.2, + "temp_f": 89.9, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 14.1, + "wind_kph": 22.7, + "wind_degree": 313, + "wind_dir": "NW", + "pressure_mb": 1011.0, + "pressure_in": 29.84, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 15, + "cloud": 5, + "feelslike_c": 30.1, + "feelslike_f": 86.2, + "windchill_c": 32.2, + "windchill_f": 89.9, + "heatindex_c": 30.1, + "heatindex_f": 86.2, + "dewpoint_c": 2.3, + "dewpoint_f": 36.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 20.4, + "gust_kph": 32.9, + "uv": 0 + }, + { + "time_epoch": 1721980800, + "time": "2024-07-26 01:00", + "temp_c": 30.7, + "temp_f": 87.3, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.2, + "wind_kph": 21.2, + "wind_degree": 311, + "wind_dir": "NW", + "pressure_mb": 1010.0, + "pressure_in": 29.84, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 17, + "cloud": 5, + "feelslike_c": 28.6, + "feelslike_f": 83.5, + "windchill_c": 30.7, + "windchill_f": 87.3, + "heatindex_c": 28.6, + "heatindex_f": 83.5, + "dewpoint_c": 2.9, + "dewpoint_f": 37.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 18.9, + "gust_kph": 30.4, + "uv": 0 + }, + { + "time_epoch": 1721984400, + "time": "2024-07-26 02:00", + "temp_c": 29.5, + "temp_f": 85.1, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.2, + "wind_kph": 21.2, + "wind_degree": 311, + "wind_dir": "NW", + "pressure_mb": 1010.0, + "pressure_in": 29.83, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 19, + "cloud": 4, + "feelslike_c": 27.4, + "feelslike_f": 81.4, + "windchill_c": 29.5, + "windchill_f": 85.1, + "heatindex_c": 27.4, + "heatindex_f": 81.4, + "dewpoint_c": 3.1, + "dewpoint_f": 37.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 19.0, + "gust_kph": 30.6, + "uv": 0 + }, + { + "time_epoch": 1721988000, + "time": "2024-07-26 03:00", + "temp_c": 28.6, + "temp_f": 83.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 11.2, + "wind_kph": 18.0, + "wind_degree": 318, + "wind_dir": "NW", + "pressure_mb": 1010.0, + "pressure_in": 29.82, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 20, + "cloud": 4, + "feelslike_c": 26.6, + "feelslike_f": 79.9, + "windchill_c": 28.6, + "windchill_f": 83.5, + "heatindex_c": 26.6, + "heatindex_f": 79.9, + "dewpoint_c": 3.1, + "dewpoint_f": 37.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 16.4, + "gust_kph": 26.4, + "uv": 0 + }, + { + "time_epoch": 1721991600, + "time": "2024-07-26 04:00", + "temp_c": 27.9, + "temp_f": 82.2, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 8.7, + "wind_kph": 14.0, + "wind_degree": 319, + "wind_dir": "NW", + "pressure_mb": 1010.0, + "pressure_in": 29.83, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 20, + "cloud": 3, + "feelslike_c": 26.0, + "feelslike_f": 78.8, + "windchill_c": 27.9, + "windchill_f": 82.2, + "heatindex_c": 26.0, + "heatindex_f": 78.8, + "dewpoint_c": 2.9, + "dewpoint_f": 37.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 12.7, + "gust_kph": 20.5, + "uv": 0 + }, + { + "time_epoch": 1721995200, + "time": "2024-07-26 05:00", + "temp_c": 27.3, + "temp_f": 81.1, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 7.6, + "wind_kph": 12.2, + "wind_degree": 314, + "wind_dir": "NW", + "pressure_mb": 1010.0, + "pressure_in": 29.84, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 20, + "cloud": 4, + "feelslike_c": 25.5, + "feelslike_f": 77.9, + "windchill_c": 27.3, + "windchill_f": 81.1, + "heatindex_c": 25.5, + "heatindex_f": 77.9, + "dewpoint_c": 2.6, + "dewpoint_f": 36.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 11.6, + "gust_kph": 18.7, + "uv": 0 + }, + { + "time_epoch": 1721998800, + "time": "2024-07-26 06:00", + "temp_c": 27.3, + "temp_f": 81.1, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 5.8, + "wind_kph": 9.4, + "wind_degree": 314, + "wind_dir": "NW", + "pressure_mb": 1011.0, + "pressure_in": 29.85, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 21, + "cloud": 6, + "feelslike_c": 25.5, + "feelslike_f": 77.8, + "windchill_c": 27.3, + "windchill_f": 81.1, + "heatindex_c": 25.5, + "heatindex_f": 77.8, + "dewpoint_c": 2.4, + "dewpoint_f": 36.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 9.1, + "gust_kph": 14.7, + "uv": 0 + }, + { + "time_epoch": 1722002400, + "time": "2024-07-26 07:00", + "temp_c": 28.1, + "temp_f": 82.7, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 2.9, + "wind_kph": 4.7, + "wind_degree": 310, + "wind_dir": "NW", + "pressure_mb": 1011.0, + "pressure_in": 29.86, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 20, + "cloud": 8, + "feelslike_c": 26.2, + "feelslike_f": 79.1, + "windchill_c": 28.1, + "windchill_f": 82.7, + "heatindex_c": 26.2, + "heatindex_f": 79.1, + "dewpoint_c": 2.3, + "dewpoint_f": 36.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 4.1, + "gust_kph": 6.6, + "uv": 7.0 + }, + { + "time_epoch": 1722006000, + "time": "2024-07-26 08:00", + "temp_c": 29.5, + "temp_f": 85.2, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 2.0, + "wind_kph": 3.2, + "wind_degree": 273, + "wind_dir": "W", + "pressure_mb": 1012.0, + "pressure_in": 29.87, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 18, + "cloud": 8, + "feelslike_c": 27.4, + "feelslike_f": 81.4, + "windchill_c": 29.5, + "windchill_f": 85.2, + "heatindex_c": 27.4, + "heatindex_f": 81.4, + "dewpoint_c": 2.4, + "dewpoint_f": 36.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 2.3, + "gust_kph": 3.7, + "uv": 7.0 + }, + { + "time_epoch": 1722009600, + "time": "2024-07-26 09:00", + "temp_c": 31.3, + "temp_f": 88.4, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 2.7, + "wind_kph": 4.3, + "wind_degree": 267, + "wind_dir": "W", + "pressure_mb": 1011.0, + "pressure_in": 29.86, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 16, + "cloud": 7, + "feelslike_c": 29.2, + "feelslike_f": 84.6, + "windchill_c": 31.3, + "windchill_f": 88.4, + "heatindex_c": 29.2, + "heatindex_f": 84.6, + "dewpoint_c": 2.2, + "dewpoint_f": 36.0, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 3.1, + "gust_kph": 5.0, + "uv": 8.0 + }, + { + "time_epoch": 1722013200, + "time": "2024-07-26 10:00", + "temp_c": 33.3, + "temp_f": 91.9, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 4.3, + "wind_kph": 6.8, + "wind_degree": 287, + "wind_dir": "WNW", + "pressure_mb": 1011.0, + "pressure_in": 29.84, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 14, + "cloud": 7, + "feelslike_c": 31.4, + "feelslike_f": 88.5, + "windchill_c": 33.3, + "windchill_f": 91.9, + "heatindex_c": 31.4, + "heatindex_f": 88.5, + "dewpoint_c": 2.0, + "dewpoint_f": 35.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 4.9, + "gust_kph": 7.9, + "uv": 8.0 + }, + { + "time_epoch": 1722016800, + "time": "2024-07-26 11:00", + "temp_c": 35.4, + "temp_f": 95.7, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 5.8, + "wind_kph": 9.4, + "wind_degree": 304, + "wind_dir": "NW", + "pressure_mb": 1010.0, + "pressure_in": 29.83, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 12, + "cloud": 6, + "feelslike_c": 33.8, + "feelslike_f": 92.9, + "windchill_c": 35.4, + "windchill_f": 95.7, + "heatindex_c": 33.8, + "heatindex_f": 92.9, + "dewpoint_c": 1.3, + "dewpoint_f": 34.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 6.7, + "gust_kph": 10.8, + "uv": 9.0 + }, + { + "time_epoch": 1722020400, + "time": "2024-07-26 12:00", + "temp_c": 37.4, + "temp_f": 99.4, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 6.0, + "wind_kph": 9.7, + "wind_degree": 309, + "wind_dir": "NW", + "pressure_mb": 1009.0, + "pressure_in": 29.81, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 10, + "cloud": 6, + "feelslike_c": 36.5, + "feelslike_f": 97.7, + "windchill_c": 37.4, + "windchill_f": 99.4, + "heatindex_c": 36.5, + "heatindex_f": 97.7, + "dewpoint_c": 0.4, + "dewpoint_f": 32.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 7.0, + "gust_kph": 11.2, + "uv": 9.0 + }, + { + "time_epoch": 1722024000, + "time": "2024-07-26 13:00", + "temp_c": 39.3, + "temp_f": 102.8, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 4.5, + "wind_kph": 7.2, + "wind_degree": 303, + "wind_dir": "WNW", + "pressure_mb": 1008.0, + "pressure_in": 29.78, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 9, + "cloud": 5, + "feelslike_c": 39.1, + "feelslike_f": 102.5, + "windchill_c": 39.3, + "windchill_f": 102.8, + "heatindex_c": 39.1, + "heatindex_f": 102.5, + "dewpoint_c": 0.1, + "dewpoint_f": 32.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 5.1, + "gust_kph": 8.3, + "uv": 9.0 + }, + { + "time_epoch": 1722027600, + "time": "2024-07-26 14:00", + "temp_c": 40.8, + "temp_f": 105.5, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 2.9, + "wind_kph": 4.7, + "wind_degree": 284, + "wind_dir": "WNW", + "pressure_mb": 1007.0, + "pressure_in": 29.75, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 8, + "cloud": 4, + "feelslike_c": 41.3, + "feelslike_f": 106.4, + "windchill_c": 40.8, + "windchill_f": 105.5, + "heatindex_c": 41.3, + "heatindex_f": 106.4, + "dewpoint_c": 1.0, + "dewpoint_f": 33.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 3.4, + "gust_kph": 5.4, + "uv": 10.0 + }, + { + "time_epoch": 1722031200, + "time": "2024-07-26 15:00", + "temp_c": 41.8, + "temp_f": 107.2, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 1.6, + "wind_kph": 2.5, + "wind_degree": 229, + "wind_dir": "SW", + "pressure_mb": 1006.0, + "pressure_in": 29.71, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 9, + "cloud": 1, + "feelslike_c": 42.7, + "feelslike_f": 108.9, + "windchill_c": 41.8, + "windchill_f": 107.2, + "heatindex_c": 42.7, + "heatindex_f": 108.9, + "dewpoint_c": 2.5, + "dewpoint_f": 36.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 1.8, + "gust_kph": 2.9, + "uv": 10.0 + }, + { + "time_epoch": 1722034800, + "time": "2024-07-26 16:00", + "temp_c": 42.2, + "temp_f": 108.0, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 2.5, + "wind_kph": 4.0, + "wind_degree": 119, + "wind_dir": "ESE", + "pressure_mb": 1005.0, + "pressure_in": 29.68, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 9, + "cloud": 0, + "feelslike_c": 43.4, + "feelslike_f": 110.1, + "windchill_c": 42.2, + "windchill_f": 108.0, + "heatindex_c": 43.4, + "heatindex_f": 110.1, + "dewpoint_c": 3.4, + "dewpoint_f": 38.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 2.8, + "gust_kph": 4.6, + "uv": 10.0 + }, + { + "time_epoch": 1722038400, + "time": "2024-07-26 17:00", + "temp_c": 40.4, + "temp_f": 104.7, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 7.4, + "wind_kph": 11.9, + "wind_degree": 215, + "wind_dir": "SW", + "pressure_mb": 1004.0, + "pressure_in": 29.65, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 8, + "cloud": 0, + "feelslike_c": 40.6, + "feelslike_f": 105.2, + "windchill_c": 40.4, + "windchill_f": 104.7, + "heatindex_c": 40.6, + "heatindex_f": 105.2, + "dewpoint_c": 1.6, + "dewpoint_f": 35.0, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 8.5, + "gust_kph": 13.7, + "uv": 10.0 + }, + { + "time_epoch": 1722042000, + "time": "2024-07-26 18:00", + "temp_c": 38.0, + "temp_f": 100.4, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 22.1, + "wind_kph": 35.6, + "wind_degree": 306, + "wind_dir": "NW", + "pressure_mb": 1005.0, + "pressure_in": 29.67, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 10, + "cloud": 0, + "feelslike_c": 37.3, + "feelslike_f": 99.1, + "windchill_c": 38.0, + "windchill_f": 100.4, + "heatindex_c": 37.3, + "heatindex_f": 99.1, + "dewpoint_c": 1.0, + "dewpoint_f": 33.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 25.5, + "gust_kph": 41.0, + "uv": 9.0 + }, + { + "time_epoch": 1722045600, + "time": "2024-07-26 19:00", + "temp_c": 35.1, + "temp_f": 95.2, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 18.8, + "wind_kph": 30.2, + "wind_degree": 312, + "wind_dir": "NW", + "pressure_mb": 1006.0, + "pressure_in": 29.69, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 11, + "cloud": 0, + "feelslike_c": 33.7, + "feelslike_f": 92.6, + "windchill_c": 35.1, + "windchill_f": 95.3, + "heatindex_c": 33.7, + "heatindex_f": 92.6, + "dewpoint_c": 1.0, + "dewpoint_f": 33.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 21.6, + "gust_kph": 34.8, + "uv": 9.0 + }, + { + "time_epoch": 1722049200, + "time": "2024-07-26 20:00", + "temp_c": 32.6, + "temp_f": 90.7, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 17.2, + "wind_kph": 27.7, + "wind_degree": 326, + "wind_dir": "NNW", + "pressure_mb": 1007.0, + "pressure_in": 29.73, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 13, + "cloud": 0, + "feelslike_c": 30.8, + "feelslike_f": 87.4, + "windchill_c": 32.6, + "windchill_f": 90.7, + "heatindex_c": 30.8, + "heatindex_f": 87.4, + "dewpoint_c": 0.3, + "dewpoint_f": 32.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 20.4, + "gust_kph": 32.8, + "uv": 8.0 + }, + { + "time_epoch": 1722052800, + "time": "2024-07-26 21:00", + "temp_c": 30.2, + "temp_f": 86.3, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 15.7, + "wind_kph": 25.2, + "wind_degree": 326, + "wind_dir": "NNW", + "pressure_mb": 1008.0, + "pressure_in": 29.76, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 16, + "cloud": 0, + "feelslike_c": 28.3, + "feelslike_f": 82.9, + "windchill_c": 30.2, + "windchill_f": 86.3, + "heatindex_c": 28.3, + "heatindex_f": 82.9, + "dewpoint_c": 1.6, + "dewpoint_f": 35.0, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 20.1, + "gust_kph": 32.4, + "uv": 0 + }, + { + "time_epoch": 1722056400, + "time": "2024-07-26 22:00", + "temp_c": 28.2, + "temp_f": 82.8, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 14.8, + "wind_kph": 23.8, + "wind_degree": 329, + "wind_dir": "NNW", + "pressure_mb": 1009.0, + "pressure_in": 29.79, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 20, + "cloud": 0, + "feelslike_c": 26.7, + "feelslike_f": 80.0, + "windchill_c": 28.2, + "windchill_f": 82.8, + "heatindex_c": 26.7, + "heatindex_f": 80.0, + "dewpoint_c": 2.6, + "dewpoint_f": 36.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 20.1, + "gust_kph": 32.3, + "uv": 0 + }, + { + "time_epoch": 1722060000, + "time": "2024-07-26 23:00", + "temp_c": 26.8, + "temp_f": 80.2, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.0, + "wind_kph": 20.9, + "wind_degree": 323, + "wind_dir": "NW", + "pressure_mb": 1009.0, + "pressure_in": 29.8, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 24, + "cloud": 0, + "feelslike_c": 25.6, + "feelslike_f": 78.1, + "windchill_c": 26.8, + "windchill_f": 80.2, + "heatindex_c": 25.6, + "heatindex_f": 78.1, + "dewpoint_c": 4.2, + "dewpoint_f": 39.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 18.2, + "gust_kph": 29.3, + "uv": 0 + } + ] + } + ] + } +} diff --git a/lib/types/gpustat.d.ts b/lib/types/gpustat.d.ts new file mode 100644 index 0000000..ede3378 --- /dev/null +++ b/lib/types/gpustat.d.ts @@ -0,0 +1,25 @@ +export type GPU_Stat_Process = { + username: string; + command: string; + full_command: string[]; + gpu_memory_usage: number; + cpu_percent: number; + cpu_memory_usage: number; + pid: number; +}; + +export type GPU_Stat = { + index: number; + uuid: string; + name: string; + "temperature.gpu": number; + "fan.speed": number; + "utilization.gpu": number; + "utilization.enc": number; + "utilization.dec": number; + "power.draw": number; + "enforced.power.limit": number; + "memory.used": number; + "memory.total": number; + processes: Process[]; +}; diff --git a/lib/types/network.d.ts b/lib/types/network.d.ts new file mode 100644 index 0000000..63c8804 --- /dev/null +++ b/lib/types/network.d.ts @@ -0,0 +1,10 @@ +export type AccessPoint = { + bssid: string | null; + address: string | null; + lastSeen: number; + ssid: string | null; + active: boolean; + strength: number; + frequency: number; + iconName: string | undefined; +} diff --git a/lib/types/options.d.ts b/lib/types/options.d.ts new file mode 100644 index 0000000..3f340bf --- /dev/null +++ b/lib/types/options.d.ts @@ -0,0 +1,3 @@ +export type Unit = "imperial" | "metric"; +export type PowerOptions = "sleep" | "reboot" | "logout" | "shutdown"; +export type NotificationAnchor = "top" | "top right" | "top left" | "bottom" | "bottom right" | "bottom left"; diff --git a/lib/types/power.d.ts b/lib/types/power.d.ts new file mode 100644 index 0000000..5a44633 --- /dev/null +++ b/lib/types/power.d.ts @@ -0,0 +1 @@ +export type Action = "sleep" | "reboot" | "logout" | "shutdown"; diff --git a/lib/types/weather.d.ts b/lib/types/weather.d.ts new file mode 100644 index 0000000..44eaa0d --- /dev/null +++ b/lib/types/weather.d.ts @@ -0,0 +1,107 @@ +export type Weather = { + location: Location; + current: Current; + forecast: Forecast; +} + +export type Current = { + last_updated_epoch?: number; + last_updated?: string; + temp_c: number; + temp_f: number; + is_day: number; + condition: Condition; + wind_mph: number; + wind_kph: number; + wind_degree: number; + wind_dir: string; + pressure_mb: number; + pressure_in: number; + precip_mm: number; + precip_in: number; + humidity: number; + cloud: number; + feelslike_c: number; + feelslike_f: number; + windchill_c: number; + windchill_f: number; + heatindex_c: number; + heatindex_f: number; + dewpoint_c: number; + dewpoint_f: number; + vis_km: number; + vis_miles: number; + uv: number; + gust_mph: number; + gust_kph: number; + time_epoch?: number; + time?: string; + snow_cm?: number; + will_it_rain?: number; + chance_of_rain?: number; + will_it_snow?: number; + chance_of_snow?: number; +} + +export type Condition = { + text: string; + icon: string; + code: number; +} + +export type Forecast = { + forecastday: Forecastday[]; +} + +export type Forecastday = { + date: string; + date_epoch: number; + day: Day; + astro: Astro; + hour: Current[]; +} + +export type Astro = { + sunrise: string; + sunset: string; + moonrise: string; + moonset: string; + moon_phase: string; + moon_illumination: number; + is_moon_up: number; + is_sun_up: number; +} + +export type Day = { + maxtemp_c: number; + maxtemp_f: number; + mintemp_c: number; + mintemp_f: number; + avgtemp_c: number; + avgtemp_f: number; + maxwind_mph: number; + maxwind_kph: number; + totalprecip_mm: number; + totalprecip_in: number; + totalsnow_cm: number; + avgvis_km: number; + avgvis_miles: number; + avghumidity: number; + daily_will_it_rain: number; + daily_chance_of_rain: number; + daily_will_it_snow: number; + daily_chance_of_snow: number; + condition: Condition; + uv: number; +} + +export type Location = { + name: string; + region: string; + country: string; + lat: number; + lon: number; + tz_id: string; + localtime_epoch: number; + localtime: string; +} diff --git a/lib/types/widget.d.ts b/lib/types/widget.d.ts new file mode 100644 index 0000000..7fcda61 --- /dev/null +++ b/lib/types/widget.d.ts @@ -0,0 +1,3 @@ +export type Exclusivity = 'normal' | 'ignore' | 'exclusive'; +export type Anchor = "left" | "right" | "top" | "down"; +export type Transition = "none" | "crossfade" | "slide_right" | "slide_left" | "slide_up" | "slide_down"; diff --git a/lib/types/workspace.d.ts b/lib/types/workspace.d.ts new file mode 100644 index 0000000..1117291 --- /dev/null +++ b/lib/types/workspace.d.ts @@ -0,0 +1,8 @@ +export type WorkspaceRule = { + workspaceString: string, + monitor: string, +} + +export type WorkspaceMap = { + [key: string]: number[], +} diff --git a/modules/bar/battery/index.js b/modules/bar/battery/index.js deleted file mode 100644 index c028929..0000000 --- a/modules/bar/battery/index.js +++ /dev/null @@ -1,82 +0,0 @@ -const battery = await Service.import("battery"); -import { openMenu } from "../utils.js"; -import options from "options"; - -const { label: show_label } = options.bar.battery; - -const BatteryLabel = () => { - const isVis = Variable(battery.available); - - const icon = () => - battery - .bind("percent") - .as((p) => `battery-level-${Math.floor(p / 10) * 10}-symbolic`); - - battery.connect("changed", ({ available }) => { - isVis.value = available; - }); - - const formatTime = (seconds) => { - const hours = Math.floor(seconds / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - return { hours, minutes }; - }; - - const generateTooltip = (timeSeconds, isCharging, isCharged) => { - if (isCharged) { - return "Fully Charged!!!"; - } - - const { hours, minutes } = formatTime(timeSeconds); - if (isCharging) { - return `${hours} hours ${minutes} minutes until full`; - } else { - return `${hours} hours ${minutes} minutes left`; - } - }; - - return { - component: Widget.Box({ - class_name: "battery", - visible: battery.bind("available"), - tooltip_text: battery.bind("time_remaining").as((t) => t.toString()), - children: Utils.merge( - [battery.bind("available"), show_label.bind("value")], - (batAvail, showLabel) => { - if (batAvail && showLabel) { - return [ - Widget.Icon({ icon: icon() }), - Widget.Label({ - label: battery.bind("percent").as((p) => ` ${p}%`), - }), - ]; - } else if (batAvail && !showLabel) { - return [Widget.Icon({ icon: icon() })]; - } else { - return []; - } - }, - ), - setup: (self) => { - self.hook(battery, () => { - if (battery.available) { - self.tooltip_text = generateTooltip( - battery.time_remaining, - battery.charging, - battery.charged, - ); - } - }); - }, - }), - isVis, - boxClass: "battery", - props: { - on_primary_click: (clicked, event) => { - openMenu(clicked, event, "energymenu"); - }, - }, - }; -}; - -export { BatteryLabel }; diff --git a/modules/bar/battery/index.ts b/modules/bar/battery/index.ts new file mode 100644 index 0000000..977a2a8 --- /dev/null +++ b/modules/bar/battery/index.ts @@ -0,0 +1,84 @@ +const battery = await Service.import("battery"); +import Gdk from 'gi://Gdk?version=3.0'; +import EventHandler from 'types/widgets/button.ts' +import { openMenu } from "../utils.js"; +import options from "options"; + +const { label: show_label } = options.bar.battery; + +const BatteryLabel = () => { + const isVis = Variable(battery.available); + + const icon = () => + battery + .bind("percent") + .as((p) => `battery-level-${Math.floor(p / 10) * 10}-symbolic`); + + battery.connect("changed", ({ available }) => { + isVis.value = available; + }); + + const formatTime = (seconds: number) => { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + return { hours, minutes }; + }; + + const generateTooltip = (timeSeconds: number, isCharging: boolean, isCharged: boolean) => { + if (isCharged) { + return "Fully Charged!!!"; + } + + const { hours, minutes } = formatTime(timeSeconds); + if (isCharging) { + return `${hours} hours ${minutes} minutes until full`; + } else { + return `${hours} hours ${minutes} minutes left`; + } + }; + + return { + component: Widget.Box({ + class_name: "battery", + visible: battery.bind("available"), + tooltip_text: battery.bind("time_remaining").as((t) => t.toString()), + children: Utils.merge( + [battery.bind("available"), show_label.bind("value")], + (batAvail, showLabel) => { + if (batAvail && showLabel) { + return [ + Widget.Icon({ icon: icon() }), + Widget.Label({ + label: battery.bind("percent").as((p) => ` ${p}%`), + }), + ]; + } else if (batAvail && !showLabel) { + return [Widget.Icon({ icon: icon() })]; + } else { + return []; + } + }, + ), + setup: (self) => { + self.hook(battery, () => { + if (battery.available) { + self.tooltip_text = generateTooltip( + battery.time_remaining, + battery.charging, + battery.charged, + ); + } + }); + }, + }), + isVis, + boxClass: "battery", + props: { + on_primary_click: (clicked: any, event: Gdk.Event) => { + openMenu(clicked, event, "energymenu"); + }, + }, + }; +}; + +export { BatteryLabel }; diff --git a/modules/bar/bluetooth/index.js b/modules/bar/bluetooth/index.js deleted file mode 100644 index f7747a0..0000000 --- a/modules/bar/bluetooth/index.js +++ /dev/null @@ -1,38 +0,0 @@ -const bluetooth = await Service.import('bluetooth') -import options from "options"; -import { openMenu } from "../utils.js"; - -const Bluetooth = () => { - const btIcon = Widget.Label({ - label: bluetooth.bind("enabled").as((v) => v ? "󰂯" : "󰂲"), - class_name: "bar-bt_icon", - }); - - const btText = Widget.Label({ - label: Utils.merge([bluetooth.bind("enabled"), options.bar.bluetooth.label.bind("value")], (btEnabled, showLabel) => { - if (showLabel) { - return btEnabled ? " On" : " Off" - } - return ""; - - }), - class_name: "bar-bt_label", - }); - - return { - component: Widget.Box({ - class_name: "volume", - children: [btIcon, btText], - }), - isVisible: true, - boxClass: "bluetooth", - props: { - on_primary_click: (clicked, event) => { - openMenu(clicked, event, "bluetoothmenu"); - }, - }, - }; - -} - -export { Bluetooth } diff --git a/modules/bar/bluetooth/index.ts b/modules/bar/bluetooth/index.ts new file mode 100644 index 0000000..402af23 --- /dev/null +++ b/modules/bar/bluetooth/index.ts @@ -0,0 +1,39 @@ +const bluetooth = await Service.import('bluetooth') +import Gdk from 'gi://Gdk?version=3.0'; +import options from "options"; +import { openMenu } from "../utils.js"; + +const Bluetooth = () => { + const btIcon = Widget.Label({ + label: bluetooth.bind("enabled").as((v) => v ? "󰂯" : "󰂲"), + class_name: "bar-bt_icon", + }); + + const btText = Widget.Label({ + label: Utils.merge([bluetooth.bind("enabled"), options.bar.bluetooth.label.bind("value")], (btEnabled, showLabel) => { + if (showLabel) { + return btEnabled ? " On" : " Off" + } + return ""; + + }), + class_name: "bar-bt_label", + }); + + return { + component: Widget.Box({ + class_name: "volume", + children: [btIcon, btText], + }), + isVisible: true, + boxClass: "bluetooth", + props: { + on_primary_click: (clicked: any, event: Gdk.Event) => { + openMenu(clicked, event, "bluetoothmenu"); + }, + }, + }; + +} + +export { Bluetooth } diff --git a/modules/bar/clock/index.js b/modules/bar/clock/index.js deleted file mode 100644 index 27823a8..0000000 --- a/modules/bar/clock/index.js +++ /dev/null @@ -1,27 +0,0 @@ -import GLib from "gi://GLib"; -import { openMenu } from "../utils.js"; -import options from "options"; -const { format } = options.bar.clock; - -const date = Variable(GLib.DateTime.new_now_local(), { - poll: [1000, () => GLib.DateTime.new_now_local()], -}); -const time = Utils.derive([date, format], (c, f) => c.format(f) || ""); - -const Clock = () => { - return { - component: Widget.Label({ - class_name: "clock", - label: time.bind(), - }), - isVisible: true, - boxClass: "clock", - props: { - on_primary_click: (clicked, event) => { - openMenu(clicked, event, "calendarmenu"); - }, - }, - }; -}; - -export { Clock }; diff --git a/modules/bar/clock/index.ts b/modules/bar/clock/index.ts new file mode 100644 index 0000000..dcab26d --- /dev/null +++ b/modules/bar/clock/index.ts @@ -0,0 +1,28 @@ +import Gdk from 'gi://Gdk?version=3.0'; +import GLib from "gi://GLib"; +import { openMenu } from "../utils.js"; +import options from "options"; +const { format } = options.bar.clock; + +const date = Variable(GLib.DateTime.new_now_local(), { + poll: [1000, () => GLib.DateTime.new_now_local()], +}); +const time = Utils.derive([date, format], (c, f) => c.format(f) || ""); + +const Clock = () => { + return { + component: Widget.Label({ + class_name: "clock", + label: time.bind(), + }), + isVisible: true, + boxClass: "clock", + props: { + on_primary_click: (clicked: any, event: Gdk.Event) => { + openMenu(clicked, event, "calendarmenu"); + }, + }, + }; +}; + +export { Clock }; diff --git a/modules/bar/media/index.js b/modules/bar/media/index.js deleted file mode 100644 index cfe571a..0000000 --- a/modules/bar/media/index.js +++ /dev/null @@ -1,101 +0,0 @@ -const mpris = await Service.import("mpris"); -import { openMenu } from "../utils.js"; - -const Media = () => { - const activePlayer = Variable(mpris.players[0]); - - mpris.connect("changed", (value) => { - const statusOrder = { - Playing: 1, - Paused: 2, - Stopped: 3, - }; - - if (value.players.length === 0) { - activePlayer.value = mpris.players[0]; - return; - } - - const isPlaying = value.players.find( - (p) => p["play-back-status"] === "Playing", - ); - - if (isPlaying) { - activePlayer.value = value.players.sort( - (a, b) => - statusOrder[a["play-back-status"]] - - statusOrder[b["play-back-status"]], - )[0]; - } - }); - - const getIconForPlayer = (playerName) => { - const windowTitleMap = [ - ["Mozilla Firefox", "󰈹 "], - ["Microsoft Edge", "󰇩 "], - ["(.*)Discord(.*)", " "], - ["Plex", "󰚺 "], - ["(.*) Spotify Free", "󰓇 "], - ["(.*)Spotify Premium", "󰓇 "], - ["Spotify", "󰓇 "], - ["(.*)", "󰝚 "], - ]; - - const foundMatch = windowTitleMap.find((wt) => - RegExp(wt[0]).test(playerName), - ); - - return foundMatch ? foundMatch[1] : "󰝚"; - }; - - const songIcon = Variable(""); - - const label = Utils.watch("󰎇 Media 󰎇", mpris, "changed", () => { - if (activePlayer.value) { - const { track_title, identity } = activePlayer.value; - songIcon.value = getIconForPlayer(identity); - return track_title.length === 0 - ? ` No media playing...` - : ` ${track_title}`; - } else { - songIcon.value = ""; - return "󰎇 Media 󰎇"; - } - }); - - return { - component: Widget.Box({ - visible: false, - child: Widget.Box({ - class_name: "media", - child: Widget.Box({ - children: [ - Widget.Label({ - class_name: "bar-media_icon", - label: songIcon.bind("value"), - maxWidthChars: 30, - }), - Widget.Label({ - label, - truncate: "end", - wrap: true, - maxWidthChars: 30, - }), - ], - }), - }), - }), - isVisible: false, - boxClass: "media", - name: "media", - props: { - on_scroll_up: () => mpris.getPlayer("")?.next(), - on_scroll_down: () => mpris.getPlayer("")?.previous(), - on_primary_click: (clicked, event) => { - openMenu(clicked, event, "mediamenu"); - }, - }, - }; -}; - -export { Media }; diff --git a/modules/bar/media/index.ts b/modules/bar/media/index.ts new file mode 100644 index 0000000..7b48255 --- /dev/null +++ b/modules/bar/media/index.ts @@ -0,0 +1,102 @@ +import Gdk from 'gi://Gdk?version=3.0'; +const mpris = await Service.import("mpris"); +import { openMenu } from "../utils.js"; + +const Media = () => { + const activePlayer = Variable(mpris.players[0]); + + mpris.connect("changed", (value) => { + const statusOrder = { + Playing: 1, + Paused: 2, + Stopped: 3, + }; + + if (value.players.length === 0) { + activePlayer.value = mpris.players[0]; + return; + } + + const isPlaying = value.players.find( + (p) => p["play-back-status"] === "Playing", + ); + + if (isPlaying) { + activePlayer.value = value.players.sort( + (a, b) => + statusOrder[a["play-back-status"]] - + statusOrder[b["play-back-status"]], + )[0]; + } + }); + + const getIconForPlayer = (playerName: string) => { + const windowTitleMap = [ + ["Mozilla Firefox", "󰈹 "], + ["Microsoft Edge", "󰇩 "], + ["(.*)Discord(.*)", " "], + ["Plex", "󰚺 "], + ["(.*) Spotify Free", "󰓇 "], + ["(.*)Spotify Premium", "󰓇 "], + ["Spotify", "󰓇 "], + ["(.*)", "󰝚 "], + ]; + + const foundMatch = windowTitleMap.find((wt) => + RegExp(wt[0]).test(playerName), + ); + + return foundMatch ? foundMatch[1] : "󰝚"; + }; + + const songIcon = Variable(""); + + const label = Utils.watch("󰎇 Media 󰎇", mpris, "changed", () => { + if (activePlayer.value) { + const { track_title, identity } = activePlayer.value; + songIcon.value = getIconForPlayer(identity); + return track_title.length === 0 + ? ` No media playing...` + : ` ${track_title}`; + } else { + songIcon.value = ""; + return "󰎇 Media 󰎇"; + } + }); + + return { + component: Widget.Box({ + visible: false, + child: Widget.Box({ + class_name: "media", + child: Widget.Box({ + children: [ + Widget.Label({ + class_name: "bar-media_icon", + label: songIcon.bind("value"), + maxWidthChars: 30, + }), + Widget.Label({ + label, + truncate: "end", + wrap: true, + maxWidthChars: 30, + }), + ], + }), + }), + }), + isVisible: false, + boxClass: "media", + name: "media", + props: { + on_scroll_up: () => mpris.getPlayer("")?.next(), + on_scroll_down: () => mpris.getPlayer("")?.previous(), + on_primary_click: (clicked: any, event: Gdk.Event) => { + openMenu(clicked, event, "mediamenu"); + }, + }, + }; +}; + +export { Media }; diff --git a/modules/bar/menu/index.js b/modules/bar/menu/index.js deleted file mode 100644 index 16a406f..0000000 --- a/modules/bar/menu/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import { openMenu } from "../utils.js"; -import options from "options"; - -const Menu = () => { - return { - component: Widget.Box({ - child: Widget.Label({ - class_name: "bar-menu_label", - label: options.bar.launcher.icon.bind("value"), - }), - }), - isVisible: true, - boxClass: "dashboard", - props: { - on_primary_click: (clicked, event) => { - openMenu(clicked, event, "dashboardmenu"); - }, - }, - }; -}; - -export { Menu }; diff --git a/modules/bar/menu/index.ts b/modules/bar/menu/index.ts new file mode 100644 index 0000000..2e29e17 --- /dev/null +++ b/modules/bar/menu/index.ts @@ -0,0 +1,23 @@ +import Gdk from 'gi://Gdk?version=3.0'; +import { openMenu } from "../utils.js"; +import options from "options"; + +const Menu = () => { + return { + component: Widget.Box({ + child: Widget.Label({ + class_name: "bar-menu_label", + label: options.bar.launcher.icon.bind("value"), + }), + }), + isVisible: true, + boxClass: "dashboard", + props: { + on_primary_click: (clicked: any, event: Gdk.Event) => { + openMenu(clicked, event, "dashboardmenu"); + }, + }, + }; +}; + +export { Menu }; diff --git a/modules/bar/network/index.js b/modules/bar/network/index.ts similarity index 94% rename from modules/bar/network/index.js rename to modules/bar/network/index.ts index 61bb16b..6d02ef7 100644 --- a/modules/bar/network/index.js +++ b/modules/bar/network/index.ts @@ -1,3 +1,4 @@ +import Gdk from 'gi://Gdk?version=3.0'; const network = await Service.import("network"); import options from "options"; import { openMenu } from "../utils.js"; @@ -52,7 +53,7 @@ const Network = () => { isVisible: true, boxClass: "network", props: { - on_primary_click: (clicked, event) => { + on_primary_click: (clicked: any, event: Gdk.Event) => { openMenu(clicked, event, "networkmenu"); }, }, diff --git a/modules/bar/notifications/index.js b/modules/bar/notifications/index.js deleted file mode 100644 index 2b7b997..0000000 --- a/modules/bar/notifications/index.js +++ /dev/null @@ -1,46 +0,0 @@ -import { openMenu } from "../utils.js"; -import options from "options"; - -const { show_total } = options.bar.notifications; - -const notifs = await Service.import("notifications"); - -export const Notifications = () => { - return { - component: Widget.Box({ - hpack: "start", - child: Widget.Box({ - hpack: "start", - class_name: "bar-notifications", - children: Utils.merge( - [notifs.bind("notifications"), notifs.bind("dnd"), show_total.bind("value")], - (notif, dnd, showTotal) => { - const notifIcon = Widget.Label({ - hpack: "center", - class_name: "bar-notifications-label", - label: dnd ? "󰂛" : notif.length > 0 ? "󱅫" : "󰂚", - }); - - const notifLabel = Widget.Label({ - hpack: "center", - class_name: "bar-notifications-total", - label: notif.length.toString(), - }); - - if (showTotal) { - return [notifIcon, notifLabel]; - } - return [notifIcon]; - }, - ), - }), - }), - isVisible: true, - boxClass: "notifications", - props: { - on_primary_click: (clicked, event) => { - openMenu(clicked, event, "notificationsmenu"); - }, - }, - }; -}; diff --git a/modules/bar/notifications/index.ts b/modules/bar/notifications/index.ts new file mode 100644 index 0000000..ec5e1cd --- /dev/null +++ b/modules/bar/notifications/index.ts @@ -0,0 +1,47 @@ +import Gdk from 'gi://Gdk?version=3.0'; +import { openMenu } from "../utils.js"; +import options from "options"; + +const { show_total } = options.bar.notifications; + +const notifs = await Service.import("notifications"); + +export const Notifications = () => { + return { + component: Widget.Box({ + hpack: "start", + child: Widget.Box({ + hpack: "start", + class_name: "bar-notifications", + children: Utils.merge( + [notifs.bind("notifications"), notifs.bind("dnd"), show_total.bind("value")], + (notif, dnd, showTotal) => { + const notifIcon = Widget.Label({ + hpack: "center", + class_name: "bar-notifications-label", + label: dnd ? "󰂛" : notif.length > 0 ? "󱅫" : "󰂚", + }); + + const notifLabel = Widget.Label({ + hpack: "center", + class_name: "bar-notifications-total", + label: notif.length.toString(), + }); + + if (showTotal) { + return [notifIcon, notifLabel]; + } + return [notifIcon]; + }, + ), + }), + }), + isVisible: true, + boxClass: "notifications", + props: { + on_primary_click: (clicked: any, event: Gdk.Event) => { + openMenu(clicked, event, "notificationsmenu"); + }, + }, + }; +}; diff --git a/modules/bar/systray/index.js b/modules/bar/systray/index.js deleted file mode 100644 index eb9dc3c..0000000 --- a/modules/bar/systray/index.js +++ /dev/null @@ -1,47 +0,0 @@ -const systemtray = await Service.import("systemtray"); -import { bash } from "lib/utils"; -import options from "options"; - -const { ignore } = options.bar.systray; - -const SysTray = () => { - const isVis = Variable(false); - - const items = Utils.merge( - [systemtray.bind("items"), ignore.bind("value")], - (items, ignored) => { - const filteredTray = items.filter(({ id }) => !ignored.includes(id)); - - isVis.value = filteredTray.length > 0; - - return filteredTray.map((item) => { - if (item.menu !== undefined) { - item.menu["class_name"] = "systray-menu"; - } - - return Widget.Button({ - cursor: "pointer", - child: Widget.Icon({ - class_name: "systray-icon", - icon: item.bind("icon"), - }), - on_primary_click: (_, event) => item.activate(event), - on_secondary_click: (_, event) => item.openMenu(event), - tooltip_markup: item.bind("tooltip_markup"), - }); - }); - }, - ); - - return { - component: Widget.Box({ - class_name: "systray", - children: items, - }), - isVisible: true, - boxClass: "systray", - isVis, - }; -}; - -export { SysTray }; diff --git a/modules/bar/systray/index.ts b/modules/bar/systray/index.ts new file mode 100644 index 0000000..7b44612 --- /dev/null +++ b/modules/bar/systray/index.ts @@ -0,0 +1,47 @@ +import Gdk from 'gi://Gdk?version=3.0'; +const systemtray = await Service.import("systemtray"); +import options from "options"; + +const { ignore } = options.bar.systray; + +const SysTray = () => { + const isVis = Variable(false); + + const items = Utils.merge( + [systemtray.bind("items"), ignore.bind("value")], + (items, ignored) => { + const filteredTray = items.filter(({ id }) => !ignored.includes(id)); + + isVis.value = filteredTray.length > 0; + + return filteredTray.map((item) => { + if (item.menu !== undefined) { + item.menu["class_name"] = "systray-menu"; + } + + return Widget.Button({ + cursor: "pointer", + child: Widget.Icon({ + class_name: "systray-icon", + icon: item.bind("icon"), + }), + on_primary_click: (_: any, event: Gdk.Event) => item.activate(event), + on_secondary_click: (_, event) => item.openMenu(event), + tooltip_markup: item.bind("tooltip_markup"), + }); + }); + }, + ); + + return { + component: Widget.Box({ + class_name: "systray", + children: items, + }), + isVisible: true, + boxClass: "systray", + isVis, + }; +}; + +export { SysTray }; diff --git a/modules/bar/utils.js b/modules/bar/utils.js deleted file mode 100644 index 6adfdfe..0000000 --- a/modules/bar/utils.js +++ /dev/null @@ -1,47 +0,0 @@ -export const closeAllMenus = () => { - const menuWindows = App.windows - .filter((w) => { - if (w.name) { - return /.*menu/.test(w.name); - } - - return false; - }) - .map((w) => w.name); - - menuWindows.forEach((w) => { - if (w) { - App.closeWindow(w); - } - }); -}; - -export const openMenu = (clicked, event, window) => { - /* - * NOTE: We have to make some adjustments so the menu pops up relatively - * to the center of the button clicked. We don't want the menu to spawn - * offcenter dependending on which edge of the button you click on. - * ------------- - * To fix this, we take the x coordinate of the click within the button's bounds. - * If you click the left edge of a 100 width button, then the x axis will be 0 - * and if you click the right edge then the x axis will be 100. - * ------------- - * Then we divide the width of the button by 2 to get the center of the button and then get - * the offset by subtracting the clicked x coordinate. Then we can apply that offset - * to the x coordinate of the click relative to the screen to get the center of the - * icon click. - */ - - const middleOfButton = Math.floor(clicked.get_allocated_width() / 2); - const xAxisOfButtonClick = clicked.get_pointer()[0]; - const middleOffset = middleOfButton - xAxisOfButtonClick; - - const clickPos = event.get_root_coords(); - const adjustedXCoord = clickPos[1] + middleOffset; - const coords = [adjustedXCoord, clickPos[2]]; - - globalMousePos.value = coords; - - closeAllMenus(); - App.toggleWindow(window); -}; diff --git a/modules/bar/utils.ts b/modules/bar/utils.ts new file mode 100644 index 0000000..d1db2be --- /dev/null +++ b/modules/bar/utils.ts @@ -0,0 +1,51 @@ +import Gdk from 'gi://Gdk?version=3.0'; + +import { globalMousePos } from 'globals'; + +export const closeAllMenus = () => { + const menuWindows = App.windows + .filter((w) => { + if (w.name) { + return /.*menu/.test(w.name); + } + + return false; + }) + .map((w) => w.name); + + menuWindows.forEach((w) => { + if (w) { + App.closeWindow(w); + } + }); +}; + +export const openMenu = (clicked: any, event: Gdk.Event, window: string) => { + /* + * NOTE: We have to make some adjustments so the menu pops up relatively + * to the center of the button clicked. We don't want the menu to spawn + * offcenter dependending on which edge of the button you click on. + * ------------- + * To fix this, we take the x coordinate of the click within the button's bounds. + * If you click the left edge of a 100 width button, then the x axis will be 0 + * and if you click the right edge then the x axis will be 100. + * ------------- + * Then we divide the width of the button by 2 to get the center of the button and then get + * the offset by subtracting the clicked x coordinate. Then we can apply that offset + * to the x coordinate of the click relative to the screen to get the center of the + * icon click. + */ + + const middleOfButton = Math.floor(clicked.get_allocated_width() / 2); + const xAxisOfButtonClick = clicked.get_pointer()[0]; + const middleOffset = middleOfButton - xAxisOfButtonClick; + + const clickPos = event.get_root_coords(); + const adjustedXCoord = clickPos[1] + middleOffset; + const coords = [adjustedXCoord, clickPos[2]]; + + globalMousePos.value = coords; + + closeAllMenus(); + App.toggleWindow(window); +}; diff --git a/modules/bar/volume/index.js b/modules/bar/volume/index.js deleted file mode 100644 index 4985424..0000000 --- a/modules/bar/volume/index.js +++ /dev/null @@ -1,62 +0,0 @@ -const audio = await Service.import("audio"); -import { openMenu } from "../utils.js"; -import options from "options"; - -import { globalMousePos } from "globals.js"; - -const Volume = () => { - const icons = { - 101: "󰕾", - 66: "󰕾", - 34: "󰖀", - 1: "󰕿", - 0: "󰝟", - }; - - const getIcon = () => { - const icon = Utils.merge( - [audio.speaker.bind("is_muted"), audio.speaker.bind("volume")], - (isMuted, vol) => { - return isMuted - ? 0 - : [101, 66, 34, 1, 0].find((threshold) => threshold <= vol * 100); - }, - ); - - return icon.as((i) => icons[i]); - }; - - const volIcn = Widget.Label({ - vpack: "center", - label: getIcon(), - class_name: "bar-volume_icon", - }); - - const volPct = Widget.Label({ - vpack: "center", - label: audio.speaker.bind("volume").as((v) => ` ${Math.floor(v * 100)}%`), - class_name: "bar-volume_percentage", - }); - - return { - component: Widget.Box({ - vpack: "center", - class_name: "volume", - children: options.bar.volume.label.bind("value").as((showLabel) => { - if (showLabel) { - return [volIcn, volPct]; - } - return [volIcn]; - }), - }), - isVisible: true, - boxClass: "volume", - props: { - on_primary_click: (clicked, event) => { - openMenu(clicked, event, "audiomenu"); - }, - }, - }; -}; - -export { Volume }; diff --git a/modules/bar/volume/index.ts b/modules/bar/volume/index.ts new file mode 100644 index 0000000..b5e065c --- /dev/null +++ b/modules/bar/volume/index.ts @@ -0,0 +1,63 @@ +import Gdk from 'gi://Gdk?version=3.0'; +const audio = await Service.import("audio"); +import { openMenu } from "../utils.js"; +import options from "options"; + +import { globalMousePos } from "globals.js"; + +const Volume = () => { + const icons = { + 101: "󰕾", + 66: "󰕾", + 34: "󰖀", + 1: "󰕿", + 0: "󰝟", + }; + + const getIcon = () => { + const icon = Utils.merge( + [audio.speaker.bind("is_muted"), audio.speaker.bind("volume")], + (isMuted, vol) => { + return isMuted + ? 0 + : [101, 66, 34, 1, 0].find((threshold) => threshold <= vol * 100); + }, + ); + + return icon.as((i) => i !== undefined ? icons[i] : 101); + }; + + const volIcn = Widget.Label({ + vpack: "center", + label: getIcon(), + class_name: "bar-volume_icon", + }); + + const volPct = Widget.Label({ + vpack: "center", + label: audio.speaker.bind("volume").as((v) => ` ${Math.floor(v * 100)}%`), + class_name: "bar-volume_percentage", + }); + + return { + component: Widget.Box({ + vpack: "center", + class_name: "volume", + children: options.bar.volume.label.bind("value").as((showLabel) => { + if (showLabel) { + return [volIcn, volPct]; + } + return [volIcn]; + }), + }), + isVisible: true, + boxClass: "volume", + props: { + on_primary_click: (clicked: any, event: Gdk.Event) => { + openMenu(clicked, event, "audiomenu"); + }, + }, + }; +}; + +export { Volume }; diff --git a/modules/bar/window_title/index.js b/modules/bar/window_title/index.js deleted file mode 100644 index ba96487..0000000 --- a/modules/bar/window_title/index.js +++ /dev/null @@ -1,36 +0,0 @@ -const hyprland = await Service.import("hyprland"); - -const filterTitle = (windowtitle) => { - const windowTitleMap = [ - ["kitty", "󰄛 Kitty Terminal"], - ["firefox", "󰈹 Firefox"], - ["microsoft-edge", "󰇩 Edge"], - ["discord", " Discord"], - ["org.kde.dolphin", " Dolphin"], - ["plex", "󰚺 Plex"], - ["steam", " Steam"], - ["spotify", "󰓇 Spotify"], - ["obsidian", "󱓧 Obsidian"], - ["^$", "󰇄 Desktop"], - ["(.+)", `󰣆 ${windowtitle.class.charAt(0).toUpperCase() + windowtitle.class.slice(1)}`], - ]; - - const foundMatch = windowTitleMap.find((wt) => - RegExp(wt[0]).test(windowtitle.class.toLowerCase()), - ); - - return foundMatch ? foundMatch[1] : windowtitle.class; -}; - -const ClientTitle = () => { - return { - component: Widget.Label({ - class_name: "window_title", - label: hyprland.active.bind("client").as((v) => filterTitle(v)), - }), - isVisible: true, - boxClass: "windowtitle", - }; -}; - -export { ClientTitle }; diff --git a/modules/bar/window_title/index.ts b/modules/bar/window_title/index.ts new file mode 100644 index 0000000..c8b6953 --- /dev/null +++ b/modules/bar/window_title/index.ts @@ -0,0 +1,37 @@ +const hyprland = await Service.import("hyprland"); +import { ActiveClient } from 'types/service/hyprland' + +const filterTitle = (windowtitle: ActiveClient) => { + const windowTitleMap = [ + ["kitty", "󰄛 Kitty Terminal"], + ["firefox", "󰈹 Firefox"], + ["microsoft-edge", "󰇩 Edge"], + ["discord", " Discord"], + ["org.kde.dolphin", " Dolphin"], + ["plex", "󰚺 Plex"], + ["steam", " Steam"], + ["spotify", "󰓇 Spotify"], + ["obsidian", "󱓧 Obsidian"], + ["^$", "󰇄 Desktop"], + ["(.+)", `󰣆 ${windowtitle.class.charAt(0).toUpperCase() + windowtitle.class.slice(1)}`], + ]; + + const foundMatch = windowTitleMap.find((wt) => + RegExp(wt[0]).test(windowtitle.class.toLowerCase()), + ); + + return foundMatch ? foundMatch[1] : windowtitle.class; +}; + +const ClientTitle = () => { + return { + component: Widget.Label({ + class_name: "window_title", + label: hyprland.active.bind("client").as((v) => filterTitle(v)), + }), + isVisible: true, + boxClass: "windowtitle", + }; +}; + +export { ClientTitle }; diff --git a/modules/bar/workspaces/index.js b/modules/bar/workspaces/index.ts similarity index 94% rename from modules/bar/workspaces/index.js rename to modules/bar/workspaces/index.ts index ab4458c..d817803 100644 --- a/modules/bar/workspaces/index.js +++ b/modules/bar/workspaces/index.ts @@ -1,14 +1,15 @@ const hyprland = await Service.import("hyprland"); +import { WorkspaceRule, WorkspaceMap } from "lib/types/workspace"; import options from "options"; const { workspaces, monitorSpecific } = options.bar.workspaces; -function range(length, start = 1) { +function range(length: number, start = 1) { return Array.from({ length }, (_, i) => i + start); } const Workspaces = (monitor = -1, ws = 8) => { - const getWorkspacesForMonitor = (curWs, wsRules) => { + const getWorkspacesForMonitor = (curWs: number, wsRules: WorkspaceMap) => { if (!wsRules || !Object.keys(wsRules).length) { return true; } @@ -20,13 +21,13 @@ const Workspaces = (monitor = -1, ws = 8) => { return wsRules[currentMonitorName].includes(curWs); }; - const getWorkspaceRules = () => { + const getWorkspaceRules = (): WorkspaceMap => { try { const rules = Utils.exec("hyprctl workspacerules -j"); const workspaceRules = {}; - JSON.parse(rules).forEach((rule, index) => { + JSON.parse(rules).forEach((rule: WorkspaceRule, index: number) => { if (Object.hasOwnProperty.call(workspaceRules, rule.monitor)) { workspaceRules[rule.monitor].push(index + 1); } else { @@ -37,6 +38,7 @@ const Workspaces = (monitor = -1, ws = 8) => { return workspaceRules; } catch (err) { console.error(err); + return {}; } }; diff --git a/modules/icons/index.js b/modules/icons/index.ts similarity index 99% rename from modules/icons/index.js rename to modules/icons/index.ts index f22fd66..a573875 100644 --- a/modules/icons/index.js +++ b/modules/icons/index.ts @@ -143,6 +143,7 @@ export default { light: "light-mode-symbolic", }, weather: { + warning: "dialog-warning-symbolic", sunny: "weather-clear-symbolic", clear: "weather-clear-night-symbolic", partly_cloudy: "weather-few-clouds-symbolic", diff --git a/modules/menus/DropdownMenu.js b/modules/menus/DropdownMenu.js deleted file mode 100644 index 93d3911..0000000 --- a/modules/menus/DropdownMenu.js +++ /dev/null @@ -1,145 +0,0 @@ -const hyprland = await Service.import("hyprland"); - -export const Padding = (name) => - Widget.EventBox({ - hexpand: true, - vexpand: true, - can_focus: true, - child: Widget.Box(), - setup: (w) => w.on("button-press-event", () => App.toggleWindow(name)), - }); - -const moveBoxToCursor = (self, fixed) => { - if (fixed) { - return; - } - - globalMousePos.connect("changed", ({ value }) => { - const currentWidth = self.child.get_allocation().width; - - let monWidth = hyprland.monitors[hyprland.active.monitor.id].width; - let monHeight = hyprland.monitors[hyprland.active.monitor.id].height; - - // If GDK Scaling is applied, then get divide width by scaling - // to get the proper coordinates. - // Ex: On a 2860px wide monitor... if scaling is set to 2, then the right - // end of the monitor is the 1430th pixel. - const gdkScale = Utils.exec('bash -c "echo $GDK_SCALE"'); - - if (/^\d+(.\d+)?$/.test(gdkScale)) { - const scale = parseFloat(gdkScale); - monWidth = monWidth / scale; - monHeight = monHeight / scale; - } - - // If monitor is vertical (transform = 1 || 3) swap height and width - if (hyprland.monitors[hyprland.active.monitor.id].transform % 2 !== 0) { - [monWidth, monHeight] = [monHeight, monWidth]; - } - - let marginRight = monWidth - currentWidth / 2; - marginRight = fixed ? marginRight - monWidth / 2 : marginRight - value[0]; - let marginLeft = monWidth - currentWidth - marginRight; - - const minimumMargin = 0; - - if (marginRight < minimumMargin) { - marginRight = minimumMargin; - marginLeft = monWidth - currentWidth - minimumMargin; - } - - if (marginLeft < minimumMargin) { - marginLeft = minimumMargin; - marginRight = monWidth - currentWidth - minimumMargin; - } - - const marginTop = 45; - const marginBottom = monHeight - marginTop; - self.set_margin_left(marginLeft); - self.set_margin_right(marginRight); - self.set_margin_bottom(marginBottom); - }); -}; - -// NOTE: We make the window visible for 2 seconds (on startup) so the child -// elements can allocat their proper dimensions. -// Otherwise the width that we rely on for menu positioning is set improperly -// for the first time we open a menu of each type. -const initRender = Variable(true); - -setTimeout(() => { - initRender.value = false; -}, 2000); - -export default ({ - name, - child, - layout = "center", - transition, - exclusivity = "ignore", - fixed = false, - ...props -}) => - Widget.Window({ - name, - class_names: [name, "dropdown-menu"], - setup: (w) => w.keybind("Escape", () => App.closeWindow(name)), - visible: initRender.bind("value"), - keymode: "on-demand", - exclusivity, - layer: "top", - anchor: ["top", "left"], - child: Widget.EventBox({ - class_name: "parent-event", - on_primary_click: () => App.closeWindow(name), - on_secondary_click: () => App.closeWindow(name), - child: Widget.Box({ - class_name: "top-eb", - vertical: true, - children: [ - Widget.EventBox({ - class_name: "mid-eb event-top-padding", - hexpand: true, - vexpand: false, - can_focus: false, - child: Widget.Box(), - setup: (w) => { - w.on("button-press-event", () => App.toggleWindow(name)); - w.set_margin_top(1); - }, - }), - Widget.EventBox({ - class_name: "in-eb menu-event-box", - on_primary_click: () => { - return true; - }, - on_secondary_click: () => { - return true; - }, - setup: (self) => { - moveBoxToCursor(self, fixed); - }, - child: Widget.Box({ - class_name: "dropdown-menu-container", - css: "padding: 1px; margin: -1px;", - child: Widget.Revealer({ - revealChild: false, - setup: (self) => - self.hook(App, (_, wname, visible) => { - if (wname === name) self.reveal_child = visible; - }), - transition: "crossfade", - transitionDuration: 350, - child: Widget.Box({ - class_name: "dropdown-menu-container", - can_focus: true, - children: [child], - }), - }), - }), - }), - ], - }), - }), - ...props, - }); diff --git a/modules/menus/DropdownMenu.ts b/modules/menus/DropdownMenu.ts new file mode 100644 index 0000000..5c3cfc4 --- /dev/null +++ b/modules/menus/DropdownMenu.ts @@ -0,0 +1,147 @@ +const hyprland = await Service.import("hyprland"); +import { globalMousePos } from "globals"; +import { Exclusivity } from "lib/types/widget"; + +export const Padding = (name: string) => + Widget.EventBox({ + hexpand: true, + vexpand: true, + can_focus: true, + child: Widget.Box(), + setup: (w) => w.on("button-press-event", () => App.toggleWindow(name)), + }); + +const moveBoxToCursor = (self: any, fixed: boolean) => { + if (fixed) { + return; + } + + globalMousePos.connect("changed", ({ value }) => { + const currentWidth = self.child.get_allocation().width; + + let monWidth = hyprland.monitors[hyprland.active.monitor.id].width; + let monHeight = hyprland.monitors[hyprland.active.monitor.id].height; + + // If GDK Scaling is applied, then get divide width by scaling + // to get the proper coordinates. + // Ex: On a 2860px wide monitor... if scaling is set to 2, then the right + // end of the monitor is the 1430th pixel. + const gdkScale = Utils.exec('bash -c "echo $GDK_SCALE"'); + + if (/^\d+(.\d+)?$/.test(gdkScale)) { + const scale = parseFloat(gdkScale); + monWidth = monWidth / scale; + monHeight = monHeight / scale; + } + + // If monitor is vertical (transform = 1 || 3) swap height and width + if (hyprland.monitors[hyprland.active.monitor.id].transform % 2 !== 0) { + [monWidth, monHeight] = [monHeight, monWidth]; + } + + let marginRight = monWidth - currentWidth / 2; + marginRight = fixed ? marginRight - monWidth / 2 : marginRight - value[0]; + let marginLeft = monWidth - currentWidth - marginRight; + + const minimumMargin = 0; + + if (marginRight < minimumMargin) { + marginRight = minimumMargin; + marginLeft = monWidth - currentWidth - minimumMargin; + } + + if (marginLeft < minimumMargin) { + marginLeft = minimumMargin; + marginRight = monWidth - currentWidth - minimumMargin; + } + + const marginTop = 45; + const marginBottom = monHeight - marginTop; + self.set_margin_left(marginLeft); + self.set_margin_right(marginRight); + self.set_margin_bottom(marginBottom); + }); +}; + +// NOTE: We make the window visible for 2 seconds (on startup) so the child +// elements can allocat their proper dimensions. +// Otherwise the width that we rely on for menu positioning is set improperly +// for the first time we open a menu of each type. +const initRender = Variable(true); + +setTimeout(() => { + initRender.value = false; +}, 2000); + +export default ({ + name, + child, + layout = "center", + transition, + exclusivity = "ignore" as Exclusivity, + fixed = false, + ...props +}) => + Widget.Window({ + name, + class_names: [name, "dropdown-menu"], + setup: (w) => w.keybind("Escape", () => App.closeWindow(name)), + visible: initRender.bind("value"), + keymode: "on-demand", + exclusivity, + layer: "top", + anchor: ["top", "left"], + child: Widget.EventBox({ + class_name: "parent-event", + on_primary_click: () => App.closeWindow(name), + on_secondary_click: () => App.closeWindow(name), + child: Widget.Box({ + class_name: "top-eb", + vertical: true, + children: [ + Widget.EventBox({ + class_name: "mid-eb event-top-padding", + hexpand: true, + vexpand: false, + can_focus: false, + child: Widget.Box(), + setup: (w) => { + w.on("button-press-event", () => App.toggleWindow(name)); + w.set_margin_top(1); + }, + }), + Widget.EventBox({ + class_name: "in-eb menu-event-box", + on_primary_click: () => { + return true; + }, + on_secondary_click: () => { + return true; + }, + setup: (self) => { + moveBoxToCursor(self, fixed); + }, + child: Widget.Box({ + class_name: "dropdown-menu-container", + css: "padding: 1px; margin: -1px;", + child: Widget.Revealer({ + revealChild: false, + setup: (self) => + self.hook(App, (_, wname, visible) => { + if (wname === name) self.reveal_child = visible; + }), + transition: "crossfade", + transitionDuration: 350, + child: Widget.Box({ + class_name: "dropdown-menu-container", + can_focus: true, + children: [child], + }), + }), + }), + }), + ], + }), + }), + ...props, + }); diff --git a/modules/menus/PopupWindow.js b/modules/menus/PopupWindow.js deleted file mode 100644 index dbe89c9..0000000 --- a/modules/menus/PopupWindow.js +++ /dev/null @@ -1,165 +0,0 @@ -export const Padding = (name, opts = {}) => - Widget.EventBox({ - class_name: opts?.className || "", - hexpand: true, - vexpand: typeof opts?.vexpand === "boolean" ? opts.vexpand : true, - can_focus: false, - child: Widget.Box(), - setup: (w) => w.on("button-press-event", () => App.toggleWindow(name)), - }); - -const PopupRevealer = (name, child, transition = "slide_down") => - Widget.Box( - { css: "padding: 1px;" }, - Widget.Revealer({ - transition, - child: Widget.Box({ - class_name: `window-content ${name}-window`, - child, - }), - transitionDuration: 200, - setup: (self) => - self.hook(App, (_, wname, visible) => { - if (wname === name) self.reveal_child = visible; - }), - }), - ); - -const Layout = (name, child, transition) => ({ - center: () => - Widget.CenterBox( - {}, - Padding(name), - Widget.CenterBox( - { vertical: true }, - Padding(name), - PopupRevealer(name, child, transition), - Padding(name), - ), - Padding(name), - ), - top: () => - Widget.CenterBox( - {}, - Padding(name), - Widget.Box( - { vertical: true }, - PopupRevealer(name, child, transition), - Padding(name), - ), - Padding(name), - ), - "top-right": () => - Widget.Box( - {}, - Padding(name), - Widget.Box( - { - hexpand: false, - vertical: true, - }, - Padding(name, { - vexpand: false, - className: "event-top-padding", - }), - PopupRevealer(name, child, transition), - Padding(name), - ), - ), - "top-center": () => - Widget.Box( - {}, - Padding(name), - Widget.Box( - { - hexpand: false, - vertical: true, - }, - Padding(name, { - vexpand: false, - className: "event-top-padding", - }), - PopupRevealer(name, child, transition), - Padding(name), - ), - Padding(name), - ), - "top-left": () => - Widget.Box( - {}, - Widget.Box( - { - hexpand: false, - vertical: true, - }, - Padding(name, { - vexpand: false, - className: "event-top-padding", - }), - PopupRevealer(name, child, transition), - Padding(name), - ), - Padding(name), - ), - "bottom-left": () => - Widget.Box( - {}, - Widget.Box( - { - hexpand: false, - vertical: true, - }, - Padding(name), - PopupRevealer(name, child, transition), - ), - Padding(name), - ), - "bottom-center": () => - Widget.Box( - {}, - Padding(name), - Widget.Box( - { - hexpand: false, - vertical: true, - }, - Padding(name), - PopupRevealer(name, child, transition), - ), - Padding(name), - ), - "bottom-right": () => - Widget.Box( - {}, - Padding(name), - Widget.Box( - { - hexpand: false, - vertical: true, - }, - Padding(name), - PopupRevealer(name, child, transition), - ), - ), -}); - -export default ({ - name, - child, - layout = "center", - transition, - exclusivity = "ignore", - ...props -}) => - Widget.Window({ - name, - class_names: [name, "popup-window"], - setup: (w) => w.keybind("Escape", () => App.closeWindow(name)), - visible: false, - keymode: "on-demand", - exclusivity, - layer: "top", - anchor: ["top", "bottom", "right", "left"], - child: Layout(name, child, transition)[layout](), - ...props, - }); diff --git a/modules/menus/PopupWindow.ts b/modules/menus/PopupWindow.ts new file mode 100644 index 0000000..af26df4 --- /dev/null +++ b/modules/menus/PopupWindow.ts @@ -0,0 +1,172 @@ +import { Exclusivity, Transition } from "lib/types/widget"; + +type Opts = { + className: string + vexpand: boolean +} + +export const Padding = (name: string, opts: Opts) => + Widget.EventBox({ + class_name: opts?.className || "", + hexpand: true, + vexpand: typeof opts?.vexpand === "boolean" ? opts.vexpand : true, + can_focus: false, + child: Widget.Box(), + setup: (w) => w.on("button-press-event", () => App.toggleWindow(name)), + }); + +const PopupRevealer = (name: string, child: any, transition = "slide_down" as Transition) => + Widget.Box( + { css: "padding: 1px;" }, + Widget.Revealer({ + transition, + child: Widget.Box({ + class_name: `window-content ${name}-window`, + child, + }), + transitionDuration: 200, + setup: (self) => + self.hook(App, (_, wname, visible) => { + if (wname === name) self.reveal_child = visible; + }), + }), + ); + +const Layout = (name: string, child: any, transition: Transition) => ({ + center: () => + Widget.CenterBox( + {}, + Padding(name, {} as Opts), + Widget.CenterBox( + { vertical: true }, + Padding(name, {} as Opts), + PopupRevealer(name, child, transition), + Padding(name, {} as Opts), + ), + Padding(name, {} as Opts), + ), + top: () => + Widget.CenterBox( + {}, + Padding(name, {} as Opts), + Widget.Box( + { vertical: true }, + PopupRevealer(name, child, transition), + Padding(name, {} as Opts), + ), + Padding(name, {} as Opts), + ), + "top-right": () => + Widget.Box( + {}, + Padding(name, {} as Opts), + Widget.Box( + { + hexpand: false, + vertical: true, + }, + Padding(name, { + vexpand: false, + className: "event-top-padding", + }), + PopupRevealer(name, child, transition), + Padding(name, {} as Opts), + ), + ), + "top-center": () => + Widget.Box( + {}, + Padding(name, {} as Opts), + Widget.Box( + { + hexpand: false, + vertical: true, + }, + Padding(name, { + vexpand: false, + className: "event-top-padding", + }), + PopupRevealer(name, child, transition), + Padding(name, {} as Opts), + ), + Padding(name, {} as Opts), + ), + "top-left": () => + Widget.Box( + {}, + Widget.Box( + { + hexpand: false, + vertical: true, + }, + Padding(name, { + vexpand: false, + className: "event-top-padding", + }), + PopupRevealer(name, child, transition), + Padding(name, {} as Opts), + ), + Padding(name, {} as Opts), + ), + "bottom-left": () => + Widget.Box( + {}, + Widget.Box( + { + hexpand: false, + vertical: true, + }, + Padding(name, {} as Opts), + PopupRevealer(name, child, transition), + ), + Padding(name, {} as Opts), + ), + "bottom-center": () => + Widget.Box( + {}, + Padding(name, {} as Opts), + Widget.Box( + { + hexpand: false, + vertical: true, + }, + Padding(name, {} as Opts), + PopupRevealer(name, child, transition), + ), + Padding(name, {} as Opts), + ), + "bottom-right": () => + Widget.Box( + {}, + Padding(name, {} as Opts), + Widget.Box( + { + hexpand: false, + vertical: true, + }, + Padding(name, {} as Opts), + PopupRevealer(name, child, transition), + ), + ), +}); + +export default ({ + name, + child, + layout = "center", + transition, + exclusivity = "ignore" as Exclusivity, + ...props +}) => + Widget.Window({ + name, + class_names: [name, "popup-window"], + setup: (w) => w.keybind("Escape", () => App.closeWindow(name)), + visible: false, + keymode: "on-demand", + exclusivity, + layer: "top", + anchor: ["top", "bottom", "right", "left"], + child: Layout(name, child, transition)[layout](), + ...props, + }); diff --git a/modules/menus/audio/active/SelectedInput.js b/modules/menus/audio/active/SelectedInput.ts similarity index 100% rename from modules/menus/audio/active/SelectedInput.js rename to modules/menus/audio/active/SelectedInput.ts diff --git a/modules/menus/audio/active/SelectedPlayback.js b/modules/menus/audio/active/SelectedPlayback.ts similarity index 100% rename from modules/menus/audio/active/SelectedPlayback.js rename to modules/menus/audio/active/SelectedPlayback.ts diff --git a/modules/menus/audio/active/index.js b/modules/menus/audio/active/index.ts similarity index 100% rename from modules/menus/audio/active/index.js rename to modules/menus/audio/active/index.ts diff --git a/modules/menus/audio/available/InputDevices.js b/modules/menus/audio/available/InputDevices.js deleted file mode 100644 index 99c3856..0000000 --- a/modules/menus/audio/available/InputDevices.js +++ /dev/null @@ -1,58 +0,0 @@ -const audio = await Service.import("audio"); - -const renderInputDevices = (inputDevices) => { - if (!inputDevices.length) { - return [ - Widget.Box({ - class_name: `menu-unfound-button input`, - child: Widget.Box({ - children: [ - Widget.Label({ - class_name: "menu-button-name input", - label: "No input devices found...", - }), - ], - }), - }), - ]; - } - return inputDevices.map((device) => { - return Widget.Button({ - on_primary_click: () => (audio.microphone = device), - class_name: `menu-button audio input ${device}`, - child: Widget.Box({ - children: [ - Widget.Box({ - hpack: "start", - children: [ - Widget.Label({ - class_name: audio.microphone - .bind("description") - .as((v) => - device.description === v - ? "menu-button-icon active input" - : "menu-button-icon input", - ), - label: "", - }), - Widget.Label({ - truncate: "end", - wrap: true, - class_name: audio.microphone - .bind("description") - .as((v) => - device.description === v - ? "menu-button-name active input" - : "menu-button-name input", - ), - label: device.description, - }), - ], - }), - ], - }), - }); - }); -}; - -export { renderInputDevices }; diff --git a/modules/menus/audio/available/InputDevices.ts b/modules/menus/audio/available/InputDevices.ts new file mode 100644 index 0000000..aac0685 --- /dev/null +++ b/modules/menus/audio/available/InputDevices.ts @@ -0,0 +1,65 @@ +const audio = await Service.import("audio"); +import { Stream } from "types/service/audio"; + +const renderInputDevices = (inputDevices: Stream[]) => { + if (inputDevices.length === 0) { + return [ + Widget.Button({ + class_name: `menu-unfound-button input`, + child: Widget.Box({ + children: [ + Widget.Box({ + hpack: "start", + children: [ + Widget.Label({ + class_name: "menu-button-name input", + label: "No input devices found...", + }), + ], + }), + ], + }), + }), + ]; + } + return inputDevices.map((device) => { + return Widget.Button({ + on_primary_click: () => (audio.microphone = device), + class_name: `menu-button audio input ${device}`, + child: Widget.Box({ + children: [ + Widget.Box({ + hpack: "start", + children: [ + Widget.Label({ + wrap: true, + class_name: audio.microphone + .bind("description") + .as((v) => + device.description === v + ? "menu-button-icon active input" + : "menu-button-icon input", + ), + label: "", + }), + Widget.Label({ + truncate: "end", + wrap: true, + class_name: audio.microphone + .bind("description") + .as((v) => + device.description === v + ? "menu-button-name active input" + : "menu-button-name input", + ), + label: device.description, + }), + ], + }), + ], + }), + }); + }); +}; + +export { renderInputDevices }; diff --git a/modules/menus/audio/available/PlaybackDevices.js b/modules/menus/audio/available/PlaybackDevices.js deleted file mode 100644 index efe232e..0000000 --- a/modules/menus/audio/available/PlaybackDevices.js +++ /dev/null @@ -1,58 +0,0 @@ -const audio = await Service.import("audio"); - -const renderPlaybacks = (playbackDevices) => { - return playbackDevices.map((device) => { - if (device.description === "Dummy Output") { - return Widget.Box({ - class_name: "menu-unfound-button playback", - child: Widget.Box({ - children: [ - Widget.Label({ - class_name: "menu-button-name playback", - label: "No playback devices found...", - }), - ], - }), - }); - } - return Widget.Button({ - class_name: `menu-button audio playback ${device}`, - on_primary_click: () => (audio.speaker = device), - child: Widget.Box({ - children: [ - Widget.Box({ - hpack: "start", - children: [ - Widget.Label({ - truncate: "end", - wrap: true, - class_name: audio.speaker - .bind("description") - .as((v) => - device.description === v - ? "menu-button-icon active playback" - : "menu-button-icon playback", - ), - label: "", - }), - Widget.Label({ - class_name: audio.speaker - .bind("description") - .as((v) => - device.description === v - ? "menu-button-name active playback" - : "menu-button-name playback", - ), - truncate: "end", - wrap: true, - label: device.description, - }), - ], - }), - ], - }), - }); - }); -}; - -export { renderPlaybacks }; diff --git a/modules/menus/audio/available/PlaybackDevices.ts b/modules/menus/audio/available/PlaybackDevices.ts new file mode 100644 index 0000000..07c3fad --- /dev/null +++ b/modules/menus/audio/available/PlaybackDevices.ts @@ -0,0 +1,59 @@ +const audio = await Service.import("audio"); +import { Stream } from "types/service/audio"; + +const renderPlaybacks = (playbackDevices: Stream[]) => { + return playbackDevices.map((device) => { + if (device.description === "Dummy Output") { + return Widget.Box({ + class_name: "menu-unfound-button playback", + child: Widget.Box({ + children: [ + Widget.Label({ + class_name: "menu-button-name playback", + label: "No playback devices found...", + }), + ], + }), + }); + } + return Widget.Button({ + class_name: `menu-button audio playback ${device}`, + on_primary_click: () => (audio.speaker = device), + child: Widget.Box({ + children: [ + Widget.Box({ + hpack: "start", + children: [ + Widget.Label({ + truncate: "end", + wrap: true, + class_name: audio.speaker + .bind("description") + .as((v) => + device.description === v + ? "menu-button-icon active playback" + : "menu-button-icon playback", + ), + label: "", + }), + Widget.Label({ + truncate: "end", + wrap: true, + class_name: audio.speaker + .bind("description") + .as((v) => + device.description === v + ? "menu-button-name active playback" + : "menu-button-name playback", + ), + label: device.description, + }), + ], + }), + ], + }), + }); + }); +}; + +export { renderPlaybacks }; diff --git a/modules/menus/audio/available/index.js b/modules/menus/audio/available/index.js deleted file mode 100644 index ce486b1..0000000 --- a/modules/menus/audio/available/index.js +++ /dev/null @@ -1,75 +0,0 @@ -const audio = await Service.import("audio"); -import { renderInputDevices } from "./InputDevices.js"; -import { renderPlaybacks } from "./PlaybackDevices.js"; - -const availableDevices = () => { - return Widget.Box({ - vertical: true, - children: [ - Widget.Box({ - class_name: "menu-section-container playback", - vertical: true, - children: [ - Widget.Box({ - class_name: "menu-label-container playback", - hpack: "fill", - child: Widget.Label({ - class_name: "menu-label audio playback", - hexpand: true, - hpack: "start", - label: "Playback Devices", - }), - }), - Widget.Box({ - class_name: "menu-items-section playback", - vertical: true, - children: [ - Widget.Box({ - class_name: "menu-container playback", - vertical: true, - children: [ - Widget.Box({ - vertical: true, - children: audio - .bind("speakers") - .as((v) => renderPlaybacks(v)), - }), - ], - }), - ], - }), - Widget.Box({ - class_name: "menu-label-container input", - hpack: "fill", - child: Widget.Label({ - class_name: "menu-label audio input", - hexpand: true, - hpack: "start", - label: "Input Devices", - }), - }), - Widget.Box({ - class_name: "menu-items-section input", - vertical: true, - children: [ - Widget.Box({ - class_name: "menu-container input", - vertical: true, - children: [ - Widget.Box({ - vertical: true, - children: audio - .bind("microphones") - .as((v) => renderInputDevices(v)), - }), - ], - }), - ], - }), - ], - }), - ], - }); -}; - -export { availableDevices }; diff --git a/modules/menus/audio/available/index.ts b/modules/menus/audio/available/index.ts new file mode 100644 index 0000000..1dca28d --- /dev/null +++ b/modules/menus/audio/available/index.ts @@ -0,0 +1,75 @@ +const audio = await Service.import("audio"); +import { renderInputDevices } from "./InputDevices.js"; +import { renderPlaybacks } from "./PlaybackDevices.js"; + +const availableDevices = () => { + return Widget.Box({ + vertical: true, + children: [ + Widget.Box({ + class_name: "menu-section-container playback", + vertical: true, + children: [ + Widget.Box({ + class_name: "menu-label-container playback", + hpack: "fill", + child: Widget.Label({ + class_name: "menu-label audio playback", + hexpand: true, + hpack: "start", + label: "Playback Devices", + }), + }), + Widget.Box({ + class_name: "menu-items-section playback", + vertical: true, + children: [ + Widget.Box({ + class_name: "menu-container playback", + vertical: true, + children: [ + Widget.Box({ + vertical: true, + children: audio + .bind("speakers") + .as((v) => renderPlaybacks(v)), + }), + ], + }), + ], + }), + Widget.Box({ + class_name: "menu-label-container input", + hpack: "fill", + child: Widget.Label({ + class_name: "menu-label audio input", + hexpand: true, + hpack: "start", + label: "Input Devices", + }), + }), + Widget.Box({ + class_name: "menu-items-section input", + vertical: true, + children: [ + Widget.Box({ + class_name: "menu-container input", + vertical: true, + children: [ + Widget.Box({ + vertical: true, + children: audio + .bind("microphones") + .as((v) => renderInputDevices(v)), + }), + ], + }), + ], + }), + ], + }), + ], + }); +}; + +export { availableDevices }; diff --git a/modules/menus/audio/index.js b/modules/menus/audio/index.ts similarity index 100% rename from modules/menus/audio/index.js rename to modules/menus/audio/index.ts diff --git a/modules/menus/audio/utils.js b/modules/menus/audio/utils.ts similarity index 100% rename from modules/menus/audio/utils.js rename to modules/menus/audio/utils.ts diff --git a/modules/menus/bluetooth/devices/connectedControls.js b/modules/menus/bluetooth/devices/connectedControls.js deleted file mode 100644 index f32fd0b..0000000 --- a/modules/menus/bluetooth/devices/connectedControls.js +++ /dev/null @@ -1,76 +0,0 @@ -const connectedControls = (dev, connectedDevices) => { - if (!connectedDevices.includes(dev.address)) { - return Widget.Box({}); - } - - return Widget.Box({ - vpack: "start", - class_name: "bluetooth-controls", - children: [ - Widget.Button({ - class_name: "menu-icon-button unpair bluetooth", - child: Widget.Label({ - tooltip_text: dev.paired ? "Unpair" : "Pair", - class_name: "menu-icon-button-label unpair bluetooth", - label: dev.paired ? "" : "", - }), - on_primary_click: () => - Utils.execAsync([ - "bash", - "-c", - `bluetoothctl ${dev.paired ? "unpair" : "pair"} ${dev.address}`, - ]).catch((err) => - console.error( - `bluetoothctl ${dev.paired ? "unpair" : "pair"} ${dev.address}`, - err, - ), - ), - }), - Widget.Button({ - class_name: "menu-icon-button disconnect bluetooth", - child: Widget.Label({ - tooltip_text: dev.connected ? "Disconnect" : "Connect", - class_name: "menu-icon-button-label disconnect bluetooth", - label: dev.connected ? "󱘖" : "", - }), - on_primary_click: () => dev.setConnection(!dev.connected), - }), - Widget.Button({ - class_name: "menu-icon-button untrust bluetooth", - child: Widget.Label({ - tooltip_text: dev.trusted ? "Untrust" : "Trust", - class_name: "menu-icon-button-label untrust bluetooth", - label: dev.trusted ? "" : "󱖡", - }), - on_primary_click: () => - Utils.execAsync([ - "bash", - "-c", - `bluetoothctl ${dev.trusted ? "untrust" : "trust"} ${dev.address}`, - ]).catch((err) => - console.error( - `bluetoothctl ${dev.trusted ? "untrust" : "trust"} ${dev.address}`, - err, - ), - ), - }), - Widget.Button({ - class_name: "menu-icon-button delete bluetooth", - child: Widget.Label({ - tooltip_text: "Forget", - class_name: "menu-icon-button-label delete bluetooth", - label: "󰆴", - }), - on_primary_click: () => { - Utils.execAsync([ - "bash", - "-c", - `bluetoothctl remove ${dev.address}`, - ]).catch((err) => console.error("Bluetooth Remove", err)); - }, - }), - ], - }); -}; - -export { connectedControls }; diff --git a/modules/menus/bluetooth/devices/connectedControls.ts b/modules/menus/bluetooth/devices/connectedControls.ts new file mode 100644 index 0000000..c241c0b --- /dev/null +++ b/modules/menus/bluetooth/devices/connectedControls.ts @@ -0,0 +1,78 @@ +import { BluetoothDevice } from "types/service/bluetooth"; + +const connectedControls = (dev: BluetoothDevice, connectedDevices: BluetoothDevice[]) => { + if (!connectedDevices.includes(dev.address)) { + return Widget.Box({}); + } + + return Widget.Box({ + vpack: "start", + class_name: "bluetooth-controls", + children: [ + Widget.Button({ + class_name: "menu-icon-button unpair bluetooth", + child: Widget.Label({ + tooltip_text: dev.paired ? "Unpair" : "Pair", + class_name: "menu-icon-button-label unpair bluetooth", + label: dev.paired ? "" : "", + }), + on_primary_click: () => + Utils.execAsync([ + "bash", + "-c", + `bluetoothctl ${dev.paired ? "unpair" : "pair"} ${dev.address}`, + ]).catch((err) => + console.error( + `bluetoothctl ${dev.paired ? "unpair" : "pair"} ${dev.address}`, + err, + ), + ), + }), + Widget.Button({ + class_name: "menu-icon-button disconnect bluetooth", + child: Widget.Label({ + tooltip_text: dev.connected ? "Disconnect" : "Connect", + class_name: "menu-icon-button-label disconnect bluetooth", + label: dev.connected ? "󱘖" : "", + }), + on_primary_click: () => dev.setConnection(!dev.connected), + }), + Widget.Button({ + class_name: "menu-icon-button untrust bluetooth", + child: Widget.Label({ + tooltip_text: dev.trusted ? "Untrust" : "Trust", + class_name: "menu-icon-button-label untrust bluetooth", + label: dev.trusted ? "" : "󱖡", + }), + on_primary_click: () => + Utils.execAsync([ + "bash", + "-c", + `bluetoothctl ${dev.trusted ? "untrust" : "trust"} ${dev.address}`, + ]).catch((err) => + console.error( + `bluetoothctl ${dev.trusted ? "untrust" : "trust"} ${dev.address}`, + err, + ), + ), + }), + Widget.Button({ + class_name: "menu-icon-button delete bluetooth", + child: Widget.Label({ + tooltip_text: "Forget", + class_name: "menu-icon-button-label delete bluetooth", + label: "󰆴", + }), + on_primary_click: () => { + Utils.execAsync([ + "bash", + "-c", + `bluetoothctl remove ${dev.address}`, + ]).catch((err) => console.error("Bluetooth Remove", err)); + }, + }), + ], + }); +}; + +export { connectedControls }; diff --git a/modules/menus/bluetooth/devices/devicelist.js b/modules/menus/bluetooth/devices/devicelist.js deleted file mode 100644 index 7511bd7..0000000 --- a/modules/menus/bluetooth/devices/devicelist.js +++ /dev/null @@ -1,137 +0,0 @@ -import { connectedControls } from "./connectedControls.js"; -import { getBluetoothIcon } from "../utils.js"; - -const devices = (bluetooth, self) => { - return self.hook(bluetooth, () => { - if (!bluetooth.enabled) { - return (self.child = Widget.Box({ - class_name: "bluetooth-items", - vertical: true, - expand: true, - vpack: "center", - hpack: "center", - children: [ - Widget.Label({ - class_name: "dim", - hexpand: true, - label: "Bluetooth is disabled", - }), - ], - })); - } - - const availableDevices = bluetooth.devices - .filter( - (btDev) => btDev.name !== null, - ) - .sort((a, b) => { - if (a.connected || a.paired) { - return -1; - } - - if (b.connected || b.paired) { - return 1; - } - - return b.name - a.name; - }); - - const conDevNames = availableDevices - .filter((d) => d.connected || d.paired) - .map((d) => d.address); - - if (!availableDevices.length) { - return (self.child = Widget.Box({ - class_name: "bluetooth-items", - vertical: true, - expand: true, - vpack: "center", - hpack: "center", - children: [ - Widget.Label({ - class_name: "no-bluetooth-devices dim", - hexpand: true, - label: "No devices currently found", - }), - Widget.Label({ - class_name: "search-bluetooth-label dim", - hexpand: true, - label: "Press '󰑐' to search", - }), - ], - })); - } - - return (self.child = Widget.Box({ - vertical: true, - children: availableDevices.map((device) => { - return Widget.Box({ - children: [ - Widget.Button({ - hexpand: true, - class_name: `bluetooth-element-item ${device}`, - on_primary_click: () => { - if (!conDevNames.includes(device.address)) - device.setConnection(true); - }, - child: Widget.Box({ - hexpand: true, - children: [ - Widget.Box({ - hexpand: true, - hpack: "start", - class_name: "menu-button-container", - children: [ - Widget.Label({ - vpack: "start", - class_name: `menu-button-icon bluetooth ${conDevNames.includes(device.address) ? "active" : ""}`, - label: getBluetoothIcon(`${device["icon-name"]}-symbolic`), - }), - Widget.Box({ - vertical: true, - vpack: "center", - children: [ - Widget.Label({ - vpack: "center", - hpack: "start", - class_name: "menu-button-name bluetooth", - truncate: "end", - wrap: true, - label: device.alias, - }), - Widget.Revealer({ - hpack: "start", - reveal_child: device.connected || device.paired, - child: Widget.Label({ - hpack: "start", - class_name: "connection-status dim", - label: device.connected ? "Connected" : "Paired", - }), - }), - ], - }), - ], - }), - Widget.Box({ - hpack: "end", - children: device.connecting - ? [ - Widget.Spinner({ - vpack: "start", - class_name: "spinner bluetooth", - }), - ] - : [], - }), - ], - }), - }), - connectedControls(device, conDevNames), - ], - }); - }), - })); - }); -}; - -export { devices }; diff --git a/modules/menus/bluetooth/devices/devicelist.ts b/modules/menus/bluetooth/devices/devicelist.ts new file mode 100644 index 0000000..2066857 --- /dev/null +++ b/modules/menus/bluetooth/devices/devicelist.ts @@ -0,0 +1,139 @@ +import { Bluetooth } from "types/service/bluetooth.js"; +import Box from "types/widgets/box.js"; +import { connectedControls } from "./connectedControls.js"; +import { getBluetoothIcon } from "../utils.js"; + +const devices = (bluetooth: Bluetooth, self: Box) => { + return self.hook(bluetooth, () => { + if (!bluetooth.enabled) { + return (self.child = Widget.Box({ + class_name: "bluetooth-items", + vertical: true, + expand: true, + vpack: "center", + hpack: "center", + children: [ + Widget.Label({ + class_name: "dim", + hexpand: true, + label: "Bluetooth is disabled", + }), + ], + })); + } + + const availableDevices = bluetooth.devices + .filter( + (btDev) => btDev.name !== null, + ) + .sort((a, b) => { + if (a.connected || a.paired) { + return -1; + } + + if (b.connected || b.paired) { + return 1; + } + + return b.name - a.name; + }); + + const conDevNames = availableDevices + .filter((d) => d.connected || d.paired) + .map((d) => d.address); + + if (!availableDevices.length) { + return (self.child = Widget.Box({ + class_name: "bluetooth-items", + vertical: true, + expand: true, + vpack: "center", + hpack: "center", + children: [ + Widget.Label({ + class_name: "no-bluetooth-devices dim", + hexpand: true, + label: "No devices currently found", + }), + Widget.Label({ + class_name: "search-bluetooth-label dim", + hexpand: true, + label: "Press '󰑐' to search", + }), + ], + })); + } + + return (self.child = Widget.Box({ + vertical: true, + children: availableDevices.map((device) => { + return Widget.Box({ + children: [ + Widget.Button({ + hexpand: true, + class_name: `bluetooth-element-item ${device}`, + on_primary_click: () => { + if (!conDevNames.includes(device.address)) + device.setConnection(true); + }, + child: Widget.Box({ + hexpand: true, + children: [ + Widget.Box({ + hexpand: true, + hpack: "start", + class_name: "menu-button-container", + children: [ + Widget.Label({ + vpack: "start", + class_name: `menu-button-icon bluetooth ${conDevNames.includes(device.address) ? "active" : ""}`, + label: getBluetoothIcon(`${device["icon-name"]}-symbolic`), + }), + Widget.Box({ + vertical: true, + vpack: "center", + children: [ + Widget.Label({ + vpack: "center", + hpack: "start", + class_name: "menu-button-name bluetooth", + truncate: "end", + wrap: true, + label: device.alias, + }), + Widget.Revealer({ + hpack: "start", + reveal_child: device.connected || device.paired, + child: Widget.Label({ + hpack: "start", + class_name: "connection-status dim", + label: device.connected ? "Connected" : "Paired", + }), + }), + ], + }), + ], + }), + Widget.Box({ + hpack: "end", + children: device.connecting + ? [ + Widget.Spinner({ + vpack: "start", + class_name: "spinner bluetooth", + }), + ] + : [], + }), + ], + }), + }), + connectedControls(device, conDevNames), + ], + }); + }), + })); + }); +}; + +export { devices }; diff --git a/modules/menus/bluetooth/devices/index.js b/modules/menus/bluetooth/devices/index.js deleted file mode 100644 index 750570c..0000000 --- a/modules/menus/bluetooth/devices/index.js +++ /dev/null @@ -1,27 +0,0 @@ -const bluetooth = await Service.import("bluetooth"); -import { label } from "./label.js"; -import { devices } from "./devicelist.js"; - -const Devices = () => { - return Widget.Box({ - class_name: "menu-section-container", - vertical: true, - children: [ - label(bluetooth), - Widget.Box({ - class_name: "menu-items-section", - // hscroll: 'never', - // vscroll: 'always', - child: Widget.Box({ - class_name: "menu-content", - vertical: true, - setup: (self) => { - devices(bluetooth, self); - }, - }), - }), - ], - }); -}; - -export { Devices }; diff --git a/modules/menus/bluetooth/devices/index.ts b/modules/menus/bluetooth/devices/index.ts new file mode 100644 index 0000000..9b7d3a1 --- /dev/null +++ b/modules/menus/bluetooth/devices/index.ts @@ -0,0 +1,25 @@ +const bluetooth = await Service.import("bluetooth"); +import { label } from "./label.js"; +import { devices } from "./devicelist.js"; + +const Devices = () => { + return Widget.Box({ + class_name: "menu-section-container", + vertical: true, + children: [ + label(bluetooth), + Widget.Box({ + class_name: "menu-items-section", + child: Widget.Box({ + class_name: "menu-content", + vertical: true, + setup: (self) => { + devices(bluetooth, self); + }, + }), + }), + ], + }); +}; + +export { Devices }; diff --git a/modules/menus/bluetooth/devices/label.js b/modules/menus/bluetooth/devices/label.js deleted file mode 100644 index 2ffa608..0000000 --- a/modules/menus/bluetooth/devices/label.js +++ /dev/null @@ -1,74 +0,0 @@ -const label = (bluetooth) => { - const searchInProgress = Variable(false); - const startRotation = () => { - searchInProgress.value = true; - setTimeout(() => { - searchInProgress.value = false; - }, 10 * 1000); - }; - - return Widget.Box({ - class_name: "menu-label-container", - hpack: "fill", - vpack: "start", - children: [ - Widget.Label({ - class_name: "menu-label", - vpack: "center", - hpack: "start", - label: "Bluetooth", - }), - Widget.Box({ - class_name: "controls-container", - vpack: "start", - children: [ - Widget.Switch({ - class_name: "menu-switch bluetooth", - hexpand: true, - hpack: "end", - active: bluetooth.bind("enabled"), - on_activate: ({ active }) => { - searchInProgress.value = false; - Utils.execAsync([ - "bash", - "-c", - `bluetoothctl power ${active ? "on" : "off"}`, - ]).catch((err) => - console.error( - `bluetoothctl power ${active ? "on" : "off"}`, - err, - ), - ); - }, - }), - Widget.Separator({ - class_name: "menu-separator bluetooth", - }), - Widget.Button({ - vpack: "center", - class_name: "menu-icon-button search", - on_primary_click: () => { - startRotation(); - Utils.execAsync([ - "bash", - "-c", - "bluetoothctl --timeout 120 scan on", - ]).catch((err) => { - searchInProgress.value = false; - console.error("bluetoothctl --timeout 120 scan on", err); - }); - }, - child: Widget.Icon({ - class_name: searchInProgress - .bind("value") - .as((v) => (v ? "spinning" : "")), - icon: "view-refresh-symbolic", - }), - }), - ], - }), - ], - }); -}; - -export { label }; diff --git a/modules/menus/bluetooth/devices/label.ts b/modules/menus/bluetooth/devices/label.ts new file mode 100644 index 0000000..d5b8555 --- /dev/null +++ b/modules/menus/bluetooth/devices/label.ts @@ -0,0 +1,75 @@ +import { Bluetooth } from "types/service/bluetooth"; +const label = (bluetooth: Bluetooth) => { + const searchInProgress = Variable(false); + const startRotation = () => { + searchInProgress.value = true; + setTimeout(() => { + searchInProgress.value = false; + }, 10 * 1000); + }; + + return Widget.Box({ + class_name: "menu-label-container", + hpack: "fill", + vpack: "start", + children: [ + Widget.Label({ + class_name: "menu-label", + vpack: "center", + hpack: "start", + label: "Bluetooth", + }), + Widget.Box({ + class_name: "controls-container", + vpack: "start", + children: [ + Widget.Switch({ + class_name: "menu-switch bluetooth", + hexpand: true, + hpack: "end", + active: bluetooth.bind("enabled"), + on_activate: ({ active }) => { + searchInProgress.value = false; + Utils.execAsync([ + "bash", + "-c", + `bluetoothctl power ${active ? "on" : "off"}`, + ]).catch((err) => + console.error( + `bluetoothctl power ${active ? "on" : "off"}`, + err, + ), + ); + }, + }), + Widget.Separator({ + class_name: "menu-separator bluetooth", + }), + Widget.Button({ + vpack: "center", + class_name: "menu-icon-button search", + on_primary_click: () => { + startRotation(); + Utils.execAsync([ + "bash", + "-c", + "bluetoothctl --timeout 120 scan on", + ]).catch((err) => { + searchInProgress.value = false; + console.error("bluetoothctl --timeout 120 scan on", err); + }); + }, + child: Widget.Icon({ + class_name: searchInProgress + .bind("value") + .as((v) => (v ? "spinning" : "")), + icon: "view-refresh-symbolic", + }), + }), + ], + }), + ], + }); +}; + +export { label }; diff --git a/modules/menus/bluetooth/index.js b/modules/menus/bluetooth/index.ts similarity index 100% rename from modules/menus/bluetooth/index.js rename to modules/menus/bluetooth/index.ts diff --git a/modules/menus/bluetooth/utils.js b/modules/menus/bluetooth/utils.js deleted file mode 100644 index 6cb3866..0000000 --- a/modules/menus/bluetooth/utils.js +++ /dev/null @@ -1,31 +0,0 @@ -const getBluetoothIcon = (iconName) => { - const deviceIconMap = [ - ["^audio-card*", "󰎄"], - ["^audio-headphones*", "󰋋"], - ["^audio-headset*", "󰋎"], - ["^audio-input*", "󰍬"], - ["^audio-speakers*", "󰓃"], - ["^bluetooth*", "󰂯"], - ["^camera*", "󰄀"], - ["^computer*", "󰟀"], - ["^input-gaming*", "󰍬"], - ["^input-keyboard*", "󰌌"], - ["^input-mouse*", "󰍽"], - ["^input-tablet*", "󰓶"], - ["^media*", "󱛟"], - ["^modem*", "󱂇"], - ["^network*", "󱂇"], - ["^phone*", "󰄞"], - ["^printer*", "󰐪"], - ["^scanner*", "󰚫"], - ["^video-camera*", "󰕧"], - ]; - - const foundMatch = deviceIconMap.find((icon) => - RegExp(icon[0]).test(iconName.toLowerCase()), - ); - - return foundMatch ? foundMatch[1] : "󰂯"; -}; - -export { getBluetoothIcon }; diff --git a/modules/menus/bluetooth/utils.ts b/modules/menus/bluetooth/utils.ts new file mode 100644 index 0000000..2024f8b --- /dev/null +++ b/modules/menus/bluetooth/utils.ts @@ -0,0 +1,31 @@ +const getBluetoothIcon = (iconName: string) => { + const deviceIconMap = [ + ["^audio-card*", "󰎄"], + ["^audio-headphones*", "󰋋"], + ["^audio-headset*", "󰋎"], + ["^audio-input*", "󰍬"], + ["^audio-speakers*", "󰓃"], + ["^bluetooth*", "󰂯"], + ["^camera*", "󰄀"], + ["^computer*", "󰟀"], + ["^input-gaming*", "󰍬"], + ["^input-keyboard*", "󰌌"], + ["^input-mouse*", "󰍽"], + ["^input-tablet*", "󰓶"], + ["^media*", "󱛟"], + ["^modem*", "󱂇"], + ["^network*", "󱂇"], + ["^phone*", "󰄞"], + ["^printer*", "󰐪"], + ["^scanner*", "󰚫"], + ["^video-camera*", "󰕧"], + ]; + + const foundMatch = deviceIconMap.find((icon) => + RegExp(icon[0]).test(iconName.toLowerCase()), + ); + + return foundMatch ? foundMatch[1] : "󰂯"; +}; + +export { getBluetoothIcon }; diff --git a/modules/menus/calendar/calendar.js b/modules/menus/calendar/calendar.ts similarity index 100% rename from modules/menus/calendar/calendar.js rename to modules/menus/calendar/calendar.ts diff --git a/modules/menus/calendar/index.js b/modules/menus/calendar/index.ts similarity index 100% rename from modules/menus/calendar/index.js rename to modules/menus/calendar/index.ts diff --git a/modules/menus/calendar/time/index.js b/modules/menus/calendar/time/index.ts similarity index 100% rename from modules/menus/calendar/time/index.js rename to modules/menus/calendar/time/index.ts diff --git a/modules/menus/calendar/weather/hourly/icon/index.js b/modules/menus/calendar/weather/hourly/icon/index.ts similarity index 67% rename from modules/menus/calendar/weather/hourly/icon/index.js rename to modules/menus/calendar/weather/hourly/icon/index.ts index ceeac30..0f9517b 100644 --- a/modules/menus/calendar/weather/hourly/icon/index.js +++ b/modules/menus/calendar/weather/hourly/icon/index.ts @@ -1,6 +1,8 @@ +import { Weather } from "lib/types/weather.js"; +import { Variable } from "types/variable.js"; import icons from "../../../../../icons/index.js"; -export const HourlyIcon = (theWeather, getNextEpoch) => { +export const HourlyIcon = (theWeather: Variable, getNextEpoch: any) => { return Widget.Icon({ class_name: "hourly-weather-icon", icon: theWeather.bind("value").as((w) => { @@ -16,9 +18,11 @@ export const HourlyIcon = (theWeather, getNextEpoch) => { let iconQuery = weatherAtEpoch?.condition.text .trim() .toLowerCase() - .replaceAll(" ", "_"); + .replaceAll(" ", "_") + || "warning" + ; - if (!weatherAtEpoch?.isDay && iconQuery === "partly_cloudy") { + if (!weatherAtEpoch?.is_day && iconQuery === "partly_cloudy") { iconQuery = "partly_cloudy_night"; } diff --git a/modules/menus/calendar/weather/hourly/index.js b/modules/menus/calendar/weather/hourly/index.js deleted file mode 100644 index bfde498..0000000 --- a/modules/menus/calendar/weather/hourly/index.js +++ /dev/null @@ -1,44 +0,0 @@ -import { HourlyIcon } from "./icon/index.js"; -import { HourlyTemp } from "./temperature/index.js"; -import { HourlyTime } from "./time/index.js"; - -export const Hourly = (theWeather) => { - return Widget.Box({ - vertical: false, - hexpand: true, - hpack: "fill", - class_name: "hourly-weather-container", - children: [1, 2, 3, 4].map((hoursFromNow) => { - const getNextEpoch = (wthr) => { - const currentEpoch = wthr.location.localtime_epoch; - const epochAtHourStart = currentEpoch - (currentEpoch % 3600); - let nextEpoch = 3600 * hoursFromNow + epochAtHourStart; - - 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; - } - return nextEpoch; - }; - - return Widget.Box({ - class_name: "hourly-weather-item", - hexpand: true, - vertical: true, - children: [ - HourlyTime(theWeather, getNextEpoch), - HourlyIcon(theWeather, getNextEpoch), - HourlyTemp(theWeather, getNextEpoch), - ], - }); - }), - }); -}; diff --git a/modules/menus/calendar/weather/hourly/index.ts b/modules/menus/calendar/weather/hourly/index.ts new file mode 100644 index 0000000..046bf92 --- /dev/null +++ b/modules/menus/calendar/weather/hourly/index.ts @@ -0,0 +1,46 @@ +import { Weather } from "lib/types/weather"; +import { Variable } from "types/variable"; +import { HourlyIcon } from "./icon/index.js"; +import { HourlyTemp } from "./temperature/index.js"; +import { HourlyTime } from "./time/index.js"; + +export const Hourly = (theWeather: Variable) => { + return Widget.Box({ + vertical: false, + hexpand: true, + hpack: "fill", + class_name: "hourly-weather-container", + children: [1, 2, 3, 4].map((hoursFromNow) => { + const getNextEpoch = (wthr: Weather) => { + const currentEpoch = wthr.location.localtime_epoch; + const epochAtHourStart = currentEpoch - (currentEpoch % 3600); + let nextEpoch = 3600 * hoursFromNow + epochAtHourStart; + + 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; + } + return nextEpoch; + }; + + return Widget.Box({ + class_name: "hourly-weather-item", + hexpand: true, + vertical: true, + children: [ + HourlyTime(theWeather, getNextEpoch), + HourlyIcon(theWeather, getNextEpoch), + HourlyTemp(theWeather, getNextEpoch), + ], + }); + }), + }); +}; diff --git a/modules/menus/calendar/weather/hourly/temperature/index.js b/modules/menus/calendar/weather/hourly/temperature/index.js deleted file mode 100644 index 1ba4324..0000000 --- a/modules/menus/calendar/weather/hourly/temperature/index.js +++ /dev/null @@ -1,27 +0,0 @@ -import options from "options"; - -const { unit } = options.menus.clock.weather; - -export const HourlyTemp = (theWeather, getNextEpoch) => { - return Widget.Label({ - class_name: "hourly-weather-temp", - label: Utils.merge( - [theWeather.bind("value"), unit.bind("value")], - (wthr, unt) => { - if (!Object.keys(wthr).length) { - return "-"; - } - - const nextEpoch = getNextEpoch(wthr); - const weatherAtEpoch = wthr.forecast.forecastday[0].hour.find( - (h) => h.time_epoch === nextEpoch, - ); - - if (unt === "imperial") { - return `${weatherAtEpoch ? Math.ceil(weatherAtEpoch.temp_f) : "-"}° F`; - } - return `${weatherAtEpoch ? Math.ceil(weatherAtEpoch.temp_c) : "-"}° C`; - }, - ), - }); -}; diff --git a/modules/menus/calendar/weather/hourly/temperature/index.ts b/modules/menus/calendar/weather/hourly/temperature/index.ts new file mode 100644 index 0000000..d649358 --- /dev/null +++ b/modules/menus/calendar/weather/hourly/temperature/index.ts @@ -0,0 +1,29 @@ +import { Weather } from "lib/types/weather"; +import { Variable } from "types/variable"; +import options from "options"; + +const { unit } = options.menus.clock.weather; + +export const HourlyTemp = (theWeather: Variable, getNextEpoch: any) => { + return Widget.Label({ + class_name: "hourly-weather-temp", + label: Utils.merge( + [theWeather.bind("value"), unit.bind("value")], + (wthr, unt) => { + if (!Object.keys(wthr).length) { + return "-"; + } + + const nextEpoch = getNextEpoch(wthr); + const weatherAtEpoch = wthr.forecast.forecastday[0].hour.find( + (h) => h.time_epoch === nextEpoch, + ); + + if (unt === "imperial") { + return `${weatherAtEpoch ? Math.ceil(weatherAtEpoch.temp_f) : "-"}° F`; + } + return `${weatherAtEpoch ? Math.ceil(weatherAtEpoch.temp_c) : "-"}° C`; + }, + ), + }); +}; diff --git a/modules/menus/calendar/weather/hourly/time/index.js b/modules/menus/calendar/weather/hourly/time/index.js deleted file mode 100644 index d244602..0000000 --- a/modules/menus/calendar/weather/hourly/time/index.js +++ /dev/null @@ -1,18 +0,0 @@ -export const HourlyTime = (theWeather, getNextEpoch) => { - return Widget.Label({ - class_name: "hourly-weather-time", - label: theWeather.bind("value").as((w) => { - if (!Object.keys(w).length) { - return "-"; - } - - const nextEpoch = getNextEpoch(w); - const dateAtEpoch = new Date(nextEpoch * 1000); - let hours = dateAtEpoch.getHours(); - const ampm = hours >= 12 ? "PM" : "AM"; - hours = hours % 12 || 12; - - return `${hours}${ampm}`; - }), - }); -}; diff --git a/modules/menus/calendar/weather/hourly/time/index.ts b/modules/menus/calendar/weather/hourly/time/index.ts new file mode 100644 index 0000000..48e2ebe --- /dev/null +++ b/modules/menus/calendar/weather/hourly/time/index.ts @@ -0,0 +1,21 @@ +import { Weather } from "lib/types/weather"; +import { Variable } from "types/variable"; + +export const HourlyTime = (theWeather: Variable, getNextEpoch: any) => { + return Widget.Label({ + class_name: "hourly-weather-time", + label: theWeather.bind("value").as((w) => { + if (!Object.keys(w).length) { + return "-"; + } + + const nextEpoch = getNextEpoch(w); + const dateAtEpoch = new Date(nextEpoch * 1000); + let hours = dateAtEpoch.getHours(); + const ampm = hours >= 12 ? "PM" : "AM"; + hours = hours % 12 || 12; + + return `${hours}${ampm}`; + }), + }); +}; diff --git a/modules/menus/calendar/weather/icon/index.js b/modules/menus/calendar/weather/icon/index.js deleted file mode 100644 index 3d5371f..0000000 --- a/modules/menus/calendar/weather/icon/index.js +++ /dev/null @@ -1,25 +0,0 @@ -import icons from "../../../../icons/index.js"; - -export const TodayIcon = (theWeather) => { - return Widget.Box({ - vpack: "center", - hpack: "start", - class_name: "calendar-menu-weather today icon container", - children: [ - Widget.Icon({ - class_name: "calendar-menu-weather today icon", - icon: theWeather.bind("value").as((v) => { - let iconQuery = v.current.condition.text - .trim() - .toLowerCase() - .replaceAll(" ", "_"); - - if (!v.current.isDay && iconQuery === "partly_cloudy") { - iconQuery = "partly_cloudy_night"; - } - return icons.weather[iconQuery]; - }), - }), - ], - }); -}; diff --git a/modules/menus/calendar/weather/icon/index.ts b/modules/menus/calendar/weather/icon/index.ts new file mode 100644 index 0000000..f5a0fc7 --- /dev/null +++ b/modules/menus/calendar/weather/icon/index.ts @@ -0,0 +1,27 @@ +import { Weather } from "lib/types/weather"; +import { Variable } from "types/variable"; +import icons from "../../../../icons/index.js"; + +export const TodayIcon = (theWeather: Variable) => { + return Widget.Box({ + vpack: "center", + hpack: "start", + class_name: "calendar-menu-weather today icon container", + children: [ + Widget.Icon({ + class_name: "calendar-menu-weather today icon", + icon: theWeather.bind("value").as((v) => { + let iconQuery = v.current.condition.text + .trim() + .toLowerCase() + .replaceAll(" ", "_"); + + if (!v.current.is_day && iconQuery === "partly_cloudy") { + iconQuery = "partly_cloudy_night"; + } + return icons.weather[iconQuery]; + }), + }), + ], + }); +}; diff --git a/modules/menus/calendar/weather/index.js b/modules/menus/calendar/weather/index.ts similarity index 77% rename from modules/menus/calendar/weather/index.js rename to modules/menus/calendar/weather/index.ts index 13328d8..c6a44f2 100644 --- a/modules/menus/calendar/weather/index.js +++ b/modules/menus/calendar/weather/index.ts @@ -3,41 +3,12 @@ import { TodayIcon } from "./icon/index.js"; import { TodayStats } from "./stats/index.js"; import { TodayTemperature } from "./temperature/index.js"; import { Hourly } from "./hourly/index.js"; +import { Weather } from "lib/types/weather.js"; +import { DEFAULT_WEATHER } from "lib/types/defaults/weather.js"; const { key, interval, location } = options.menus.clock.weather; -const defaultWeather = { - location: { - localtime_epoch: 1719471600, - }, - current: { - temp_f: 0, - wind_mph: 0, - condition: { - text: "Clear", - }, - }, - forecast: { - forecastday: [ - { - day: { - daily_chance_of_rain: 0, - }, - hour: [ - { - time_epoch: 1719471600, - temp_f: 0, - condition: { - text: "Clear", - }, - }, - ], - }, - ], - }, -}; - -const theWeather = Variable(defaultWeather); +const theWeather = Variable(DEFAULT_WEATHER); const WeatherWidget = () => { return Widget.Box({ @@ -64,7 +35,7 @@ const WeatherWidget = () => { }) .catch((err) => { console.error(`Failed to fetch weather: ${err}`); - theWeather.value = defaultWeather; + theWeather.value = DEFAULT_WEATHER; }); }); }, diff --git a/modules/menus/calendar/weather/stats/index.js b/modules/menus/calendar/weather/stats/index.js deleted file mode 100644 index def90db..0000000 --- a/modules/menus/calendar/weather/stats/index.js +++ /dev/null @@ -1,52 +0,0 @@ -import options from "options"; - -const { unit } = options.menus.clock.weather; - -export const TodayStats = (theWeather) => { - return Widget.Box({ - class_name: "calendar-menu-weather today stats container", - hpack: "end", - vpack: "center", - vertical: true, - children: [ - Widget.Box({ - class_name: "weather wind", - children: [ - Widget.Label({ - class_name: "weather wind icon", - label: "", - }), - Widget.Label({ - class_name: "weather wind label", - label: Utils.merge( - [theWeather.bind("value"), unit.bind("value")], - (wthr, unt) => { - if (unt === "imperial") { - return `${Math.floor(wthr.current.wind_mph)} mph`; - } - return `${Math.floor(wthr.current.wind_kph)} kph`; - }, - ), - }), - ], - }), - Widget.Box({ - class_name: "weather precip", - children: [ - Widget.Label({ - class_name: "weather precip icon", - label: "", - }), - Widget.Label({ - class_name: "weather precip label", - label: theWeather - .bind("value") - .as( - (v) => `${v.forecast.forecastday[0].day.daily_chance_of_rain}%`, - ), - }), - ], - }), - ], - }); -}; diff --git a/modules/menus/calendar/weather/stats/index.ts b/modules/menus/calendar/weather/stats/index.ts new file mode 100644 index 0000000..c459c70 --- /dev/null +++ b/modules/menus/calendar/weather/stats/index.ts @@ -0,0 +1,55 @@ +import { Weather } from "lib/types/weather"; +import { Variable } from "types/variable"; +import options from "options"; +import { Unit } from "lib/types/options"; + +const { unit } = options.menus.clock.weather; + +export const TodayStats = (theWeather: Variable) => { + return Widget.Box({ + class_name: "calendar-menu-weather today stats container", + hpack: "end", + vpack: "center", + vertical: true, + children: [ + Widget.Box({ + class_name: "weather wind", + children: [ + Widget.Label({ + class_name: "weather wind icon", + label: "", + }), + Widget.Label({ + class_name: "weather wind label", + label: Utils.merge( + [theWeather.bind("value"), unit.bind("value")], + (wthr: Weather, unt: Unit) => { + if (unt === "imperial") { + return `${Math.floor(wthr.current.wind_mph)} mph`; + } + return `${Math.floor(wthr.current.wind_kph)} kph`; + }, + ), + }), + ], + }), + Widget.Box({ + class_name: "weather precip", + children: [ + Widget.Label({ + class_name: "weather precip icon", + label: "", + }), + Widget.Label({ + class_name: "weather precip label", + label: theWeather + .bind("value") + .as( + (v) => `${v.forecast.forecastday[0].day.daily_chance_of_rain}%`, + ), + }), + ], + }), + ], + }); +}; diff --git a/modules/menus/calendar/weather/temperature/index.js b/modules/menus/calendar/weather/temperature/index.js deleted file mode 100644 index f03a972..0000000 --- a/modules/menus/calendar/weather/temperature/index.js +++ /dev/null @@ -1,92 +0,0 @@ -import options from "options"; -const { unit } = options.menus.clock.weather; - -export const TodayTemperature = (theWeather) => { - const getIcon = (fahren) => { - const icons = { - 100: "", - 75: "", - 50: "", - 25: "", - 0: "", - }; - const colors = { - 100: "weather-color red", - 75: "weather-color orange", - 50: "weather-color lavender", - 25: "weather-color blue", - 0: "weather-color sky", - }; - - const threshold = - fahren < 0 - ? 0 - : [100, 75, 50, 25, 0].find((threshold) => threshold <= fahren); - - return { - icon: icons[threshold], - color: colors[threshold], - }; - }; - - return Widget.Box({ - hpack: "center", - vpack: "center", - vertical: true, - children: [ - Widget.Box({ - hexpand: true, - vpack: "center", - class_name: "calendar-menu-weather today temp container", - vertical: false, - children: [ - Widget.Box({ - hexpand: true, - hpack: "center", - children: [ - Widget.Label({ - class_name: "calendar-menu-weather today temp label", - label: Utils.merge( - [theWeather.bind("value"), unit.bind("value")], - (wthr, unt) => { - if (unt === "imperial") { - return `${Math.ceil(wthr.current.temp_f)}° F`; - } else { - return `${Math.ceil(wthr.current.temp_c)}° C`; - } - }, - ), - }), - Widget.Label({ - class_name: theWeather - .bind("value") - .as( - (v) => - `calendar-menu-weather today temp label icon ${getIcon(Math.ceil(v.current.temp_f)).color}`, - ), - label: theWeather - .bind("value") - .as((v) => getIcon(Math.ceil(v.current.temp_f)).icon), - }), - ], - }), - ], - }), - Widget.Box({ - hpack: "center", - child: Widget.Label({ - max_width_chars: 17, - truncate: "end", - lines: 2, - class_name: theWeather - .bind("value") - .as( - (v) => - `calendar-menu-weather today condition label ${getIcon(Math.ceil(v.current.temp_f)).color}`, - ), - label: theWeather.bind("value").as((v) => v.current.condition.text), - }), - }), - ], - }); -}; diff --git a/modules/menus/calendar/weather/temperature/index.ts b/modules/menus/calendar/weather/temperature/index.ts new file mode 100644 index 0000000..5577118 --- /dev/null +++ b/modules/menus/calendar/weather/temperature/index.ts @@ -0,0 +1,94 @@ +import { Weather } from "lib/types/weather"; +import { Variable } from "types/variable"; +import options from "options"; +const { unit } = options.menus.clock.weather; + +export const TodayTemperature = (theWeather: Variable) => { + const getIcon = (fahren: number) => { + const icons = { + 100: "", + 75: "", + 50: "", + 25: "", + 0: "", + }; + const colors = { + 100: "weather-color red", + 75: "weather-color orange", + 50: "weather-color lavender", + 25: "weather-color blue", + 0: "weather-color sky", + }; + + const threshold = + fahren < 0 + ? 0 + : [100, 75, 50, 25, 0].find((threshold) => threshold <= fahren); + + return { + icon: icons[threshold || 50], + color: colors[threshold || 50], + }; + }; + + return Widget.Box({ + hpack: "center", + vpack: "center", + vertical: true, + children: [ + Widget.Box({ + hexpand: true, + vpack: "center", + class_name: "calendar-menu-weather today temp container", + vertical: false, + children: [ + Widget.Box({ + hexpand: true, + hpack: "center", + children: [ + Widget.Label({ + class_name: "calendar-menu-weather today temp label", + label: Utils.merge( + [theWeather.bind("value"), unit.bind("value")], + (wthr, unt) => { + if (unt === "imperial") { + return `${Math.ceil(wthr.current.temp_f)}° F`; + } else { + return `${Math.ceil(wthr.current.temp_c)}° C`; + } + }, + ), + }), + Widget.Label({ + class_name: theWeather + .bind("value") + .as( + (v) => + `calendar-menu-weather today temp label icon ${getIcon(Math.ceil(v.current.temp_f)).color}`, + ), + label: theWeather + .bind("value") + .as((v) => getIcon(Math.ceil(v.current.temp_f)).icon), + }), + ], + }), + ], + }), + Widget.Box({ + hpack: "center", + child: Widget.Label({ + max_width_chars: 17, + truncate: "end", + lines: 2, + class_name: theWeather + .bind("value") + .as( + (v) => + `calendar-menu-weather today condition label ${getIcon(Math.ceil(v.current.temp_f)).color}`, + ), + label: theWeather.bind("value").as((v) => v.current.condition.text), + }), + }), + ], + }); +}; diff --git a/modules/menus/dashboard/controls/index.js b/modules/menus/dashboard/controls/index.ts similarity index 100% rename from modules/menus/dashboard/controls/index.js rename to modules/menus/dashboard/controls/index.ts diff --git a/modules/menus/dashboard/directories/index.js b/modules/menus/dashboard/directories/index.js deleted file mode 100644 index 7bd24a4..0000000 --- a/modules/menus/dashboard/directories/index.js +++ /dev/null @@ -1,133 +0,0 @@ -import GLib from "gi://GLib"; -import options from "options"; - -const { left, right } = options.menus.dashboard.directories; - -const Directories = () => { - return Widget.Box({ - class_name: "dashboard-card directories-container", - vpack: "fill", - hpack: "fill", - expand: true, - children: [ - Widget.Box({ - vertical: true, - expand: true, - class_name: "section right", - children: [ - Widget.Button({ - hpack: "start", - expand: true, - class_name: "directory-link left top", - on_primary_click: left.directory1.command - .bind("value") - .as((cmd) => { - return () => { - App.closeWindow("dashboardmenu"); - Utils.execAsync(cmd); - }; - }), - child: Widget.Label({ - hpack: "start", - label: left.directory1.label.bind("value"), - }), - }), - Widget.Button({ - expand: true, - hpack: "start", - class_name: "directory-link left middle", - on_primary_click: left.directory2.command - .bind("value") - .as((cmd) => { - return () => { - App.closeWindow("dashboardmenu"); - Utils.execAsync(cmd); - }; - }), - child: Widget.Label({ - hpack: "start", - label: left.directory2.label.bind("value"), - }), - }), - Widget.Button({ - expand: true, - hpack: "start", - class_name: "directory-link left bottom", - on_primary_click: left.directory3.command - .bind("value") - .as((cmd) => { - return () => { - App.closeWindow("dashboardmenu"); - Utils.execAsync(cmd); - }; - }), - child: Widget.Label({ - hpack: "start", - label: left.directory3.label.bind("value"), - }), - }), - ], - }), - Widget.Box({ - vertical: true, - expand: true, - class_name: "section left", - children: [ - Widget.Button({ - hpack: "start", - expand: true, - class_name: "directory-link right top", - on_primary_click: right.directory1.command - .bind("value") - .as((cmd) => { - return () => { - App.closeWindow("dashboardmenu"); - Utils.execAsync(cmd); - }; - }), - child: Widget.Label({ - hpack: "start", - label: right.directory1.label.bind("value"), - }), - }), - Widget.Button({ - expand: true, - hpack: "start", - class_name: "directory-link right middle", - on_primary_click: right.directory2.command - .bind("value") - .as((cmd) => { - return () => { - App.closeWindow("dashboardmenu"); - Utils.execAsync(cmd); - }; - }), - child: Widget.Label({ - hpack: "start", - label: right.directory2.label.bind("value"), - }), - }), - Widget.Button({ - expand: true, - hpack: "start", - class_name: "directory-link right bottom", - on_primary_click: right.directory3.command - .bind("value") - .as((cmd) => { - return () => { - App.closeWindow("dashboardmenu"); - Utils.execAsync(cmd); - }; - }), - child: Widget.Label({ - hpack: "start", - label: right.directory3.label.bind("value"), - }), - }), - ], - }), - ], - }); -}; - -export { Directories }; diff --git a/modules/menus/dashboard/directories/index.ts b/modules/menus/dashboard/directories/index.ts new file mode 100644 index 0000000..6841858 --- /dev/null +++ b/modules/menus/dashboard/directories/index.ts @@ -0,0 +1,132 @@ +import options from "options"; + +const { left, right } = options.menus.dashboard.directories; + +const Directories = () => { + return Widget.Box({ + class_name: "dashboard-card directories-container", + vpack: "fill", + hpack: "fill", + expand: true, + children: [ + Widget.Box({ + vertical: true, + expand: true, + class_name: "section right", + children: [ + Widget.Button({ + hpack: "start", + expand: true, + class_name: "directory-link left top", + on_primary_click: left.directory1.command + .bind("value") + .as((cmd) => { + return () => { + App.closeWindow("dashboardmenu"); + Utils.execAsync(cmd); + }; + }), + child: Widget.Label({ + hpack: "start", + label: left.directory1.label.bind("value"), + }), + }), + Widget.Button({ + expand: true, + hpack: "start", + class_name: "directory-link left middle", + on_primary_click: left.directory2.command + .bind("value") + .as((cmd) => { + return () => { + App.closeWindow("dashboardmenu"); + Utils.execAsync(cmd); + }; + }), + child: Widget.Label({ + hpack: "start", + label: left.directory2.label.bind("value"), + }), + }), + Widget.Button({ + expand: true, + hpack: "start", + class_name: "directory-link left bottom", + on_primary_click: left.directory3.command + .bind("value") + .as((cmd) => { + return () => { + App.closeWindow("dashboardmenu"); + Utils.execAsync(cmd); + }; + }), + child: Widget.Label({ + hpack: "start", + label: left.directory3.label.bind("value"), + }), + }), + ], + }), + Widget.Box({ + vertical: true, + expand: true, + class_name: "section left", + children: [ + Widget.Button({ + hpack: "start", + expand: true, + class_name: "directory-link right top", + on_primary_click: right.directory1.command + .bind("value") + .as((cmd) => { + return () => { + App.closeWindow("dashboardmenu"); + Utils.execAsync(cmd); + }; + }), + child: Widget.Label({ + hpack: "start", + label: right.directory1.label.bind("value"), + }), + }), + Widget.Button({ + expand: true, + hpack: "start", + class_name: "directory-link right middle", + on_primary_click: right.directory2.command + .bind("value") + .as((cmd) => { + return () => { + App.closeWindow("dashboardmenu"); + Utils.execAsync(cmd); + }; + }), + child: Widget.Label({ + hpack: "start", + label: right.directory2.label.bind("value"), + }), + }), + Widget.Button({ + expand: true, + hpack: "start", + class_name: "directory-link right bottom", + on_primary_click: right.directory3.command + .bind("value") + .as((cmd) => { + return () => { + App.closeWindow("dashboardmenu"); + Utils.execAsync(cmd); + }; + }), + child: Widget.Label({ + hpack: "start", + label: right.directory3.label.bind("value"), + }), + }), + ], + }), + ], + }); +}; + +export { Directories }; diff --git a/modules/menus/dashboard/index.js b/modules/menus/dashboard/index.ts similarity index 100% rename from modules/menus/dashboard/index.js rename to modules/menus/dashboard/index.ts diff --git a/modules/menus/dashboard/profile/index.js b/modules/menus/dashboard/profile/index.js deleted file mode 100644 index 81630a4..0000000 --- a/modules/menus/dashboard/profile/index.js +++ /dev/null @@ -1,79 +0,0 @@ -import icons from "../../../icons/index.js"; -import powermenu from "../../power/helpers/actions.js"; - -import options from "options"; -const { image, name } = options.menus.dashboard.powermenu.avatar; - -const Profile = () => { - const handleClick = (action) => { - App.closeWindow("dashboardmenu"); - return powermenu.action(action); - }; - - return Widget.Box({ - class_name: "profiles-container", - hpack: "fill", - hexpand: true, - children: [ - Widget.Box({ - class_name: "profile-picture-container dashboard-card", - hexpand: true, - vertical: true, - children: [ - Widget.Icon({ - hpack: "center", - class_name: "profile-picture", - icon: image.bind("value"), - }), - Widget.Label({ - hpack: "center", - class_name: "profile-name", - label: name.bind("value").as((v) => { - if (v === "system") { - return Utils.exec("bash -c whoami"); - } - return v; - }), - }), - ], - }), - Widget.Box({ - class_name: "power-menu-container dashboard-card", - vertical: true, - vexpand: true, - children: [ - Widget.Button({ - class_name: "dashboard-button shutdown", - on_clicked: () => handleClick("shutdown"), - tooltip_text: "Shut Down", - vexpand: true, - child: Widget.Icon(icons.powermenu.shutdown), - }), - Widget.Button({ - class_name: "dashboard-button restart", - on_clicked: () => handleClick("reboot"), - tooltip_text: "Restart", - vexpand: true, - child: Widget.Icon(icons.powermenu.reboot), - }), - Widget.Button({ - class_name: "dashboard-button lock", - on_clicked: () => handleClick("logout"), - tooltip_text: "Log Out", - vexpand: true, - child: Widget.Icon(icons.powermenu.logout), - }), - Widget.Button({ - class_name: "dashboard-button sleep", - on_clicked: () => handleClick("sleep"), - tooltip_text: "Sleep", - vexpand: true, - child: Widget.Icon(icons.powermenu.sleep), - }), - ], - }), - ], - }); -}; - -export { Profile }; diff --git a/modules/menus/dashboard/profile/index.ts b/modules/menus/dashboard/profile/index.ts new file mode 100644 index 0000000..e17e925 --- /dev/null +++ b/modules/menus/dashboard/profile/index.ts @@ -0,0 +1,80 @@ +import icons from "../../../icons/index.js"; +import powermenu from "../../power/helpers/actions.js"; +import { PowerOptions } from "lib/types/options.js"; + +import options from "options"; +const { image, name } = options.menus.dashboard.powermenu.avatar; + +const Profile = () => { + const handleClick = (action: PowerOptions) => { + App.closeWindow("dashboardmenu"); + return powermenu.action(action); + }; + + return Widget.Box({ + class_name: "profiles-container", + hpack: "fill", + hexpand: true, + children: [ + Widget.Box({ + class_name: "profile-picture-container dashboard-card", + hexpand: true, + vertical: true, + children: [ + Widget.Icon({ + hpack: "center", + class_name: "profile-picture", + icon: image.bind("value"), + }), + Widget.Label({ + hpack: "center", + class_name: "profile-name", + label: name.bind("value").as((v) => { + if (v === "system") { + return Utils.exec("bash -c whoami"); + } + return v; + }), + }), + ], + }), + Widget.Box({ + class_name: "power-menu-container dashboard-card", + vertical: true, + vexpand: true, + children: [ + Widget.Button({ + class_name: "dashboard-button shutdown", + on_clicked: () => handleClick("shutdown"), + tooltip_text: "Shut Down", + vexpand: true, + child: Widget.Icon(icons.powermenu.shutdown), + }), + Widget.Button({ + class_name: "dashboard-button restart", + on_clicked: () => handleClick("reboot"), + tooltip_text: "Restart", + vexpand: true, + child: Widget.Icon(icons.powermenu.reboot), + }), + Widget.Button({ + class_name: "dashboard-button lock", + on_clicked: () => handleClick("logout"), + tooltip_text: "Log Out", + vexpand: true, + child: Widget.Icon(icons.powermenu.logout), + }), + Widget.Button({ + class_name: "dashboard-button sleep", + on_clicked: () => handleClick("sleep"), + tooltip_text: "Sleep", + vexpand: true, + child: Widget.Icon(icons.powermenu.sleep), + }), + ], + }), + ], + }); +}; + +export { Profile }; diff --git a/modules/menus/dashboard/shortcuts/index.js b/modules/menus/dashboard/shortcuts/index.ts similarity index 98% rename from modules/menus/dashboard/shortcuts/index.js rename to modules/menus/dashboard/shortcuts/index.ts index 5648839..7c744c3 100644 --- a/modules/menus/dashboard/shortcuts/index.js +++ b/modules/menus/dashboard/shortcuts/index.ts @@ -16,16 +16,12 @@ const Shortcuts = () => { }, ], }); - const handleClick = (action, resolver, tOut = 250) => { + const handleClick = (action: any, tOut: number = 250) => { App.closeWindow("dashboardmenu"); setTimeout(() => { Utils.execAsync(action) .then((res) => { - if (typeof resolver === "function") { - return resolver(res); - } - return res; }) .catch((err) => err); diff --git a/modules/menus/dashboard/stats/index.js b/modules/menus/dashboard/stats/index.ts similarity index 97% rename from modules/menus/dashboard/stats/index.js rename to modules/menus/dashboard/stats/index.ts index 381f5b3..ca8c54c 100644 --- a/modules/menus/dashboard/stats/index.js +++ b/modules/menus/dashboard/stats/index.ts @@ -1,11 +1,12 @@ import options from "options"; +import { GPU_Stat } from "lib/types/gpustat"; const { terminal } = options; const Stats = () => { const divide = ([total, free]) => free / total; - const formatSizeInGB = (sizeInKB) => + const formatSizeInGB = (sizeInKB: number) => Number((sizeInKB / 1024 ** 2).toFixed(2)); const cpu = Variable(0, { @@ -73,8 +74,10 @@ const Stats = () => { const totalGpu = 100; const usedGpu = - data.gpus.reduce((acc, gpu) => acc + gpu["utilization.gpu"], 0) / - data.gpus.length; + data.gpus.reduce((acc: number, gpu: GPU_Stat) => { + + return acc + gpu["utilization.gpu"] + }, 0) / data.gpus.length; return divide([totalGpu, usedGpu]); } catch (e) { diff --git a/modules/menus/energy/brightness/index.js b/modules/menus/energy/brightness/index.js deleted file mode 100644 index a9b6c84..0000000 --- a/modules/menus/energy/brightness/index.js +++ /dev/null @@ -1,59 +0,0 @@ -import brightness from "../../../../services/Brightness.js"; -import icons from "../../../icons/index.js"; - -const Brightness = () => { - return Widget.Box({ - class_name: "menu-section-container brightness", - vertical: true, - children: [ - Widget.Box({ - class_name: "menu-label-container", - hpack: "fill", - child: Widget.Label({ - class_name: "menu-label", - hexpand: true, - hpack: "start", - label: "Brightness", - }), - }), - Widget.Box({ - class_name: "menu-items-section", - vpack: "fill", - vexpand: true, - vertical: true, - child: Widget.Box({ - class_name: "brightness-container", - children: [ - Widget.Icon({ - vexpand: true, - vpack: "center", - class_name: "brightness-slider-icon", - icon: icons.brightness.screen, - }), - Widget.Slider({ - vpack: "center", - vexpand: true, - value: brightness.bind("screen_value"), - class_name: "menu-active-slider menu-slider brightness", - draw_value: false, - hexpand: true, - min: 0, - max: 1, - onChange: ({ value }) => (brightness.screen_value = value), - }), - Widget.Label({ - vpack: "center", - vexpand: true, - class_name: "brightness-slider-label", - label: brightness - .bind("screen_value") - .as((b) => `${Math.floor(b * 100)}%`), - }), - ], - }), - }), - ], - }); -}; - -export { Brightness }; diff --git a/modules/menus/energy/brightness/index.ts b/modules/menus/energy/brightness/index.ts new file mode 100644 index 0000000..c375375 --- /dev/null +++ b/modules/menus/energy/brightness/index.ts @@ -0,0 +1,59 @@ +import brightness from "../../../../services/Brightness.js"; +import icons from "../../../icons/index.js"; + +const Brightness = () => { + return Widget.Box({ + class_name: "menu-section-container brightness", + vertical: true, + children: [ + Widget.Box({ + class_name: "menu-label-container", + hpack: "fill", + child: Widget.Label({ + class_name: "menu-label", + hexpand: true, + hpack: "start", + label: "Brightness", + }), + }), + Widget.Box({ + class_name: "menu-items-section", + vpack: "fill", + vexpand: true, + vertical: true, + child: Widget.Box({ + class_name: "brightness-container", + children: [ + Widget.Icon({ + vexpand: true, + vpack: "center", + class_name: "brightness-slider-icon", + icon: icons.brightness.screen, + }), + Widget.Slider({ + vpack: "center", + vexpand: true, + value: brightness.bind("screen_value"), + class_name: "menu-active-slider menu-slider brightness", + draw_value: false, + hexpand: true, + min: 0, + max: 1, + onChange: ({ value }) => (brightness.screen_value = value), + }), + Widget.Label({ + vpack: "center", + vexpand: true, + class_name: "brightness-slider-label", + label: brightness + .bind("screen_value") + .as((b) => `${Math.floor(b * 100)}%`), + }), + ], + }), + }), + ], + }); +}; + +export { Brightness }; diff --git a/modules/menus/energy/index.js b/modules/menus/energy/index.js deleted file mode 100644 index dab0347..0000000 --- a/modules/menus/energy/index.js +++ /dev/null @@ -1,25 +0,0 @@ -import DropdownMenu from "../DropdownMenu.js"; -import { EnergyProfiles } from "./profiles/index.js"; -import { Brightness } from "./brightness/index.js"; - -export default () => { - return DropdownMenu({ - name: "energymenu", - transition: "crossfade", - child: Widget.Box({ - class_name: "menu-items energy", - hpack: "fill", - hexpand: true, - child: Widget.Box({ - vertical: true, - hpack: "fill", - hexpand: true, - class_name: "menu-items-container energy", - children: [ - Brightness(), - EnergyProfiles(), - ], - }), - }), - }); -}; diff --git a/modules/menus/energy/index.ts b/modules/menus/energy/index.ts new file mode 100644 index 0000000..514f8a1 --- /dev/null +++ b/modules/menus/energy/index.ts @@ -0,0 +1,25 @@ +import DropdownMenu from "../DropdownMenu.js"; +import { EnergyProfiles } from "./profiles/index.js"; +import { Brightness } from "./brightness/index.js"; + +export default () => { + return DropdownMenu({ + name: "energymenu", + transition: "crossfade", + child: Widget.Box({ + class_name: "menu-items energy", + hpack: "fill", + hexpand: true, + child: Widget.Box({ + vertical: true, + hpack: "fill", + hexpand: true, + class_name: "menu-items-container energy", + children: [ + Brightness(), + EnergyProfiles(), + ], + }), + }), + }); +}; diff --git a/modules/menus/energy/profiles/index.js b/modules/menus/energy/profiles/index.js deleted file mode 100644 index 077076b..0000000 --- a/modules/menus/energy/profiles/index.js +++ /dev/null @@ -1,58 +0,0 @@ -const powerProfiles = await Service.import("powerprofiles"); -import icons from "../../../icons/index.js"; - -const EnergyProfiles = () => { - return Widget.Box({ - class_name: "menu-section-container energy", - vertical: true, - children: [ - Widget.Box({ - class_name: "menu-label-container", - hpack: "fill", - child: Widget.Label({ - class_name: "menu-label", - hexpand: true, - hpack: "start", - label: "Power Profile", - }), - }), - Widget.Box({ - class_name: "menu-items-section", - vpack: "fill", - vexpand: true, - vertical: true, - children: powerProfiles.bind("profiles").as((profiles) => { - return profiles.map((prof) => { - const ProfileLabels = { - "power-saver": "Power Saver", - balanced: "Balanced", - performance: "Performance", - }; - return Widget.Button({ - on_primary_click: () => { - powerProfiles.active_profile = prof.Profile; - }, - class_name: powerProfiles.bind("active_profile").as((active) => { - return `power-profile-item ${active === prof.Profile ? "active" : ""}`; - }), - child: Widget.Box({ - children: [ - Widget.Icon({ - class_name: "power-profile-icon", - icon: icons.powerprofile[prof.Profile], - }), - Widget.Label({ - class_name: "power-profile-label", - label: ProfileLabels[prof.Profile], - }), - ], - }), - }); - }); - }), - }), - ], - }); -}; - -export { EnergyProfiles }; diff --git a/modules/menus/energy/profiles/index.ts b/modules/menus/energy/profiles/index.ts new file mode 100644 index 0000000..80ba93f --- /dev/null +++ b/modules/menus/energy/profiles/index.ts @@ -0,0 +1,58 @@ +const powerProfiles = await Service.import("powerprofiles"); +import icons from "../../../icons/index.js"; + +const EnergyProfiles = () => { + return Widget.Box({ + class_name: "menu-section-container energy", + vertical: true, + children: [ + Widget.Box({ + class_name: "menu-label-container", + hpack: "fill", + child: Widget.Label({ + class_name: "menu-label", + hexpand: true, + hpack: "start", + label: "Power Profile", + }), + }), + Widget.Box({ + class_name: "menu-items-section", + vpack: "fill", + vexpand: true, + vertical: true, + children: powerProfiles.bind("profiles").as((profiles) => { + return profiles.map((prof) => { + const ProfileLabels = { + "power-saver": "Power Saver", + balanced: "Balanced", + performance: "Performance", + }; + return Widget.Button({ + on_primary_click: () => { + powerProfiles.active_profile = prof.Profile; + }, + class_name: powerProfiles.bind("active_profile").as((active) => { + return `power-profile-item ${active === prof.Profile ? "active" : ""}`; + }), + child: Widget.Box({ + children: [ + Widget.Icon({ + class_name: "power-profile-icon", + icon: icons.powerprofile[prof.Profile], + }), + Widget.Label({ + class_name: "power-profile-label", + label: ProfileLabels[prof.Profile], + }), + ], + }), + }); + }); + }), + }), + ], + }); +}; + +export { EnergyProfiles }; diff --git a/modules/menus/main.js b/modules/menus/main.ts similarity index 71% rename from modules/menus/main.js rename to modules/menus/main.ts index dd09519..8a2de30 100644 --- a/modules/menus/main.js +++ b/modules/menus/main.ts @@ -10,14 +10,14 @@ import EnergyMenu from "./energy/index.js"; import DashboardMenu from "./dashboard/index.js"; export default [ - PowerMenu(), - Verification(), - AudioMenu(), - NetworkMenu(), - BluetoothMenu(), - MediaMenu(), - NotificationsMenu(), - CalendarMenu(), - EnergyMenu(), - DashboardMenu(), + PowerMenu(), + Verification(), + AudioMenu(), + NetworkMenu(), + BluetoothMenu(), + MediaMenu(), + NotificationsMenu(), + CalendarMenu(), + EnergyMenu(), + DashboardMenu(), ]; diff --git a/modules/menus/media/components/bar.js b/modules/menus/media/components/bar.js deleted file mode 100644 index 122dd68..0000000 --- a/modules/menus/media/components/bar.js +++ /dev/null @@ -1,67 +0,0 @@ -const media = await Service.import("mpris"); - -const Bar = (getPlayerInfo) => { - return Widget.Box({ - class_name: "media-indicator-current-progress-bar", - hexpand: true, - children: [ - Widget.Box({ - hexpand: true, - child: Widget.Slider({ - hexpand: true, - tooltip_text: "--", - class_name: "menu-slider media progress", - draw_value: false, - on_change: ({ value }) => { - const foundPlayer = getPlayerInfo(media); - if (foundPlayer === undefined) { - return; - } - return (foundPlayer.position = value * foundPlayer.length); - }, - setup: (self) => { - const update = () => { - const foundPlayer = getPlayerInfo(media); - if (foundPlayer !== undefined) { - const value = foundPlayer.position / foundPlayer.length; - self.value = value > 0 ? value : 0; - } else { - self.value = 0; - } - }; - self.hook(media, update); - self.poll(1000, update); - - function updateTooltip() { - const foundPlayer = getPlayerInfo(media); - if (foundPlayer === undefined) { - return self.tooltip_text = '00:00' - } - const curHour = Math.floor(foundPlayer.position / 3600); - const curMin = Math.floor((foundPlayer.position % 3600) / 60); - const curSec = Math.floor(foundPlayer.position % 60); - - if ( - typeof foundPlayer.position === "number" && - foundPlayer.position >= 0 - ) { - // WARN: These nested ternaries are absolutely disgusting lol - self.tooltip_text = `${ - curHour > 0 - ? (curHour < 10 ? "0" + curHour : curHour) + ":" - : "" - }${curMin < 10 ? "0" + curMin : curMin}:${curSec < 10 ? "0" + curSec : curSec}`; - } else { - self.tooltip_text = `00:00`; - } - } - self.poll(1000, updateTooltip); - self.hook(media, updateTooltip); - }, - }), - }), - ], - }); -}; - -export { Bar }; diff --git a/modules/menus/media/components/bar.ts b/modules/menus/media/components/bar.ts new file mode 100644 index 0000000..75914ad --- /dev/null +++ b/modules/menus/media/components/bar.ts @@ -0,0 +1,66 @@ +const media = await Service.import("mpris"); + +const Bar = (getPlayerInfo: Function) => { + return Widget.Box({ + class_name: "media-indicator-current-progress-bar", + hexpand: true, + children: [ + Widget.Box({ + hexpand: true, + child: Widget.Slider({ + hexpand: true, + tooltip_text: "--", + class_name: "menu-slider media progress", + draw_value: false, + on_change: ({ value }) => { + const foundPlayer = getPlayerInfo(media); + if (foundPlayer === undefined) { + return; + } + return (foundPlayer.position = value * foundPlayer.length); + }, + setup: (self) => { + const update = () => { + const foundPlayer = getPlayerInfo(media); + if (foundPlayer !== undefined) { + const value = foundPlayer.position / foundPlayer.length; + self.value = value > 0 ? value : 0; + } else { + self.value = 0; + } + }; + self.hook(media, update); + self.poll(1000, update); + + function updateTooltip() { + const foundPlayer = getPlayerInfo(media); + if (foundPlayer === undefined) { + return self.tooltip_text = '00:00' + } + const curHour = Math.floor(foundPlayer.position / 3600); + const curMin = Math.floor((foundPlayer.position % 3600) / 60); + const curSec = Math.floor(foundPlayer.position % 60); + + if ( + typeof foundPlayer.position === "number" && + foundPlayer.position >= 0 + ) { + // WARN: These nested ternaries are absolutely disgusting lol + self.tooltip_text = `${curHour > 0 + ? (curHour < 10 ? "0" + curHour : curHour) + ":" + : "" + }${curMin < 10 ? "0" + curMin : curMin}:${curSec < 10 ? "0" + curSec : curSec}`; + } else { + self.tooltip_text = `00:00`; + } + } + self.poll(1000, updateTooltip); + self.hook(media, updateTooltip); + }, + }), + }), + ], + }); +}; + +export { Bar }; diff --git a/modules/menus/media/components/controls.js b/modules/menus/media/components/controls.js deleted file mode 100644 index c8f51e1..0000000 --- a/modules/menus/media/components/controls.js +++ /dev/null @@ -1,187 +0,0 @@ -import icons from "../../../icons/index.js"; -const media = await Service.import("mpris"); - -const Controls = (getPlayerInfo) => { - const isLoopActive = (player) => { - return player["loop-status"] !== null && - ["track", "playlist"].includes(player["loop-status"].toLowerCase()) - ? "active" - : ""; - }; - - const isShuffleActive = (player) => { - return player["shuffle-status"] !== null && player["shuffle-status"] - ? "active" - : ""; - }; - - return Widget.Box({ - class_name: "media-indicator-current-player-controls", - vertical: true, - children: [ - Widget.Box({ - class_name: "media-indicator-current-controls", - hpack: "center", - children: [ - Widget.Box({ - class_name: "media-indicator-control shuffle", - children: [ - Widget.Button({ - hpack: "center", - hasTooltip: true, - setup: (self) => { - self.hook(media, () => { - const foundPlayer = getPlayerInfo(); - if (foundPlayer === undefined) { - self.tooltip_text = "Unavailable"; - self.class_name = - "media-indicator-control-button shuffle disabled"; - return; - } - - self.tooltip_text = - foundPlayer.shuffle_status !== null - ? foundPlayer.shuffle_status - ? "Shuffling" - : "Not Shuffling" - : null; - self.on_primary_click = () => foundPlayer.shuffle(); - self.class_name = `media-indicator-control-button shuffle ${isShuffleActive(foundPlayer)} ${foundPlayer.shuffle_status !== null ? "enabled" : "disabled"}`; - }); - }, - child: Widget.Icon(icons.mpris.shuffle["enabled"]), - }), - ], - }), - Widget.Box({ - children: [ - Widget.Button({ - hpack: "center", - child: Widget.Icon(icons.mpris.prev), - setup: (self) => { - self.hook(media, () => { - const foundPlayer = getPlayerInfo(); - if (foundPlayer === undefined) { - self.class_name = - "media-indicator-control-button prev disabled"; - return; - } - - self.on_primary_click = () => foundPlayer.previous(); - self.class_name = `media-indicator-control-button prev ${foundPlayer.can_go_prev !== null && foundPlayer.can_go_prev ? "enabled" : "disabled"}`; - }); - }, - }), - ], - }), - Widget.Box({ - children: [ - Widget.Button({ - hpack: "center", - setup: (self) => { - self.hook(media, () => { - const foundPlayer = getPlayerInfo(); - if (foundPlayer === undefined) { - self.class_name = - "media-indicator-control-button play disabled"; - return; - } - - self.on_primary_click = () => foundPlayer.playPause(); - self.class_name = `media-indicator-control-button play ${foundPlayer.can_play !== null ? "enabled" : "disabled"}`; - }); - }, - child: Widget.Icon({ - icon: Utils.watch( - icons.mpris.paused, - media, - "changed", - () => { - const foundPlayer = getPlayerInfo(); - if (foundPlayer === undefined) { - return icons.mpris["paused"]; - } - return icons.mpris[ - foundPlayer.play_back_status.toLowerCase() - ]; - }, - ), - }), - }), - ], - }), - Widget.Box({ - class_name: `media-indicator-control next`, - children: [ - Widget.Button({ - hpack: "center", - child: Widget.Icon(icons.mpris.next), - setup: (self) => { - self.hook(media, () => { - const foundPlayer = getPlayerInfo(); - if (foundPlayer === undefined) { - self.class_name = - "media-indicator-control-button next disabled"; - return; - } - - self.on_primary_click = () => foundPlayer.next(); - self.class_name = `media-indicator-control-button next ${foundPlayer.can_go_next !== null && foundPlayer.can_go_next ? "enabled" : "disabled"}`; - }); - }, - }), - ], - }), - Widget.Box({ - class_name: "media-indicator-control loop", - children: [ - Widget.Button({ - hpack: "center", - setup: (self) => { - self.hook(media, () => { - const foundPlayer = getPlayerInfo(); - if (foundPlayer === undefined) { - self.tooltip_text = "Unavailable"; - self.class_name = - "media-indicator-control-button shuffle disabled"; - return; - } - - self.tooltip_text = - foundPlayer.loop_status !== null - ? foundPlayer.loop_status - ? "Shuffling" - : "Not Shuffling" - : null; - self.on_primary_click = () => foundPlayer.loop(); - self.class_name = `media-indicator-control-button loop ${isLoopActive(foundPlayer)} ${foundPlayer.loop_status !== null ? "enabled" : "disabled"}`; - }); - }, - child: Widget.Icon({ - setup: (self) => { - self.hook(media, () => { - const foundPlayer = getPlayerInfo(); - if (foundPlayer === undefined) { - self.icon = icons.mpris.loop["none"]; - return; - } - - self.icon = - foundPlayer.loop_status === null - ? icons.mpris.loop["none"] - : icons.mpris.loop[ - foundPlayer.loop_status?.toLowerCase() - ]; - }); - }, - }), - }), - ], - }), - ], - }), - ], - }); -}; - -export { Controls }; diff --git a/modules/menus/media/components/controls.ts b/modules/menus/media/components/controls.ts new file mode 100644 index 0000000..54471f5 --- /dev/null +++ b/modules/menus/media/components/controls.ts @@ -0,0 +1,188 @@ +import { MprisPlayer } from "types/service/mpris.js"; +import icons from "../../../icons/index.js"; +const media = await Service.import("mpris"); + +const Controls = (getPlayerInfo: Function) => { + const isLoopActive = (player: MprisPlayer) => { + return player["loop-status"] !== null && + ["track", "playlist"].includes(player["loop-status"].toLowerCase()) + ? "active" + : ""; + }; + + const isShuffleActive = (player: MprisPlayer) => { + return player["shuffle-status"] !== null && player["shuffle-status"] + ? "active" + : ""; + }; + + return Widget.Box({ + class_name: "media-indicator-current-player-controls", + vertical: true, + children: [ + Widget.Box({ + class_name: "media-indicator-current-controls", + hpack: "center", + children: [ + Widget.Box({ + class_name: "media-indicator-control shuffle", + children: [ + Widget.Button({ + hpack: "center", + hasTooltip: true, + setup: (self) => { + self.hook(media, () => { + const foundPlayer = getPlayerInfo(); + if (foundPlayer === undefined) { + self.tooltip_text = "Unavailable"; + self.class_name = + "media-indicator-control-button shuffle disabled"; + return; + } + + self.tooltip_text = + foundPlayer.shuffle_status !== null + ? foundPlayer.shuffle_status + ? "Shuffling" + : "Not Shuffling" + : null; + self.on_primary_click = () => foundPlayer.shuffle(); + self.class_name = `media-indicator-control-button shuffle ${isShuffleActive(foundPlayer)} ${foundPlayer.shuffle_status !== null ? "enabled" : "disabled"}`; + }); + }, + child: Widget.Icon(icons.mpris.shuffle["enabled"]), + }), + ], + }), + Widget.Box({ + children: [ + Widget.Button({ + hpack: "center", + child: Widget.Icon(icons.mpris.prev), + setup: (self) => { + self.hook(media, () => { + const foundPlayer = getPlayerInfo(); + if (foundPlayer === undefined) { + self.class_name = + "media-indicator-control-button prev disabled"; + return; + } + + self.on_primary_click = () => foundPlayer.previous(); + self.class_name = `media-indicator-control-button prev ${foundPlayer.can_go_prev !== null && foundPlayer.can_go_prev ? "enabled" : "disabled"}`; + }); + }, + }), + ], + }), + Widget.Box({ + children: [ + Widget.Button({ + hpack: "center", + setup: (self) => { + self.hook(media, () => { + const foundPlayer = getPlayerInfo(); + if (foundPlayer === undefined) { + self.class_name = + "media-indicator-control-button play disabled"; + return; + } + + self.on_primary_click = () => foundPlayer.playPause(); + self.class_name = `media-indicator-control-button play ${foundPlayer.can_play !== null ? "enabled" : "disabled"}`; + }); + }, + child: Widget.Icon({ + icon: Utils.watch( + icons.mpris.paused, + media, + "changed", + () => { + const foundPlayer = getPlayerInfo(); + if (foundPlayer === undefined) { + return icons.mpris["paused"]; + } + return icons.mpris[ + foundPlayer.play_back_status.toLowerCase() + ]; + }, + ), + }), + }), + ], + }), + Widget.Box({ + class_name: `media-indicator-control next`, + children: [ + Widget.Button({ + hpack: "center", + child: Widget.Icon(icons.mpris.next), + setup: (self) => { + self.hook(media, () => { + const foundPlayer = getPlayerInfo(); + if (foundPlayer === undefined) { + self.class_name = + "media-indicator-control-button next disabled"; + return; + } + + self.on_primary_click = () => foundPlayer.next(); + self.class_name = `media-indicator-control-button next ${foundPlayer.can_go_next !== null && foundPlayer.can_go_next ? "enabled" : "disabled"}`; + }); + }, + }), + ], + }), + Widget.Box({ + class_name: "media-indicator-control loop", + children: [ + Widget.Button({ + hpack: "center", + setup: (self) => { + self.hook(media, () => { + const foundPlayer = getPlayerInfo(); + if (foundPlayer === undefined) { + self.tooltip_text = "Unavailable"; + self.class_name = + "media-indicator-control-button shuffle disabled"; + return; + } + + self.tooltip_text = + foundPlayer.loop_status !== null + ? foundPlayer.loop_status + ? "Shuffling" + : "Not Shuffling" + : null; + self.on_primary_click = () => foundPlayer.loop(); + self.class_name = `media-indicator-control-button loop ${isLoopActive(foundPlayer)} ${foundPlayer.loop_status !== null ? "enabled" : "disabled"}`; + }); + }, + child: Widget.Icon({ + setup: (self) => { + self.hook(media, () => { + const foundPlayer = getPlayerInfo(); + if (foundPlayer === undefined) { + self.icon = icons.mpris.loop["none"]; + return; + } + + self.icon = + foundPlayer.loop_status === null + ? icons.mpris.loop["none"] + : icons.mpris.loop[ + foundPlayer.loop_status?.toLowerCase() + ]; + }); + }, + }), + }), + ], + }), + ], + }), + ], + }); +}; + +export { Controls }; diff --git a/modules/menus/media/components/mediainfo.js b/modules/menus/media/components/mediainfo.js deleted file mode 100644 index 8237b8e..0000000 --- a/modules/menus/media/components/mediainfo.js +++ /dev/null @@ -1,85 +0,0 @@ -const media = await Service.import("mpris"); - -const MediaInfo = (getPlayerInfo) => { - return Widget.Box({ - class_name: "media-indicator-current-media-info", - hpack: "center", - hexpand: true, - vertical: true, - children: [ - Widget.Box({ - class_name: "media-indicator-current-song-name", - hpack: "center", - children: [ - Widget.Label({ - truncate: "end", - max_width_chars: 31, - wrap: true, - class_name: "media-indicator-current-song-name-label", - setup: (self) => { - self.hook(media, () => { - const curPlayer = getPlayerInfo(); - return (self.label = - curPlayer !== undefined && curPlayer["track-title"].length - ? curPlayer["track-title"] - : "No Media Currently Playing"); - }); - }, - }), - ], - }), - Widget.Box({ - class_name: "media-indicator-current-song-author", - hpack: "center", - children: [ - Widget.Label({ - truncate: "end", - wrap: true, - max_width_chars: 35, - class_name: "media-indicator-current-song-author-label", - setup: (self) => { - self.hook(media, () => { - const curPlayer = getPlayerInfo(); - - const makeArtistList = (trackArtists) => { - if (trackArtists.length === 1 && !trackArtists[0].length) { - return "-----"; - } - - return trackArtists.join(", "); - }; - return (self.label = - curPlayer !== undefined && curPlayer["track-artists"].length - ? makeArtistList(curPlayer["track-artists"]) - : "-----"); - }); - }, - }), - ], - }), - Widget.Box({ - class_name: "media-indicator-current-song-album", - hpack: "center", - children: [ - Widget.Label({ - truncate: "end", - wrap: true, - max_width_chars: 40, - class_name: "media-indicator-current-song-album-label", - setup: (self) => { - self.hook(media, () => { - const curPlayer = getPlayerInfo(); - return (self.label = - curPlayer !== undefined && curPlayer["track-album"].length - ? curPlayer["track-album"] - : "---"); - }); - }, - }), - ], - }), - ], - }); -}; - -export { MediaInfo }; diff --git a/modules/menus/media/components/mediainfo.ts b/modules/menus/media/components/mediainfo.ts new file mode 100644 index 0000000..b8a64aa --- /dev/null +++ b/modules/menus/media/components/mediainfo.ts @@ -0,0 +1,85 @@ +const media = await Service.import("mpris"); + +const MediaInfo = (getPlayerInfo: Function) => { + return Widget.Box({ + class_name: "media-indicator-current-media-info", + hpack: "center", + hexpand: true, + vertical: true, + children: [ + Widget.Box({ + class_name: "media-indicator-current-song-name", + hpack: "center", + children: [ + Widget.Label({ + truncate: "end", + max_width_chars: 31, + wrap: true, + class_name: "media-indicator-current-song-name-label", + setup: (self) => { + self.hook(media, () => { + const curPlayer = getPlayerInfo(); + return (self.label = + curPlayer !== undefined && curPlayer["track-title"].length + ? curPlayer["track-title"] + : "No Media Currently Playing"); + }); + }, + }), + ], + }), + Widget.Box({ + class_name: "media-indicator-current-song-author", + hpack: "center", + children: [ + Widget.Label({ + truncate: "end", + wrap: true, + max_width_chars: 35, + class_name: "media-indicator-current-song-author-label", + setup: (self) => { + self.hook(media, () => { + const curPlayer = getPlayerInfo(); + + const makeArtistList = (trackArtists: string[]) => { + if (trackArtists.length === 1 && !trackArtists[0].length) { + return "-----"; + } + + return trackArtists.join(", "); + }; + return (self.label = + curPlayer !== undefined && curPlayer["track-artists"].length + ? makeArtistList(curPlayer["track-artists"]) + : "-----"); + }); + }, + }), + ], + }), + Widget.Box({ + class_name: "media-indicator-current-song-album", + hpack: "center", + children: [ + Widget.Label({ + truncate: "end", + wrap: true, + max_width_chars: 40, + class_name: "media-indicator-current-song-album-label", + setup: (self) => { + self.hook(media, () => { + const curPlayer = getPlayerInfo(); + return (self.label = + curPlayer !== undefined && curPlayer["track-album"].length + ? curPlayer["track-album"] + : "---"); + }); + }, + }), + ], + }), + ], + }); +}; + +export { MediaInfo }; diff --git a/modules/menus/media/index.js b/modules/menus/media/index.ts similarity index 100% rename from modules/menus/media/index.js rename to modules/menus/media/index.ts diff --git a/modules/menus/media/media.js b/modules/menus/media/media.js deleted file mode 100644 index 8ec5b62..0000000 --- a/modules/menus/media/media.js +++ /dev/null @@ -1,75 +0,0 @@ -const media = await Service.import("mpris"); -import { MediaInfo } from "./components/mediainfo.js"; -import { Controls } from "./components/controls.js"; -import { Bar } from "./components/bar.js"; - -const Media = () => { - const curPlayer = Variable(""); - - media.connect("changed", () => { - const statusOrder = { - Playing: 1, - Paused: 2, - Stopped: 3, - }; - - const isPlaying = media.players.find( - (p) => p["play-back-status"] === "Playing", - ); - - if (isPlaying) { - curPlayer.value = media.players.sort( - (a, b) => - statusOrder[a["play-back-status"]] - - statusOrder[b["play-back-status"]], - )[0].name; - } - }); - - const getPlayerInfo = () => { - return media.players.find((p) => p.name === curPlayer.value); - }; - - return Widget.Box({ - class_name: "menu-section-container", - children: [ - Widget.Box({ - class_name: "menu-items-section", - vertical: false, - child: Widget.Box({ - class_name: "menu-content", - children: [ - Widget.Box({ - class_name: "media-content", - child: Widget.Box({ - class_name: "media-indicator-right-section", - hpack: "fill", - hexpand: true, - vertical: true, - children: [ - MediaInfo(getPlayerInfo), - Controls(getPlayerInfo), - Bar(getPlayerInfo), - ], - }), - }), - ], - setup: (self) => { - self.hook(media, () => { - const curPlayer = getPlayerInfo(); - if (curPlayer !== undefined) { - self.css = `background-image: linear-gradient( - rgba(30, 30, 46, 0.85), - rgba(30, 30, 46, 0.9), - #1e1e2e 40em), url("${curPlayer.track_cover_url}"); - `; - } - }); - }, - }), - }), - ], - }); -}; - -export { Media }; diff --git a/modules/menus/media/media.ts b/modules/menus/media/media.ts new file mode 100644 index 0000000..959157d --- /dev/null +++ b/modules/menus/media/media.ts @@ -0,0 +1,76 @@ +const media = await Service.import("mpris"); +import { MediaInfo } from "./components/mediainfo.js"; +import { Controls } from "./components/controls.js"; +import { Bar } from "./components/bar.js"; +import { MprisPlayer } from "types/service/mpris.js"; + +const Media = () => { + const curPlayer = Variable(""); + + media.connect("changed", () => { + const statusOrder = { + Playing: 1, + Paused: 2, + Stopped: 3, + }; + + const isPlaying = media.players.find( + (p) => p["play-back-status"] === "Playing", + ); + + if (isPlaying) { + curPlayer.value = media.players.sort( + (a, b) => + statusOrder[a["play-back-status"]] - + statusOrder[b["play-back-status"]], + )[0].name; + } + }); + + const getPlayerInfo = (): MprisPlayer => { + return media.players.find((p) => p.name === curPlayer.value) || media.players[0]; + }; + + return Widget.Box({ + class_name: "menu-section-container", + children: [ + Widget.Box({ + class_name: "menu-items-section", + vertical: false, + child: Widget.Box({ + class_name: "menu-content", + children: [ + Widget.Box({ + class_name: "media-content", + child: Widget.Box({ + class_name: "media-indicator-right-section", + hpack: "fill", + hexpand: true, + vertical: true, + children: [ + MediaInfo(getPlayerInfo), + Controls(getPlayerInfo), + Bar(getPlayerInfo), + ], + }), + }), + ], + setup: (self) => { + self.hook(media, () => { + const curPlayer = getPlayerInfo(); + if (curPlayer !== undefined) { + self.css = `background-image: linear-gradient( + rgba(30, 30, 46, 0.85), + rgba(30, 30, 46, 0.9), + #1e1e2e 40em), url("${curPlayer.track_cover_url}"); + `; + } + }); + }, + }), + }), + ], + }); +}; + +export { Media }; diff --git a/modules/menus/network/ethernet/index.js b/modules/menus/network/ethernet/index.ts similarity index 100% rename from modules/menus/network/ethernet/index.js rename to modules/menus/network/ethernet/index.ts diff --git a/modules/menus/network/index.js b/modules/menus/network/index.ts similarity index 100% rename from modules/menus/network/index.js rename to modules/menus/network/index.ts diff --git a/modules/menus/network/utils.js b/modules/menus/network/utils.js deleted file mode 100644 index c105166..0000000 --- a/modules/menus/network/utils.js +++ /dev/null @@ -1,23 +0,0 @@ -const getWifiIcon = (iconName) => { - const deviceIconMap = [ - ["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 }; diff --git a/modules/menus/network/utils.ts b/modules/menus/network/utils.ts new file mode 100644 index 0000000..a46e85b --- /dev/null +++ b/modules/menus/network/utils.ts @@ -0,0 +1,23 @@ +const getWifiIcon = (iconName: string) => { + const deviceIconMap = [ + ["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 }; diff --git a/modules/menus/network/wifi/APStaging.js b/modules/menus/network/wifi/APStaging.js deleted file mode 100644 index f8bf186..0000000 --- a/modules/menus/network/wifi/APStaging.js +++ /dev/null @@ -1,97 +0,0 @@ -const renderWapStaging = (self, network, staging, connecting) => { - Utils.merge([network.bind("wifi"), staging.bind("value")], () => { - if (!Object.keys(staging.value).length) { - return (self.child = Widget.Box()); - } - - return (self.child = Widget.Box({ - class_name: "network-element-item staging", - vertical: true, - children: [ - Widget.Box({ - hpack: "fill", - hexpand: true, - children: [ - Widget.Icon({ - class_name: `network-icon wifi`, - icon: `${staging.value.iconName}`, - }), - Widget.Box({ - class_name: "connection-container", - hexpand: true, - vertical: true, - children: [ - Widget.Label({ - class_name: "active-connection", - hpack: "start", - truncate: "end", - wrap: true, - label: staging.value.ssid, - }), - ], - }), - Widget.Revealer({ - hpack: "end", - reveal_child: connecting - .bind("value") - .as((c) => staging.value.bssid === c), - child: Widget.Spinner({ - class_name: "spinner wap", - }), - }), - ], - }), - Widget.Box({ - class_name: "network-password-input-container", - hpack: "fill", - hexpand: true, - children: [ - Widget.Entry({ - hpack: "start", - hexpand: true, - visibility: false, - class_name: "network-password-input", - placeholder_text: "enter password", - onAccept: (selfInp) => { - connecting.value = staging.value.bssid; - Utils.execAsync( - `nmcli dev wifi connect ${staging.value.bssid} password ${selfInp.text}`, - ) - .catch((err) => { - connecting.value = ""; - console.error( - `Failed to connect to wifi: ${staging.value.ssid}... ${err}`, - ); - Utils.notify({ - summary: "Network", - body: err, - timeout: 5000, - }); - }) - .then(() => { - connecting.value = ""; - staging.value = {}; - }); - selfInp.text = ""; - }, - }), - Widget.Button({ - hpack: "end", - class_name: "close-network-password-input-button", - on_primary_click: () => { - connecting.value = ""; - staging.value = {}; - }, - child: Widget.Icon({ - class_name: "close-network-password-input-icon", - icon: "window-close-symbolic", - }), - }), - ], - }), - ], - })); - }); -}; - -export { renderWapStaging }; diff --git a/modules/menus/network/wifi/APStaging.ts b/modules/menus/network/wifi/APStaging.ts new file mode 100644 index 0000000..40bc5d0 --- /dev/null +++ b/modules/menus/network/wifi/APStaging.ts @@ -0,0 +1,100 @@ +import { Network } from "types/service/network"; +import { Variable } from "types/variable"; +import { AccessPoint } from "lib/types/network"; +const renderWapStaging = (self: any, network: Network, staging: Variable, connecting: Variable) => { + Utils.merge([network.bind("wifi"), staging.bind("value")], () => { + if (!Object.keys(staging.value).length) { + return (self.child = Widget.Box()); + } + + return (self.child = Widget.Box({ + class_name: "network-element-item staging", + vertical: true, + children: [ + Widget.Box({ + hpack: "fill", + hexpand: true, + children: [ + Widget.Icon({ + class_name: `network-icon wifi`, + icon: `${staging.value.iconName}`, + }), + Widget.Box({ + class_name: "connection-container", + hexpand: true, + vertical: true, + children: [ + Widget.Label({ + class_name: "active-connection", + hpack: "start", + truncate: "end", + wrap: true, + label: staging.value.ssid, + }), + ], + }), + Widget.Revealer({ + hpack: "end", + reveal_child: connecting + .bind("value") + .as((c) => staging.value.bssid === c), + child: Widget.Spinner({ + class_name: "spinner wap", + }), + }), + ], + }), + Widget.Box({ + class_name: "network-password-input-container", + hpack: "fill", + hexpand: true, + children: [ + Widget.Entry({ + hpack: "start", + hexpand: true, + visibility: false, + class_name: "network-password-input", + placeholder_text: "enter password", + onAccept: (selfInp) => { + connecting.value = staging.value.bssid || ""; + Utils.execAsync( + `nmcli dev wifi connect ${staging.value.bssid} password ${selfInp.text}`, + ) + .catch((err) => { + connecting.value = ""; + console.error( + `Failed to connect to wifi: ${staging.value.ssid}... ${err}`, + ); + Utils.notify({ + summary: "Network", + body: err, + timeout: 5000, + }); + }) + .then(() => { + connecting.value = ""; + staging.value = {} as AccessPoint; + }); + selfInp.text = ""; + }, + }), + Widget.Button({ + hpack: "end", + class_name: "close-network-password-input-button", + on_primary_click: () => { + connecting.value = ""; + staging.value = {} as AccessPoint; + }, + child: Widget.Icon({ + class_name: "close-network-password-input-icon", + icon: "window-close-symbolic", + }), + }), + ], + }), + ], + })); + }); +}; + +export { renderWapStaging }; diff --git a/modules/menus/network/wifi/WirelessAPs.js b/modules/menus/network/wifi/WirelessAPs.js deleted file mode 100644 index fb00c6f..0000000 --- a/modules/menus/network/wifi/WirelessAPs.js +++ /dev/null @@ -1,211 +0,0 @@ -import { getWifiIcon } from "../utils.js"; -const renderWAPs = (self, network, staging, connecting) => { - const getIdBySsid = (ssid, nmcliOutput) => { - 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]; - } - } - return null; - }; - - const WifiStatusMap = { - unknown: "Status Unknown", - unmanaged: "Unmanaged", - unavailable: "Unavailable", - disconnected: "Disconnected", - prepare: "Preparing Connecting", - config: "Connecting", - need_auth: "Needs Authentication", - ip_config: "Requesting IP", - ip_check: "Checking Access", - secondaries: "Waiting on Secondaries", - activated: "Connected", - deactivating: "Disconnecting", - failed: "Connection Failed", - }; - self.hook(network, () => { - Utils.merge([staging.bind("value"), connecting.bind("value")], () => { - // Sometimes the network service will yield a "this._device is undefined" when - // trying to access the "access_points" property. So we must validate that - // it's not 'undefined' - let WAPs = - network.wifi._device !== undefined ? network.wifi["access-points"] : []; - - const dedupeWAPs = () => { - const dedupMap = {}; - WAPs.forEach((item) => { - if (!Object.hasOwnProperty.call(dedupMap, item.ssid)) { - dedupMap[item.ssid] = item; - } - }); - - return Object.keys(dedupMap).map((itm) => dedupMap[itm]); - }; - - WAPs = dedupeWAPs(); - - const isInStaging = (wap) => { - if (Object.keys(staging.value).length === 0) { - return false; - } - - return wap.bssid === staging.value.bssid; - }; - - const isDisconnecting = (wap) => { - if (wap.ssid === network.wifi.ssid) { - return network.wifi.state.toLowerCase() === "deactivating"; - } - return false; - }; - - const filteredWAPs = WAPs.filter((ap) => { - return ap.ssid !== "Unknown" && !isInStaging(ap); - }).sort((a, b) => { - if (network.wifi.ssid === a.ssid) { - return -1; - } - - if (network.wifi.ssid === b.ssid) { - return 1; - } - - return b.strength - a.strength; - }); - - if (filteredWAPs.length <= 0 && Object.keys(staging.value).length === 0) { - return (self.child = Widget.Label({ - class_name: "waps-not-found dim", - expand: true, - hpack: "center", - vpack: "center", - label: "No Wi-Fi Networks Found", - })); - } - return (self.children = filteredWAPs.map((ap) => { - return Widget.Box({ - children: [ - Widget.Button({ - on_primary_click: () => { - if (ap.bssid === connecting.value || ap.active) { - return; - } - - connecting.value = ap.bssid; - Utils.execAsync(`nmcli device wifi connect ${ap.bssid}`) - .then(() => { - connecting.value = ""; - staging.value = {}; - }) - .catch((err) => { - if ( - err - .toLowerCase() - .includes("secrets were required, but not provided") - ) { - staging.value = ap; - } else { - Utils.notify({ - summary: "Network", - body: err, - timeout: 5000, - }); - } - connecting.value = ""; - }); - }, - class_name: "network-element-item", - child: Widget.Box({ - hexpand: true, - children: [ - Widget.Box({ - hpack: "start", - hexpand: true, - children: [ - Widget.Label({ - vpack: "start", - class_name: `network-icon wifi ${ap.ssid === network.wifi.ssid ? "active" : ""}`, - label: getWifiIcon(`${ap["iconName"]}`), - }), - Widget.Box({ - class_name: "connection-container", - vpack: "center", - vertical: true, - children: [ - Widget.Label({ - vpack: "center", - class_name: "active-connection", - hpack: "start", - truncate: "end", - wrap: true, - label: ap.ssid, - }), - Widget.Revealer({ - revealChild: ap.ssid === network.wifi.ssid, - child: Widget.Label({ - hpack: "start", - class_name: "connection-status dim", - label: - WifiStatusMap[network.wifi.state.toLowerCase()], - }), - }), - ], - }), - ], - }), - Widget.Revealer({ - hpack: "end", - vpack: "start", - reveal_child: - ap.bssid === connecting.value || isDisconnecting(ap), - child: Widget.Spinner({ - vpack: "start", - class_name: "spinner wap", - }), - }), - ], - }), - }), - Widget.Revealer({ - vpack: "start", - reveal_child: ap.bssid !== connecting.value && ap.active, - child: Widget.Button({ - tooltip_text: "Delete/Forget Network", - class_name: "menu-icon-button network disconnect", - on_primary_click: () => { - connecting.value = ap.bssid; - Utils.execAsync("nmcli connection show --active").then(() => { - Utils.execAsync("nmcli connection show --active").then( - (res) => { - const connectionId = getIdBySsid(ap.ssid, res); - - Utils.execAsync( - `nmcli connection delete ${connectionId} "${ap.ssid}"`, - ) - .then(() => (connecting.value = "")) - .catch((err) => { - connecting.value = ""; - console.error( - `Error while forgetting "${ap.ssid}": ${err}`, - ); - }); - }, - ); - }); - }, - child: Widget.Label({ - label: "󰚃", - }), - }), - }), - ], - }); - })); - }); - }); -}; - -export { renderWAPs }; diff --git a/modules/menus/network/wifi/WirelessAPs.ts b/modules/menus/network/wifi/WirelessAPs.ts new file mode 100644 index 0000000..3b4506b --- /dev/null +++ b/modules/menus/network/wifi/WirelessAPs.ts @@ -0,0 +1,216 @@ +import { Network } from "types/service/network.js"; +import { AccessPoint } from "lib/types/network.js"; +import { Variable } from "types/variable.js"; +import { getWifiIcon } from "../utils.js"; +const renderWAPs = (self: any, network: Network, staging: Variable, connecting: Variable) => { + const getIdBySsid = (ssid: string, nmcliOutput: string) => { + 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]; + } + } + return null; + }; + + const WifiStatusMap = { + unknown: "Status Unknown", + unmanaged: "Unmanaged", + unavailable: "Unavailable", + disconnected: "Disconnected", + prepare: "Preparing Connecting", + config: "Connecting", + need_auth: "Needs Authentication", + ip_config: "Requesting IP", + ip_check: "Checking Access", + secondaries: "Waiting on Secondaries", + activated: "Connected", + deactivating: "Disconnecting", + failed: "Connection Failed", + }; + self.hook(network, () => { + Utils.merge([staging.bind("value"), connecting.bind("value")], () => { + // Sometimes the network service will yield a "this._device is undefined" when + // trying to access the "access_points" property. So we must validate that + // it's not 'undefined' + // + // Also this is an AGS bug that needs to be fixed + let WAPs = + network.wifi._device !== undefined ? network.wifi["access-points"] : []; + + const dedupeWAPs = () => { + const dedupMap = {}; + WAPs.forEach((item: AccessPoint) => { + if (item.ssid !== null && !Object.hasOwnProperty.call(dedupMap, item.ssid)) { + dedupMap[item.ssid] = item; + } + }); + + return Object.keys(dedupMap).map((itm) => dedupMap[itm]); + }; + + WAPs = dedupeWAPs(); + + const isInStaging = (wap: AccessPoint) => { + if (Object.keys(staging.value).length === 0) { + return false; + } + + return wap.bssid === staging.value.bssid; + }; + + const isDisconnecting = (wap: AccessPoint) => { + if (wap.ssid === network.wifi.ssid) { + return network.wifi.state.toLowerCase() === "deactivating"; + } + return false; + }; + + const filteredWAPs = WAPs.filter((ap: AccessPoint) => { + return ap.ssid !== "Unknown" && !isInStaging(ap); + }).sort((a: AccessPoint, b: AccessPoint) => { + if (network.wifi.ssid === a.ssid) { + return -1; + } + + if (network.wifi.ssid === b.ssid) { + return 1; + } + + return b.strength - a.strength; + }); + + if (filteredWAPs.length <= 0 && Object.keys(staging.value).length === 0) { + return (self.child = Widget.Label({ + class_name: "waps-not-found dim", + expand: true, + hpack: "center", + vpack: "center", + label: "No Wi-Fi Networks Found", + })); + } + return (self.children = filteredWAPs.map((ap: AccessPoint) => { + return Widget.Box({ + children: [ + Widget.Button({ + on_primary_click: () => { + if (ap.bssid === connecting.value || ap.active) { + return; + } + + connecting.value = ap.bssid || ""; + Utils.execAsync(`nmcli device wifi connect ${ap.bssid}`) + .then(() => { + connecting.value = ""; + staging.value = {} as AccessPoint; + }) + .catch((err) => { + if ( + err + .toLowerCase() + .includes("secrets were required, but not provided") + ) { + staging.value = ap; + } else { + Utils.notify({ + summary: "Network", + body: err, + timeout: 5000, + }); + } + connecting.value = ""; + }); + }, + class_name: "network-element-item", + child: Widget.Box({ + hexpand: true, + children: [ + Widget.Box({ + hpack: "start", + hexpand: true, + children: [ + Widget.Label({ + vpack: "start", + class_name: `network-icon wifi ${ap.ssid === network.wifi.ssid ? "active" : ""}`, + label: getWifiIcon(`${ap["iconName"]}`), + }), + Widget.Box({ + class_name: "connection-container", + vpack: "center", + vertical: true, + children: [ + Widget.Label({ + vpack: "center", + class_name: "active-connection", + hpack: "start", + truncate: "end", + wrap: true, + label: ap.ssid, + }), + Widget.Revealer({ + revealChild: ap.ssid === network.wifi.ssid, + child: Widget.Label({ + hpack: "start", + class_name: "connection-status dim", + label: + WifiStatusMap[network.wifi.state.toLowerCase()], + }), + }), + ], + }), + ], + }), + Widget.Revealer({ + hpack: "end", + vpack: "start", + reveal_child: + ap.bssid === connecting.value || isDisconnecting(ap), + child: Widget.Spinner({ + vpack: "start", + class_name: "spinner wap", + }), + }), + ], + }), + }), + Widget.Revealer({ + vpack: "start", + reveal_child: ap.bssid !== connecting.value && ap.active, + child: Widget.Button({ + tooltip_text: "Delete/Forget Network", + class_name: "menu-icon-button network disconnect", + on_primary_click: () => { + connecting.value = ap.bssid || ""; + Utils.execAsync("nmcli connection show --active").then(() => { + Utils.execAsync("nmcli connection show --active").then( + (res) => { + const connectionId = getIdBySsid(ap.ssid || "", res); + + Utils.execAsync( + `nmcli connection delete ${connectionId} "${ap.ssid}"`, + ) + .then(() => (connecting.value = "")) + .catch((err) => { + connecting.value = ""; + console.error( + `Error while forgetting "${ap.ssid}": ${err}`, + ); + }); + }, + ); + }); + }, + child: Widget.Label({ + label: "󰚃", + }), + }), + }), + ], + }); + })); + }); + }); +}; + +export { renderWAPs }; diff --git a/modules/menus/network/wifi/index.js b/modules/menus/network/wifi/index.js deleted file mode 100644 index 1554c2f..0000000 --- a/modules/menus/network/wifi/index.js +++ /dev/null @@ -1,72 +0,0 @@ -const network = await Service.import("network"); -import { renderWAPs } from "./WirelessAPs.js"; -import { renderWapStaging } from "./APStaging.js"; - -const Staging = Variable({}); -const Connecting = Variable(""); - -const searchInProgress = Variable(false); - -const startRotation = () => { - searchInProgress.value = true; - setTimeout(() => { - searchInProgress.value = false; - }, 5 * 1000); -}; - -const Wifi = () => { - return Widget.Box({ - class_name: "menu-section-container wifi", - vertical: true, - children: [ - Widget.Box({ - class_name: "menu-label-container", - hpack: "fill", - children: [ - Widget.Label({ - class_name: "menu-label", - hexpand: true, - hpack: "start", - label: "Wi-Fi", - }), - Widget.Button({ - vpack: "center", - hpack: "end", - class_name: "menu-icon-button search network", - on_primary_click: () => { - startRotation(); - network.wifi.scan(); - }, - child: Widget.Icon({ - class_name: searchInProgress - .bind("value") - .as((v) => (v ? "spinning" : "")), - icon: "view-refresh-symbolic", - }), - }), - ], - }), - Widget.Box({ - class_name: "menu-items-section", - vertical: true, - children: [ - Widget.Box({ - class_name: "wap-staging", - setup: (self) => { - renderWapStaging(self, network, Staging, Connecting); - }, - }), - Widget.Box({ - class_name: "available-waps", - vertical: true, - setup: (self) => { - renderWAPs(self, network, Staging, Connecting); - }, - }), - ], - }), - ], - }); -}; - -export { Wifi }; diff --git a/modules/menus/network/wifi/index.ts b/modules/menus/network/wifi/index.ts new file mode 100644 index 0000000..8fedae3 --- /dev/null +++ b/modules/menus/network/wifi/index.ts @@ -0,0 +1,73 @@ +const network = await Service.import("network"); +import { renderWAPs } from "./WirelessAPs.js"; +import { renderWapStaging } from "./APStaging.js"; +import { AccessPoint } from "lib/types/network.js"; + +const Staging = Variable({} as AccessPoint); +const Connecting = Variable(""); + +const searchInProgress = Variable(false); + +const startRotation = () => { + searchInProgress.value = true; + setTimeout(() => { + searchInProgress.value = false; + }, 5 * 1000); +}; + +const Wifi = () => { + return Widget.Box({ + class_name: "menu-section-container wifi", + vertical: true, + children: [ + Widget.Box({ + class_name: "menu-label-container", + hpack: "fill", + children: [ + Widget.Label({ + class_name: "menu-label", + hexpand: true, + hpack: "start", + label: "Wi-Fi", + }), + Widget.Button({ + vpack: "center", + hpack: "end", + class_name: "menu-icon-button search network", + on_primary_click: () => { + startRotation(); + network.wifi.scan(); + }, + child: Widget.Icon({ + class_name: searchInProgress + .bind("value") + .as((v) => (v ? "spinning" : "")), + icon: "view-refresh-symbolic", + }), + }), + ], + }), + Widget.Box({ + class_name: "menu-items-section", + vertical: true, + children: [ + Widget.Box({ + class_name: "wap-staging", + setup: (self) => { + renderWapStaging(self, network, Staging, Connecting); + }, + }), + Widget.Box({ + class_name: "available-waps", + vertical: true, + setup: (self) => { + renderWAPs(self, network, Staging, Connecting); + }, + }), + ], + }), + ], + }); +}; + +export { Wifi }; diff --git a/modules/menus/notifications/controls/index.js b/modules/menus/notifications/controls/index.js deleted file mode 100644 index 4019db1..0000000 --- a/modules/menus/notifications/controls/index.js +++ /dev/null @@ -1,56 +0,0 @@ -const Controls = (notifs) => { - return Widget.Box({ - class_name: "notification-menu-controls", - expand: false, - vertical: false, - children: [ - Widget.Box({ - class_name: "menu-label-container notifications", - hpack: "start", - vpack: "center", - expand: true, - children: [ - Widget.Label({ - class_name: "menu-label notifications", - label: "Notifications", - }), - ], - }), - Widget.Box({ - hpack: "end", - vpack: "center", - expand: false, - children: [ - Widget.Switch({ - class_name: "menu-switch notifications", - active: notifs.bind("dnd").as((dnd) => !dnd), - on_activate: ({ active }) => { - notifs.dnd = !active; - }, - }), - Widget.Box({ - children: [ - Widget.Separator({ - hpack: "center", - vexpand: true, - vertical: true, - class_name: "menu-separator notification-controls", - }), - Widget.Button({ - class_name: "clear-notifications-button", - tooltip_text: "Clear Notifications", - on_primary_click: () => notifs.clear(), - child: Widget.Label({ - class_name: "clear-notifications-label", - label: "", - }), - }), - ], - }), - ], - }), - ], - }); -}; - -export { Controls }; diff --git a/modules/menus/notifications/controls/index.ts b/modules/menus/notifications/controls/index.ts new file mode 100644 index 0000000..0bbbce1 --- /dev/null +++ b/modules/menus/notifications/controls/index.ts @@ -0,0 +1,56 @@ +const Controls = (notifs) => { + return Widget.Box({ + class_name: "notification-menu-controls", + expand: false, + vertical: false, + children: [ + Widget.Box({ + class_name: "menu-label-container notifications", + hpack: "start", + vpack: "center", + expand: true, + children: [ + Widget.Label({ + class_name: "menu-label notifications", + label: "Notifications", + }), + ], + }), + Widget.Box({ + hpack: "end", + vpack: "center", + expand: false, + children: [ + Widget.Switch({ + class_name: "menu-switch notifications", + active: notifs.bind("dnd").as((dnd: boolean) => !dnd), + on_activate: ({ active }) => { + notifs.dnd = !active; + }, + }), + Widget.Box({ + children: [ + Widget.Separator({ + hpack: "center", + vexpand: true, + vertical: true, + class_name: "menu-separator notification-controls", + }), + Widget.Button({ + class_name: "clear-notifications-button", + tooltip_text: "Clear Notifications", + on_primary_click: () => notifs.clear(), + child: Widget.Label({ + class_name: "clear-notifications-label", + label: "", + }), + }), + ], + }), + ], + }), + ], + }); +}; + +export { Controls }; diff --git a/modules/menus/notifications/index.js b/modules/menus/notifications/index.ts similarity index 100% rename from modules/menus/notifications/index.js rename to modules/menus/notifications/index.ts diff --git a/modules/menus/notifications/notification/actions/index.js b/modules/menus/notifications/notification/actions/index.js deleted file mode 100644 index 4cfe857..0000000 --- a/modules/menus/notifications/notification/actions/index.js +++ /dev/null @@ -1,47 +0,0 @@ -const Actions = (notif, notifs) => { - if (notif.actions !== undefined && notif.actions.length > 0) { - return Widget.Box({ - class_name: "notification-card-actions menu", - hexpand: true, - vpack: "end", - children: notif.actions.map((action) => { - return Widget.Button({ - hexpand: true, - class_name: "notification-action-buttons menu", - on_primary_click: () => { - if (action.id.includes("scriptAction:-")) { - App.closeWindow("notificationsmenu"); - Utils.execAsync( - `${action.id.replace("scriptAction:-", "")}`, - ).catch((err) => console.error(err)); - notifs.CloseNotification(notif.id); - } else { - App.closeWindow("notificationsmenu"); - notif.invoke(action.id); - } - }, - child: Widget.Box({ - hpack: "center", - hexpand: true, - children: [ - Widget.Label({ - class_name: "notification-action-buttons-label menu", - hexpand: true, - max_width_chars: 15, - truncate: "end", - wrap: true, - label: action.label, - }), - ], - }), - }); - }), - }); - } - - return Widget.Box({ - class_name: "spacer", - }); -}; - -export { Actions }; diff --git a/modules/menus/notifications/notification/actions/index.ts b/modules/menus/notifications/notification/actions/index.ts new file mode 100644 index 0000000..3d9f2c9 --- /dev/null +++ b/modules/menus/notifications/notification/actions/index.ts @@ -0,0 +1,48 @@ +import { Notification, Notifications } from "types/service/notifications"; +const Actions = (notif: Notification, notifs: Notifications) => { + if (notif.actions !== undefined && notif.actions.length > 0) { + return Widget.Box({ + class_name: "notification-card-actions menu", + hexpand: true, + vpack: "end", + children: notif.actions.map((action) => { + return Widget.Button({ + hexpand: true, + class_name: "notification-action-buttons menu", + on_primary_click: () => { + if (action.id.includes("scriptAction:-")) { + App.closeWindow("notificationsmenu"); + Utils.execAsync( + `${action.id.replace("scriptAction:-", "")}`, + ).catch((err) => console.error(err)); + notifs.CloseNotification(notif.id); + } else { + App.closeWindow("notificationsmenu"); + notif.invoke(action.id); + } + }, + child: Widget.Box({ + hpack: "center", + hexpand: true, + children: [ + Widget.Label({ + class_name: "notification-action-buttons-label menu", + hexpand: true, + max_width_chars: 15, + truncate: "end", + wrap: true, + label: action.label, + }), + ], + }), + }); + }), + }); + } + + return Widget.Box({ + class_name: "spacer", + }); +}; + +export { Actions }; diff --git a/modules/menus/notifications/notification/body/index.js b/modules/menus/notifications/notification/body/index.js deleted file mode 100644 index 99e864c..0000000 --- a/modules/menus/notifications/notification/body/index.js +++ /dev/null @@ -1,23 +0,0 @@ -import { notifHasImg } from "../../utils.js"; - -export const Body = (notif) => { - return Widget.Box({ - vpack: "start", - hexpand: true, - class_name: "notification-card-body menu", - children: [ - Widget.Label({ - hexpand: true, - use_markup: true, - xalign: 0, - justification: "left", - truncate: "end", - lines: 2, - max_width_chars: !notifHasImg(notif) ? 35 : 28, - wrap: true, - class_name: "notification-card-body-label menu", - label: notif["body"], - }), - ], - }); -}; diff --git a/modules/menus/notifications/notification/body/index.ts b/modules/menus/notifications/notification/body/index.ts new file mode 100644 index 0000000..2372de2 --- /dev/null +++ b/modules/menus/notifications/notification/body/index.ts @@ -0,0 +1,24 @@ +import { notifHasImg } from "../../utils.js"; +import { Notification } from "types/service/notifications"; + +export const Body = (notif: Notification) => { + return Widget.Box({ + vpack: "start", + hexpand: true, + class_name: "notification-card-body menu", + children: [ + Widget.Label({ + hexpand: true, + use_markup: true, + xalign: 0, + justification: "left", + truncate: "end", + lines: 2, + max_width_chars: !notifHasImg(notif) ? 35 : 28, + wrap: true, + class_name: "notification-card-body-label menu", + label: notif["body"], + }), + ], + }); +}; diff --git a/modules/menus/notifications/notification/close/index.js b/modules/menus/notifications/notification/close/index.js deleted file mode 100644 index 0267964..0000000 --- a/modules/menus/notifications/notification/close/index.js +++ /dev/null @@ -1,12 +0,0 @@ -export const CloseButton = (notif, notifs) => { - return Widget.Button({ - class_name: "close-notification-button menu", - on_primary_click: () => { - notifs.CloseNotification(notif.id); - }, - child: Widget.Label({ - label: "󰅜", - hpack: "center", - }), - }); -}; diff --git a/modules/menus/notifications/notification/close/index.ts b/modules/menus/notifications/notification/close/index.ts new file mode 100644 index 0000000..4ef3dff --- /dev/null +++ b/modules/menus/notifications/notification/close/index.ts @@ -0,0 +1,13 @@ +import { Notification, Notifications } from "types/service/notifications"; +export const CloseButton = (notif: Notification, notifs: Notifications) => { + return Widget.Button({ + class_name: "close-notification-button menu", + on_primary_click: () => { + notifs.CloseNotification(notif.id); + }, + child: Widget.Label({ + label: "󰅜", + hpack: "center", + }), + }); +}; diff --git a/modules/menus/notifications/notification/header/icon.js b/modules/menus/notifications/notification/header/icon.ts similarity index 100% rename from modules/menus/notifications/notification/header/icon.js rename to modules/menus/notifications/notification/header/icon.ts diff --git a/modules/menus/notifications/notification/header/index.js b/modules/menus/notifications/notification/header/index.js deleted file mode 100644 index 4653743..0000000 --- a/modules/menus/notifications/notification/header/index.js +++ /dev/null @@ -1,48 +0,0 @@ -import GLib from "gi://GLib"; -import { NotificationIcon } from "./icon.js"; -import { notifHasImg } from "../../utils.js"; - -const time = (time, format = "%I:%M %p") => - GLib.DateTime.new_from_unix_local(time).format(format); - -export const Header = (notif) => { - return Widget.Box({ - vertical: false, - hexpand: true, - children: [ - Widget.Box({ - class_name: "notification-card-header menu", - hpack: "start", - children: [NotificationIcon(notif)], - }), - Widget.Box({ - class_name: "notification-card-header menu", - hexpand: true, - vpack: "start", - children: [ - Widget.Label({ - class_name: "notification-card-header-label menu", - hpack: "start", - hexpand: true, - vexpand: true, - max_width_chars: !notifHasImg(notif) ? 34 : 22, - truncate: "end", - wrap: true, - label: notif["summary"], - }), - ], - }), - Widget.Box({ - class_name: "notification-card-header menu", - hpack: "end", - vpack: "start", - hexpand: true, - child: Widget.Label({ - vexpand: true, - class_name: "notification-time", - label: time(notif.time), - }), - }), - ], - }); -}; diff --git a/modules/menus/notifications/notification/header/index.ts b/modules/menus/notifications/notification/header/index.ts new file mode 100644 index 0000000..0a3de1d --- /dev/null +++ b/modules/menus/notifications/notification/header/index.ts @@ -0,0 +1,49 @@ +import GLib from "gi://GLib"; +import { Notification } from "types/service/notifications"; +import { NotificationIcon } from "./icon.js"; +import { notifHasImg } from "../../utils.js"; + +const time = (time: number, format = "%I:%M %p") => + GLib.DateTime.new_from_unix_local(time).format(format); + +export const Header = (notif: Notification) => { + return Widget.Box({ + vertical: false, + hexpand: true, + children: [ + Widget.Box({ + class_name: "notification-card-header menu", + hpack: "start", + children: [NotificationIcon(notif)], + }), + Widget.Box({ + class_name: "notification-card-header menu", + hexpand: true, + vpack: "start", + children: [ + Widget.Label({ + class_name: "notification-card-header-label menu", + hpack: "start", + hexpand: true, + vexpand: true, + max_width_chars: !notifHasImg(notif) ? 34 : 22, + truncate: "end", + wrap: true, + label: notif["summary"], + }), + ], + }), + Widget.Box({ + class_name: "notification-card-header menu", + hpack: "end", + vpack: "start", + hexpand: true, + child: Widget.Label({ + vexpand: true, + class_name: "notification-time", + label: time(notif.time), + }), + }), + ], + }); +}; diff --git a/modules/menus/notifications/notification/image/index.js b/modules/menus/notifications/notification/image/index.js deleted file mode 100644 index 8eb159f..0000000 --- a/modules/menus/notifications/notification/image/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import { notifHasImg } from "../../utils.js"; - -const Image = (notif) => { - if (notifHasImg(notif)) { - return Widget.Box({ - class_name: "notification-card-image-container menu", - hpack: "center", - vpack: "center", - vexpand: false, - child: Widget.Box({ - hpack: "center", - vexpand: false, - class_name: "notification-card-image menu", - css: `background-image: url("${notif.image}")`, - }), - }); - } - - return Widget.Box(); -}; - -export { Image }; diff --git a/modules/menus/notifications/notification/image/index.ts b/modules/menus/notifications/notification/image/index.ts new file mode 100644 index 0000000..c132385 --- /dev/null +++ b/modules/menus/notifications/notification/image/index.ts @@ -0,0 +1,23 @@ +import { Notification } from "types/service/notifications"; +import { notifHasImg } from "../../utils.js"; + +const Image = (notif: Notification) => { + if (notifHasImg(notif)) { + return Widget.Box({ + class_name: "notification-card-image-container menu", + hpack: "center", + vpack: "center", + vexpand: false, + child: Widget.Box({ + hpack: "center", + vexpand: false, + class_name: "notification-card-image menu", + css: `background-image: url("${notif.image}")`, + }), + }); + } + + return Widget.Box(); +}; + +export { Image }; diff --git a/modules/menus/notifications/notification/index.js b/modules/menus/notifications/notification/index.js deleted file mode 100644 index 9743782..0000000 --- a/modules/menus/notifications/notification/index.js +++ /dev/null @@ -1,59 +0,0 @@ -import { notifHasImg } from "../utils.js"; -import { Header } from "./header/index.js"; -import { Actions } from "./actions/index.js"; -import { Image } from "./image/index.js"; -import { Placeholder } from "./placeholder/index.js"; -import { Body } from "./body/index.js"; -import { CloseButton } from "./close/index.js"; - -const NotificationCard = (notifs) => { - return Widget.Box({ - class_name: "menu-content-container notifications", - hpack: "center", - vexpand: true, - spacing: 0, - vertical: true, - setup: (self) => { - self.hook(notifs, () => { - const sortedNotifications = notifs.notifications.sort( - (a, b) => b.time - a.time, - ); - - if (notifs.notifications.length <= 0) { - return (self.children = [Placeholder(notifs)]); - } - - return (self.children = sortedNotifications.map((notif) => { - return Widget.Box({ - class_name: "notification-card-content-container", - children: [ - Widget.Box({ - class_name: "notification-card menu", - vpack: "start", - hexpand: true, - vexpand: false, - children: [ - Image(notif), - Widget.Box({ - vpack: "center", - vertical: true, - hexpand: true, - class_name: `notification-card-content ${!notifHasImg(notif) ? "noimg" : " menu"}`, - children: [ - Header(notif), - Body(notif), - Actions(notif, notifs), - ], - }), - ], - }), - CloseButton(notif, notifs), - ], - }); - })); - }); - }, - }); -}; - -export { NotificationCard }; diff --git a/modules/menus/notifications/notification/index.ts b/modules/menus/notifications/notification/index.ts new file mode 100644 index 0000000..d975c28 --- /dev/null +++ b/modules/menus/notifications/notification/index.ts @@ -0,0 +1,60 @@ +import { Notifications, Notification } from "types/service/notifications"; +import { notifHasImg } from "../utils.js"; +import { Header } from "./header/index.js"; +import { Actions } from "./actions/index.js"; +import { Image } from "./image/index.js"; +import { Placeholder } from "./placeholder/index.js"; +import { Body } from "./body/index.js"; +import { CloseButton } from "./close/index.js"; + +const NotificationCard = (notifs: Notifications) => { + return Widget.Box({ + class_name: "menu-content-container notifications", + hpack: "center", + vexpand: true, + spacing: 0, + vertical: true, + setup: (self) => { + self.hook(notifs, () => { + const sortedNotifications = notifs.notifications.sort( + (a, b) => b.time - a.time, + ); + + if (notifs.notifications.length <= 0) { + return (self.children = [Placeholder(notifs)]); + } + + return (self.children = sortedNotifications.map((notif: Notification) => { + return Widget.Box({ + class_name: "notification-card-content-container", + children: [ + Widget.Box({ + class_name: "notification-card menu", + vpack: "start", + hexpand: true, + vexpand: false, + children: [ + Image(notif), + Widget.Box({ + vpack: "center", + vertical: true, + hexpand: true, + class_name: `notification-card-content ${!notifHasImg(notif) ? "noimg" : " menu"}`, + children: [ + Header(notif), + Body(notif), + Actions(notif, notifs), + ], + }), + ], + }), + CloseButton(notif, notifs), + ], + }); + })); + }); + }, + }); +}; + +export { NotificationCard }; diff --git a/modules/menus/notifications/notification/placeholder/index.js b/modules/menus/notifications/notification/placeholder/index.js deleted file mode 100644 index 61cbb2b..0000000 --- a/modules/menus/notifications/notification/placeholder/index.js +++ /dev/null @@ -1,27 +0,0 @@ -const Placeholder = (notifs) => { - return Widget.Box({ - class_name: "notification-label-container", - vpack: "fill", - hpack: "center", - expand: true, - child: Widget.Box({ - vpack: "center", - vertical: true, - expand: true, - children: [ - Widget.Label({ - vpack: "center", - class_name: "placeholder-label dim bell", - label: notifs.bind("dnd").as((dnd) => (dnd ? "󰂛" : "󰂚")), - }), - Widget.Label({ - vpack: "start", - class_name: "placehold-label dim message", - label: "You're all caught up :)", - }), - ], - }), - }); -}; - -export { Placeholder }; diff --git a/modules/menus/notifications/notification/placeholder/index.ts b/modules/menus/notifications/notification/placeholder/index.ts new file mode 100644 index 0000000..32b4473 --- /dev/null +++ b/modules/menus/notifications/notification/placeholder/index.ts @@ -0,0 +1,29 @@ +import { Notifications } from "types/service/notifications"; + +const Placeholder = (notifs: Notifications) => { + return Widget.Box({ + class_name: "notification-label-container", + vpack: "fill", + hpack: "center", + expand: true, + child: Widget.Box({ + vpack: "center", + vertical: true, + expand: true, + children: [ + Widget.Label({ + vpack: "center", + class_name: "placeholder-label dim bell", + label: notifs.bind("dnd").as((dnd) => (dnd ? "󰂛" : "󰂚")), + }), + Widget.Label({ + vpack: "start", + class_name: "placehold-label dim message", + label: "You're all caught up :)", + }), + ], + }), + }); +}; + +export { Placeholder }; diff --git a/modules/menus/notifications/utils.js b/modules/menus/notifications/utils.js deleted file mode 100644 index 033d622..0000000 --- a/modules/menus/notifications/utils.js +++ /dev/null @@ -1,5 +0,0 @@ -const notifHasImg = (notif) => { - return notif.image !== undefined && notif.image.length; -}; - -export { notifHasImg }; diff --git a/modules/menus/notifications/utils.ts b/modules/menus/notifications/utils.ts new file mode 100644 index 0000000..f050165 --- /dev/null +++ b/modules/menus/notifications/utils.ts @@ -0,0 +1,7 @@ +import { Notification } from "types/service/notifications"; + +const notifHasImg = (notif: Notification) => { + return notif.image !== undefined && notif.image.length; +}; + +export { notifHasImg }; diff --git a/modules/menus/power/helpers/actions.js b/modules/menus/power/helpers/actions.js deleted file mode 100644 index efb8543..0000000 --- a/modules/menus/power/helpers/actions.js +++ /dev/null @@ -1,50 +0,0 @@ -import options from "options"; -const { sleep, reboot, logout, shutdown } = options.menus.dashboard.powermenu; - -class PowerMenu extends Service { - static { - Service.register( - this, - {}, - { - title: ["string"], - cmd: ["string"], - }, - ); - } - - #title = ""; - #cmd = ""; - - get title() { - return this.#title; - } - - action(action) { - [this.#cmd, this.#title] = { - sleep: [sleep.value, "Sleep"], - reboot: [reboot.value, "Reboot"], - logout: [logout.value, "Log Out"], - shutdown: [shutdown.value, "Shutdown"], - }[action]; - - this.notify("cmd"); - this.notify("title"); - this.emit("changed"); - App.closeWindow("powermenu"); - App.openWindow("verification"); - } - - shutdown = () => { - this.action("shutdown"); - }; - - exec = () => { - App.closeWindow("verification"); - Utils.exec(this.#cmd); - }; -} - -const powermenu = new PowerMenu(); -Object.assign(globalThis, { powermenu }); -export default powermenu; diff --git a/modules/menus/power/helpers/actions.ts b/modules/menus/power/helpers/actions.ts new file mode 100644 index 0000000..fa8b935 --- /dev/null +++ b/modules/menus/power/helpers/actions.ts @@ -0,0 +1,51 @@ +import { Action } from "lib/types/power"; +import options from "options"; +const { sleep, reboot, logout, shutdown } = options.menus.dashboard.powermenu; + +class PowerMenu extends Service { + static { + Service.register( + this, + {}, + { + title: ["string"], + cmd: ["string"], + }, + ); + } + + #title = ""; + #cmd = ""; + + get title() { + return this.#title; + } + + action(action: Action) { + [this.#cmd, this.#title] = { + sleep: [sleep.value, "Sleep"], + reboot: [reboot.value, "Reboot"], + logout: [logout.value, "Log Out"], + shutdown: [shutdown.value, "Shutdown"], + }[action]; + + this.notify("cmd"); + this.notify("title"); + this.emit("changed"); + App.closeWindow("powermenu"); + App.openWindow("verification"); + } + + shutdown = () => { + this.action("shutdown"); + }; + + exec = () => { + App.closeWindow("verification"); + Utils.exec(this.#cmd); + }; +} + +const powermenu = new PowerMenu(); +Object.assign(globalThis, { powermenu }); +export default powermenu; diff --git a/modules/menus/power/index.js b/modules/menus/power/index.js deleted file mode 100644 index bfd7bd0..0000000 --- a/modules/menus/power/index.js +++ /dev/null @@ -1,37 +0,0 @@ -import PopupWindow from "../PopupWindow.js"; -import powermenu from "./helpers/actions.js"; -import icons from "../../icons/index.js"; - -const SysButton = (action, label) => - Widget.Button({ - class_name: `widget-button powermenu-button-${action}`, - on_clicked: () => powermenu.action(action), - child: Widget.Box({ - vertical: true, - class_name: "system-button widget-box", - children: [ - Widget.Icon({ - class_name: `system-button_icon ${action}`, - icon: icons.powermenu[action], - }), - Widget.Label({ - class_name: `system-button_label ${action}`, - label, - }), - ], - }), - }); -export default () => - PopupWindow({ - name: "powermenu", - transition: "crossfade", - child: Widget.Box({ - class_name: "powermenu horizontal", - children: [ - SysButton("shutdown", "SHUTDOWN"), - SysButton("logout", "LOG OUT"), - SysButton("reboot", "REBOOT"), - SysButton("sleep", "SLEEP"), - ], - }), - }); diff --git a/modules/menus/power/index.ts b/modules/menus/power/index.ts new file mode 100644 index 0000000..aa9065e --- /dev/null +++ b/modules/menus/power/index.ts @@ -0,0 +1,38 @@ +import { Action } from "lib/types/power.js"; +import PopupWindow from "../PopupWindow.js"; +import powermenu from "./helpers/actions.js"; +import icons from "../../icons/index.js"; + +const SysButton = (action: Action, label: string) => + Widget.Button({ + class_name: `widget-button powermenu-button-${action}`, + on_clicked: () => powermenu.action(action), + child: Widget.Box({ + vertical: true, + class_name: "system-button widget-box", + children: [ + Widget.Icon({ + class_name: `system-button_icon ${action}`, + icon: icons.powermenu[action], + }), + Widget.Label({ + class_name: `system-button_label ${action}`, + label, + }), + ], + }), + }); +export default () => + PopupWindow({ + name: "powermenu", + transition: "crossfade", + child: Widget.Box({ + class_name: "powermenu horizontal", + children: [ + SysButton("shutdown", "SHUTDOWN"), + SysButton("logout", "LOG OUT"), + SysButton("reboot", "REBOOT"), + SysButton("sleep", "SLEEP"), + ], + }), + }); diff --git a/modules/menus/power/verification.js b/modules/menus/power/verification.ts similarity index 100% rename from modules/menus/power/verification.js rename to modules/menus/power/verification.ts diff --git a/modules/notifications/actions/index.js b/modules/notifications/actions/index.js deleted file mode 100644 index eb61f55..0000000 --- a/modules/notifications/actions/index.js +++ /dev/null @@ -1,43 +0,0 @@ -const Action = (notif, notifs) => { - if (notif.actions !== undefined && notif.actions.length > 0) { - return Widget.Box({ - class_name: "notification-card-actions", - hexpand: true, - vpack: "end", - children: notif.actions.map((action) => { - return Widget.Button({ - hexpand: true, - class_name: "notification-action-buttons", - on_primary_click: () => { - if (action.id.includes("scriptAction:-")) { - Utils.execAsync( - `${action.id.replace("scriptAction:-", "")}`, - ).catch((err) => console.error(err)); - notifs.CloseNotification(notif.id); - } else { - notif.invoke(action.id); - } - }, - child: Widget.Box({ - hpack: "center", - hexpand: true, - children: [ - Widget.Label({ - class_name: "notification-action-buttons-label", - hexpand: true, - label: action.label, - max_width_chars: 15, - truncate: "end", - wrap: true, - }), - ], - }), - }); - }), - }); - } - - return Widget.Box(); -}; - -export { Action }; diff --git a/modules/notifications/actions/index.ts b/modules/notifications/actions/index.ts new file mode 100644 index 0000000..85e3eb9 --- /dev/null +++ b/modules/notifications/actions/index.ts @@ -0,0 +1,45 @@ +import { Notification, Notifications } from "types/service/notifications"; + +const Action = (notif: Notification, notifs: Notifications) => { + if (notif.actions !== undefined && notif.actions.length > 0) { + return Widget.Box({ + class_name: "notification-card-actions", + hexpand: true, + vpack: "end", + children: notif.actions.map((action) => { + return Widget.Button({ + hexpand: true, + class_name: "notification-action-buttons", + on_primary_click: () => { + if (action.id.includes("scriptAction:-")) { + Utils.execAsync( + `${action.id.replace("scriptAction:-", "")}`, + ).catch((err) => console.error(err)); + notifs.CloseNotification(notif.id); + } else { + notif.invoke(action.id); + } + }, + child: Widget.Box({ + hpack: "center", + hexpand: true, + children: [ + Widget.Label({ + class_name: "notification-action-buttons-label", + hexpand: true, + label: action.label, + max_width_chars: 15, + truncate: "end", + wrap: true, + }), + ], + }), + }); + }), + }); + } + + return Widget.Box(); +}; + +export { Action }; diff --git a/modules/notifications/body/index.js b/modules/notifications/body/index.js deleted file mode 100644 index 0c4be9f..0000000 --- a/modules/notifications/body/index.js +++ /dev/null @@ -1,23 +0,0 @@ -import { notifHasImg } from "../../menus/notifications/utils.js"; - -export const Body = (notif) => { - return Widget.Box({ - vpack: "start", - hexpand: true, - class_name: "notification-card-body", - children: [ - Widget.Label({ - hexpand: true, - use_markup: true, - xalign: 0, - justification: "left", - truncate: "end", - lines: 2, - max_width_chars: !notifHasImg(notif) ? 35 : 28, - wrap: true, - class_name: "notification-card-body-label", - label: notif["body"], - }), - ], - }); -}; diff --git a/modules/notifications/body/index.ts b/modules/notifications/body/index.ts new file mode 100644 index 0000000..0369615 --- /dev/null +++ b/modules/notifications/body/index.ts @@ -0,0 +1,24 @@ +import { Notification } from "types/service/notifications"; +import { notifHasImg } from "../../menus/notifications/utils.js"; + +export const Body = (notif: Notification) => { + return Widget.Box({ + vpack: "start", + hexpand: true, + class_name: "notification-card-body", + children: [ + Widget.Label({ + hexpand: true, + use_markup: true, + xalign: 0, + justification: "left", + truncate: "end", + lines: 2, + max_width_chars: !notifHasImg(notif) ? 35 : 28, + wrap: true, + class_name: "notification-card-body-label", + label: notif["body"], + }), + ], + }); +}; diff --git a/modules/notifications/close/index.js b/modules/notifications/close/index.js deleted file mode 100644 index cbe3981..0000000 --- a/modules/notifications/close/index.js +++ /dev/null @@ -1,12 +0,0 @@ -export const CloseButton = (notif, notifs) => { - return Widget.Button({ - class_name: "close-notification-button", - on_primary_click: () => { - notifs.CloseNotification(notif.id); - }, - child: Widget.Label({ - label: "󰅜", - hpack: "center", - }), - }); -}; diff --git a/modules/notifications/close/index.ts b/modules/notifications/close/index.ts new file mode 100644 index 0000000..2da8b7e --- /dev/null +++ b/modules/notifications/close/index.ts @@ -0,0 +1,14 @@ +import { Notification, Notifications } from "types/service/notifications"; + +export const CloseButton = (notif: Notification, notifs: Notifications) => { + return Widget.Button({ + class_name: "close-notification-button", + on_primary_click: () => { + notifs.CloseNotification(notif.id); + }, + child: Widget.Label({ + label: "󰅜", + hpack: "center", + }), + }); +}; diff --git a/modules/notifications/header/icon.js b/modules/notifications/header/icon.ts similarity index 100% rename from modules/notifications/header/icon.js rename to modules/notifications/header/icon.ts diff --git a/modules/notifications/header/index.js b/modules/notifications/header/index.js deleted file mode 100644 index b3ed5ea..0000000 --- a/modules/notifications/header/index.js +++ /dev/null @@ -1,49 +0,0 @@ -import GLib from "gi://GLib"; -import { notifHasImg } from "../../menus/notifications/utils.js"; -import { NotificationIcon } from "./icon.js"; - -const time = (time, format = "%I:%M %p") => - GLib.DateTime.new_from_unix_local(time).format(format); - -export const Header = (notif) => { - return Widget.Box({ - vertical: false, - hexpand: true, - children: [ - Widget.Box({ - class_name: "notification-card-header", - hpack: "start", - children: [NotificationIcon(notif)], - }), - Widget.Box({ - class_name: "notification-card-header", - hexpand: true, - hpack: "start", - vpack: "start", - children: [ - Widget.Label({ - class_name: "notification-card-header-label", - hpack: "start", - hexpand: true, - vexpand: true, - max_width_chars: !notifHasImg(notif) ? 30 : 19, - truncate: "end", - wrap: true, - label: notif["summary"], - }), - ], - }), - Widget.Box({ - class_name: "notification-card-header menu", - hpack: "end", - vpack: "start", - hexpand: true, - child: Widget.Label({ - vexpand: true, - class_name: "notification-time", - label: time(notif.time), - }), - }), - ], - }); -}; diff --git a/modules/notifications/header/index.ts b/modules/notifications/header/index.ts new file mode 100644 index 0000000..72e2476 --- /dev/null +++ b/modules/notifications/header/index.ts @@ -0,0 +1,50 @@ +import GLib from "gi://GLib"; +import { notifHasImg } from "../../menus/notifications/utils.js"; +import { NotificationIcon } from "./icon.js"; +import { Notification } from "types/service/notifications"; + +const time = (time: number, format = "%I:%M %p") => + GLib.DateTime.new_from_unix_local(time).format(format); + +export const Header = (notif: Notification) => { + return Widget.Box({ + vertical: false, + hexpand: true, + children: [ + Widget.Box({ + class_name: "notification-card-header", + hpack: "start", + children: [NotificationIcon(notif)], + }), + Widget.Box({ + class_name: "notification-card-header", + hexpand: true, + hpack: "start", + vpack: "start", + children: [ + Widget.Label({ + class_name: "notification-card-header-label", + hpack: "start", + hexpand: true, + vexpand: true, + max_width_chars: !notifHasImg(notif) ? 30 : 19, + truncate: "end", + wrap: true, + label: notif["summary"], + }), + ], + }), + Widget.Box({ + class_name: "notification-card-header menu", + hpack: "end", + vpack: "start", + hexpand: true, + child: Widget.Label({ + vexpand: true, + class_name: "notification-time", + label: time(notif.time), + }), + }), + ], + }); +}; diff --git a/modules/notifications/image/index.js b/modules/notifications/image/index.js deleted file mode 100644 index 9542bb3..0000000 --- a/modules/notifications/image/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import { notifHasImg } from "../../menus/notifications/utils.js"; - -const Image = (notif) => { - if (notifHasImg(notif)) { - return Widget.Box({ - class_name: "notification-card-image-container", - hpack: "center", - vpack: "center", - vexpand: false, - child: Widget.Box({ - hpack: "center", - vexpand: false, - class_name: "notification-card-image", - css: `background-image: url("${notif.image}")`, - }), - }); - } - - return Widget.Box(); -}; - -export { Image }; diff --git a/modules/notifications/image/index.ts b/modules/notifications/image/index.ts new file mode 100644 index 0000000..eb068a9 --- /dev/null +++ b/modules/notifications/image/index.ts @@ -0,0 +1,23 @@ +import { Notification } from "types/service/notifications"; +import { notifHasImg } from "../../menus/notifications/utils.js"; + +const Image = (notif: Notification) => { + if (notifHasImg(notif)) { + return Widget.Box({ + class_name: "notification-card-image-container", + hpack: "center", + vpack: "center", + vexpand: false, + child: Widget.Box({ + hpack: "center", + vexpand: false, + class_name: "notification-card-image", + css: `background-image: url("${notif.image}")`, + }), + }); + } + + return Widget.Box(); +}; + +export { Image }; diff --git a/modules/notifications/index.js b/modules/notifications/index.js deleted file mode 100644 index 0ceeb30..0000000 --- a/modules/notifications/index.js +++ /dev/null @@ -1,53 +0,0 @@ -const notifs = await Service.import("notifications"); -import options from "options"; -import { notifHasImg } from "../menus/notifications/utils.js"; -import { Image } from "./image/index.js"; -import { Action } from "./actions/index.js"; -import { Header } from "./header/index.js"; -import { Body } from "./body/index.js"; -import { CloseButton } from "./close/index.js"; - -const { position } = options.notifications; - -export default () => { - notifs.popupTimeout = 7000; - const getPosition = (pos) => { - return pos.split(" "); - } - - return Widget.Window({ - name: "notifications-window", - class_name: "notifications-window", - monitor: 2, - layer: "top", - anchor: position.bind("value").as(v => getPosition(v)), - exclusivity: "ignore", - child: Widget.Box({ - class_name: "notification-card-container", - vertical: true, - hexpand: true, - setup: (self) => { - self.hook(notifs, () => { - return (self.children = notifs.popups.map((notif) => { - return Widget.Box({ - class_name: "notification-card", - vpack: "start", - hexpand: true, - children: [ - Image(notif), - Widget.Box({ - vpack: "start", - vertical: true, - hexpand: true, - class_name: `notification-card-content ${!notifHasImg(notif) ? "noimg" : ""}`, - children: [Header(notif), Body(notif), Action(notif, notifs)], - }), - CloseButton(notif, notifs), - ], - }); - })); - }); - }, - }), - }); -}; diff --git a/modules/notifications/index.ts b/modules/notifications/index.ts new file mode 100644 index 0000000..d6114c7 --- /dev/null +++ b/modules/notifications/index.ts @@ -0,0 +1,63 @@ +const notifs = await Service.import("notifications"); +import options from "options"; +import { notifHasImg } from "../menus/notifications/utils.js"; +import { Image } from "./image/index.js"; +import { Action } from "./actions/index.js"; +import { Header } from "./header/index.js"; +import { Body } from "./body/index.js"; +import { CloseButton } from "./close/index.js"; +import { NotificationAnchor } from "lib/types/options"; + +const { position } = options.notifications; + +export default () => { + notifs.popupTimeout = 7000; + const getPosition = (pos: NotificationAnchor): ("top" | "bottom" | "left" | "right")[] => { + const positionMap: { [key: string]: ("top" | "bottom" | "left" | "right")[] } = { + "top": ["top"], + "top right": ["top", "right"], + "top left": ["top", "left"], + "bottom": ["bottom"], + "bottom right": ["bottom", "right"], + "bottom left": ["bottom", "left"] + }; + + return positionMap[pos] || ["top"]; + } + + return Widget.Window({ + name: "notifications-window", + class_name: "notifications-window", + monitor: 2, + layer: "top", + anchor: position.bind("value").as(v => getPosition(v)), + exclusivity: "ignore", + child: Widget.Box({ + class_name: "notification-card-container", + vertical: true, + hexpand: true, + setup: (self) => { + self.hook(notifs, () => { + return (self.children = notifs.popups.map((notif) => { + return Widget.Box({ + class_name: "notification-card", + vpack: "start", + hexpand: true, + children: [ + Image(notif), + Widget.Box({ + vpack: "start", + vertical: true, + hexpand: true, + class_name: `notification-card-content ${!notifHasImg(notif) ? "noimg" : ""}`, + children: [Header(notif), Body(notif), Action(notif, notifs)], + }), + CloseButton(notif, notifs), + ], + }); + })); + }); + }, + }), + }); +}; diff --git a/modules/shared/barItemBox.js b/modules/shared/barItemBox.ts similarity index 100% rename from modules/shared/barItemBox.js rename to modules/shared/barItemBox.ts diff --git a/services/Brightness.js b/services/Brightness.ts similarity index 97% rename from services/Brightness.js rename to services/Brightness.ts index 86d8788..895f6f9 100644 --- a/services/Brightness.js +++ b/services/Brightness.ts @@ -72,7 +72,7 @@ class BrightnessService extends Service { // overwriting the connect method, let's you // change the default event that widgets connect to - connect(event = 'screen-changed', callback) { + connect(event: string = 'screen-changed', callback: any) { return super.connect(event, callback); } }