forked from Shinonome/dots-hyprland
ags: sync
This commit is contained in:
@@ -6,22 +6,20 @@ import ChatGPT from '../../../services/chatgpt.js';
|
||||
import { MaterialIcon } from "../../../lib/materialicon.js";
|
||||
import { setupCursorHover, setupCursorHoverInfo } from "../../../lib/cursorhover.js";
|
||||
import { SystemMessage, ChatMessage } from "./chatgpt_chatmessage.js";
|
||||
import { ConfigToggle } from '../../../lib/configwidgets.js';
|
||||
import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../../lib/configwidgets.js';
|
||||
import { markdownTest } from '../../../lib/md2pango.js';
|
||||
import { MarginRevealer } from '../../../lib/advancedrevealers.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: [
|
||||
@@ -63,41 +61,62 @@ export const chatGPTInfo = Box({
|
||||
]
|
||||
})
|
||||
|
||||
export const chatGPTSettings = Revealer({
|
||||
export const chatGPTSettings = MarginRevealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: 150,
|
||||
revealChild: true,
|
||||
connections: [
|
||||
[ChatGPT, (self) => {
|
||||
self.revealChild = false;
|
||||
}, 'newMsg'],
|
||||
[ChatGPT, (self) => {
|
||||
self.revealChild = true;
|
||||
}, 'clear'],
|
||||
[ChatGPT, (self) => Utils.timeout(200, () => {
|
||||
self._hide(self);
|
||||
}), 'newMsg'],
|
||||
[ChatGPT, (self) => Utils.timeout(200, () => {
|
||||
self._show(self);
|
||||
}), 'clear'],
|
||||
],
|
||||
child: Box({
|
||||
vertical: true,
|
||||
hpack: 'fill',
|
||||
className: 'sidebar-chat-settings',
|
||||
children: [
|
||||
ConfigToggle({
|
||||
icon: 'cycle',
|
||||
name: 'Cycle models',
|
||||
desc: 'Helps avoid exceeding the API rate of 3 messages per minute.\nTurn this on if you message rapidly.',
|
||||
initValue: ChatGPT.cycleModels,
|
||||
onChange: (self, newValue) => {
|
||||
ChatGPT.cycleModels = newValue;
|
||||
},
|
||||
}),
|
||||
ConfigToggle({
|
||||
icon: 'description',
|
||||
name: 'Assistant prompt',
|
||||
desc: 'Tells ChatGPT\n 1. It\'s a sidebar assistant on Linux\n 2. Be short and concise\n 3. Use markdown features extensively\nLeave this off for a vanilla ChatGPT experience.',
|
||||
initValue: ChatGPT.assistantPrompt,
|
||||
onChange: (self, newValue) => {
|
||||
ChatGPT.assistantPrompt = newValue;
|
||||
ConfigSegmentedSelection({
|
||||
hpack: 'center',
|
||||
icon: 'casino',
|
||||
name: 'Randomness',
|
||||
desc: 'ChatGPT\'s temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1',
|
||||
options: [
|
||||
{ value: 0.00, name: 'Precise', },
|
||||
{ value: 0.50, name: 'Balanced', },
|
||||
{ value: 1.00, name: 'Creative', },
|
||||
],
|
||||
initIndex: 1,
|
||||
onChange: (value, name) => {
|
||||
ChatGPT.temperature = value;
|
||||
},
|
||||
}),
|
||||
ConfigGap({ vertical: true, size: 10 }), // Note: size can only be 5, 10, or 15
|
||||
Box({
|
||||
vertical: true,
|
||||
hpack: 'fill',
|
||||
className: 'sidebar-chat-settings-toggles',
|
||||
children: [
|
||||
ConfigToggle({
|
||||
icon: 'cycle',
|
||||
name: 'Cycle models',
|
||||
desc: 'Helps avoid exceeding the API rate of 3 messages per minute.\nTurn this on if you message rapidly.',
|
||||
initValue: ChatGPT.cycleModels,
|
||||
onChange: (self, newValue) => {
|
||||
ChatGPT.cycleModels = newValue;
|
||||
},
|
||||
}),
|
||||
ConfigToggle({
|
||||
icon: 'description',
|
||||
name: 'Assistant prompt',
|
||||
desc: 'Tells ChatGPT\n 1. It\'s a sidebar assistant on Linux\n 2. Be short and concise\n 3. Use markdown features extensively\nLeave this off for a vanilla ChatGPT experience.',
|
||||
initValue: ChatGPT.assistantPrompt,
|
||||
onChange: (self, newValue) => {
|
||||
ChatGPT.assistantPrompt = newValue;
|
||||
},
|
||||
}),
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
});
|
||||
@@ -136,7 +155,7 @@ export const chatGPTWelcome = Box({
|
||||
children: [
|
||||
chatGPTInfo,
|
||||
openaiApiKeyInstructions,
|
||||
chatGPTSettings,
|
||||
chatGPTSettings, ``
|
||||
]
|
||||
})
|
||||
});
|
||||
@@ -148,27 +167,37 @@ export const chatContent = Box({
|
||||
[ChatGPT, (box, id) => {
|
||||
const message = ChatGPT.messages[id];
|
||||
if (!message) return;
|
||||
box.add(ChatMessage(message))
|
||||
box.add(ChatMessage(message, chatGPTView))
|
||||
}, 'newMsg'],
|
||||
[ChatGPT, (box) => {
|
||||
box.children = [chatGPTWelcome];
|
||||
}, 'clear'],
|
||||
[ChatGPT, (box) => {
|
||||
box.children = [chatGPTWelcome];
|
||||
}, 'initialized'],
|
||||
]
|
||||
});
|
||||
|
||||
const clearChat = () => {
|
||||
ChatGPT.clear();
|
||||
const children = chatContent.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
child.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export const chatGPTView = Scrollable({
|
||||
className: 'sidebar-chat-viewport',
|
||||
vexpand: true,
|
||||
child: chatContent,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
chatGPTWelcome,
|
||||
chatContent,
|
||||
]
|
||||
}),
|
||||
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));
|
||||
})
|
||||
@@ -183,7 +212,8 @@ export const chatGPTCommands = Box({
|
||||
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
|
||||
onClicked: () => chatContent.add(SystemMessage(
|
||||
`Key stored in:\n\`${ChatGPT.keyPath}\`\nTo update this key, type \`/key YOUR_API_KEY\``,
|
||||
'/key')),
|
||||
'/key',
|
||||
chatGPTView)),
|
||||
setup: setupCursorHover,
|
||||
label: '/key',
|
||||
}),
|
||||
@@ -191,14 +221,15 @@ export const chatGPTCommands = Box({
|
||||
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
|
||||
onClicked: () => chatContent.add(SystemMessage(
|
||||
`Currently using \`${ChatGPT.modelName}\``,
|
||||
'/model'
|
||||
'/model',
|
||||
chatGPTView
|
||||
)),
|
||||
setup: setupCursorHover,
|
||||
label: '/model',
|
||||
}),
|
||||
Button({
|
||||
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
|
||||
onClicked: () => ChatGPT.clear(),
|
||||
onClicked: () => clearChat(),
|
||||
setup: setupCursorHover,
|
||||
label: '/clear',
|
||||
}),
|
||||
@@ -210,26 +241,39 @@ export const chatGPTSendMessage = (text) => {
|
||||
if (text.length == 0) return;
|
||||
if (ChatGPT.key.length == 0) {
|
||||
ChatGPT.key = text;
|
||||
chatContent.add(SystemMessage(`Key saved to\n\`${ChatGPT.keyPath}\``, 'API Key'));
|
||||
chatContent.add(SystemMessage(`Key saved to\n\`${ChatGPT.keyPath}\``, 'API Key', chatGPTView));
|
||||
text = '';
|
||||
return;
|
||||
}
|
||||
// Commands
|
||||
if (text.startsWith('/')) {
|
||||
if (text.startsWith('/clear')) ChatGPT.clear();
|
||||
else if (text.startsWith('/model')) chatContent.add(SystemMessage(`Currently using \`${ChatGPT.modelName}\``, '/model'))
|
||||
if (text.startsWith('/clear')) clearChat();
|
||||
else if (text.startsWith('/model')) chatContent.add(SystemMessage(`Currently using \`${ChatGPT.modelName}\``, '/model', chatGPTView))
|
||||
else if (text.startsWith('/prompt')) {
|
||||
const firstSpaceIndex = text.indexOf(' ');
|
||||
const prompt = text.slice(firstSpaceIndex + 1);
|
||||
if (firstSpaceIndex == -1 || prompt.length < 1) {
|
||||
chatContent.add(SystemMessage(`Usage: \`/prompt MESSAGE\``, '/prompt', chatGPTView))
|
||||
}
|
||||
else {
|
||||
ChatGPT.addMessage('user', prompt)
|
||||
}
|
||||
}
|
||||
else if (text.startsWith('/key')) {
|
||||
const parts = text.split(' ');
|
||||
if (parts.length == 1) chatContent.add(SystemMessage(`Key stored in:\n\`${ChatGPT.keyPath}\`\nTo update this key, type \`/key YOUR_API_KEY\``, '/key'));
|
||||
if (parts.length == 1) chatContent.add(SystemMessage(
|
||||
`Key stored in:\n\`${ChatGPT.keyPath}\`\nTo update this key, type \`/key YOUR_API_KEY\``,
|
||||
'/key',
|
||||
chatGPTView));
|
||||
else {
|
||||
ChatGPT.key = parts[1];
|
||||
chatContent.add(SystemMessage(`Updated API Key at\n\`${ChatGPT.keyPath}\``, '/key'));
|
||||
chatContent.add(SystemMessage(`Updated API Key at\n\`${ChatGPT.keyPath}\``, '/key', chatGPTView));
|
||||
}
|
||||
}
|
||||
else if (text.startsWith('/test'))
|
||||
chatContent.add(SystemMessage(markdownTest, `Markdown test`));
|
||||
chatContent.add(SystemMessage(markdownTest, `Markdown test`, chatGPTView));
|
||||
else
|
||||
chatContent.add(SystemMessage(`Invalid command.`, 'Error'))
|
||||
chatContent.add(SystemMessage(`Invalid command.`, 'Error', chatGPTView))
|
||||
}
|
||||
else {
|
||||
ChatGPT.send(text);
|
||||
|
||||
@@ -9,7 +9,7 @@ import GtkSource from "gi://GtkSource?version=3.0";
|
||||
const CUSTOM_SOURCEVIEW_SCHEME_PATH = `${App.configDir}/data/sourceviewtheme.xml`;
|
||||
const CUSTOM_SCHEME_ID = 'custom';
|
||||
const USERNAME = GLib.get_user_name();
|
||||
const CHATGPT_CURSOR = ' >> ';
|
||||
const CHATGPT_CURSOR = ' (o) ';
|
||||
const MESSAGE_SCROLL_DELAY = 13; // In milliseconds, the time before an updated message scrolls to bottom
|
||||
|
||||
/////////////////////// Custom source view colorscheme /////////////////////////
|
||||
@@ -92,10 +92,6 @@ const CodeBlock = (content = '', lang = 'txt') => {
|
||||
}),
|
||||
Button({
|
||||
className: 'sidebar-chat-codeblock-topbar-btn',
|
||||
onClicked: (self) => {
|
||||
// execAsync(['bash', '-c', `wl-copy '${content}'`, `&`]).catch(print);
|
||||
execAsync([`wl-copy`, `${sourceView.label}`]).catch(print);
|
||||
},
|
||||
child: Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
@@ -104,8 +100,13 @@ const CodeBlock = (content = '', lang = 'txt') => {
|
||||
label: 'Copy',
|
||||
})
|
||||
]
|
||||
})
|
||||
})
|
||||
}),
|
||||
onClicked: (self) => {
|
||||
const copyContent = sourceView.get_buffer().get_text(0, 0, 0); // TODO: fix this
|
||||
console.log(copyContent);
|
||||
execAsync([`wl-copy`, `${copyContent}`]).catch(print);
|
||||
},
|
||||
}),
|
||||
]
|
||||
})
|
||||
// Source view
|
||||
@@ -148,7 +149,11 @@ const MessageContent = (content) => {
|
||||
properties: [
|
||||
['fullUpdate', (self, content, useCursor = false) => {
|
||||
// Clear and add first text widget
|
||||
contentBox.get_children().forEach(ch => ch.destroy());
|
||||
const children = contentBox.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
child.destroy();
|
||||
}
|
||||
contentBox.add(TextBlock())
|
||||
// Loop lines. Put normal text in markdown parser
|
||||
// and put code into code highlighter (TODO)
|
||||
@@ -214,7 +219,7 @@ const MessageContent = (content) => {
|
||||
return contentBox;
|
||||
}
|
||||
|
||||
export const ChatMessage = (message) => {
|
||||
export const ChatMessage = (message, scrolledWindow) => {
|
||||
const messageContentBox = MessageContent(message.content);
|
||||
const thisMessage = Box({
|
||||
className: 'sidebar-chat-message',
|
||||
@@ -243,7 +248,7 @@ export const ChatMessage = (message) => {
|
||||
[message, (self) => { // Message update
|
||||
messageContentBox._fullUpdate(messageContentBox, message.content, message.role != 'user');
|
||||
Utils.timeout(MESSAGE_SCROLL_DELAY, () => {
|
||||
const scrolledWindow = thisMessage.get_parent().get_parent();
|
||||
if (!scrolledWindow) return;
|
||||
var adjustment = scrolledWindow.get_vadjustment();
|
||||
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
|
||||
});
|
||||
@@ -258,7 +263,7 @@ export const ChatMessage = (message) => {
|
||||
return thisMessage;
|
||||
}
|
||||
|
||||
export const SystemMessage = (content, commandName) => {
|
||||
export const SystemMessage = (content, commandName, scrolledWindow) => {
|
||||
const messageContentBox = MessageContent(content);
|
||||
const thisMessage = Box({
|
||||
className: 'sidebar-chat-message',
|
||||
@@ -282,7 +287,7 @@ export const SystemMessage = (content, commandName) => {
|
||||
})
|
||||
],
|
||||
setup: (self) => Utils.timeout(MESSAGE_SCROLL_DELAY, () => {
|
||||
const scrolledWindow = thisMessage.get_parent().get_parent();
|
||||
if (!scrolledWindow) return;
|
||||
var adjustment = scrolledWindow.get_vadjustment();
|
||||
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
|
||||
})
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
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";
|
||||
import WaifuService from '../../../services/waifus.js';
|
||||
|
||||
export const waifuTabIcon = Box({
|
||||
hpack: 'center',
|
||||
className: 'sidebar-chat-apiswitcher-icon',
|
||||
homogeneous: true,
|
||||
children: [
|
||||
MaterialIcon('photo_library', 'norm'),
|
||||
]
|
||||
});
|
||||
|
||||
const waifuContent = Box({
|
||||
className: 'spacing-v-15',
|
||||
vertical: true,
|
||||
connections: [
|
||||
[WaifuService, (box, id) => {
|
||||
const message = WaifuService.responses[id];
|
||||
if (!message) return;
|
||||
box.add(Label({
|
||||
label: message,
|
||||
}))
|
||||
}, 'newResponse'],
|
||||
]
|
||||
});
|
||||
|
||||
export const waifuView = Scrollable({
|
||||
className: 'sidebar-chat-viewport',
|
||||
vexpand: true,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
waifuContent,
|
||||
]
|
||||
}),
|
||||
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: '/call',
|
||||
}),
|
||||
]
|
||||
});
|
||||
|
||||
export const waifuCallAPI = (text) => {
|
||||
// Do something on send
|
||||
WaifuService.fetch(text);
|
||||
}
|
||||
@@ -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,20 @@ const APIS = [
|
||||
sendCommand: chatGPTSendMessage,
|
||||
contentWidget: chatGPTView,
|
||||
commandBar: chatGPTCommands,
|
||||
tabIcon: Box({}),
|
||||
}
|
||||
tabIcon: chatGPTTabIcon,
|
||||
placeholderText: 'Message ChatGPT',
|
||||
},
|
||||
{
|
||||
name: 'Waifus',
|
||||
sendCommand: waifuCallAPI,
|
||||
contentWidget: waifuView,
|
||||
commandBar: waifuCommands,
|
||||
tabIcon: waifuTabIcon,
|
||||
placeholderText: 'Enter tags',
|
||||
},
|
||||
];
|
||||
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 +77,37 @@ 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;
|
||||
chatEntry.placeholderText = APIS[id].placeholderText,
|
||||
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 +116,5 @@ export default Widget.Box({
|
||||
apiContentStack,
|
||||
apiCommandStack,
|
||||
textboxArea,
|
||||
]
|
||||
],
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ export default () => PopupWindow({
|
||||
focusable: true,
|
||||
anchor: ['left', 'top', 'bottom'],
|
||||
name: 'sideleft',
|
||||
// exclusivity: 'exclusive',
|
||||
showClassName: 'sideleft-show',
|
||||
hideClassName: 'sideleft-hide',
|
||||
child: SidebarLeft(),
|
||||
|
||||
@@ -18,6 +18,9 @@ export const SidebarModule = ({
|
||||
className: 'txt-small txt',
|
||||
label: `${name}`,
|
||||
}),
|
||||
Box({
|
||||
hexpand: true,
|
||||
}),
|
||||
Label({
|
||||
className: 'sidebar-module-btn-arrow',
|
||||
})
|
||||
|
||||
@@ -7,6 +7,5 @@ import { SidebarModule } from './module.js';
|
||||
export const QuickScripts = () => SidebarModule({
|
||||
name: 'Quick scripts',
|
||||
child: Box({
|
||||
|
||||
})
|
||||
})
|
||||
@@ -7,77 +7,137 @@ 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.773rem;', // Shushhhh
|
||||
})
|
||||
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._toggle(self),
|
||||
// QoL: Focus Pin button on open. Hit keybind -> space/enter = toggle pin state
|
||||
connections: [[App, (self, currentName, visible) => {
|
||||
if (currentName === 'sideleft' && visible) {
|
||||
self.grab_focus();
|
||||
}
|
||||
}]]
|
||||
})
|
||||
|
||||
export default () => Box({
|
||||
// vertical: true,
|
||||
vexpand: true,
|
||||
hexpand: true,
|
||||
css: 'min-width: 2px;',
|
||||
children: [
|
||||
EventBox({
|
||||
onPrimaryClick: () => App.closeWindow('sideleft'),
|
||||
@@ -87,23 +147,65 @@ export default () => Box({
|
||||
Box({
|
||||
vertical: true,
|
||||
vexpand: true,
|
||||
className: 'sidebar-left',
|
||||
className: 'sidebar-left spacing-v-10',
|
||||
children: [
|
||||
navBar,
|
||||
Box({
|
||||
className: 'spacing-h-10',
|
||||
children: [
|
||||
navBar,
|
||||
pinButton,
|
||||
]
|
||||
}),
|
||||
contentStack,
|
||||
]
|
||||
],
|
||||
connections: [[App, (self, currentName, visible) => {
|
||||
if (currentName === 'sideleft') {
|
||||
self.toggleClassName('sidebar-pinned', pinButton._enabled && visible);
|
||||
}
|
||||
}]]
|
||||
}),
|
||||
],
|
||||
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_Tab)
|
||||
switchToTab((currentTabId + 1) % contents.length);
|
||||
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_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
|
||||
event.get_keyval()[1] >= 32 && event.get_keyval()[1] <= 126 &&
|
||||
widget != chatEntry && event.get_keyval()[1] != Gdk.KEY_space)
|
||||
||
|
||||
((event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&
|
||||
event.get_keyval()[1] === Gdk.KEY_v)
|
||||
) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}],
|
||||
],
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user