diff --git a/.config/ags/config.js b/.config/ags/config.js index 3c6f9c185..4e09423df 100644 --- a/.config/ags/config.js +++ b/.config/ags/config.js @@ -1,7 +1,6 @@ "strict mode"; // Import import { App, Utils } from './imports.js'; -import { firstRunWelcome } from './services/messages.js'; // Widgets import Bar from './widgets/bar/main.js'; import Cheatsheet from './widgets/cheatsheet/main.js'; @@ -15,18 +14,20 @@ import Session from './widgets/session/main.js'; import SideLeft from './widgets/sideleft/main.js'; import SideRight from './widgets/sideright/main.js'; -// Longer than actual anim time (see styles) to make sure widgets animate fully -const CLOSE_ANIM_TIME = 210; +const CLOSE_ANIM_TIME = 210; // Longer than actual anim time (see styles) to make sure widgets animate fully // Init cache and check first run Utils.exec(`bash -c 'mkdir -p ~/.cache/ags/user/colorschemes'`); - // SCSS compilation Utils.exec(`bash -c 'echo "" > ${App.configDir}/scss/_musicwal.scss'`); // reset music styles Utils.exec(`bash -c 'echo "" > ${App.configDir}/scss/_musicmaterial.scss'`); // reset music styles -Utils.exec(`sassc ${App.configDir}/scss/main.scss ${App.configDir}/style.css`); -App.resetCss(); -App.applyCss(`${App.configDir}/style.css`); +function applyStyle() { + Utils.exec(`sassc ${App.configDir}/scss/main.scss ${App.configDir}/style.css`); + App.resetCss(); + App.applyCss(`${App.configDir}/style.css`); + console.log('[LOG] Styles loaded') +} +applyStyle(); // Config object export default { diff --git a/.config/ags/lib/md2pango.js b/.config/ags/lib/md2pango.js index b59cd566a..e14a5cb60 100644 --- a/.config/ags/lib/md2pango.js +++ b/.config/ags/lib/md2pango.js @@ -28,7 +28,7 @@ const m2p_styles = [ { name: EMPH, re: /\*(\S.*?\S)\*/g, sub: "$1" }, // { name: EMPH, re: /_(\S.*?\S)_/g, sub: "$1" }, { name: HEXCOLOR, re: /#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/g, sub: ` #$1 ` }, - { name: INLCODE, re: /(`)([^`]*)(`)/g, sub: ` $2 ` }, + { name: INLCODE, re: /(`)([^`]*)(`)/g, sub: ` $2 ` }, // { name: UND, re: /(__|\*\*)(\S[\s\S]*?\S)(__|\*\*)/g, sub: "$2" }, ] diff --git a/.config/ags/scss/_sidebars.scss b/.config/ags/scss/_sidebars.scss index 61618d2f8..f2341a8ae 100644 --- a/.config/ags/scss/_sidebars.scss +++ b/.config/ags/scss/_sidebars.scss @@ -500,9 +500,26 @@ $onChatgpt: $onPrimary; min-height: 1.705rem; } +.sidebar-chat-apiswitcher { + @include full-rounding; + @include group-padding; + background-color: $surface; +} + +.sidebar-chat-apiswitcher-icon { + @include full-rounding; + min-width: 2.182rem; + min-height: 2.182rem; + color: $onSurface; +} + +.sidebar-chat-apiswitcher-icon-enabled { + color: $primary; +} + .sidebar-chat-viewport { @include menu_decel; - margin: 0.682rem 0rem; + // margin: 0.682rem 0rem; padding: 0.682rem 0rem; } @@ -720,4 +737,5 @@ $onChatgpt: $onPrimary; .sidebar-pin-enabled:active { background-color: mix($primary, $onPrimary, 80%); -} \ No newline at end of file +} + diff --git a/.config/ags/style.css b/.config/ags/style.css index 3e3588415..53f3aec50 100644 --- a/.config/ags/style.css +++ b/.config/ags/style.css @@ -1769,9 +1769,24 @@ tooltip { min-width: 1.705rem; min-height: 1.705rem; } +.sidebar-chat-apiswitcher { + border-radius: 9999px; + -gtk-outline-radius: 9999px; + padding: 0.341rem; + background-color: #1e1c20; } + +.sidebar-chat-apiswitcher-icon { + border-radius: 9999px; + -gtk-outline-radius: 9999px; + min-width: 2.182rem; + min-height: 2.182rem; + color: #e7e1e6; } + +.sidebar-chat-apiswitcher-icon-enabled { + color: #d6baff; } + .sidebar-chat-viewport { transition: 300ms cubic-bezier(0.1, 1, 0, 1); - margin: 0.682rem 0rem; padding: 0.682rem 0rem; } .sidebar-chat-textarea { diff --git a/.config/ags/widgets/sideleft/apis/chatgpt.js b/.config/ags/widgets/sideleft/apis/chatgpt.js index 268322c99..314877326 100644 --- a/.config/ags/widgets/sideleft/apis/chatgpt.js +++ b/.config/ags/widgets/sideleft/apis/chatgpt.js @@ -9,19 +9,16 @@ import { SystemMessage, ChatMessage } from "./chatgpt_chatmessage.js"; import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../../lib/configwidgets.js'; import { markdownTest } from '../../../lib/md2pango.js'; -const chatGPTTabIcon = Icon({ +export const chatGPTTabIcon = Box({ hpack: 'center', - className: 'sidebar-chat-welcome-logo', - icon: `${App.configDir}/assets/openai-logomark.svg`, - setup: (self) => Utils.timeout(1, () => { - const styleContext = self.get_style_context(); - const width = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL); - const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL); - self.size = Math.max(width, height, 1) * 116 / 180; // Why such a specific proportion? See https://openai.com/brand#logos - }) + className: 'sidebar-chat-apiswitcher-icon', + homogeneous: true, + children: [ + MaterialIcon('forum', 'norm'), + ], }); -export const chatGPTInfo = Box({ +const chatGPTInfo = Box({ vertical: true, className: 'spacing-v-15', children: [ @@ -195,11 +192,12 @@ export const chatGPTView = Scrollable({ ] }), setup: (scrolledWindow) => { + // Show scrollbar scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); const vScrollbar = scrolledWindow.get_vscrollbar(); vScrollbar.get_style_context().add_class('sidebar-scrollbar'); - - Utils.timeout(1, () => { // Fix click-to-scroll-widget-to-view behavior + // Avoid click-to-scroll-widget-to-view behavior + Utils.timeout(1, () => { const viewport = scrolledWindow.child; viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined)); }) diff --git a/.config/ags/widgets/sideleft/apis/waifu.js b/.config/ags/widgets/sideleft/apis/waifu.js new file mode 100644 index 000000000..34cfb0214 --- /dev/null +++ b/.config/ags/widgets/sideleft/apis/waifu.js @@ -0,0 +1,55 @@ +const { Gdk, GLib, Gtk, Pango } = imports.gi; +import { App, Utils, Widget } from '../../../imports.js'; +const { Box, Button, Entry, EventBox, Icon, Label, Revealer, Scrollable, Stack } = Widget; +const { execAsync, exec } = Utils; +import { MaterialIcon } from "../../../lib/materialicon.js"; +import { setupCursorHover, setupCursorHoverInfo } from "../../../lib/cursorhover.js"; + +export const waifuTabIcon = Box({ + hpack: 'center', + className: 'sidebar-chat-apiswitcher-icon', + homogeneous: true, + children: [ + MaterialIcon('photo_library', 'norm'), + ] +}); + +export const waifuView = Scrollable({ + className: 'sidebar-chat-viewport', + vexpand: true, + child: Box({ + vertical: true, + children: [ + ] + }), + setup: (scrolledWindow) => { + // Show scrollbar + scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); + const vScrollbar = scrolledWindow.get_vscrollbar(); + vScrollbar.get_style_context().add_class('sidebar-scrollbar'); + // Avoid click-to-scroll-widget-to-view behavior + Utils.timeout(1, () => { + const viewport = scrolledWindow.child; + viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined)); + }) + } +}); + +export const waifuCommands = Box({ + className: 'spacing-h-5', + children: [ + Box({ hexpand: true }), + Button({ + className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small', + onClicked: () => { + // command do something + }, + setup: setupCursorHover, + label: '/A command button', + }), + ] +}); + +export const waifuCallAPI = (text) => { + // Do something on send +} \ No newline at end of file diff --git a/.config/ags/widgets/sideleft/apiwidgets.js b/.config/ags/widgets/sideleft/apiwidgets.js index c44ef2b39..88d1539d5 100644 --- a/.config/ags/widgets/sideleft/apiwidgets.js +++ b/.config/ags/widgets/sideleft/apiwidgets.js @@ -1,10 +1,12 @@ +const { Gtk, Gdk } = imports.gi; import { App, Utils, Widget } from '../../imports.js'; const { Box, Button, Entry, EventBox, Icon, Label, Revealer, Scrollable, Stack } = Widget; const { execAsync, exec } = Utils; import { setupCursorHover, setupCursorHoverInfo } from "../../lib/cursorhover.js"; // APIs import ChatGPT from '../../services/chatgpt.js'; -import { chatGPTView, chatGPTCommands, chatGPTSendMessage } from './apis/chatgpt.js'; +import { chatGPTView, chatGPTCommands, chatGPTSendMessage, chatGPTTabIcon } from './apis/chatgpt.js'; +import { waifuView, waifuCommands, waifuCallAPI, waifuTabIcon } from './apis/waifu.js'; const APIS = [ { @@ -12,20 +14,18 @@ const APIS = [ sendCommand: chatGPTSendMessage, contentWidget: chatGPTView, commandBar: chatGPTCommands, - tabIcon: Box({}), - } + tabIcon: chatGPTTabIcon, + }, + { + name: 'Waifus', + sendCommand: waifuCallAPI, + contentWidget: waifuView, + commandBar: waifuCommands, + tabIcon: waifuTabIcon, + }, ]; let currentApiId = 0; - -const apiSwitcher = Box({ - vertical: true, - children: [ - Box({ - homogeneous: true, - children: APIS.map(api => api.tabIcon), - }), - ] -}) +APIS[currentApiId].tabIcon.toggleClassName('sidebar-chat-apiswitcher-icon-enabled', true); export const chatEntry = Entry({ className: 'sidebar-chat-entry', @@ -75,7 +75,36 @@ const apiCommandStack = Stack({ items: APIS.map(api => [api.name, api.commandBar]), }) +function switchToTab(id) { + APIS[currentApiId].tabIcon.toggleClassName('sidebar-chat-apiswitcher-icon-enabled', false); + APIS[id].tabIcon.toggleClassName('sidebar-chat-apiswitcher-icon-enabled', true); + apiContentStack.shown = APIS[id].name; + apiCommandStack.shown = APIS[id].name; + currentApiId = id; +} +const apiSwitcher = Box({ + homogeneous: true, + children: [ + Box({ + className: 'sidebar-chat-apiswitcher spacing-h-5', + hpack: 'center', + children: APIS.map((api, id) => Button({ + child: api.tabIcon, + tooltipText: api.name, + setup: setupCursorHover, + onClicked: () => { + switchToTab(id); + } + })), + }), + ] +}) + export default Widget.Box({ + properties: [ + ['nextTab', () => switchToTab(Math.min(currentApiId + 1, APIS.length - 1))], + ['prevTab', () => switchToTab(Math.max(0, currentApiId-1))], + ], vertical: true, className: 'spacing-v-10', homogeneous: false, @@ -84,5 +113,5 @@ export default Widget.Box({ apiContentStack, apiCommandStack, textboxArea, - ] + ], }); diff --git a/.config/ags/widgets/sideleft/sideleft.js b/.config/ags/widgets/sideleft/sideleft.js index 9d9954511..32150a8e3 100644 --- a/.config/ags/widgets/sideleft/sideleft.js +++ b/.config/ags/widgets/sideleft/sideleft.js @@ -7,105 +7,124 @@ import { setupCursorHover } from "../../lib/cursorhover.js"; import { NavigationIndicator } from "../../lib/navigationindicator.js"; import toolBox from './toolbox.js'; import apiWidgets from './apiwidgets.js'; -import { chatEntry } from './apiwidgets.js'; +import apiwidgets, { chatEntry } from './apiwidgets.js'; -const SidebarTabButton = (stack, stackItem, navIndicator, navIndex, icon, label) => Widget.Button({ +const contents = [ + { + name: 'apis', + content: apiWidgets, + materialIcon: 'api', + friendlyName: 'APIs', + }, + { + name: 'tools', + content: toolBox, + materialIcon: 'home_repair_service', + friendlyName: 'Tools', + }, +] +let currentTabId = 0; + +const contentStack = Stack({ + vexpand: true, + transition: 'slide_left_right', + items: contents.map(item => [item.name, item.content]), +}) + +function switchToTab(id) { + const allTabs = navTabs.get_children(); + const tabButton = allTabs[id]; + allTabs[currentTabId].toggleClassName('sidebar-selector-tab-active', false); + allTabs[id].toggleClassName('sidebar-selector-tab-active', true); + contentStack.shown = contents[id].name; + if (tabButton) { + // Fancy highlighter line width + const buttonWidth = tabButton.get_allocated_width(); + const highlightWidth = tabButton.get_children()[0].get_allocated_width(); + navIndicator.css = ` + font-size: ${id}px; + padding: 0px ${(buttonWidth - highlightWidth) / 2}px; + `; + } + currentTabId = id; +} +const SidebarTabButton = (navIndex) => Widget.Button({ // hexpand: true, className: 'sidebar-selector-tab', onClicked: (self) => { - stack.shown = stackItem; - // Add active class to self and remove for others - const allTabs = self.get_parent().get_children(); - for (let i = 0; i < allTabs.length; i++) { - if (allTabs[i] != self) allTabs[i].toggleClassName('sidebar-selector-tab-active', false); - else self.toggleClassName('sidebar-selector-tab-active', true); - } - // Fancy highlighter line width - const buttonWidth = self.get_allocated_width(); - const highlightWidth = self.get_children()[0].get_allocated_width(); - navIndicator.css = ` - font-size: ${navIndex}px; - padding: 0px ${(buttonWidth - highlightWidth) / 2}px; - `; + switchToTab(navIndex); }, child: Box({ hpack: 'center', className: 'spacing-h-5', children: [ - MaterialIcon(icon, 'larger'), + MaterialIcon(contents[navIndex].materialIcon, 'larger'), Label({ className: 'txt txt-smallie', - label: label, + label: `${contents[navIndex].friendlyName}`, }) ] }), setup: (button) => Utils.timeout(1, () => { setupCursorHover(button); - button.toggleClassName('sidebar-selector-tab-active', defaultTab === stackItem); + button.toggleClassName('sidebar-selector-tab-active', currentTabId == navIndex); }), }); -const defaultTab = 'apis'; -const contentStack = Stack({ - vexpand: true, - transition: 'slide_left_right', - items: [ - ['apis', apiWidgets], - ['tools', toolBox], - ], -}) +const navTabs = Box({ + homogeneous: true, + children: contents.map((item, id) => + SidebarTabButton(id, item.materialIcon, item.friendlyName) + ), +}); const navIndicator = NavigationIndicator(2, false, { // The line thing className: 'sidebar-selector-highlight', css: 'font-size: 0px; padding: 0rem 4.160rem;', // Shushhhh -}) +}); const navBar = Box({ vertical: true, hexpand: true, children: [ - Box({ - homogeneous: true, - children: [ - SidebarTabButton(contentStack, 'apis', navIndicator, 0, 'api', 'APIs'), - SidebarTabButton(contentStack, 'tools', navIndicator, 1, 'home_repair_service', 'Tools'), - ] - }), + navTabs, navIndicator, ] -}) +}); const pinButton = Button({ properties: [ ['enabled', false], + ['toggle', (self) => { + self._enabled = !self._enabled; + self.toggleClassName('sidebar-pin-enabled', self._enabled); + + const sideleftWindow = App.getWindow('sideleft'); + const barWindow = App.getWindow('bar'); + const cornerTopLeftWindow = App.getWindow('cornertl'); + const sideleftContent = sideleftWindow.get_children()[0].get_children()[0].get_children()[1]; + + sideleftContent.toggleClassName('sidebar-pinned', self._enabled); + + if (self._enabled) { + sideleftWindow.layer = 'bottom'; + barWindow.layer = 'bottom'; + cornerTopLeftWindow.layer = 'bottom'; + sideleftWindow.exclusivity = 'exclusive'; + } + else { + sideleftWindow.layer = 'top'; + barWindow.layer = 'top'; + cornerTopLeftWindow.layer = 'top'; + sideleftWindow.exclusivity = 'normal'; + } + }], ], vpack: 'start', className: 'sidebar-pin', child: MaterialIcon('push_pin', 'larger'), tooltipText: 'Pin sidebar', - onClicked: (self) => { - self._enabled = !self._enabled; - self.toggleClassName('sidebar-pin-enabled', self._enabled); - - const sideleftWindow = App.getWindow('sideleft'); - const barWindow = App.getWindow('bar'); - const cornerTopLeftWindow = App.getWindow('cornertl'); - const sideleftContent = sideleftWindow.get_children()[0].get_children()[0].get_children()[1]; - - sideleftWindow.exclusivity = (self._enabled ? 'exclusive' : 'normal'); - sideleftContent.toggleClassName('sidebar-pinned', self._enabled); - - if(self._enabled) { - sideleftWindow.layer = 'bottom'; - barWindow.layer = 'bottom'; - cornerTopLeftWindow.layer = 'bottom'; - } - else { - sideleftWindow.layer = 'top'; - barWindow.layer = 'top'; - cornerTopLeftWindow.layer = 'top'; - } - }, + onClicked: (self) => self._toggle(self), // QoL: Focus Pin button on open. Hit keybind -> space/enter = toggle pin state connections: [[App, (self, currentName, visible) => { if (currentName === 'sideleft' && visible) { @@ -128,7 +147,7 @@ export default () => Box({ Box({ vertical: true, vexpand: true, - className: 'sidebar-left', + className: 'sidebar-left spacing-v-10', children: [ Box({ className: 'spacing-h-10', @@ -147,15 +166,38 @@ export default () => Box({ }), ], connections: [ - ['key-press-event', (widget, event) => { // Typing - if (event.get_keyval()[1] >= 32 && event.get_keyval()[1] <= 126 && - widget != chatEntry && event.get_keyval()[1] != Gdk.KEY_space) { - if (contentStack.shown == 'apis') { + ['key-press-event', (widget, event) => { // Handle keybinds + if (event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) { + // Pin sidebar + if (event.get_keyval()[1] == Gdk.KEY_p) + pinButton._toggle(pinButton); + // Switch sidebar tab + else if (event.get_keyval()[1] === Gdk.KEY_Page_Up) + switchToTab(Math.max(currentTabId - 1), 0); + else if (event.get_keyval()[1] === Gdk.KEY_Page_Down) + switchToTab(Math.min(currentTabId + 1), contents.length); + } + if (contentStack.shown == 'apis') { // If api tab is focused + // Automatically focus entry when typing + if (event.get_keyval()[1] >= 32 && event.get_keyval()[1] <= 126 && + widget != chatEntry && event.get_keyval()[1] != Gdk.KEY_space) { chatEntry.grab_focus(); chatEntry.set_text(chatEntry.text + String.fromCharCode(event.get_keyval()[1])); chatEntry.set_position(-1); } + // Switch API type + else if (!(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) && + event.get_keyval()[1] === Gdk.KEY_Page_Down) { + const toSwitchTab = contentStack.get_visible_child(); + toSwitchTab._nextTab(); + } + else if (!(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) && + event.get_keyval()[1] === Gdk.KEY_Page_Up) { + const toSwitchTab = contentStack.get_visible_child(); + toSwitchTab._prevTab(); + } } + }], ], });