Added ags to hyprland
This commit is contained in:
203
home/desktops/hyprland/ags/modules/sideright/calendar.js
Normal file
203
home/desktops/hyprland/ags/modules/sideright/calendar.js
Normal file
@@ -0,0 +1,203 @@
|
||||
const { Gio } = imports.gi;
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { Box, Button, Label } = Widget;
|
||||
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
|
||||
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
|
||||
|
||||
import { TodoWidget } from "./todolist.js";
|
||||
import { getCalendarLayout } from "./calendar_layout.js";
|
||||
|
||||
let calendarJson = getCalendarLayout(undefined, true);
|
||||
let monthshift = 0;
|
||||
|
||||
function getDateInXMonthsTime(x) {
|
||||
var currentDate = new Date(); // Get the current date
|
||||
var targetMonth = currentDate.getMonth() + x; // Calculate the target month
|
||||
var targetYear = currentDate.getFullYear(); // Get the current year
|
||||
|
||||
// Adjust the year and month if necessary
|
||||
targetYear += Math.floor(targetMonth / 12);
|
||||
targetMonth = (targetMonth % 12 + 12) % 12;
|
||||
|
||||
// Create a new date object with the target year and month
|
||||
var targetDate = new Date(targetYear, targetMonth, 1);
|
||||
|
||||
// Set the day to the last day of the month to get the desired date
|
||||
// targetDate.setDate(0);
|
||||
|
||||
return targetDate;
|
||||
}
|
||||
|
||||
const weekDays = [ // MONDAY IS THE FIRST DAY OF THE WEEK :HESRIGHTYOUKNOW:
|
||||
{ day: 'Mo', today: 0 },
|
||||
{ day: 'Tu', today: 0 },
|
||||
{ day: 'We', today: 0 },
|
||||
{ day: 'Th', today: 0 },
|
||||
{ day: 'Fr', today: 0 },
|
||||
{ day: 'Sa', today: 0 },
|
||||
{ day: 'Su', today: 0 },
|
||||
]
|
||||
|
||||
const CalendarDay = (day, today) => Widget.Button({
|
||||
className: `sidebar-calendar-btn ${today == 1 ? 'sidebar-calendar-btn-today' : (today == -1 ? 'sidebar-calendar-btn-othermonth' : '')}`,
|
||||
child: Widget.Overlay({
|
||||
child: Box({}),
|
||||
overlays: [Label({
|
||||
hpack: 'center',
|
||||
className: 'txt-smallie txt-semibold sidebar-calendar-btn-txt',
|
||||
label: String(day),
|
||||
})],
|
||||
})
|
||||
})
|
||||
|
||||
const CalendarWidget = () => {
|
||||
const calendarMonthYear = Widget.Button({
|
||||
className: 'txt txt-large sidebar-calendar-monthyear-btn',
|
||||
onClicked: () => shiftCalendarXMonths(0),
|
||||
setup: (button) => {
|
||||
button.label = `${new Date().toLocaleString('default', { month: 'long' })} ${new Date().getFullYear()}`;
|
||||
setupCursorHover(button);
|
||||
}
|
||||
});
|
||||
const addCalendarChildren = (box, calendarJson) => {
|
||||
const children = box.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
child.destroy();
|
||||
}
|
||||
box.children = calendarJson.map((row, i) => Widget.Box({
|
||||
className: 'spacing-h-5',
|
||||
children: row.map((day, i) => CalendarDay(day.day, day.today)),
|
||||
}))
|
||||
}
|
||||
function shiftCalendarXMonths(x) {
|
||||
if (x == 0) monthshift = 0;
|
||||
else monthshift += x;
|
||||
var newDate;
|
||||
if (monthshift == 0) newDate = new Date();
|
||||
else newDate = getDateInXMonthsTime(monthshift);
|
||||
|
||||
calendarJson = getCalendarLayout(newDate, (monthshift == 0));
|
||||
calendarMonthYear.label = `${monthshift == 0 ? '' : '• '}${newDate.toLocaleString('default', { month: 'long' })} ${newDate.getFullYear()}`;
|
||||
addCalendarChildren(calendarDays, calendarJson);
|
||||
}
|
||||
const calendarHeader = Widget.Box({
|
||||
className: 'spacing-h-5 sidebar-calendar-header',
|
||||
setup: (box) => {
|
||||
box.pack_start(calendarMonthYear, false, false, 0);
|
||||
box.pack_end(Widget.Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
Button({
|
||||
className: 'sidebar-calendar-monthshift-btn',
|
||||
onClicked: () => shiftCalendarXMonths(-1),
|
||||
child: MaterialIcon('chevron_left', 'norm'),
|
||||
setup: setupCursorHover,
|
||||
}),
|
||||
Button({
|
||||
className: 'sidebar-calendar-monthshift-btn',
|
||||
onClicked: () => shiftCalendarXMonths(1),
|
||||
child: MaterialIcon('chevron_right', 'norm'),
|
||||
setup: setupCursorHover,
|
||||
})
|
||||
]
|
||||
}), false, false, 0);
|
||||
}
|
||||
})
|
||||
const calendarDays = Widget.Box({
|
||||
hexpand: true,
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
setup: (box) => {
|
||||
addCalendarChildren(box, calendarJson);
|
||||
}
|
||||
});
|
||||
return Widget.EventBox({
|
||||
onScrollUp: () => shiftCalendarXMonths(-1),
|
||||
onScrollDown: () => shiftCalendarXMonths(1),
|
||||
child: Widget.Box({
|
||||
hpack: 'center',
|
||||
children: [
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: [
|
||||
calendarHeader,
|
||||
Widget.Box({
|
||||
homogeneous: true,
|
||||
className: 'spacing-h-5',
|
||||
children: weekDays.map((day, i) => CalendarDay(day.day, day.today))
|
||||
}),
|
||||
calendarDays,
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
const defaultShown = 'calendar';
|
||||
const contentStack = Widget.Stack({
|
||||
hexpand: true,
|
||||
children: {
|
||||
'calendar': CalendarWidget(),
|
||||
'todo': TodoWidget(),
|
||||
// 'stars': Widget.Label({ label: 'GitHub feed will be here' }),
|
||||
},
|
||||
transition: 'slide_up_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
setup: (stack) => Utils.timeout(1, () => {
|
||||
stack.shown = defaultShown;
|
||||
})
|
||||
})
|
||||
|
||||
const StackButton = (stackItemName, icon, name) => Widget.Button({
|
||||
className: 'button-minsize sidebar-navrail-btn txt-small spacing-h-5',
|
||||
onClicked: (button) => {
|
||||
contentStack.shown = stackItemName;
|
||||
const kids = button.get_parent().get_children();
|
||||
for (let i = 0; i < kids.length; i++) {
|
||||
if (kids[i] != button) kids[i].toggleClassName('sidebar-navrail-btn-active', false);
|
||||
else button.toggleClassName('sidebar-navrail-btn-active', true);
|
||||
}
|
||||
},
|
||||
child: Box({
|
||||
className: 'spacing-v-5',
|
||||
vertical: true,
|
||||
children: [
|
||||
Label({
|
||||
className: `txt icon-material txt-hugeass`,
|
||||
label: icon,
|
||||
}),
|
||||
Label({
|
||||
label: name,
|
||||
className: 'txt txt-smallie',
|
||||
}),
|
||||
]
|
||||
}),
|
||||
setup: (button) => Utils.timeout(1, () => {
|
||||
setupCursorHover(button);
|
||||
button.toggleClassName('sidebar-navrail-btn-active', defaultShown === stackItemName);
|
||||
})
|
||||
});
|
||||
|
||||
export const ModuleCalendar = () => Box({
|
||||
className: 'sidebar-group spacing-h-5',
|
||||
setup: (box) => {
|
||||
box.pack_start(Box({
|
||||
vpack: 'center',
|
||||
homogeneous: true,
|
||||
vertical: true,
|
||||
className: 'sidebar-navrail spacing-v-10',
|
||||
children: [
|
||||
StackButton('calendar', 'calendar_month', 'Calendar'),
|
||||
StackButton('todo', 'done_outline', 'To Do'),
|
||||
// StackButton(box, 'stars', 'star', 'GitHub'),
|
||||
]
|
||||
}), false, false, 0);
|
||||
box.pack_end(contentStack, false, false, 0);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
function checkLeapYear(year) {
|
||||
return (
|
||||
year % 400 == 0 ||
|
||||
(year % 4 == 0 && year % 100 != 0));
|
||||
}
|
||||
|
||||
function getMonthDays(month, year) {
|
||||
const leapYear = checkLeapYear(year);
|
||||
if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 31;
|
||||
if (month == 2 && leapYear) return 29;
|
||||
if (month == 2 && !leapYear) return 28;
|
||||
return 30;
|
||||
}
|
||||
|
||||
function getNextMonthDays(month, year) {
|
||||
const leapYear = checkLeapYear(year);
|
||||
if (month == 1 && leapYear) return 29;
|
||||
if (month == 1 && !leapYear) return 28;
|
||||
if (month == 12) return 31;
|
||||
if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 30;
|
||||
return 31;
|
||||
}
|
||||
|
||||
function getPrevMonthDays(month, year) {
|
||||
const leapYear = checkLeapYear(year);
|
||||
if (month == 3 && leapYear) return 29;
|
||||
if (month == 3 && !leapYear) return 28;
|
||||
if (month == 1) return 31;
|
||||
if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 30;
|
||||
return 31;
|
||||
}
|
||||
|
||||
export function getCalendarLayout(dateObject, highlight) {
|
||||
if (!dateObject) dateObject = new Date();
|
||||
const weekday = (dateObject.getDay() + 6) % 7; // MONDAY IS THE FIRST DAY OF THE WEEK
|
||||
const day = dateObject.getDate();
|
||||
const month = dateObject.getMonth() + 1;
|
||||
const year = dateObject.getFullYear();
|
||||
const weekdayOfMonthFirst = (weekday + 35 - (day - 1)) % 7;
|
||||
const daysInMonth = getMonthDays(month, year);
|
||||
const daysInNextMonth = getNextMonthDays(month, year);
|
||||
const daysInPrevMonth = getPrevMonthDays(month, year);
|
||||
|
||||
// Fill
|
||||
var monthDiff = (weekdayOfMonthFirst == 0 ? 0 : -1);
|
||||
var toFill, dim;
|
||||
if(weekdayOfMonthFirst == 0) {
|
||||
toFill = 1;
|
||||
dim = daysInMonth;
|
||||
}
|
||||
else {
|
||||
toFill = (daysInPrevMonth - (weekdayOfMonthFirst - 1));
|
||||
dim = daysInPrevMonth;
|
||||
}
|
||||
var calendar = [...Array(6)].map(() => Array(7));
|
||||
var i = 0, j = 0;
|
||||
while (i < 6 && j < 7) {
|
||||
calendar[i][j] = {
|
||||
"day": toFill,
|
||||
"today": ((toFill == day && monthDiff == 0 && highlight) ? 1 : (
|
||||
monthDiff == 0 ? 0 :
|
||||
-1
|
||||
))
|
||||
};
|
||||
// Increment
|
||||
toFill++;
|
||||
if (toFill > dim) { // Next month?
|
||||
monthDiff++;
|
||||
if (monthDiff == 0)
|
||||
dim = daysInMonth;
|
||||
else if (monthDiff == 1)
|
||||
dim = daysInNextMonth;
|
||||
toFill = 1;
|
||||
}
|
||||
// Next tile
|
||||
j++;
|
||||
if (j == 7) {
|
||||
j = 0;
|
||||
i++;
|
||||
}
|
||||
|
||||
}
|
||||
return calendar;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
|
||||
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
const { Box, Button, Icon, Label, Revealer, Scrollable, Slider, Stack } = Widget;
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
|
||||
import { iconExists } from '../../.miscutils/icons.js';
|
||||
|
||||
const AppVolume = (stream) => Box({
|
||||
className: 'sidebar-volmixer-stream spacing-h-10',
|
||||
children: [
|
||||
Icon({
|
||||
className: 'sidebar-volmixer-stream-appicon',
|
||||
vpack: 'center',
|
||||
tooltipText: stream.stream.name,
|
||||
setup: (self) => {
|
||||
self.hook(stream, (self) => {
|
||||
self.icon = stream.stream.name.toLowerCase();
|
||||
})
|
||||
},
|
||||
}),
|
||||
Box({
|
||||
hexpand: true,
|
||||
vpack: 'center',
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: [
|
||||
Label({
|
||||
xalign: 0,
|
||||
maxWidthChars: 1,
|
||||
truncate: 'end',
|
||||
label: stream.description,
|
||||
className: 'txt-small',
|
||||
setup: (self) => self.hook(stream, (self) => {
|
||||
self.label = `${stream.stream.name} • ${stream.description}`
|
||||
})
|
||||
}),
|
||||
Slider({
|
||||
drawValue: false,
|
||||
hpack: 'fill',
|
||||
className: 'sidebar-volmixer-stream-slider',
|
||||
value: stream.volume,
|
||||
min: 0, max: 1,
|
||||
onChange: ({ value }) => {
|
||||
stream.volume = value;
|
||||
},
|
||||
setup: (self) => self.hook(stream, (self) => {
|
||||
self.value = stream.volume;
|
||||
})
|
||||
}),
|
||||
// Box({
|
||||
// homogeneous: true,
|
||||
// className: 'test',
|
||||
// children: [AnimatedSlider({
|
||||
// className: 'sidebar-volmixer-stream-slider',
|
||||
// value: stream.volume,
|
||||
// })],
|
||||
// })
|
||||
]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
const AudioDevices = (input = false) => {
|
||||
const dropdownShown = Variable(false);
|
||||
const DeviceStream = (stream) => Button({
|
||||
child: Box({
|
||||
className: 'txt spacing-h-10',
|
||||
children: [
|
||||
iconExists(stream.iconName) ? Icon({
|
||||
className: 'txt-norm symbolic-icon',
|
||||
icon: stream.iconName,
|
||||
}) : MaterialIcon(input ? 'mic_external_on' : 'media_output', 'norm'),
|
||||
Label({
|
||||
hexpand: true,
|
||||
xalign: 0,
|
||||
className: 'txt-small',
|
||||
truncate: 'end',
|
||||
maxWidthChars: 1,
|
||||
label: stream.description,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
onClicked: (self) => {
|
||||
if (input) Audio.microphone = stream;
|
||||
else Audio.speaker = stream;
|
||||
dropdownShown.value = false;
|
||||
},
|
||||
setup: setupCursorHover,
|
||||
})
|
||||
const activeDevice = Button({
|
||||
onClicked: () => { dropdownShown.value = !dropdownShown.value; },
|
||||
child: Box({
|
||||
className: 'txt spacing-h-10',
|
||||
children: [
|
||||
MaterialIcon(input ? 'mic_external_on' : 'media_output', 'norm'),
|
||||
Label({
|
||||
hexpand: true,
|
||||
xalign: 0,
|
||||
className: 'txt-small',
|
||||
truncate: 'end',
|
||||
maxWidthChars: 1,
|
||||
label: `${input ? '[In]' : '[Out]'}`,
|
||||
setup: (self) => self.hook(Audio, (self) => {
|
||||
self.label = `${input ? '[In]' : '[Out]'} ${input ? Audio.microphone.description : Audio.speaker.description}`;
|
||||
})
|
||||
}),
|
||||
Label({
|
||||
className: `icon-material txt-norm`,
|
||||
setup: (self) => self.hook(dropdownShown, (self) => {
|
||||
self.label = dropdownShown.value ? 'expand_less' : 'expand_more';
|
||||
})
|
||||
})
|
||||
],
|
||||
}),
|
||||
setup: setupCursorHover,
|
||||
});
|
||||
const deviceSelector = Revealer({
|
||||
transition: 'slide_down',
|
||||
revealChild: dropdownShown.bind("value"),
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Box({ className: 'separator-line margin-top-5 margin-bottom-5' }),
|
||||
Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5 margin-top-5',
|
||||
attribute: {
|
||||
'updateStreams': (self) => {
|
||||
const streams = input ? Audio.microphones : Audio.speakers;
|
||||
self.children = streams.map(stream => DeviceStream(stream));
|
||||
},
|
||||
},
|
||||
setup: (self) => self
|
||||
.hook(Audio, self.attribute.updateStreams, 'stream-added')
|
||||
.hook(Audio, self.attribute.updateStreams, 'stream-removed')
|
||||
,
|
||||
}),
|
||||
]
|
||||
})
|
||||
})
|
||||
return Box({
|
||||
hpack: 'fill',
|
||||
className: 'sidebar-volmixer-deviceselector',
|
||||
vertical: true,
|
||||
children: [
|
||||
activeDevice,
|
||||
deviceSelector,
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
export default (props) => {
|
||||
const emptyContent = Box({
|
||||
homogeneous: true,
|
||||
children: [Box({
|
||||
vertical: true,
|
||||
vpack: 'center',
|
||||
className: 'txt spacing-v-10',
|
||||
children: [
|
||||
Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5 txt-subtext',
|
||||
children: [
|
||||
MaterialIcon('brand_awareness', 'gigantic'),
|
||||
Label({ label: 'No audio source', className: 'txt-small' }),
|
||||
]
|
||||
}),
|
||||
]
|
||||
})]
|
||||
});
|
||||
const appList = Scrollable({
|
||||
vexpand: true,
|
||||
child: Box({
|
||||
attribute: {
|
||||
'updateStreams': (self) => {
|
||||
const streams = Audio.apps;
|
||||
self.children = streams.map(stream => AppVolume(stream));
|
||||
},
|
||||
},
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
setup: (self) => self
|
||||
.hook(Audio, self.attribute.updateStreams, 'stream-added')
|
||||
.hook(Audio, self.attribute.updateStreams, 'stream-removed')
|
||||
,
|
||||
})
|
||||
})
|
||||
const devices = Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: [
|
||||
AudioDevices(false),
|
||||
AudioDevices(true),
|
||||
]
|
||||
})
|
||||
const mainContent = Stack({
|
||||
children: {
|
||||
'empty': emptyContent,
|
||||
'list': appList,
|
||||
},
|
||||
setup: (self) => self.hook(Audio, (self) => {
|
||||
self.shown = (Audio.apps.length > 0 ? 'list' : 'empty')
|
||||
}),
|
||||
})
|
||||
return Box({
|
||||
...props,
|
||||
className: 'spacing-v-5',
|
||||
vertical: true,
|
||||
children: [
|
||||
mainContent,
|
||||
devices,
|
||||
]
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { Box, Button, Icon, Label, Scrollable, Slider, Stack, Overlay } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
|
||||
import { ConfigToggle } from '../../.commonwidgets/configwidgets.js';
|
||||
|
||||
// can't connect: sync_problem
|
||||
|
||||
const USE_SYMBOLIC_ICONS = true;
|
||||
|
||||
const BluetoothDevice = (device) => {
|
||||
// console.log(device);
|
||||
const deviceIcon = Icon({
|
||||
className: 'sidebar-bluetooth-appicon',
|
||||
vpack: 'center',
|
||||
tooltipText: device.name,
|
||||
setup: (self) => self.hook(device, (self) => {
|
||||
self.icon = `${device.iconName}${USE_SYMBOLIC_ICONS ? '-symbolic' : ''}`;
|
||||
}),
|
||||
});
|
||||
const deviceStatus = Box({
|
||||
hexpand: true,
|
||||
vpack: 'center',
|
||||
vertical: true,
|
||||
children: [
|
||||
Label({
|
||||
xalign: 0,
|
||||
maxWidthChars: 1,
|
||||
truncate: 'end',
|
||||
label: device.name,
|
||||
className: 'txt-small',
|
||||
setup: (self) => self.hook(device, (self) => {
|
||||
self.label = device.name;
|
||||
}),
|
||||
}),
|
||||
Label({
|
||||
xalign: 0,
|
||||
maxWidthChars: 1,
|
||||
truncate: 'end',
|
||||
label: device.connected ? 'Connected' : (device.paired ? 'Paired' : ''),
|
||||
className: 'txt-subtext',
|
||||
setup: (self) => self.hook(device, (self) => {
|
||||
self.label = device.connected ? 'Connected' : (device.paired ? 'Paired' : '');
|
||||
}),
|
||||
}),
|
||||
]
|
||||
});
|
||||
const deviceConnectButton = ConfigToggle({
|
||||
vpack: 'center',
|
||||
expandWidget: false,
|
||||
desc: 'Toggle connection',
|
||||
initValue: device.connected,
|
||||
onChange: (self, newValue) => {
|
||||
device.setConnection(newValue);
|
||||
},
|
||||
extraSetup: (self) => self.hook(device, (self) => {
|
||||
Utils.timeout(200, () => self.enabled.value = device.connected);
|
||||
}),
|
||||
})
|
||||
const deviceRemoveButton = Button({
|
||||
vpack: 'center',
|
||||
className: 'sidebar-bluetooth-device-remove',
|
||||
child: MaterialIcon('delete', 'norm'),
|
||||
tooltipText: 'Remove device',
|
||||
setup: setupCursorHover,
|
||||
onClicked: () => execAsync(['bluetoothctl', 'remove', device.address]).catch(print),
|
||||
});
|
||||
return Box({
|
||||
className: 'sidebar-bluetooth-device spacing-h-10',
|
||||
children: [
|
||||
deviceIcon,
|
||||
deviceStatus,
|
||||
Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
deviceConnectButton,
|
||||
deviceRemoveButton,
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
export default (props) => {
|
||||
const emptyContent = Box({
|
||||
homogeneous: true,
|
||||
children: [Box({
|
||||
vertical: true,
|
||||
vpack: 'center',
|
||||
className: 'txt spacing-v-10',
|
||||
children: [
|
||||
Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5 txt-subtext',
|
||||
children: [
|
||||
MaterialIcon('bluetooth_disabled', 'gigantic'),
|
||||
Label({ label: 'No Bluetooth devices', className: 'txt-small' }),
|
||||
]
|
||||
}),
|
||||
]
|
||||
})]
|
||||
});
|
||||
const deviceList = Overlay({
|
||||
passThrough: true,
|
||||
child: Scrollable({
|
||||
vexpand: true,
|
||||
child: Box({
|
||||
attribute: {
|
||||
'updateDevices': (self) => {
|
||||
const devices = Bluetooth.devices;
|
||||
self.children = devices.map(d => BluetoothDevice(d));
|
||||
},
|
||||
},
|
||||
vertical: true,
|
||||
className: 'spacing-v-5 margin-bottom-15',
|
||||
setup: (self) => self
|
||||
.hook(Bluetooth, self.attribute.updateDevices, 'device-added')
|
||||
.hook(Bluetooth, self.attribute.updateDevices, 'device-removed')
|
||||
,
|
||||
})
|
||||
}),
|
||||
overlays: [Box({
|
||||
className: 'sidebar-centermodules-scrollgradient-bottom'
|
||||
})]
|
||||
});
|
||||
const mainContent = Stack({
|
||||
children: {
|
||||
'empty': emptyContent,
|
||||
'list': deviceList,
|
||||
},
|
||||
setup: (self) => self.hook(Bluetooth, (self) => {
|
||||
self.shown = (Bluetooth.devices.length > 0 ? 'list' : 'empty')
|
||||
}),
|
||||
})
|
||||
const bottomBar = Box({
|
||||
homogeneous: true,
|
||||
children: [Button({
|
||||
hpack: 'center',
|
||||
className: 'txt-small txt sidebar-centermodules-bottombar-button',
|
||||
onClicked: () => {
|
||||
execAsync(['bash', '-c', userOptions.apps.bluetooth]).catch(print);
|
||||
closeEverything();
|
||||
},
|
||||
label: 'More',
|
||||
setup: setupCursorHover,
|
||||
})],
|
||||
})
|
||||
return Box({
|
||||
...props,
|
||||
className: 'spacing-v-5',
|
||||
vertical: true,
|
||||
children: [
|
||||
mainContent,
|
||||
bottomBar
|
||||
]
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
const { GLib } = imports.gi;
|
||||
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { Box, Button, Icon, Label, Scrollable, Slider, Stack } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
|
||||
import { ConfigGap, ConfigSpinButton, ConfigToggle } from '../../.commonwidgets/configwidgets.js';
|
||||
|
||||
const HyprlandToggle = ({ icon, name, desc = null, option, enableValue = 1, disableValue = 0, extraOnChange = () => { } }) => ConfigToggle({
|
||||
icon: icon,
|
||||
name: name,
|
||||
desc: desc,
|
||||
initValue: JSON.parse(exec(`hyprctl getoption -j ${option}`))["int"] != 0,
|
||||
onChange: (self, newValue) => {
|
||||
execAsync(['hyprctl', 'keyword', option, `${newValue ? enableValue : disableValue}`]).catch(print);
|
||||
extraOnChange(self, newValue);
|
||||
}
|
||||
});
|
||||
|
||||
const HyprlandSpinButton = ({ icon, name, desc = null, option, ...rest }) => ConfigSpinButton({
|
||||
icon: icon,
|
||||
name: name,
|
||||
desc: desc,
|
||||
initValue: Number(JSON.parse(exec(`hyprctl getoption -j ${option}`))["int"]),
|
||||
onChange: (self, newValue) => {
|
||||
execAsync(['hyprctl', 'keyword', option, `${newValue}`]).catch(print);
|
||||
},
|
||||
...rest,
|
||||
});
|
||||
|
||||
const Subcategory = (children) => Box({
|
||||
className: 'margin-left-20',
|
||||
vertical: true,
|
||||
children: children,
|
||||
})
|
||||
|
||||
export default (props) => {
|
||||
const ConfigSection = ({ name, children }) => Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: [
|
||||
Label({
|
||||
hpack: 'center',
|
||||
className: 'txt txt-large margin-left-10',
|
||||
label: name,
|
||||
}),
|
||||
Box({
|
||||
className: 'margin-left-10 margin-right-10',
|
||||
vertical: true,
|
||||
children: children,
|
||||
})
|
||||
]
|
||||
})
|
||||
const mainContent = Scrollable({
|
||||
vexpand: true,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-10',
|
||||
children: [
|
||||
ConfigSection({
|
||||
name: 'Effects', children: [
|
||||
ConfigToggle({
|
||||
icon: 'border_clear',
|
||||
name: 'Transparency',
|
||||
desc: '[AGS]\nMake shell elements transparent\nBlur is also recommended if you enable this',
|
||||
initValue: exec(`bash -c "sed -n \'2p\' ${GLib.get_user_state_dir()}/ags/user/colormode.txt"`) == "transparent",
|
||||
onChange: (self, newValue) => {
|
||||
const transparency = newValue == 0 ? "opaque" : "transparent";
|
||||
console.log(transparency);
|
||||
execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && sed -i "2s/.*/${transparency}/" ${GLib.get_user_state_dir()}/ags/user/colormode.txt`])
|
||||
.then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
|
||||
.catch(print);
|
||||
},
|
||||
}),
|
||||
HyprlandToggle({ icon: 'blur_on', name: 'Blur', desc: "[Hyprland]\nEnable blur on transparent elements\nDoesn't affect performance/power consumption unless you have transparent windows.", option: "decoration:blur:enabled" }),
|
||||
Subcategory([
|
||||
HyprlandToggle({ icon: 'stack_off', name: 'X-ray', desc: "[Hyprland]\nMake everything behind a window/layer except the wallpaper not rendered on its blurred surface\nRecommended to improve performance (if you don't abuse transparency/blur) ", option: "decoration:blur:xray" }),
|
||||
HyprlandSpinButton({ icon: 'target', name: 'Size', desc: '[Hyprland]\nAdjust the blur radius. Generally doesn\'t affect performance\nHigher = more color spread', option: 'decoration:blur:size', minValue: 1, maxValue: 1000 }),
|
||||
HyprlandSpinButton({ icon: 'repeat', name: 'Passes', desc: '[Hyprland] Adjust the number of runs of the blur algorithm\nMore passes = more spread and power consumption\n4 is recommended\n2- would look weird and 6+ would look lame.', option: 'decoration:blur:passes', minValue: 1, maxValue: 10 }),
|
||||
]),
|
||||
ConfigGap({}),
|
||||
HyprlandToggle({
|
||||
icon: 'animation', name: 'Animations', desc: '[Hyprland] [GTK]\nEnable animations', option: 'animations:enabled',
|
||||
extraOnChange: (self, newValue) => execAsync(['gsettings', 'set', 'org.gnome.desktop.interface', 'enable-animations', `${newValue}`])
|
||||
}),
|
||||
Subcategory([
|
||||
ConfigSpinButton({
|
||||
icon: 'clear_all',
|
||||
name: 'Choreography delay',
|
||||
desc: 'In milliseconds, the delay between animations of a series',
|
||||
initValue: userOptions.animations.choreographyDelay,
|
||||
step: 10, minValue: 0, maxValue: 1000,
|
||||
onChange: (self, newValue) => {
|
||||
userOptions.animations.choreographyDelay = newValue
|
||||
},
|
||||
})
|
||||
]),
|
||||
]
|
||||
}),
|
||||
ConfigSection({
|
||||
name: 'Developer', children: [
|
||||
HyprlandToggle({ icon: 'speed', name: 'Show FPS', desc: "[Hyprland]\nShow FPS overlay on top-left corner", option: "debug:overlay" }),
|
||||
HyprlandToggle({ icon: 'sort', name: 'Log to stdout', desc: "[Hyprland]\nPrint LOG, ERR, WARN, etc. messages to the console", option: "debug:enable_stdout_logs" }),
|
||||
HyprlandToggle({ icon: 'motion_sensor_active', name: 'Damage tracking', desc: "[Hyprland]\nEnable damage tracking\nGenerally, leave it on.\nTurn off only when a shader doesn't work", option: "debug:damage_tracking", enableValue: 2 }),
|
||||
HyprlandToggle({ icon: 'destruction', name: 'Damage blink', desc: "[Hyprland] [Epilepsy warning!]\nShow screen damage flashes", option: "debug:damage_blink" }),
|
||||
]
|
||||
}),
|
||||
]
|
||||
})
|
||||
});
|
||||
const footNote = Box({
|
||||
homogeneous: true,
|
||||
children: [Label({
|
||||
hpack: 'center',
|
||||
className: 'txt txt-italic txt-subtext margin-5',
|
||||
label: 'Not all changes are saved',
|
||||
})]
|
||||
})
|
||||
return Box({
|
||||
...props,
|
||||
className: 'spacing-v-5',
|
||||
vertical: true,
|
||||
children: [
|
||||
mainContent,
|
||||
footNote,
|
||||
]
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// This file is for the notification list on the sidebar
|
||||
// For the popup notifications, see onscreendisplay.js
|
||||
// The actual widget for each single notification is in ags/modules/.commonwidgets/notification.js
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
|
||||
const { Box, Button, Label, Revealer, Scrollable, Stack } = Widget;
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
|
||||
import Notification from '../../.commonwidgets/notification.js';
|
||||
import { ConfigToggle } from '../../.commonwidgets/configwidgets.js';
|
||||
|
||||
export default (props) => {
|
||||
const notifEmptyContent = Box({
|
||||
homogeneous: true,
|
||||
children: [Box({
|
||||
vertical: true,
|
||||
vpack: 'center',
|
||||
className: 'txt spacing-v-10',
|
||||
children: [
|
||||
Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5 txt-subtext',
|
||||
children: [
|
||||
MaterialIcon('notifications_active', 'gigantic'),
|
||||
Label({ label: 'No notifications', className: 'txt-small' }),
|
||||
]
|
||||
}),
|
||||
]
|
||||
})]
|
||||
});
|
||||
const notificationList = Box({
|
||||
vertical: true,
|
||||
vpack: 'start',
|
||||
className: 'spacing-v-5-revealer',
|
||||
setup: (self) => self
|
||||
.hook(Notifications, (box, id) => {
|
||||
if (box.get_children().length == 0) { // On init there's no notif, or 1st notif
|
||||
Notifications.notifications
|
||||
.forEach(n => {
|
||||
box.pack_end(Notification({
|
||||
notifObject: n,
|
||||
isPopup: false,
|
||||
}), false, false, 0)
|
||||
});
|
||||
box.show_all();
|
||||
return;
|
||||
}
|
||||
// 2nd or later notif
|
||||
const notif = Notifications.getNotification(id);
|
||||
const NewNotif = Notification({
|
||||
notifObject: notif,
|
||||
isPopup: false,
|
||||
});
|
||||
if (NewNotif) {
|
||||
box.pack_end(NewNotif, false, false, 0);
|
||||
box.show_all();
|
||||
}
|
||||
}, 'notified')
|
||||
.hook(Notifications, (box, id) => {
|
||||
if (!id) return;
|
||||
for (const ch of box.children) {
|
||||
if (ch._id === id) {
|
||||
ch.attribute.destroyWithAnims();
|
||||
}
|
||||
}
|
||||
}, 'closed')
|
||||
,
|
||||
});
|
||||
const ListActionButton = (icon, name, action) => Button({
|
||||
className: 'sidebar-centermodules-bottombar-button',
|
||||
onClicked: action,
|
||||
child: Box({
|
||||
hpack: 'center',
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
MaterialIcon(icon, 'norm'),
|
||||
Label({
|
||||
className: 'txt-small',
|
||||
label: name,
|
||||
})
|
||||
]
|
||||
}),
|
||||
setup: setupCursorHover,
|
||||
});
|
||||
const silenceButton = ListActionButton('notifications_paused', 'Silence', (self) => {
|
||||
Notifications.dnd = !Notifications.dnd;
|
||||
self.toggleClassName('notif-listaction-btn-enabled', Notifications.dnd);
|
||||
});
|
||||
// const silenceToggle = ConfigToggle({
|
||||
// expandWidget: false,
|
||||
// icon: 'do_not_disturb_on',
|
||||
// name: 'Do Not Disturb',
|
||||
// initValue: false,
|
||||
// onChange: (self, newValue) => {
|
||||
// Notifications.dnd = newValue;
|
||||
// },
|
||||
// })
|
||||
const clearButton = Revealer({
|
||||
transition: 'slide_right',
|
||||
transitionDuration: userOptions.animations.durationSmall,
|
||||
setup: (self) => self.hook(Notifications, (self) => {
|
||||
self.revealChild = Notifications.notifications.length > 0;
|
||||
}),
|
||||
child: ListActionButton('clear_all', 'Clear', () => {
|
||||
Notifications.clear();
|
||||
const kids = notificationList.get_children();
|
||||
for (let i = 0; i < kids.length; i++) {
|
||||
const kid = kids[i];
|
||||
Utils.timeout(userOptions.animations.choreographyDelay * i, () => kid.attribute.destroyWithAnims());
|
||||
}
|
||||
})
|
||||
})
|
||||
const notifCount = Label({
|
||||
attribute: {
|
||||
updateCount: (self) => {
|
||||
const count = Notifications.notifications.length;
|
||||
if (count > 0) self.label = `${count} notifications`;
|
||||
else self.label = '';
|
||||
},
|
||||
},
|
||||
hexpand: true,
|
||||
xalign: 0,
|
||||
className: 'txt-small margin-left-10',
|
||||
label: `${Notifications.notifications.length}`,
|
||||
setup: (self) => self
|
||||
.hook(Notifications, (box, id) => self.attribute.updateCount(self), 'notified')
|
||||
.hook(Notifications, (box, id) => self.attribute.updateCount(self), 'dismissed')
|
||||
.hook(Notifications, (box, id) => self.attribute.updateCount(self), 'closed')
|
||||
,
|
||||
});
|
||||
const listTitle = Box({
|
||||
vpack: 'start',
|
||||
className: 'txt spacing-h-5',
|
||||
children: [
|
||||
notifCount,
|
||||
silenceButton,
|
||||
// silenceToggle,
|
||||
// Box({ hexpand: true }),
|
||||
clearButton,
|
||||
]
|
||||
});
|
||||
const notifList = Scrollable({
|
||||
hexpand: true,
|
||||
hscroll: 'never',
|
||||
vscroll: 'automatic',
|
||||
child: Box({
|
||||
vexpand: true,
|
||||
homogeneous: true,
|
||||
children: [notificationList],
|
||||
}),
|
||||
setup: (self) => {
|
||||
const vScrollbar = self.get_vscrollbar();
|
||||
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
|
||||
}
|
||||
});
|
||||
const listContents = Stack({
|
||||
transition: 'crossfade',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
children: {
|
||||
'empty': notifEmptyContent,
|
||||
'list': notifList,
|
||||
},
|
||||
setup: (self) => self.hook(Notifications, (self) => {
|
||||
self.shown = (Notifications.notifications.length > 0 ? 'list' : 'empty')
|
||||
}),
|
||||
});
|
||||
return Box({
|
||||
...props,
|
||||
className: 'spacing-v-5',
|
||||
vertical: true,
|
||||
children: [
|
||||
listContents,
|
||||
listTitle,
|
||||
]
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import Network from "resource:///com/github/Aylur/ags/service/network.js";
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { Box, Button, Entry, Icon, Label, Revealer, Scrollable, Slider, Stack, Overlay } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
|
||||
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
|
||||
import { ConfigToggle } from '../../.commonwidgets/configwidgets.js';
|
||||
|
||||
const MATERIAL_SYMBOL_SIGNAL_STRENGTH = {
|
||||
'network-wireless-signal-excellent-symbolic': "signal_wifi_4_bar",
|
||||
'network-wireless-signal-good-symbolic': "network_wifi_3_bar",
|
||||
'network-wireless-signal-ok-symbolic': "network_wifi_2_bar",
|
||||
'network-wireless-signal-weak-symbolic': "network_wifi_1_bar",
|
||||
'network-wireless-signal-none-symbolic': "signal_wifi_0_bar",
|
||||
}
|
||||
|
||||
let connectAttempt = '';
|
||||
|
||||
const WifiNetwork = (accessPoint) => {
|
||||
const networkStrength = MaterialIcon(MATERIAL_SYMBOL_SIGNAL_STRENGTH[accessPoint.iconName], 'hugerass')
|
||||
const networkName = Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Label({
|
||||
hpack: 'start',
|
||||
label: accessPoint.ssid
|
||||
}),
|
||||
accessPoint.active ? Label({
|
||||
hpack: 'start',
|
||||
className: 'txt-smaller txt-subtext',
|
||||
label: "Selected",
|
||||
}) : null,
|
||||
]
|
||||
});
|
||||
return Button({
|
||||
onClicked: accessPoint.active ? () => { } : () => execAsync(`nmcli device wifi connect ${accessPoint.bssid}`)
|
||||
// .catch(e => {
|
||||
// Utils.notify({
|
||||
// summary: "Network",
|
||||
// body: e,
|
||||
// actions: { "Open network manager": () => execAsync("nm-connection-editor").catch(print) }
|
||||
// });
|
||||
// })
|
||||
.catch(print),
|
||||
child: Box({
|
||||
className: 'sidebar-wifinetworks-network spacing-h-10',
|
||||
children: [
|
||||
networkStrength,
|
||||
networkName,
|
||||
Box({ hexpand: true }),
|
||||
accessPoint.active ? MaterialIcon('check', 'large') : null,
|
||||
],
|
||||
}),
|
||||
setup: accessPoint.active ? () => { } : setupCursorHover,
|
||||
})
|
||||
}
|
||||
|
||||
const CurrentNetwork = () => {
|
||||
let authLock = false;
|
||||
// console.log(Network.wifi);
|
||||
const bottomSeparator = Box({
|
||||
className: 'separator-line',
|
||||
});
|
||||
const networkName = Box({
|
||||
vertical: true,
|
||||
hexpand: true,
|
||||
children: [
|
||||
Label({
|
||||
hpack: 'start',
|
||||
className: 'txt-smaller txt-subtext',
|
||||
label: "Current network",
|
||||
}),
|
||||
Label({
|
||||
hpack: 'start',
|
||||
label: Network.wifi?.ssid,
|
||||
setup: (self) => self.hook(Network, (self) => {
|
||||
if (authLock) return;
|
||||
self.label = Network.wifi?.ssid;
|
||||
}),
|
||||
}),
|
||||
]
|
||||
});
|
||||
const networkStatus = Box({
|
||||
children: [Label({
|
||||
vpack: 'center',
|
||||
className: 'txt-subtext',
|
||||
setup: (self) => self.hook(Network, (self) => {
|
||||
if (authLock) return;
|
||||
self.label = Network.wifi.state;
|
||||
}),
|
||||
})]
|
||||
})
|
||||
const networkAuth = Revealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: Box({
|
||||
className: 'margin-top-10 spacing-v-5',
|
||||
vertical: true,
|
||||
children: [
|
||||
Label({
|
||||
className: 'margin-left-5',
|
||||
hpack: 'start',
|
||||
label: "Authentication",
|
||||
}),
|
||||
Entry({
|
||||
className: 'sidebar-wifinetworks-auth-entry',
|
||||
visibility: false, // Password dots
|
||||
onAccept: (self) => {
|
||||
authLock = false;
|
||||
networkAuth.revealChild = false;
|
||||
execAsync(`nmcli device wifi connect '${connectAttempt}' password '${self.text}'`)
|
||||
.catch(print);
|
||||
}
|
||||
})
|
||||
]
|
||||
}),
|
||||
setup: (self) => self.hook(Network, (self) => {
|
||||
if (Network.wifi.state == 'failed' || Network.wifi.state == 'need_auth') {
|
||||
authLock = true;
|
||||
connectAttempt = Network.wifi.ssid;
|
||||
self.revealChild = true;
|
||||
}
|
||||
}),
|
||||
});
|
||||
const actualContent = Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-10',
|
||||
children: [
|
||||
Box({
|
||||
className: 'sidebar-wifinetworks-network',
|
||||
vertical: true,
|
||||
children: [
|
||||
Box({
|
||||
className: 'spacing-h-10',
|
||||
children: [
|
||||
MaterialIcon('language', 'hugerass'),
|
||||
networkName,
|
||||
networkStatus,
|
||||
|
||||
]
|
||||
}),
|
||||
networkAuth,
|
||||
]
|
||||
}),
|
||||
bottomSeparator,
|
||||
]
|
||||
});
|
||||
return Box({
|
||||
vertical: true,
|
||||
children: [Revealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
revealChild: Network.wifi,
|
||||
child: actualContent,
|
||||
})]
|
||||
})
|
||||
}
|
||||
|
||||
export default (props) => {
|
||||
const networkList = Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-10',
|
||||
children: [Overlay({
|
||||
passThrough: true,
|
||||
child: Scrollable({
|
||||
vexpand: true,
|
||||
child: Box({
|
||||
attribute: {
|
||||
'updateNetworks': (self) => {
|
||||
const accessPoints = Network.wifi?.access_points || [];
|
||||
self.children = Object.values(accessPoints.reduce((a, accessPoint) => {
|
||||
// Only keep max strength networks by ssid
|
||||
if (!a[accessPoint.ssid] || a[accessPoint.ssid].strength < accessPoint.strength) {
|
||||
a[accessPoint.ssid] = accessPoint;
|
||||
a[accessPoint.ssid].active |= accessPoint.active;
|
||||
}
|
||||
|
||||
return a;
|
||||
}, {})).map(n => WifiNetwork(n));
|
||||
},
|
||||
},
|
||||
vertical: true,
|
||||
className: 'spacing-v-5 margin-bottom-15',
|
||||
setup: (self) => self.hook(Network, self.attribute.updateNetworks),
|
||||
})
|
||||
}),
|
||||
overlays: [Box({
|
||||
className: 'sidebar-centermodules-scrollgradient-bottom'
|
||||
})]
|
||||
})]
|
||||
});
|
||||
const bottomBar = Box({
|
||||
homogeneous: true,
|
||||
children: [Button({
|
||||
hpack: 'center',
|
||||
className: 'txt-small txt sidebar-centermodules-bottombar-button',
|
||||
onClicked: () => {
|
||||
execAsync(['bash', '-c', userOptions.apps.network]).catch(print);
|
||||
closeEverything();
|
||||
},
|
||||
label: 'More',
|
||||
setup: setupCursorHover,
|
||||
})],
|
||||
})
|
||||
return Box({
|
||||
...props,
|
||||
className: 'spacing-v-10',
|
||||
vertical: true,
|
||||
children: [
|
||||
CurrentNetwork(),
|
||||
networkList,
|
||||
bottomBar,
|
||||
]
|
||||
});
|
||||
}
|
||||
18
home/desktops/hyprland/ags/modules/sideright/main.js
Normal file
18
home/desktops/hyprland/ags/modules/sideright/main.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import PopupWindow from '../.widgethacks/popupwindow.js';
|
||||
import SidebarRight from "./sideright.js";
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
const { Box } = Widget;
|
||||
import clickCloseRegion from '../.commonwidgets/clickcloseregion.js';
|
||||
|
||||
export default () => PopupWindow({
|
||||
keymode: 'on-demand',
|
||||
anchor: ['right', 'top', 'bottom'],
|
||||
name: 'sideright',
|
||||
layer: 'overlay',
|
||||
child: Box({
|
||||
children: [
|
||||
clickCloseRegion({ name: 'sideright', multimonitor: false, fillMonitor: 'horizontal' }),
|
||||
SidebarRight(),
|
||||
]
|
||||
})
|
||||
});
|
||||
280
home/desktops/hyprland/ags/modules/sideright/quicktoggles.js
Normal file
280
home/desktops/hyprland/ags/modules/sideright/quicktoggles.js
Normal file
@@ -0,0 +1,280 @@
|
||||
const { GLib } = imports.gi;
|
||||
import App from 'resource:///com/github/Aylur/ags/app.js';
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
|
||||
import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
|
||||
import Network from 'resource:///com/github/Aylur/ags/service/network.js';
|
||||
const { execAsync, exec } = Utils;
|
||||
import { BluetoothIndicator, NetworkIndicator } from '../.commonwidgets/statusicons.js';
|
||||
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
|
||||
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
|
||||
import { sidebarOptionsStack } from './sideright.js';
|
||||
|
||||
export const ToggleIconWifi = (props = {}) => Widget.Button({
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: 'Wifi | Right-click to configure',
|
||||
onClicked: () => Network.toggleWifi(),
|
||||
onSecondaryClickRelease: () => {
|
||||
execAsync(['bash', '-c', `${userOptions.apps.network}`]).catch(print);
|
||||
closeEverything();
|
||||
},
|
||||
child: NetworkIndicator(),
|
||||
setup: (self) => {
|
||||
setupCursorHover(self);
|
||||
self.hook(Network, button => {
|
||||
button.toggleClassName('sidebar-button-active', [Network.wifi?.internet, Network.wired?.internet].includes('connected'))
|
||||
button.tooltipText = (`${Network.wifi?.ssid} | Right-click to configure` || 'Unknown');
|
||||
});
|
||||
},
|
||||
...props,
|
||||
});
|
||||
|
||||
export const ToggleIconBluetooth = (props = {}) => Widget.Button({
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: 'Bluetooth | Right-click to configure',
|
||||
onClicked: () => {
|
||||
const status = Bluetooth?.enabled;
|
||||
if (status)
|
||||
exec('rfkill block bluetooth');
|
||||
else
|
||||
exec('rfkill unblock bluetooth');
|
||||
},
|
||||
onSecondaryClickRelease: () => {
|
||||
execAsync(['bash', '-c', `${userOptions.apps.bluetooth}`]).catch(print);
|
||||
closeEverything();
|
||||
},
|
||||
child: BluetoothIndicator(),
|
||||
setup: (self) => {
|
||||
setupCursorHover(self);
|
||||
self.hook(Bluetooth, button => {
|
||||
button.toggleClassName('sidebar-button-active', Bluetooth?.enabled)
|
||||
});
|
||||
},
|
||||
...props,
|
||||
});
|
||||
|
||||
export const HyprToggleIcon = async (icon, name, hyprlandConfigValue, props = {}) => {
|
||||
try {
|
||||
return Widget.Button({
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: `${name}`,
|
||||
onClicked: (button) => {
|
||||
// Set the value to 1 - value
|
||||
Utils.execAsync(`hyprctl -j getoption ${hyprlandConfigValue}`).then((result) => {
|
||||
const currentOption = JSON.parse(result).int;
|
||||
execAsync(['bash', '-c', `hyprctl keyword ${hyprlandConfigValue} ${1 - currentOption} &`]).catch(print);
|
||||
button.toggleClassName('sidebar-button-active', currentOption == 0);
|
||||
}).catch(print);
|
||||
},
|
||||
child: MaterialIcon(icon, 'norm', { hpack: 'center' }),
|
||||
setup: button => {
|
||||
button.toggleClassName('sidebar-button-active', JSON.parse(Utils.exec(`hyprctl -j getoption ${hyprlandConfigValue}`)).int == 1);
|
||||
setupCursorHover(button);
|
||||
},
|
||||
...props,
|
||||
})
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const ModuleNightLight = async (props = {}) => {
|
||||
if (!exec(`bash -c 'command -v gammastep'`)) return null;
|
||||
return Widget.Button({
|
||||
attribute: {
|
||||
enabled: false,
|
||||
},
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: 'Night Light',
|
||||
onClicked: (self) => {
|
||||
self.attribute.enabled = !self.attribute.enabled;
|
||||
self.toggleClassName('sidebar-button-active', self.attribute.enabled);
|
||||
if (self.attribute.enabled) Utils.execAsync('gammastep').catch(print)
|
||||
else Utils.execAsync('pkill gammastep')
|
||||
.then(() => {
|
||||
// disable the button until fully terminated to avoid race
|
||||
self.sensitive = false;
|
||||
const source = setInterval(() => {
|
||||
Utils.execAsync('pkill -0 gammastep')
|
||||
.catch(() => {
|
||||
self.sensitive = true;
|
||||
source.destroy();
|
||||
});
|
||||
}, 500);
|
||||
})
|
||||
.catch(print);
|
||||
},
|
||||
child: MaterialIcon('nightlight', 'norm'),
|
||||
setup: (self) => {
|
||||
setupCursorHover(self);
|
||||
self.attribute.enabled = !!exec('pidof gammastep');
|
||||
self.toggleClassName('sidebar-button-active', self.attribute.enabled);
|
||||
},
|
||||
...props,
|
||||
});
|
||||
}
|
||||
|
||||
export const ModuleCloudflareWarp = async (props = {}) => {
|
||||
if (!exec(`bash -c 'command -v warp-cli'`)) return null;
|
||||
return Widget.Button({
|
||||
attribute: {
|
||||
enabled: false,
|
||||
},
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: 'Cloudflare WARP',
|
||||
onClicked: (self) => {
|
||||
self.attribute.enabled = !self.attribute.enabled;
|
||||
self.toggleClassName('sidebar-button-active', self.attribute.enabled);
|
||||
if (self.attribute.enabled) Utils.execAsync('warp-cli connect').catch(print)
|
||||
else Utils.execAsync('warp-cli disconnect').catch(print);
|
||||
},
|
||||
child: Widget.Icon({
|
||||
icon: 'cloudflare-dns-symbolic',
|
||||
className: 'txt-norm',
|
||||
}),
|
||||
setup: (self) => {
|
||||
setupCursorHover(self);
|
||||
self.attribute.enabled = !exec(`bash -c 'warp-cli status | grep Disconnected'`);
|
||||
self.toggleClassName('sidebar-button-active', self.attribute.enabled);
|
||||
},
|
||||
...props,
|
||||
});
|
||||
}
|
||||
|
||||
export const ModuleInvertColors = async (props = {}) => {
|
||||
try {
|
||||
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
|
||||
return Widget.Button({
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: 'Color inversion',
|
||||
onClicked: (button) => {
|
||||
// const shaderPath = JSON.parse(exec('hyprctl -j getoption decoration:screen_shader')).str;
|
||||
Hyprland.messageAsync('j/getoption decoration:screen_shader')
|
||||
.then((output) => {
|
||||
const shaderPath = JSON.parse(output)["str"].trim();
|
||||
if (shaderPath != "[[EMPTY]]" && shaderPath != "") {
|
||||
execAsync(['bash', '-c', `hyprctl keyword decoration:screen_shader '[[EMPTY]]'`]).catch(print);
|
||||
button.toggleClassName('sidebar-button-active', false);
|
||||
}
|
||||
else {
|
||||
Hyprland.messageAsync(`j/keyword decoration:screen_shader ${GLib.get_user_config_dir()}/hypr/shaders/invert.frag`)
|
||||
.catch(print);
|
||||
button.toggleClassName('sidebar-button-active', true);
|
||||
}
|
||||
})
|
||||
},
|
||||
child: MaterialIcon('invert_colors', 'norm'),
|
||||
setup: setupCursorHover,
|
||||
...props,
|
||||
})
|
||||
} catch {
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
export const ModuleRawInput = async (props = {}) => {
|
||||
try {
|
||||
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
|
||||
return Widget.Button({
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: 'Raw input',
|
||||
onClicked: (button) => {
|
||||
Hyprland.messageAsync('j/getoption input:accel_profile')
|
||||
.then((output) => {
|
||||
const value = JSON.parse(output)["str"].trim();
|
||||
if (value != "[[EMPTY]]" && value != "") {
|
||||
execAsync(['bash', '-c', `hyprctl keyword input:accel_profile '[[EMPTY]]'`]).catch(print);
|
||||
button.toggleClassName('sidebar-button-active', false);
|
||||
}
|
||||
else {
|
||||
Hyprland.messageAsync(`j/keyword input:accel_profile flat`)
|
||||
.catch(print);
|
||||
button.toggleClassName('sidebar-button-active', true);
|
||||
}
|
||||
})
|
||||
},
|
||||
child: MaterialIcon('mouse', 'norm'),
|
||||
setup: setupCursorHover,
|
||||
...props,
|
||||
})
|
||||
} catch {
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
export const ModuleIdleInhibitor = (props = {}) => Widget.Button({ // TODO: Make this work
|
||||
attribute: {
|
||||
enabled: false,
|
||||
},
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: 'Keep system awake',
|
||||
onClicked: (self) => {
|
||||
self.attribute.enabled = !self.attribute.enabled;
|
||||
self.toggleClassName('sidebar-button-active', self.attribute.enabled);
|
||||
if (self.attribute.enabled) Utils.execAsync(['bash', '-c', `pidof wayland-idle-inhibitor.py || ${App.configDir}/scripts/wayland-idle-inhibitor.py`]).catch(print)
|
||||
else Utils.execAsync('pkill -f wayland-idle-inhibitor.py').catch(print);
|
||||
},
|
||||
child: MaterialIcon('coffee', 'norm'),
|
||||
setup: (self) => {
|
||||
setupCursorHover(self);
|
||||
self.attribute.enabled = !!exec('pidof wayland-idle-inhibitor.py');
|
||||
self.toggleClassName('sidebar-button-active', self.attribute.enabled);
|
||||
},
|
||||
...props,
|
||||
});
|
||||
|
||||
export const ModuleEditIcon = (props = {}) => Widget.Button({ // TODO: Make this work
|
||||
...props,
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
onClicked: () => {
|
||||
execAsync(['bash', '-c', 'XDG_CURRENT_DESKTOP="gnome" gnome-control-center', '&']);
|
||||
App.closeWindow('sideright');
|
||||
},
|
||||
child: MaterialIcon('edit', 'norm'),
|
||||
setup: button => {
|
||||
setupCursorHover(button);
|
||||
}
|
||||
})
|
||||
|
||||
export const ModuleReloadIcon = (props = {}) => Widget.Button({
|
||||
...props,
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: 'Reload Environment config',
|
||||
onClicked: () => {
|
||||
execAsync(['bash', '-c', 'hyprctl reload || swaymsg reload &']);
|
||||
App.closeWindow('sideright');
|
||||
},
|
||||
child: MaterialIcon('refresh', 'norm'),
|
||||
setup: button => {
|
||||
setupCursorHover(button);
|
||||
}
|
||||
})
|
||||
|
||||
export const ModuleSettingsIcon = (props = {}) => Widget.Button({
|
||||
...props,
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: 'Open Settings',
|
||||
onClicked: () => {
|
||||
execAsync(['bash', '-c', `${userOptions.apps.settings}`, '&']);
|
||||
App.closeWindow('sideright');
|
||||
},
|
||||
child: MaterialIcon('settings', 'norm'),
|
||||
setup: button => {
|
||||
setupCursorHover(button);
|
||||
}
|
||||
})
|
||||
|
||||
export const ModulePowerIcon = (props = {}) => Widget.Button({
|
||||
...props,
|
||||
className: 'txt-small sidebar-iconbutton',
|
||||
tooltipText: 'Session',
|
||||
onClicked: () => {
|
||||
closeEverything();
|
||||
Utils.timeout(1, () => openWindowOnAllMonitors('session'));
|
||||
},
|
||||
child: MaterialIcon('power_settings_new', 'norm'),
|
||||
setup: button => {
|
||||
setupCursorHover(button);
|
||||
}
|
||||
})
|
||||
189
home/desktops/hyprland/ags/modules/sideright/sideright.js
Normal file
189
home/desktops/hyprland/ags/modules/sideright/sideright.js
Normal file
@@ -0,0 +1,189 @@
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { execAsync, exec } = Utils;
|
||||
const { Box, EventBox } = Widget;
|
||||
import {
|
||||
ToggleIconBluetooth,
|
||||
ToggleIconWifi,
|
||||
HyprToggleIcon,
|
||||
ModuleNightLight,
|
||||
ModuleInvertColors,
|
||||
ModuleIdleInhibitor,
|
||||
ModuleEditIcon,
|
||||
ModuleReloadIcon,
|
||||
ModuleSettingsIcon,
|
||||
ModulePowerIcon,
|
||||
ModuleRawInput,
|
||||
ModuleCloudflareWarp
|
||||
} from "./quicktoggles.js";
|
||||
import ModuleNotificationList from "./centermodules/notificationlist.js";
|
||||
import ModuleAudioControls from "./centermodules/audiocontrols.js";
|
||||
import ModuleWifiNetworks from "./centermodules/wifinetworks.js";
|
||||
import ModuleBluetooth from "./centermodules/bluetooth.js";
|
||||
import ModuleConfigure from "./centermodules/configure.js";
|
||||
import { ModuleCalendar } from "./calendar.js";
|
||||
import { getDistroIcon } from '../.miscutils/system.js';
|
||||
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
|
||||
import { ExpandingIconTabContainer } from '../.commonwidgets/tabcontainer.js';
|
||||
import { checkKeybind } from '../.widgetutils/keybind.js';
|
||||
|
||||
const centerWidgets = [
|
||||
{
|
||||
name: 'Notifications',
|
||||
materialIcon: 'notifications',
|
||||
contentWidget: ModuleNotificationList,
|
||||
},
|
||||
{
|
||||
name: 'Audio controls',
|
||||
materialIcon: 'volume_up',
|
||||
contentWidget: ModuleAudioControls,
|
||||
},
|
||||
{
|
||||
name: 'Bluetooth',
|
||||
materialIcon: 'bluetooth',
|
||||
contentWidget: ModuleBluetooth,
|
||||
},
|
||||
{
|
||||
name: 'Wifi networks',
|
||||
materialIcon: 'wifi',
|
||||
contentWidget: ModuleWifiNetworks,
|
||||
onFocus: () => execAsync('nmcli dev wifi list').catch(print),
|
||||
},
|
||||
{
|
||||
name: 'Live config',
|
||||
materialIcon: 'tune',
|
||||
contentWidget: ModuleConfigure,
|
||||
},
|
||||
];
|
||||
|
||||
const timeRow = Box({
|
||||
className: 'spacing-h-10 sidebar-group-invisible-morehorizpad',
|
||||
children: [
|
||||
Widget.Icon({
|
||||
icon: getDistroIcon(),
|
||||
className: 'txt txt-larger',
|
||||
}),
|
||||
Widget.Label({
|
||||
hpack: 'center',
|
||||
className: 'txt-small txt',
|
||||
setup: (self) => {
|
||||
const getUptime = async () => {
|
||||
try {
|
||||
await execAsync(['bash', '-c', 'uptime -p']);
|
||||
return execAsync(['bash', '-c', `uptime -p | sed -e 's/...//;s/ day\\| days/d/;s/ hour\\| hours/h/;s/ minute\\| minutes/m/;s/,[^,]*//2'`]);
|
||||
} catch {
|
||||
return execAsync(['bash', '-c', 'uptime']).then(output => {
|
||||
const uptimeRegex = /up\s+((\d+)\s+days?,\s+)?((\d+):(\d+)),/;
|
||||
const matches = uptimeRegex.exec(output);
|
||||
|
||||
if (matches) {
|
||||
const days = matches[2] ? parseInt(matches[2]) : 0;
|
||||
const hours = matches[4] ? parseInt(matches[4]) : 0;
|
||||
const minutes = matches[5] ? parseInt(matches[5]) : 0;
|
||||
|
||||
let formattedUptime = '';
|
||||
|
||||
if (days > 0) {
|
||||
formattedUptime += `${days} d `;
|
||||
}
|
||||
if (hours > 0) {
|
||||
formattedUptime += `${hours} h `;
|
||||
}
|
||||
formattedUptime += `${minutes} m`;
|
||||
|
||||
return formattedUptime;
|
||||
} else {
|
||||
throw new Error('Failed to parse uptime output');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
self.poll(5000, label => {
|
||||
getUptime().then(upTimeString => {
|
||||
label.label = `Uptime: ${upTimeString}`;
|
||||
}).catch(err => {
|
||||
console.error(`Failed to fetch uptime: ${err}`);
|
||||
});
|
||||
});
|
||||
},
|
||||
}),
|
||||
Widget.Box({ hexpand: true }),
|
||||
// ModuleEditIcon({ hpack: 'end' }), // TODO: Make this work
|
||||
ModuleReloadIcon({ hpack: 'end' }),
|
||||
ModuleSettingsIcon({ hpack: 'end' }),
|
||||
ModulePowerIcon({ hpack: 'end' }),
|
||||
]
|
||||
});
|
||||
|
||||
const togglesBox = Widget.Box({
|
||||
hpack: 'center',
|
||||
className: 'sidebar-togglesbox spacing-h-5',
|
||||
children: [
|
||||
ToggleIconWifi(),
|
||||
ToggleIconBluetooth(),
|
||||
// await ModuleRawInput(),
|
||||
// await HyprToggleIcon('touchpad_mouse', 'No touchpad while typing', 'input:touchpad:disable_while_typing', {}),
|
||||
await ModuleNightLight(),
|
||||
await ModuleInvertColors(),
|
||||
ModuleIdleInhibitor(),
|
||||
await ModuleCloudflareWarp(),
|
||||
]
|
||||
})
|
||||
|
||||
export const sidebarOptionsStack = ExpandingIconTabContainer({
|
||||
tabsHpack: 'center',
|
||||
tabSwitcherClassName: 'sidebar-icontabswitcher',
|
||||
icons: centerWidgets.map((api) => api.materialIcon),
|
||||
names: centerWidgets.map((api) => api.name),
|
||||
children: centerWidgets.map((api) => api.contentWidget()),
|
||||
onChange: (self, id) => {
|
||||
self.shown = centerWidgets[id].name;
|
||||
if (centerWidgets[id].onFocus) centerWidgets[id].onFocus();
|
||||
}
|
||||
});
|
||||
|
||||
export default () => Box({
|
||||
vexpand: true,
|
||||
hexpand: true,
|
||||
css: 'min-width: 2px;',
|
||||
children: [
|
||||
EventBox({
|
||||
onPrimaryClick: () => App.closeWindow('sideright'),
|
||||
onSecondaryClick: () => App.closeWindow('sideright'),
|
||||
onMiddleClick: () => App.closeWindow('sideright'),
|
||||
}),
|
||||
Box({
|
||||
vertical: true,
|
||||
vexpand: true,
|
||||
className: 'sidebar-right spacing-v-15',
|
||||
children: [
|
||||
Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: [
|
||||
timeRow,
|
||||
togglesBox,
|
||||
]
|
||||
}),
|
||||
Box({
|
||||
className: 'sidebar-group',
|
||||
children: [
|
||||
sidebarOptionsStack,
|
||||
],
|
||||
}),
|
||||
ModuleCalendar(),
|
||||
]
|
||||
}),
|
||||
],
|
||||
setup: (self) => self
|
||||
.on('key-press-event', (widget, event) => { // Handle keybinds
|
||||
if (checkKeybind(event, userOptions.keybinds.sidebar.options.nextTab)) {
|
||||
sidebarOptionsStack.nextTab();
|
||||
}
|
||||
else if (checkKeybind(event, userOptions.keybinds.sidebar.options.prevTab)) {
|
||||
sidebarOptionsStack.prevTab();
|
||||
}
|
||||
})
|
||||
,
|
||||
});
|
||||
224
home/desktops/hyprland/ags/modules/sideright/todolist.js
Normal file
224
home/desktops/hyprland/ags/modules/sideright/todolist.js
Normal file
@@ -0,0 +1,224 @@
|
||||
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
|
||||
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
||||
const { Box, Button, Label, Revealer } = Widget;
|
||||
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
|
||||
import { TabContainer } from '../.commonwidgets/tabcontainer.js';
|
||||
import Todo from "../../services/todo.js";
|
||||
import { setupCursorHover } from '../.widgetutils/cursorhover.js';
|
||||
|
||||
const TodoListItem = (task, id, isDone, isEven = false) => {
|
||||
const taskName = Widget.Label({
|
||||
hexpand: true,
|
||||
xalign: 0,
|
||||
wrap: true,
|
||||
className: 'txt txt-small sidebar-todo-txt',
|
||||
label: task.content,
|
||||
selectable: true,
|
||||
});
|
||||
const actions = Box({
|
||||
hpack: 'end',
|
||||
className: 'spacing-h-5 sidebar-todo-actions',
|
||||
children: [
|
||||
Widget.Button({ // Check/Uncheck
|
||||
vpack: 'center',
|
||||
className: 'txt sidebar-todo-item-action',
|
||||
child: MaterialIcon(`${isDone ? 'remove_done' : 'check'}`, 'norm', { vpack: 'center' }),
|
||||
onClicked: (self) => {
|
||||
const contentWidth = todoContent.get_allocated_width();
|
||||
crosser.toggleClassName('sidebar-todo-crosser-crossed', true);
|
||||
crosser.css = `margin-left: -${contentWidth}px;`;
|
||||
Utils.timeout(200, () => {
|
||||
widgetRevealer.revealChild = false;
|
||||
})
|
||||
Utils.timeout(350, () => {
|
||||
if (isDone)
|
||||
Todo.uncheck(id);
|
||||
else
|
||||
Todo.check(id);
|
||||
})
|
||||
},
|
||||
setup: setupCursorHover,
|
||||
}),
|
||||
Widget.Button({ // Remove
|
||||
vpack: 'center',
|
||||
className: 'txt sidebar-todo-item-action',
|
||||
child: MaterialIcon('delete_forever', 'norm', { vpack: 'center' }),
|
||||
onClicked: () => {
|
||||
const contentWidth = todoContent.get_allocated_width();
|
||||
crosser.toggleClassName('sidebar-todo-crosser-removed', true);
|
||||
crosser.css = `margin-left: -${contentWidth}px;`;
|
||||
Utils.timeout(200, () => {
|
||||
widgetRevealer.revealChild = false;
|
||||
})
|
||||
Utils.timeout(350, () => {
|
||||
Todo.remove(id);
|
||||
})
|
||||
},
|
||||
setup: setupCursorHover,
|
||||
}),
|
||||
]
|
||||
})
|
||||
const crosser = Widget.Box({
|
||||
className: 'sidebar-todo-crosser',
|
||||
});
|
||||
const todoContent = Widget.Box({
|
||||
className: 'sidebar-todo-item spacing-h-5',
|
||||
children: [
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
taskName,
|
||||
actions,
|
||||
]
|
||||
}),
|
||||
crosser,
|
||||
]
|
||||
});
|
||||
const widgetRevealer = Widget.Revealer({
|
||||
revealChild: true,
|
||||
transition: 'slide_down',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
child: todoContent,
|
||||
})
|
||||
return Box({
|
||||
homogeneous: true,
|
||||
children: [widgetRevealer]
|
||||
});
|
||||
}
|
||||
|
||||
const todoItems = (isDone) => Widget.Scrollable({
|
||||
hscroll: 'never',
|
||||
vscroll: 'automatic',
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
setup: (self) => self
|
||||
.hook(Todo, (self) => {
|
||||
self.children = Todo.todo_json.map((task, i) => {
|
||||
if (task.done != isDone) return null;
|
||||
return TodoListItem(task, i, isDone);
|
||||
})
|
||||
if (self.children.length == 0) {
|
||||
self.homogeneous = true;
|
||||
self.children = [
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
vertical: true,
|
||||
vpack: 'center',
|
||||
className: 'txt txt-subtext',
|
||||
children: [
|
||||
MaterialIcon(`${isDone ? 'checklist' : 'check_circle'}`, 'gigantic'),
|
||||
Label({ label: `${isDone ? 'Finished tasks will go here' : 'Nothing here!'}` })
|
||||
]
|
||||
})
|
||||
]
|
||||
}
|
||||
else self.homogeneous = false;
|
||||
}, 'updated')
|
||||
,
|
||||
}),
|
||||
setup: (listContents) => {
|
||||
const vScrollbar = listContents.get_vscrollbar();
|
||||
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
|
||||
}
|
||||
});
|
||||
|
||||
const UndoneTodoList = () => {
|
||||
const newTaskButton = Revealer({
|
||||
transition: 'slide_left',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
revealChild: true,
|
||||
child: Button({
|
||||
className: 'txt-small sidebar-todo-new',
|
||||
halign: 'end',
|
||||
vpack: 'center',
|
||||
label: '+ New task',
|
||||
setup: setupCursorHover,
|
||||
onClicked: (self) => {
|
||||
newTaskButton.revealChild = false;
|
||||
newTaskEntryRevealer.revealChild = true;
|
||||
confirmAddTask.revealChild = true;
|
||||
cancelAddTask.revealChild = true;
|
||||
newTaskEntry.grab_focus();
|
||||
}
|
||||
})
|
||||
});
|
||||
const cancelAddTask = Revealer({
|
||||
transition: 'slide_right',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
revealChild: false,
|
||||
child: Button({
|
||||
className: 'txt-norm icon-material sidebar-todo-add',
|
||||
halign: 'end',
|
||||
vpack: 'center',
|
||||
label: 'close',
|
||||
setup: setupCursorHover,
|
||||
onClicked: (self) => {
|
||||
newTaskEntryRevealer.revealChild = false;
|
||||
confirmAddTask.revealChild = false;
|
||||
cancelAddTask.revealChild = false;
|
||||
newTaskButton.revealChild = true;
|
||||
newTaskEntry.text = '';
|
||||
}
|
||||
})
|
||||
});
|
||||
const newTaskEntry = Widget.Entry({
|
||||
// hexpand: true,
|
||||
vpack: 'center',
|
||||
className: 'txt-small sidebar-todo-entry',
|
||||
placeholderText: 'Add a task...',
|
||||
onAccept: ({ text }) => {
|
||||
if (text == '') return;
|
||||
Todo.add(text)
|
||||
newTaskEntry.text = '';
|
||||
},
|
||||
onChange: ({ text }) => confirmAddTask.child.toggleClassName('sidebar-todo-add-available', text != ''),
|
||||
});
|
||||
const newTaskEntryRevealer = Revealer({
|
||||
transition: 'slide_right',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
revealChild: false,
|
||||
child: newTaskEntry,
|
||||
});
|
||||
const confirmAddTask = Revealer({
|
||||
transition: 'slide_right',
|
||||
transitionDuration: userOptions.animations.durationLarge,
|
||||
revealChild: false,
|
||||
child: Button({
|
||||
className: 'txt-norm icon-material sidebar-todo-add',
|
||||
halign: 'end',
|
||||
vpack: 'center',
|
||||
label: 'arrow_upward',
|
||||
setup: setupCursorHover,
|
||||
onClicked: (self) => {
|
||||
if (newTaskEntry.text == '') return;
|
||||
Todo.add(newTaskEntry.text);
|
||||
newTaskEntry.text = '';
|
||||
}
|
||||
})
|
||||
});
|
||||
return Box({ // The list, with a New button
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
setup: (box) => {
|
||||
box.pack_start(todoItems(false), true, true, 0);
|
||||
box.pack_start(Box({
|
||||
setup: (self) => {
|
||||
self.pack_start(cancelAddTask, false, false, 0);
|
||||
self.pack_start(newTaskEntryRevealer, true, true, 0);
|
||||
self.pack_start(confirmAddTask, false, false, 0);
|
||||
self.pack_start(newTaskButton, false, false, 0);
|
||||
}
|
||||
}), false, false, 0);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const TodoWidget = () => TabContainer({
|
||||
icons: ['format_list_bulleted', 'task_alt'],
|
||||
names: ['Unfinished', 'Done'],
|
||||
children: [
|
||||
UndoneTodoList(),
|
||||
todoItems(true),
|
||||
]
|
||||
})
|
||||
Reference in New Issue
Block a user