Compare commits

...

3 Commits

Author SHA1 Message Date
7ba9a65f19 [Build] Add linter 2025-10-17 09:47:40 +02:00
52ced69697 [Hyprland] Fix submaps 2025-10-17 09:47:25 +02:00
47bbdd9f85 [Astal] Add mode indicator 2025-10-17 09:47:18 +02:00
13 changed files with 1329 additions and 296 deletions

View File

@@ -9,7 +9,9 @@ const renderColourAsHex = util.renderColourAsHex;
// │ Theme generator (returns theme as object) │ // │ Theme generator (returns theme as object) │
// ╰───────────────────────────────────────────────╯ // ╰───────────────────────────────────────────────╯
// ─────────────────────────────────────────────────────────────────── // ───────────────────────────────────────────────────────────────────
module.exports.generateTheme = ( theme, wallpaper, lockpaper, palette ) => { module.exports.generateTheme = (
theme, wallpaper, lockpaper, palette
) => {
return { return {
'wallpaper-path': wallpaper, 'wallpaper-path': wallpaper,
'lockpaper-path': lockpaper, 'lockpaper-path': lockpaper,
@@ -49,14 +51,30 @@ module.exports.generateTheme = ( theme, wallpaper, lockpaper, palette ) => {
'colour-accent-rgba-007': renderColourAsRGBA( palette[ 0 ], 0.07 ), 'colour-accent-rgba-007': renderColourAsRGBA( palette[ 0 ], 0.07 ),
'colour-accent-hyprland': util.renderColourAsRGBAHex( palette[ 0 ], 0.8 ), 'colour-accent-hyprland': util.renderColourAsRGBAHex( palette[ 0 ], 0.8 ),
// ─────────────────────────────────────────────────────────────────── // ───────────────────────────────────────────────────────────────────
'colour-accent-gradient-1-hex': renderColourAsHex( util.getGradientColour( palette[ 0 ], 1, gradientMultipliers[ theme ] ) ), 'colour-accent-gradient-1-hex': renderColourAsHex( util.getGradientColour(
'colour-accent-gradient-2-hex': renderColourAsHex( util.getGradientColour( palette[ 0 ], 2, gradientMultipliers[ theme ] ) ), palette[ 0 ], 1, gradientMultipliers[ theme ]
'colour-accent-gradient-3-hex': renderColourAsHex( util.getGradientColour( palette[ 0 ], 3, gradientMultipliers[ theme ] ) ), ) ),
'colour-accent-gradient-4-hex': renderColourAsHex( util.getGradientColour( palette[ 0 ], 4, gradientMultipliers[ theme ] ) ), 'colour-accent-gradient-2-hex': renderColourAsHex( util.getGradientColour(
'colour-accent-gradient-5-hex': renderColourAsHex( util.getGradientColour( palette[ 0 ], 5, gradientMultipliers[ theme ] ) ), palette[ 0 ], 2, gradientMultipliers[ theme ]
'colour-accent-gradient-inverse-1-hex': renderColourAsHex( util.getGradientColour( palette[ 0 ], 1, 1 / gradientMultipliers[ theme ] ) ), ) ),
'colour-accent-gradient-inverse-2-hex': renderColourAsHex( util.getGradientColour( palette[ 0 ], 1, 1 / gradientMultipliers[ theme ] ) ), 'colour-accent-gradient-3-hex': renderColourAsHex( util.getGradientColour(
'colour-accent-gradient-inverse-3-hex': renderColourAsHex( util.getGradientColour( palette[ 0 ], 1, 1 / gradientMultipliers[ theme ] ) ), palette[ 0 ], 3, gradientMultipliers[ theme ]
) ),
'colour-accent-gradient-4-hex': renderColourAsHex( util.getGradientColour(
palette[ 0 ], 4, gradientMultipliers[ theme ]
) ),
'colour-accent-gradient-5-hex': renderColourAsHex( util.getGradientColour(
palette[ 0 ], 5, gradientMultipliers[ theme ]
) ),
'colour-accent-gradient-inverse-1-hex': renderColourAsHex( util.getGradientColour(
palette[ 0 ], 1, 1 / gradientMultipliers[ theme ]
) ),
'colour-accent-gradient-inverse-2-hex': renderColourAsHex( util.getGradientColour(
palette[ 0 ], 1, 1 / gradientMultipliers[ theme ]
) ),
'colour-accent-gradient-inverse-3-hex': renderColourAsHex( util.getGradientColour(
palette[ 0 ], 1, 1 / gradientMultipliers[ theme ]
) ),
// ── Secondary accent ───────────────────────────────────────────── // ── Secondary accent ─────────────────────────────────────────────
'colour-accent-2-hex': renderColourAsHex( palette[ 1 ] ), 'colour-accent-2-hex': renderColourAsHex( palette[ 1 ] ),
@@ -154,9 +172,9 @@ module.exports.generateTheme = ( theme, wallpaper, lockpaper, palette ) => {
// ┌ ┐ // ┌ ┐
// │ Path to this repo on disk │ // │ Path to this repo on disk │
// └ ┘ // └ ┘
'path-to-dotfiles': __dirname.slice(0, __dirname.length - 5), 'path-to-dotfiles': __dirname.slice( 0, __dirname.length - 5 ),
} };
} };
// ─────────────────────────────────────────────────────────────────── // ───────────────────────────────────────────────────────────────────
@@ -171,74 +189,265 @@ const gradientMultipliers = {
'light': 1.1, 'light': 1.1,
'bright': 1.15, 'bright': 1.15,
'test': 0.75 'test': 0.75
} };
const colours = { const colours = {
foreground: { 'foreground': {
'nordic': [ 200, 220, 255 ], 'nordic': [
'deep-dark': [ 230, 230, 230 ], 200,
'material': [ 255, 255, 255 ], // TODO: Will be calculated by material theme generator 220,
'light': [ 40, 40, 40 ], 255
'bright': [ 0, 0, 0 ], ],
'test': [ 0, 0, 0 ], 'deep-dark': [
230,
230,
230
],
'material': [
255,
255,
255
], // TODO: Will be calculated by material theme generator
'light': [
40,
40,
40
],
'bright': [
0,
0,
0
],
'test': [
0,
0,
0
],
}, },
'foreground-accent': { 'foreground-accent': {
'nordic': [ 255, 255, 255 ], 'nordic': [
'deep-dark': [ 255, 255, 255 ], 255,
'material': [ 200, 200, 200 ], // TODO: Will be calculated by material theme generator 255,
'light': [ 0, 0, 0 ], 255
'bright': [ 50, 50, 50 ], ],
'test': [ 0, 0, 0 ], 'deep-dark': [
255,
255,
255
],
'material': [
200,
200,
200
], // TODO: Will be calculated by material theme generator
'light': [
0,
0,
0
],
'bright': [
50,
50,
50
],
'test': [
0,
0,
0
],
}, },
background: { 'background': {
'nordic': [ 10, 10, 15 ], 'nordic': [
'deep-dark': [ 20, 20, 20 ], 10,
'material': [ 30, 30, 30 ], // TODO: Will be calculated by material theme generator 10,
'light': [ 230, 230, 230 ], 15
'bright': [ 255, 255, 255 ], ],
'test': [ 255, 255, 255 ], 'deep-dark': [
20,
20,
20
],
'material': [
30,
30,
30
], // TODO: Will be calculated by material theme generator
'light': [
230,
230,
230
],
'bright': [
255,
255,
255
],
'test': [
255,
255,
255
],
}, },
'background-alternative': { 'background-alternative': {
'nordic': [ 20, 20, 25 ], 'nordic': [
'deep-dark': [ 30, 30, 30 ], 20,
'material': [ 40, 40, 40 ], // TODO: Will be calculated by material theme generator 20,
'light': [ 210, 210, 210 ], 25
'bright': [ 230, 230, 230 ], ],
'test': [ 255, 255, 0 ] // brown 'deep-dark': [
30,
30,
30
],
'material': [
40,
40,
40
], // TODO: Will be calculated by material theme generator
'light': [
210,
210,
210
],
'bright': [
230,
230,
230
],
'test': [
255,
255,
0
] // brown
}, },
'background-tertiary': { 'background-tertiary': {
'nordic': [ 0, 0, 0 ], 'nordic': [
'deep-dark': [ 45, 45, 45 ], 0,
'material': [ 0, 0, 0 ], // TODO: Will be calculated by material theme generator 0,
'light': [ 180, 180, 180 ], 0
'bright': [ 200, 200, 200 ], ],
'test': [ 255, 0, 255 ] // purple 'deep-dark': [
45,
45,
45
],
'material': [
0,
0,
0
], // TODO: Will be calculated by material theme generator
'light': [
180,
180,
180
],
'bright': [
200,
200,
200
],
'test': [
255,
0,
255
] // purple
}, },
shadow: { 'shadow': {
'nordic': [ 0, 0, 2 ], 'nordic': [
'deep-dark': [ 40, 40, 40 ], 0,
'material': [ 30, 30, 30 ], // TODO: Will be calculated by material theme generator 0,
'light': [ 190, 190, 190 ], 2
'bright': [ 150, 150, 150 ], ],
'test': [ 120, 0, 0 ] // dark red 'deep-dark': [
40,
40,
40
],
'material': [
30,
30,
30
], // TODO: Will be calculated by material theme generator
'light': [
190,
190,
190
],
'bright': [
150,
150,
150
],
'test': [
120,
0,
0
] // dark red
}, },
inactive: { 'inactive': {
'nordic': [ 200, 200, 200 ], 'nordic': [
'deep-dark': [ 200, 200, 200 ], 200,
'material': [ 200, 200, 200 ], // TODO: Will be calculated by material theme generator 200,
'light': [ 65, 65, 65 ], 200
'bright': [ 60, 60, 60 ], ],
'test': [ 150, 150, 150 ] 'deep-dark': [
200,
200,
200
],
'material': [
200,
200,
200
], // TODO: Will be calculated by material theme generator
'light': [
65,
65,
65
],
'bright': [
60,
60,
60
],
'test': [
150,
150,
150
]
}, },
'inactive-background': { 'inactive-background': {
'nordic': [ 0, 0, 0 ], 'nordic': [
'deep-dark': [ 0, 0, 0 ], 0,
'material': [ 255, 255, 255 ], // TODO: Will be calculated by material theme generator 0,
'light': [ 80, 80, 80 ], 0
'bright': [ 60, 60, 60 ], ],
'test': [ 60, 60, 60 ] 'deep-dark': [
0,
0,
0
],
'material': [
255,
255,
255
], // TODO: Will be calculated by material theme generator
'light': [
80,
80,
80
],
'bright': [
60,
60,
60
],
'test': [
60,
60,
60
]
} }
} };
const fonts = { const fonts = {
'primary': { 'primary': {
'nordic': 'Comfortaa', 'nordic': 'Comfortaa',
@@ -261,20 +470,18 @@ const fonts = {
'light': 'Jetbrains Mono', 'light': 'Jetbrains Mono',
'bright': 'Jetbrains Mono', 'bright': 'Jetbrains Mono',
} }
} };
const iconTheme = { const iconTheme = {
'nordic': 'Candy', 'nordic': 'Candy',
'deep-dark': 'Candy', 'deep-dark': 'Candy',
'material': 'Candy', 'material': 'Candy',
'light': 'Candy', 'light': 'Candy',
'bright': 'Candy' 'bright': 'Candy'
} };
const yaziThemes = { const yaziThemes = {
'nordic': 'tokyo-night', 'nordic': 'tokyo-night',
'deep-dark': 'vscode-dark-modern', 'deep-dark': 'vscode-dark-modern',
'material': 'dracula', 'material': 'dracula',
'light': 'vscode-light-modern', 'light': 'vscode-light-modern',
'bright': 'vscode-light-modern', 'bright': 'vscode-light-modern',
} };

View File

@@ -1,11 +1,14 @@
const fs = require( 'fs' ); const fs = require( 'fs' );
const path = require( 'path' ); const path = require( 'path' );
const data = '' + fs.readFileSync( '/usr/share/themes/Material-Black-Blueberry/gtk-4.0/gtk.css' ); const data = '' + fs.readFileSync( '/usr/share/themes/Material-Black-Blueberry/gtk-4.0/gtk.css' );
let lineNumber = 1; let lineNumber = 1;
const indexer = {}; const indexer = {};
for (let i = 0; i < data.length; i++) {
for ( let i = 0; i < data.length; i++ ) {
const char = data[i]; const char = data[i];
if ( char === '\n' ) { if ( char === '\n' ) {
@@ -13,17 +16,22 @@ for (let i = 0; i < data.length; i++) {
} else if ( char === '#' ) { } else if ( char === '#' ) {
const extract = data.substring( i ); const extract = data.substring( i );
const col = extract.slice( 0, extract.indexOf( '\n' ) ); const col = extract.slice( 0, extract.indexOf( '\n' ) );
if ( !indexer[ col ] ) { if ( !indexer[ col ] ) {
indexer[ col ] = []; indexer[ col ] = [];
} }
indexer[ col ].push( lineNumber ); indexer[ col ].push( lineNumber );
} else if ( char === 'r' ) { } else if ( char === 'r' ) {
const extract = data.substring( i ); const extract = data.substring( i );
if ( extract.slice( 0, 3 ) === 'rgb' ) { if ( extract.slice( 0, 3 ) === 'rgb' ) {
const col = extract.slice( 0, extract.indexOf( '\n' ) ); const col = extract.slice( 0, extract.indexOf( '\n' ) );
if ( !indexer[ col ] ) { if ( !indexer[ col ] ) {
indexer[ col ] = []; indexer[ col ] = [];
} }
indexer[ col ].push( lineNumber ); indexer[ col ].push( lineNumber );
} }
} }
@@ -32,7 +40,8 @@ for (let i = 0; i < data.length; i++) {
// Output // Output
const keys = Object.keys( indexer ); const keys = Object.keys( indexer );
for (let i = 0; i < keys.length; i++) {
for ( let i = 0; i < keys.length; i++ ) {
const element = keys[i]; const element = keys[i];
if ( element.length <= 25 ) { if ( element.length <= 25 ) {

View File

@@ -1,19 +1,30 @@
import { App, Astal, Gdk, Gtk } from "astal/gtk4"; import {
import Hyprland from "./modules/Hyprland"; App, Astal, Gdk, Gtk
import Calendar from "./modules/Calendar"; } from 'astal/gtk4';
import QuickView from "./modules/QuickView"; import Calendar from './modules/Calendar';
import SystemInfo from "./modules/SystemInfo"; import {
import { CenterBox } from "astal/gtk4/widget"; CenterBox
} from 'astal/gtk4/widget';
import Hyprland from './modules/Hyprland';
import QuickView from './modules/QuickView';
import SystemInfo from './modules/SystemInfo';
const Bar = ( { gdkmonitor, name }: { gdkmonitor: Gdk.Monitor, name: string } ) => { const Bar = ( {
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor; gdkmonitor, name
}: {
'gdkmonitor': Gdk.Monitor,
'name': string
} ) => {
const {
TOP, LEFT, RIGHT
} = Astal.WindowAnchor;
return ( return (
<window <window
gdkmonitor={gdkmonitor} gdkmonitor={gdkmonitor}
cssClasses={["Bar"]} cssClasses={[ 'Bar' ]}
name={name} name={name}
namespace={"bar"} namespace={'bar'}
exclusivity={Astal.Exclusivity.EXCLUSIVE} exclusivity={Astal.Exclusivity.EXCLUSIVE}
anchor={TOP | LEFT | RIGHT} anchor={TOP | LEFT | RIGHT}
visible visible
@@ -26,6 +37,7 @@ const Bar = ( { gdkmonitor, name }: { gdkmonitor: Gdk.Monitor, name: string } )
hexpand hexpand
halign={Gtk.Align.START} halign={Gtk.Align.START}
> >
<Hyprland.ModeStatus />
<Calendar.Time /> <Calendar.Time />
<SystemInfo.SystemInfo /> <SystemInfo.SystemInfo />
<Hyprland.Workspace /> <Hyprland.Workspace />
@@ -36,7 +48,7 @@ const Bar = ( { gdkmonitor, name }: { gdkmonitor: Gdk.Monitor, name: string } )
<box <box
hexpand hexpand
halign={Gtk.Align.END} halign={Gtk.Align.END}
cssClasses={["BarRight"]} cssClasses={[ 'BarRight' ]}
> >
<Hyprland.SysTray /> <Hyprland.SysTray />
<QuickView.QuickView /> <QuickView.QuickView />
@@ -48,21 +60,24 @@ const Bar = ( { gdkmonitor, name }: { gdkmonitor: Gdk.Monitor, name: string } )
); );
}; };
const cliHandler = (args: string[]): string => { const cliHandler = ( args: string[] ): string => {
return "Not implemented"; console.debug( args );
return 'Not implemented';
}; };
const BarLauncher = ( monitor: Gdk.Monitor ) => { const BarLauncher = ( monitor: Gdk.Monitor ) => {
const windowName = `bar-${monitor.get_connector()}` const windowName = `bar-${ monitor.get_connector() }`;
const createBar = () => { const createBar = () => {
return <Bar gdkmonitor={monitor} name={windowName}></Bar> return <Bar gdkmonitor={monitor} name={windowName}></Bar>;
} };
// Actually start the bar // Actually start the bar
createBar(); createBar();
return windowName; return windowName;
} };
export default { export default {
BarLauncher, BarLauncher,

View File

@@ -12,6 +12,14 @@ window.Bar {
/* margin: 8px; */ /* margin: 8px; */
/* } */ /* } */
.mode-status { .mode-status {
color: white;
background-color: #00002dff;
padding-left: 10px;
padding-right: 10px;
margin-left: 5px;
border-radius: 20px;
font-family: $monospace-font;
&.windowing-mode { &.windowing-mode {
background-color: darkslategray; background-color: darkslategray;
color: white; color: white;
@@ -85,6 +93,7 @@ window.Bar {
.time { .time {
min-width: 11rem; min-width: 11rem;
padding: 3px; padding: 3px;
& button { & button {
box-shadow: none; box-shadow: none;
padding: 0; padding: 0;

View File

@@ -1,50 +1,59 @@
import AstalTray from "gi://AstalTray"; import {
import { bind, GObject } from "astal"; GObject, bind,
import AstalHyprland from "gi://AstalHyprland"; exec,
import { Gtk } from "astal/gtk4"; readFile
import Pango from "gi://Pango?version=1.0"; } from 'astal';
import AstalHyprland from 'gi://AstalHyprland';
import AstalTray from 'gi://AstalTray';
import {
Gtk
} from 'astal/gtk4';
import Pango from 'gi://Pango?version=1.0';
const hypr = AstalHyprland.get_default(); const hypr = AstalHyprland.get_default();
const SYNC = GObject.BindingFlags.SYNC_CREATE; const SYNC = GObject.BindingFlags.SYNC_CREATE;
const SysTray = () => { const SysTray = () => {
const trayBox = new Gtk.Box({ cssClasses: ["bar-button"] }); const trayBox = new Gtk.Box( {
'cssClasses': [ 'bar-button' ]
} );
const tray = AstalTray.get_default(); const tray = AstalTray.get_default();
const trayItems = new Map<string, Gtk.MenuButton>(); const trayItems = new Map<string, Gtk.MenuButton>();
const trayAddedHandler = tray.connect("item-added", (_, id) => { const trayAddedHandler = tray.connect( 'item-added', ( _, id ) => {
const item = tray.get_item(id); const item = tray.get_item( id );
const popover = Gtk.PopoverMenu.new_from_model(item.menu_model); const popover = Gtk.PopoverMenu.new_from_model( item.menu_model );
const icon = new Gtk.Image(); const icon = new Gtk.Image();
const button = new Gtk.MenuButton({ const button = new Gtk.MenuButton( {
popover, popover,
child: icon, 'child': icon,
cssClasses: ["tray-item"], 'cssClasses': [ 'tray-item' ],
}); } );
item.bind_property("gicon", icon, "gicon", SYNC); item.bind_property(
popover.insert_action_group("dbusmenu", item.action_group); 'gicon', icon, 'gicon', SYNC
item.connect("notify::action-group", () => { );
popover.insert_action_group("dbusmenu", item.action_group); popover.insert_action_group( 'dbusmenu', item.action_group );
}); item.connect( 'notify::action-group', () => {
popover.insert_action_group( 'dbusmenu', item.action_group );
} );
trayItems.set(id, button); trayItems.set( id, button );
trayBox.append(button); trayBox.append( button );
}); } );
const trayRemovedHandler = tray.connect( 'item-removed', ( _, id ) => {
const button = trayItems.get( id );
const trayRemovedHandler = tray.connect("item-removed", (_, id) => { if ( button ) {
const button = trayItems.get(id); trayBox.remove( button );
if (button) {
trayBox.remove(button);
button.run_dispose(); button.run_dispose();
trayItems.delete(id); trayItems.delete( id );
} }
}); } );
trayBox.connect("destroy", () => { trayBox.connect( 'destroy', () => {
tray.disconnect(trayAddedHandler); tray.disconnect( trayAddedHandler );
tray.disconnect(trayRemovedHandler); tray.disconnect( trayRemovedHandler );
}); } );
return trayBox; return trayBox;
}; };
@@ -52,25 +61,21 @@ const SysTray = () => {
const Workspace = () => { const Workspace = () => {
return ( return (
<box> <box>
{bind(hypr, "workspaces").as(wss => {bind( hypr, 'workspaces' ).as( wss => wss
wss .filter( ws => !( ws.id >= -99 && ws.id <= -2 ) ) // filter out special workspaces
.filter(ws => !(ws.id >= -99 && ws.id <= -2)) // filter out special workspaces .sort( ( a, b ) => a.id - b.id )
.sort((a, b) => a.id - b.id) .map( ws => (
.map(ws => (
<button <button
cssClasses={bind(hypr, "focusedWorkspace").as(fw => cssClasses={bind( hypr, 'focusedWorkspace' ).as( fw => ws === fw
ws === fw
? [ ? [
"focused-workspace-button", 'focused-workspace-button',
"workspace-button", 'workspace-button',
] ]
: ["workspace-button"], : [ 'workspace-button' ], )}
)}
onButtonPressed={() => ws.focus()} onButtonPressed={() => ws.focus()}
child={<label label={String(ws.id)}></label>} child={<label label={String( ws.id )}></label>}
></button> ></button>
)), ) ), )}
)}
</box> </box>
); );
}; };
@@ -80,15 +85,15 @@ const Workspace = () => {
* displaying all available clients * displaying all available clients
*/ */
const ActiveWindow = () => { const ActiveWindow = () => {
const focused = bind(hypr, "focusedClient"); const focused = bind( hypr, 'focusedClient' );
const WindowPopover = (): Gtk.Popover => { const WindowPopover = (): Gtk.Popover => {
// Set up boxes + Popover // Set up boxes + Popover
const popover = new Gtk.Popover(); const popover = new Gtk.Popover();
const popoverBox = WindowPopoverBox(); const popoverBox = WindowPopoverBox();
popover.set_child(popoverBox); popover.set_child( popoverBox );
return popover; return popover;
}; };
@@ -98,65 +103,88 @@ const ActiveWindow = () => {
// Return fully assembled HyprlandFocusedClient box // Return fully assembled HyprlandFocusedClient box
// ─────────────────────────────────────────────────────────────────── // ───────────────────────────────────────────────────────────────────
return ( return (
<box visible={focused.as(Boolean)}> <box visible={focused.as( Boolean )}>
<button <button
onClicked={() => windowPopover.popup()} onClicked={() => windowPopover.popup()}
cssClasses={["bar-button"]} cssClasses={[ 'bar-button' ]}
child={ child={
focused.as( focused.as( client => client && (
client =>
client && (
<label <label
maxWidthChars={70} maxWidthChars={70}
ellipsize={Pango.EllipsizeMode.END} ellipsize={Pango.EllipsizeMode.END}
label={bind(client, "title").as(String)} /> label={bind( client, 'title' ).as( String )} />
), ), )
)
}></button> }></button>
{windowPopover} {windowPopover}
</box > </box >
); );
}; };
type submaps = 'device' | 'launch' | 'workspace' | 'windowing' | 'screenshotting' | 'notifications' | '';
const ModeStatus = () => { const ModeStatus = () => {
const label = new Gtk.Label(); let isUsingHyprmode = false;
const map = {
"device": "D", try {
"launch": "L", const path = exec( 'bash -c "cd ~ && pwd"' ) + '/.config/hyprmode';
"workspace": "W",
"windowing": "M", isUsingHyprmode = readFile( path ).trim() === 'y';
"screenshotting": "S", } catch ( e ) {
"notifications": "N", printerr( 'Failed to read hyprmode config', e );
} }
const label = new Gtk.Label();
if ( !isUsingHyprmode ) return label;
print( '==> Using hyprmode config' );
const map = {
'device': 'D',
'launch': 'L',
'workspace': 'W',
'windowing': 'M',
'screenshotting': 'S',
'notifications': 'N',
'': 'N'
};
label.label = 'N';
label.cssClasses = [ 'mode-status' ];
// TODO: Possibly add popover to it that lists binds // TODO: Possibly add popover to it that lists binds
hypr.connect("submap", (name: string) => { hypr.connect( 'submap', ( _, name: submaps ) => {
label.label = map[name]; label.label = map[name];
label.cssClasses = ["mode-status", name + '-mode'] label.cssClasses = [
}) 'mode-status',
return name + '-mode'
} ];
} );
return label;
};
const WindowPopoverBox = () => { const WindowPopoverBox = () => {
return <box vertical> return <box vertical>
<label label={"Available Windows"} cssClasses={['title-2']}></label> <label label={'Available Windows'} cssClasses={[ 'title-2' ]}></label>
<Gtk.Separator marginTop={5} marginBottom={5}></Gtk.Separator> <Gtk.Separator marginTop={5} marginBottom={5}></Gtk.Separator>
<box vertical> <box vertical>
{bind(hypr, 'clients').as(clients => { {bind( hypr, 'clients' ).as( clients => {
return clients.map(client => { return clients.map( client => {
return <button child={ return <button child={
<box> <box>
<label label={bind(client, 'workspace').as(w => `(WS ${w.name})`)}></label> <label label={bind( client, 'workspace' ).as( w => `(WS ${ w.name })` )}></label>
<label label={bind(client, 'initialClass').as(c => `[${c}]`)}></label> <label label={bind( client, 'initialClass' ).as( c => `[${ c }]` )}></label>
<label label={bind(client, 'title')}></label> <label label={bind( client, 'title' )}></label>
</box> </box>
} }
onClicked={() => client.focus()} onClicked={() => client.focus()}
></button> ></button>;
}) } );
})} } )}
</box> </box>
</box> </box>;
} };
export default { export default {
Workspace, Workspace,

View File

@@ -1,17 +1,24 @@
import { bind } from "astal"; import AstalBattery from 'gi://AstalBattery';
import { execAsync } from "astal"; import AstalBluetooth from 'gi://AstalBluetooth';
import AstalBattery from "gi://AstalBattery"; import AstalNetwork from 'gi://AstalNetwork';
import AstalBluetooth from "gi://AstalBluetooth"; import AstalWp from 'gi://AstalWp';
import AstalNetwork from "gi://AstalNetwork"; import Brightness from '../../../util/brightness';
import AstalWp from "gi://AstalWp"; import {
import { Gtk } from "astal/gtk4"; Gtk
import Brightness from "../../../util/brightness"; } from 'astal/gtk4';
import QuickActions from "../../QuickActions/QuickActions"; import QuickActions from '../../QuickActions/QuickActions';
import {
bind
} from 'astal';
import {
execAsync
} from 'astal';
const STATE = AstalNetwork.DeviceState; const STATE = AstalNetwork.DeviceState;
const QuickView = () => { const QuickView = () => {
const qa = QuickActions.QuickActions(); const qa = QuickActions.QuickActions();
const showQuickActions = () => { const showQuickActions = () => {
qa.popup(); qa.popup();
}; };
@@ -19,14 +26,14 @@ const QuickView = () => {
return ( return (
<button <button
onClicked={() => showQuickActions()} onClicked={() => showQuickActions()}
cssClasses={["quick-action-button"]} cssClasses={[ 'quick-action-button' ]}
child={ child={
<box> <box>
<BatteryWidget></BatteryWidget> <BatteryWidget></BatteryWidget>
<Audio></Audio> <Audio></Audio>
<BluetoothWidget></BluetoothWidget> <BluetoothWidget></BluetoothWidget>
<NetworkWidget></NetworkWidget> <NetworkWidget></NetworkWidget>
<image iconName={"system-shutdown-symbolic"}></image> <image iconName={'system-shutdown-symbolic'}></image>
{qa} {qa}
</box> </box>
} }
@@ -40,37 +47,39 @@ const NetworkWidget = () => {
return ( return (
<box> <box>
<image <image
iconName={bind(network, "state").as(state => { iconName={bind( network, 'state' ).as( state => {
if (state === AstalNetwork.State.CONNECTING) { if ( state === AstalNetwork.State.CONNECTING ) {
return "chronometer-reset-symbolic"; return 'chronometer-reset-symbolic';
} else if ( } else if (
state === AstalNetwork.State.CONNECTED_LOCAL || state === AstalNetwork.State.CONNECTED_LOCAL
state === AstalNetwork.State.CONNECTED_SITE || || state === AstalNetwork.State.CONNECTED_SITE
state === AstalNetwork.State.CONNECTED_GLOBAL || state === AstalNetwork.State.CONNECTED_GLOBAL
) { ) {
return "network-wired-activated-symbolic"; return 'network-wired-activated-symbolic';
} else { } else {
return "paint-unknown-symbolic"; return 'paint-unknown-symbolic';
} }
})} } )}
cssClasses={["network-widget", "quick-view-symbol"]} cssClasses={[
visible={bind(network.wifi, "state").as( 'network-widget',
state => state !== STATE.ACTIVATED, 'quick-view-symbol'
)} ]}
visible={bind( network.wifi, 'state' ).as( state => state !== STATE.ACTIVATED, )}
></image> ></image>
<image <image
iconName={bind(network.wifi, "state").as(state => { iconName={bind( network.wifi, 'state' ).as( state => {
if (state === STATE.ACTIVATED) { if ( state === STATE.ACTIVATED ) {
return network.wifi.iconName; return network.wifi.iconName;
} else { } else {
return ""; return '';
} }
})} } )}
tooltipText={bind(network.wifi, 'ssid')} tooltipText={bind( network.wifi, 'ssid' )}
cssClasses={["network-widget", "quick-view-symbol"]} cssClasses={[
visible={bind(network.wifi, "state").as( 'network-widget',
state => state === STATE.ACTIVATED, 'quick-view-symbol'
)} ]}
visible={bind( network.wifi, 'state' ).as( state => state === STATE.ACTIVATED, )}
></image> ></image>
</box> </box>
); );
@@ -78,44 +87,40 @@ const NetworkWidget = () => {
const BluetoothWidget = () => { const BluetoothWidget = () => {
const bluetooth = AstalBluetooth.get_default(); const bluetooth = AstalBluetooth.get_default();
const enabled = bind(bluetooth, "isPowered"); const enabled = bind( bluetooth, 'isPowered' );
const connected = bind(bluetooth, "isConnected"); const connected = bind( bluetooth, 'isConnected' );
// For each connected BT device, render status // For each connected BT device, render status
return ( return (
<box> <box>
<box visible={enabled.as(e => e)}> <box visible={enabled.as( e => e )}>
<image <image
iconName={"bluetooth-active-symbolic"} iconName={'bluetooth-active-symbolic'}
visible={connected.as(c => c)} visible={connected.as( c => c )}
></image> ></image>
<image <image
iconName={"bluetooth-disconnected-symbolic"} iconName={'bluetooth-disconnected-symbolic'}
visible={connected.as(c => !c)} visible={connected.as( c => !c )}
></image> ></image>
</box> </box>
<image <image
iconName={"bluetooth-disabled-symbolic"} iconName={'bluetooth-disabled-symbolic'}
visible={enabled.as(e => !e)} visible={enabled.as( e => !e )}
></image> ></image>
<box> <box>
{bind(bluetooth, "devices").as(devices => { {bind( bluetooth, 'devices' ).as( devices => {
return devices.map(device => { return devices.map( device => {
return ( return (
<image <image
iconName={bind(device, "icon").as( iconName={bind( device, 'icon' ).as( icon => icon, )}
icon => icon, visible={bind( device, 'connected' )}
)} tooltipText={bind( device, 'batteryPercentage' ).as( n => {
visible={bind(device, "connected")} return device.get_name() + ': ' + n + '%';
tooltipText={bind(device, "batteryPercentage").as( }, )}
n => {
return device.get_name() + ': ' + n + "%";
},
)}
></image> ></image>
); );
}); } );
})} } )}
</box> </box>
</box> </box>
); );
@@ -123,21 +128,25 @@ const BluetoothWidget = () => {
let hasSentNotification = false; let hasSentNotification = false;
const BatteryWidget = () => { const BatteryWidget = () => {
const battery = AstalBattery.get_default(); const battery = AstalBattery.get_default();
if (battery.get_is_present()) {
if ( battery.get_is_present() ) {
return ( return (
<image <image
iconName={bind(battery, "batteryIconName").as(icon => icon)} iconName={bind( battery, 'batteryIconName' ).as( icon => icon )}
cssClasses={["quick-view-symbol"]} cssClasses={[ 'quick-view-symbol' ]}
tooltipText={bind(battery, 'percentage').as(p => { tooltipText={bind( battery, 'percentage' ).as( p => {
const level = Math.round(p * 100) const level = Math.round( p * 100 );
if ( level < 20 && !hasSentNotification ) { if ( level < 20 && !hasSentNotification ) {
hasSentNotification = true; hasSentNotification = true;
execAsync( 'bash -c "notify-send \'Battery level below 20%\'"' ); execAsync( 'bash -c "notify-send \'Battery level below 20%\'"' );
} }
return `Battery Level: ${level}%`
})} return `Battery Level: ${ level }%`;
} )}
></image> ></image>
); );
} else { } else {
@@ -148,14 +157,14 @@ const BatteryWidget = () => {
const BrightnessWidget = () => { const BrightnessWidget = () => {
const brightness = Brightness.get_default(); const brightness = Brightness.get_default();
const screen_brightness = bind(brightness, "screen"); const screen_brightness = bind( brightness, 'screen' );
return ( return (
<box cssClasses={["quick-view-symbol"]}> <box cssClasses={[ 'quick-view-symbol' ]}>
<image iconName={"brightness-high-symbolic"}></image> <image iconName={'brightness-high-symbolic'}></image>
<label <label
label={screen_brightness.as(b => '' + Math.round(100 * b))} label={screen_brightness.as( b => '' + Math.round( 100 * b ) )}
visible={bind(brightness, "screenAvailable")} visible={bind( brightness, 'screenAvailable' )}
></label> ></label>
</box> </box>
); );
@@ -163,31 +172,27 @@ const BrightnessWidget = () => {
const Audio = () => { const Audio = () => {
const wireplumber = AstalWp.get_default(); const wireplumber = AstalWp.get_default();
if (wireplumber) {
if ( wireplumber ) {
return ( return (
<box orientation={Gtk.Orientation.HORIZONTAL}> <box orientation={Gtk.Orientation.HORIZONTAL}>
<image <image
iconName={bind(wireplumber.defaultSpeaker, "volumeIcon").as( iconName={bind( wireplumber.defaultSpeaker, 'volumeIcon' ).as( icon => icon, )}
icon => icon, cssClasses={[ 'quick-view-symbol' ]}
)} tooltipText={bind( wireplumber.defaultSpeaker, 'volume' ).as( v => Math.round( 100 * v ) + '%' )}
cssClasses={["quick-view-symbol"]}
tooltipText={bind(wireplumber.defaultSpeaker, 'volume').as(v => Math.round(100 * v) + '%')}
></image> ></image>
<image <image
iconName={bind( iconName={bind( wireplumber.defaultMicrophone,
wireplumber.defaultMicrophone, 'volumeIcon', ).as( icon => icon )}
"volumeIcon", cssClasses={[ 'quick-view-symbol' ]}
).as(icon => icon)} tooltipText={bind( wireplumber.defaultMicrophone, 'volume' ).as( v => Math.round( 100 * v ) + '%' )}
cssClasses={["quick-view-symbol"]}
tooltipText={bind(wireplumber.defaultMicrophone, 'volume').as(v => Math.round(100 * v) + '%')}
></image> ></image>
</box> </box>
); );
} else { } else {
print( print( '[ WirePlumber ] Could not connect, Audio support in bar will be missing', );
"[ WirePlumber ] Could not connect, Audio support in bar will be missing",
); return <image iconName={'action-unavailable-symbolic'}></image>;
return <image iconName={"action-unavailable-symbolic"}></image>;
} }
}; };
@@ -195,4 +200,5 @@ const Audio = () => {
export default { export default {
QuickView, QuickView,
BrightnessWidget
}; };

View File

@@ -1,35 +1,39 @@
import { execAsync } from "astal"; import {
import { Gtk } from "astal/gtk4"; Gtk
import sysinfo from "../sysinfo"; } from 'astal/gtk4';
import {
execAsync
} from 'astal';
import sysinfo from '../sysinfo';
const info = () => { const info = () => {
return ( return (
<box vertical> <box vertical>
<label <label
label={"System Information"} label={'System Information'}
cssClasses={["title-2"]} cssClasses={[ 'title-2' ]}
></label> ></label>
<Gtk.Separator marginTop={5} marginBottom={10}></Gtk.Separator> <Gtk.Separator marginTop={5} marginBottom={10}></Gtk.Separator>
<label <label
vexpand vexpand
halign={Gtk.Align.START} halign={Gtk.Align.START}
hexpand hexpand
label={sysinfo.ramUsed(used => { label={sysinfo.ramUsed( used => {
return "RAM: " + used + ` (${sysinfo.ramUtil.get()}%)`; return 'RAM: ' + used + ` (${ sysinfo.ramUtil.get() }%)`;
})} } )}
></label> ></label>
<label <label
label={sysinfo.systemStats(stats => { label={sysinfo.systemStats( stats => {
return `CPU: ${stats.cpuTemp}, ${stats.cpuClk} return `CPU: ${ stats.cpuTemp }, ${ stats.cpuClk }
GPU: ${stats.gpuTemp}, ${stats.gpuClk} (${stats.vram} / ${stats.availableVRAM}) GPU: ${ stats.gpuTemp }, ${ stats.gpuClk } (${ stats.vram } / ${ stats.availableVRAM })
Kernel: ${stats.kernel}`; Kernel: ${ stats.kernel }`;
})} } )}
></label> ></label>
<Gtk.Separator marginTop={10}></Gtk.Separator> <Gtk.Separator marginTop={10}></Gtk.Separator>
<button <button
onClicked={() => execAsync(`/bin/sh -c "kitty --hold fish -c 'fastfetch'"`)} onClicked={() => execAsync( '/bin/sh -c "kitty --hold fish -c \'fastfetch\'"' )}
child={ child={
<label label={"View FastFetch"}></label> <label label={'View FastFetch'}></label>
}></button> }></button>
</box> </box>
); );
@@ -38,7 +42,7 @@ Kernel: ${stats.kernel}`;
const SystemInformationPanel = () => { const SystemInformationPanel = () => {
const popover = new Gtk.Popover(); const popover = new Gtk.Popover();
popover.set_child(info()); popover.set_child( info() );
return popover; return popover;
}; };
@@ -54,44 +58,44 @@ const SystemInfo = () => {
sysinfo.refreshStats(); sysinfo.refreshStats();
}; };
if (sysinfo.enabled) { if ( sysinfo.enabled ) {
return ( return (
<button <button
onClicked={() => openSysInfo()} onClicked={() => openSysInfo()}
child={ child={
<box tooltipText={sysinfo.ramUsed(v => v)}> <box tooltipText={sysinfo.ramUsed( v => v )}>
<box <box
cssClasses={[ 'quick-view-symbol' ]} cssClasses={[ 'quick-view-symbol' ]}
> >
<image <image
iconName={"power-profile-performance-symbolic"} iconName={'power-profile-performance-symbolic'}
marginEnd={1} marginEnd={1}
></image> ></image>
<label <label
label={sysinfo.cpuUtil(util => util)} label={sysinfo.cpuUtil( util => util )}
marginEnd={5} marginEnd={5}
></label> ></label>
</box> </box>
<box <box
cssClasses={[ 'quick-view-symbol' ]} cssClasses={[ 'quick-view-symbol' ]}
> >
<image iconName={"memory"}></image> <image iconName={'memory'}></image>
<label label={sysinfo.ramUtil(util => util)}></label> <label label={sysinfo.ramUtil( util => util )}></label>
</box> </box>
<box <box
cssClasses={[ 'quick-view-symbol' ]} cssClasses={[ 'quick-view-symbol' ]}
> >
<image iconName={"show-gpu-effects-symbolic"}></image> <image iconName={'show-gpu-effects-symbolic'}></image>
<label label={sysinfo.gpuUtil(util => util)}></label> <label label={sysinfo.gpuUtil( util => util )}></label>
</box> </box>
{panel} {panel}
</box> </box>
} }
cssClasses={["bar-button"]} cssClasses={[ 'bar-button' ]}
></button> ></button>
); );
} else { } else {
return <image iconName={"action-unavailable-symbolic"}></image>; return <image iconName={'action-unavailable-symbolic'}></image>;
} }
}; };

View File

@@ -3,3 +3,4 @@ $bg-color: #0A0A0F;
$accent-color: #591641; $accent-color: #591641;
$accent-color-2: #97103A; $accent-color-2: #97103A;
$shadow-color: rgba(0, 0, 2, 0.3); $shadow-color: rgba(0, 0, 2, 0.3);
$monospace-font: Source Code Pro

View File

@@ -23,26 +23,46 @@ bind = $mainMod, escape, exec, wlogout
# │ Launch submap │ # │ Launch submap │
# ╰────────────────────────────────────────────────╯ # ╰────────────────────────────────────────────────╯
bind = $mainMod, X, submap, launch bind = $mainMod, X, submap, launch
submap = launch, reset submap = launch
# ── Kitty ─────────────────────────────────────────────────────────── # ── Kitty ───────────────────────────────────────────────────────────
bind = , K, exec, kitty bind = , K, exec, kitty
bind = , K, submap, reset
bind = , Return, exec, kitty bind = , Return, exec, kitty
bind = , Return, submap, reset
# ── Others ────────────────────────────────────────────────────────── # ── Others ──────────────────────────────────────────────────────────
bind = , L, exec, librewolf bind = , L, exec, librewolf
bind = , L, submap, reset
bind = , E, exec, thunar bind = , E, exec, thunar
bind = , E, submap, reset
bind = , D, exec, vesktop bind = , D, exec, vesktop
bind = , D, submap, reset
bind = , V, exec, vscodium bind = , V, exec, vscodium
bind = , V, submap, reset
bind = , T, exec, thunderbird bind = , T, exec, thunderbird
bind = , T, submap, reset
bind = , H, exec, heroic bind = , H, exec, heroic
bind = , H, submap, reset
bind = , Z, exec, zathura bind = , Z, exec, zathura
bind = , Z, submap, reset
bind = , A, exec, notify-send 'AirPlay server starting...' --app-name="AirPlay Audio" && terminator -T "hidden-terminator" -e "systemctl start avahi-daemon && shairport-sync -a LinuxPlay" bind = , A, exec, notify-send 'AirPlay server starting...' --app-name="AirPlay Audio" && terminator -T "hidden-terminator" -e "systemctl start avahi-daemon && shairport-sync -a LinuxPlay"
bind = , A, submap, reset
bind = SHIFT, A, exec, notify-send 'AirPlay video server starting...' --app-name="AirPlay Video" && terminator -T "hidden-terminator" -e "systemctl start avahi-daemon && sleep 5 && uxplay -n LinuxVideoPlay -nh" bind = SHIFT, A, exec, notify-send 'AirPlay video server starting...' --app-name="AirPlay Video" && terminator -T "hidden-terminator" -e "systemctl start avahi-daemon && sleep 5 && uxplay -n LinuxVideoPlay -nh"
bind = SHIFT, A, submap, reset
bind = , S, exec, notify-send 'Steam is launching...' --app-name="Steam" && steam bind = , S, exec, notify-send 'Steam is launching...' --app-name="Steam" && steam
bind = , O, exec, terminator -e "~/projects/dotfiles/ai.sh" bind = , S, submap, reset
# ── Exit submap ───────────────────────────────────────────────────── # ── Exit submap ─────────────────────────────────────────────────────

View File

@@ -1,6 +1,7 @@
$mainMod = SUPER $mainMod = SUPER
source=./modal-binds/workspace.conf source=./modal-binds/workspace.conf
source=./modal-binds/launch.conf source=./modal-binds/launch.conf
source=./modal-binds/notifications.conf
source=./modal-binds/window-management.conf source=./modal-binds/window-management.conf
source=./modal-binds/screenshot.conf source=./modal-binds/screenshot.conf
source=./modal-binds/device.conf source=./modal-binds/device.conf

724
eslint.config.mjs Normal file
View File

@@ -0,0 +1,724 @@
import eslint from '@eslint/js';
import globals from 'globals';
import stylistic from '@stylistic/eslint-plugin';
import tseslint from 'typescript-eslint';
import typescript from '@typescript-eslint/eslint-plugin';
import vue from 'eslint-plugin-vue';
const style = {
'plugins': {
'@stylistic': stylistic,
'@stylistic/js': stylistic,
'@stylistic/ts': stylistic,
},
'files': [
'**/*.ts',
'**/*.js',
'**/*.mjs',
'**/*.cjs',
'**/*.tsx',
'**/*.jsx'
],
'rules': {
'sort-imports': [
'warn',
{
'ignoreCase': false,
'ignoreDeclarationSort': false,
'ignoreMemberSort': false,
'memberSyntaxSortOrder': [
'none',
'all',
'multiple',
'single'
],
'allowSeparatedGroups': false
}
],
// Formatting
'@stylistic/array-bracket-newline': [
'error',
{
'multiline': true,
'minItems': 2
}
],
'@stylistic/array-bracket-spacing': [
'error',
'always',
{
'singleValue': true,
'arraysInArrays': true,
'objectsInArrays': true
}
],
'@stylistic/array-element-newline': [
'error',
{
'multiline': true,
'minItems': 2
}
],
'@stylistic/arrow-parens': [
'error',
'as-needed'
],
'@stylistic/arrow-spacing': [
'error',
{
'before': true,
'after': true
}
],
'@stylistic/block-spacing': [
'error',
'always'
],
'@stylistic/brace-style': [
'error',
'1tbs'
],
'@stylistic/comma-spacing': [
'error',
{
'before': false,
'after': true
}
],
'@stylistic/comma-style': [
'error',
'last'
],
'@stylistic/dot-location': [
'error',
'property'
],
'@stylistic/eol-last': [
'error',
'always'
],
'@stylistic/function-call-spacing': [
'error',
'never'
],
'@stylistic/function-paren-newline': [
'error',
{
'minItems': 3
}
],
'@stylistic/function-call-argument-newline': [
'error',
'consistent'
],
'@stylistic/implicit-arrow-linebreak': [
'error',
'beside'
],
'@stylistic/indent': [
'error',
4
],
'@stylistic/key-spacing': [
'error',
{
'beforeColon': false,
'afterColon': true
}
],
'@stylistic/keyword-spacing': [
'error',
{
'before': true,
'after': true
}
],
'@stylistic/lines-between-class-members': [
'error',
'always'
],
'@stylistic/max-len': [
'warn',
{
'code': 120,
'comments': 140,
'ignoreComments': false,
'ignoreUrls': true,
'ignoreStrings': false
}
],
'@stylistic/new-parens': [
'error',
'always'
],
'@stylistic/newline-per-chained-call': [ 'error' ],
'@stylistic/no-extra-parens': [
'error',
'all',
{
'nestedBinaryExpressions': false,
'ternaryOperandBinaryExpressions': false,
'ignoreJSX': 'multi-line',
'nestedConditionalExpressions': false
}
],
'@stylistic/no-extra-semi': 'error',
'@stylistic/no-floating-decimal': 'error',
'@stylistic/no-mixed-operators': 'error',
'@stylistic/no-mixed-spaces-and-tabs': 'error',
'@stylistic/no-multi-spaces': 'error',
'@stylistic/no-multiple-empty-lines': [
'error',
{
'max': 3,
'maxEOF': 2
}
],
'@stylistic/no-tabs': 'error',
'@stylistic/no-trailing-spaces': 'error',
'@stylistic/no-whitespace-before-property': 'error',
'@stylistic/object-curly-newline': [
'error',
{
'multiline': true,
'minProperties': 1
}
],
'@stylistic/object-curly-spacing': [
'error',
'always'
],
'@stylistic/object-property-newline': 'error',
'@stylistic/operator-linebreak': [
'error',
'before'
],
'@stylistic/one-var-declaration-per-line': 'error',
'@stylistic/padded-blocks': [
'error',
{
'blocks': 'never',
'classes': 'always',
'switches': 'never',
}
],
// Padding lines. The most in-depth part of this config
'@stylistic/padding-line-between-statements': [
'error',
// Variables, Constants
{
'blankLine': 'never',
'prev': 'var',
'next': 'var'
},
{
'blankLine': 'never',
'prev': 'let',
'next': 'let'
},
{
'blankLine': 'never',
'prev': 'const',
'next': 'const'
},
{
'blankLine': 'always',
'prev': 'var',
'next': [
'block',
'block-like',
'break',
'cjs-export',
'cjs-import',
'class',
'const',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'import',
'let',
'return',
'switch',
'throw',
'try',
'var',
'with'
]
},
{
'blankLine': 'always',
'prev': 'let',
'next': [
'block',
'block-like',
'break',
'cjs-export',
'cjs-import',
'class',
'const',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'import',
'return',
'switch',
'throw',
'try',
'var',
'while',
'with'
]
},
{
'blankLine': 'always',
'prev': 'const',
'next': [
'block',
'block-like',
'break',
'cjs-export',
'cjs-import',
'class',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'import',
'let',
'return',
'switch',
'throw',
'try',
'var',
'while',
'with'
]
},
// Import
{
'blankLine': 'never',
'prev': 'import',
'next': 'import'
},
{
'blankLine': 'never',
'prev': 'cjs-import',
'next': 'cjs-import'
},
{
'blankLine': 'always',
'prev': [
'block',
'block-like',
'break',
'cjs-export',
'class',
'const',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'let',
'return',
'switch',
'throw',
'try',
'var',
'while',
'with'
],
'next': 'cjs-import'
},
{
'blankLine': 'always',
'prev': 'cjs-import',
'next': [
'block',
'block-like',
'break',
'cjs-export',
'class',
'const',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'let',
'return',
'switch',
'throw',
'try',
'var',
'while',
'with'
]
},
{
'blankLine': 'always',
'prev': [
'block',
'block-like',
'break',
'cjs-export',
'class',
'const',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'let',
'return',
'switch',
'throw',
'try',
'var',
'while',
'with'
],
'next': 'import'
},
{
'blankLine': 'always',
'prev': 'import',
'next': [
'block',
'block-like',
'break',
'cjs-export',
'class',
'const',
'continue',
'debugger',
'directive',
'do',
'empty',
'export',
'expression',
'for',
'function',
'if',
'iife',
'let',
'return',
'switch',
'throw',
'try',
'var',
'while',
'with'
]
},
// If
{
'blankLine': 'always',
'prev': '*',
'next': 'if'
},
{
'blankLine': 'always',
'prev': 'if',
'next': '*'
},
// For
{
'blankLine': 'always',
'prev': '*',
'next': 'for'
},
{
'blankLine': 'always',
'prev': 'for',
'next': '*'
},
// While
{
'blankLine': 'always',
'prev': '*',
'next': 'while'
},
{
'blankLine': 'always',
'prev': 'while',
'next': '*'
},
// Functions
{
'blankLine': 'always',
'prev': '*',
'next': 'function'
},
{
'blankLine': 'always',
'prev': 'function',
'next': '*'
},
// Block Statements
{
'blankLine': 'always',
'prev': '*',
'next': 'block-like'
},
{
'blankLine': 'always',
'prev': 'block-like',
'next': '*'
},
// Switch
{
'blankLine': 'always',
'prev': '*',
'next': 'switch'
},
{
'blankLine': 'always',
'prev': 'switch',
'next': '*'
},
// Try-Catch
{
'blankLine': 'always',
'prev': '*',
'next': 'try'
},
{
'blankLine': 'always',
'prev': 'try',
'next': '*'
},
// Throw
{
'blankLine': 'always',
'prev': '*',
'next': 'throw'
},
{
'blankLine': 'always',
'prev': 'throw',
'next': '*'
},
// Return
{
'blankLine': 'never',
'prev': 'return',
'next': '*'
},
{
'blankLine': 'always',
'prev': '*',
'next': 'return'
},
// Export
{
'blankLine': 'always',
'prev': '*',
'next': 'export'
},
{
'blankLine': 'always',
'prev': 'export',
'next': '*'
},
{
'blankLine': 'always',
'prev': '*',
'next': 'cjs-export'
},
{
'blankLine': 'always',
'prev': 'cjs-export',
'next': '*'
},
// Classes
{
'blankLine': 'always',
'prev': '*',
'next': 'class'
},
{
'blankLine': 'always',
'prev': 'class',
'next': '*'
},
],
'@stylistic/quote-props': [
'error',
'always'
],
'@stylistic/quotes': [
'error',
'single'
],
'@stylistic/rest-spread-spacing': [
'error',
'never'
],
'@stylistic/semi': [
'error',
'always'
],
'@stylistic/semi-spacing': [
'error',
{
'before': false,
'after': true
}
],
'@stylistic/semi-style': [
'error',
'last'
],
'@stylistic/space-before-blocks': [
'error',
'always'
],
'@stylistic/space-before-function-paren': [
'error',
'always'
],
'@stylistic/space-in-parens': [
'error',
'always'
],
'@stylistic/space-infix-ops': [
'error',
{
'int32Hint': false
}
],
'@stylistic/space-unary-ops': 'error',
'@stylistic/spaced-comment': [
'error',
'always'
],
'@stylistic/template-curly-spacing': [
'error',
'always'
],
'@stylistic/switch-colon-spacing': 'error',
'@stylistic/wrap-iife': [
'error',
'inside'
],
'@stylistic/wrap-regex': 'error',
'@stylistic/ts/type-annotation-spacing': 'error',
}
};
/** @type {import('eslint').Linter.Config} */
export default tseslint.config(
// Base JavaScript rules
eslint.configs.recommended,
tseslint.configs.recommended,
style,
// Vue support (including TS and JSX inside SFCs)
{
'files': [ '**/*.vue' ],
'languageOptions': {
'sourceType': 'module',
'ecmaVersion': 'latest',
'globals': globals.browser,
'parserOptions': {
'parser': tseslint.parser,
},
},
'plugins': {
'vue': vue,
'@stylistic': stylistic,
'@stylistic/js': stylistic,
'@stylistic/ts': stylistic,
'@typescript-eslint': typescript,
},
'extends': [
eslint.configs.recommended,
...vue.configs['flat/recommended']
],
'rules': {
...typescript.configs.recommended.rules,
...style.rules,
// Vue specific rules
'@stylistic/indent': 'off',
'vue/html-indent': [
'error',
4
],
'vue/html-comment-indent': [
'error',
4
],
'vue/script-indent': [
'error',
4,
{
'baseIndent': 1,
'switchCase': 1
}
],
'vue/html-self-closing': [
'error',
{
'html': {
'void': 'never',
'normal': 'never',
'component': 'always'
},
'svg': 'always',
'math': 'never'
}
],
'vue/max-attributes-per-line': [
'error',
{
'singleline': 3,
'multiline': 1,
}
],
},
},
);

8
package.json Normal file
View File

@@ -0,0 +1,8 @@
{
"devDependencies": {
"@eslint/js": "^9.37.0",
"@stylistic/eslint-plugin": "^5.4.0",
"eslint-plugin-vue": "^10.5.1",
"typescript-eslint": "^8.46.1"
}
}

View File

@@ -3,3 +3,4 @@ $bg-color: {{ colour-background-hex }};
$accent-color: {{ colour-accent-hex }}; $accent-color: {{ colour-accent-hex }};
$accent-color-2: {{ colour-accent-2-hex }}; $accent-color-2: {{ colour-accent-2-hex }};
$shadow-color: {{ colour-shadow-rgba-03 }}; $shadow-color: {{ colour-shadow-rgba-03 }};
$monospace-font: {{ font-mono }}