forked from starlight/keydisplay
make the thing
This commit is contained in:
parent
b7a9cdbd71
commit
995dedbe82
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@ -8,7 +8,7 @@
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug",
|
||||
"program": "${workspaceFolder}/src-tauri/target/debug/keydisplay",
|
||||
"program": "${workspaceFolder}/src-tauri/target/debug/copybot",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
# keydisplay
|
||||
# copybot
|
||||
|
||||
ligma balls
|
||||
you may need to install libxdo-dev, check https://github.com/Enigo-rs/Enigo for instructions
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "keydisplay",
|
||||
"name": "copybot",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
|
@ -1,9 +1,10 @@
|
||||
[package]
|
||||
name = "keydisplay"
|
||||
name = "copybot"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["starlight"]
|
||||
edition = "2021"
|
||||
repository = "https://git.stardust.wtf/starlight/copybot"
|
||||
license = "GPL-2.0-only"
|
||||
authors = ["starlight"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -11,7 +12,7 @@ edition = "2021"
|
||||
# The `_lib` suffix may seem redundant but it is necessary
|
||||
# to make the lib name unique and wouldn't conflict with the bin name.
|
||||
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
||||
name = "keydisplay_lib"
|
||||
name = "copybot_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
@ -27,3 +28,5 @@ dirs = "5.0"
|
||||
thiserror = "2.0.11"
|
||||
tauri-plugin-fs = { version = "2.0.0", features = ["watch"] }
|
||||
rdev = { version = "0.5.3", features = ["serde", "serialize"] }
|
||||
fastrand = "2.3.0"
|
||||
enigo = "0.3.0"
|
||||
|
@ -11,7 +11,7 @@
|
||||
"fs:default",
|
||||
{
|
||||
"identifier": "fs:allow-app-read-recursive",
|
||||
"allow": [{"path": "$CONFIG/keydisplay"}]
|
||||
"allow": [{"path": "$CONFIG/copybot"}]
|
||||
}
|
||||
]
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@ -22,8 +21,9 @@ pub struct Config {
|
||||
// theme = grey / night / day / catppuccin_mocha
|
||||
pub default: bool,
|
||||
pub theme: String,
|
||||
pub listen: ListenConfig,
|
||||
pub display: DisplayConfig,
|
||||
// bind = "Delete"
|
||||
pub bind: String,
|
||||
pub shift_enter_newline: bool,
|
||||
}
|
||||
|
||||
// this is hack as fvck
|
||||
@ -38,44 +38,14 @@ impl Default for Config {
|
||||
}
|
||||
}
|
||||
|
||||
// [listen]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ListenConfig {
|
||||
// keys = ["KeyZ", "KeyX"]
|
||||
pub keys: Vec<String>,
|
||||
// mouse = ["LeftButton", "RightButton"]
|
||||
pub mouse: Vec<String>,
|
||||
}
|
||||
|
||||
// [display]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct DisplayConfig {
|
||||
// "instant" or "ease"
|
||||
pub press: String,
|
||||
// display mouse buttons as a key or as an svg of a mouse
|
||||
pub mouse: String,
|
||||
// multiply default key length for the key by the float value
|
||||
pub size: Vec<HashMap<String, f64>>,
|
||||
// which keys to linebreak after
|
||||
pub r#break: Vec<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
// Provide default values
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
default: true,
|
||||
theme: "grey".to_string(),
|
||||
listen: ListenConfig {
|
||||
keys: vec!["KeyZ".to_string(), "KeyX".to_string(), "MetaLeft".to_string()],
|
||||
mouse: vec!["Left".to_string(), "Right".to_string()],
|
||||
},
|
||||
display: DisplayConfig {
|
||||
press: "ease".to_string(),
|
||||
mouse: "key".to_string(),
|
||||
size: vec![HashMap::new()],
|
||||
r#break: vec![],
|
||||
},
|
||||
bind: "Delete".to_string(),
|
||||
shift_enter_newline: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,13 @@
|
||||
use config::Config;
|
||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||
use std::{collections::HashMap, sync::Mutex};
|
||||
use tauri::{ipc::Channel, Manager, State};
|
||||
use rdev::{Event, EventType, listen};
|
||||
use enigo::{
|
||||
Direction::{Press, Release},
|
||||
Enigo, Key, Keyboard,
|
||||
};
|
||||
use rdev::{listen, Event, EventType};
|
||||
use std::sync::Mutex;
|
||||
use std::thread;
|
||||
use tauri::{ipc::Channel, Manager, State};
|
||||
|
||||
mod config;
|
||||
|
||||
@ -21,14 +25,9 @@ pub fn run() {
|
||||
update_config,
|
||||
get_default,
|
||||
get_theme,
|
||||
get_keys,
|
||||
get_mouse_buttons,
|
||||
get_mouse_display,
|
||||
get_press_display,
|
||||
get_size_display,
|
||||
get_breaks,
|
||||
get_bind,
|
||||
start_listener,
|
||||
label_from_keycode
|
||||
paste_text
|
||||
])
|
||||
.setup(|app| {
|
||||
let config = config::Config::load_or_create(get_config_path()).unwrap();
|
||||
@ -39,9 +38,7 @@ pub fn run() {
|
||||
let themes = vec!["grey", "night", "day", "catppuccin_mocha"];
|
||||
if themes.contains(&theme.as_str()) {
|
||||
println!("Setting theme to: {}", theme);
|
||||
app.manage(Mutex::new(AppState {
|
||||
config: config,
|
||||
}));
|
||||
app.manage(Mutex::new(AppState { config: config }));
|
||||
} else {
|
||||
println!(
|
||||
"{}",
|
||||
@ -49,7 +46,6 @@ pub fn run() {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
@ -62,7 +58,7 @@ pub fn run() {
|
||||
#[tauri::command]
|
||||
fn get_config_path() -> std::path::PathBuf {
|
||||
let config_dir = dirs::config_dir().expect("Config directory not found");
|
||||
config_dir.join("keydisplay")
|
||||
config_dir.join("copybot")
|
||||
}
|
||||
// update the app state with new config
|
||||
#[tauri::command]
|
||||
@ -72,7 +68,7 @@ fn update_config(state: State<'_, Mutex<AppState>>) {
|
||||
}
|
||||
#[tauri::command]
|
||||
fn get_default(state: State<'_, Mutex<AppState>>) -> Result<bool, bool> {
|
||||
let default = state.lock().unwrap().config.default.clone();
|
||||
let default = state.lock().unwrap().config.default;
|
||||
Ok(default)
|
||||
}
|
||||
#[tauri::command]
|
||||
@ -81,126 +77,48 @@ fn get_theme(state: State<'_, Mutex<AppState>>) -> Result<String, String> {
|
||||
Ok(theme)
|
||||
}
|
||||
#[tauri::command]
|
||||
fn get_keys(state: State<'_, Mutex<AppState>>) -> Result<Vec<String>, Vec<String>> {
|
||||
let keys = state.lock().unwrap().config.listen.keys.clone();
|
||||
Ok(keys)
|
||||
}
|
||||
#[tauri::command]
|
||||
fn get_mouse_buttons(state: State<'_, Mutex<AppState>>) -> Result<Vec<String>, Vec<String>> {
|
||||
let mouse_buttons = state.lock().unwrap().config.listen.mouse.clone();
|
||||
Ok(mouse_buttons)
|
||||
}
|
||||
#[tauri::command]
|
||||
fn get_press_display(state: State<'_, Mutex<AppState>>) -> Result<String, String> {
|
||||
let press_display = state.lock().unwrap().config.display.press.clone();
|
||||
Ok(press_display)
|
||||
}
|
||||
#[tauri::command]
|
||||
fn get_size_display(state: State<'_, Mutex<AppState>>) -> Result<Vec<HashMap<String, f64>>, Vec<HashMap<String, f64>>> {
|
||||
let size_display = state.lock().unwrap().config.display.size.clone();
|
||||
Ok(size_display)
|
||||
}
|
||||
#[tauri::command]
|
||||
fn get_mouse_display(state: State<'_, Mutex<AppState>>) -> Result<String, String> {
|
||||
let mouse_display = state.lock().unwrap().config.display.mouse.clone();
|
||||
Ok(mouse_display)
|
||||
}
|
||||
#[tauri::command]
|
||||
fn get_breaks(state: State<'_, Mutex<AppState>>) -> Result<Vec<String>, Vec<String>> {
|
||||
let breaks = state.lock().unwrap().config.display.r#break.clone();
|
||||
Ok(breaks)
|
||||
fn get_bind(state: State<'_, Mutex<AppState>>) -> Result<String, String> {
|
||||
let bind = state.lock().unwrap().config.bind.clone();
|
||||
Ok(bind)
|
||||
}
|
||||
|
||||
// Input events
|
||||
|
||||
#[tauri::command]
|
||||
fn start_listener(keys: Vec<String>, m_buttons: Vec<String>, channel: Channel<Event>) {
|
||||
fn start_listener(bind: String, channel: Channel<Event>) {
|
||||
thread::spawn(move || {
|
||||
println!("Started listening for keys: {:?} and buttons: {:?}", keys, m_buttons);
|
||||
listen(move |event| {
|
||||
match event.event_type {
|
||||
EventType::KeyPress(key) | EventType::KeyRelease(key) => {
|
||||
if keys.contains(&serde_json::to_string(&key).unwrap().replace("\"", "")) { channel.send(event).unwrap() };
|
||||
},
|
||||
EventType::ButtonPress(button) | EventType::ButtonRelease(button) => {
|
||||
if m_buttons.contains(&serde_json::to_string(&button).unwrap().replace("\"", "")) { channel.send(event).unwrap() };
|
||||
},
|
||||
EventType::MouseMove { x, y } => (),
|
||||
EventType::Wheel { delta_x, delta_y } => (),
|
||||
println!("Started listening for bind: {:?}", bind);
|
||||
listen(move |event| match event.event_type {
|
||||
EventType::KeyPress(key) | EventType::KeyRelease(key) => {
|
||||
if bind == serde_json::to_string(&key).unwrap().replace("\"", "") {
|
||||
channel.send(event).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
_ => (),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Other rust
|
||||
#[tauri::command]
|
||||
fn label_from_keycode(code: &str) -> &str {
|
||||
match code {
|
||||
// Keyboard
|
||||
// Fine as-is
|
||||
"Alt"|"AltGr"|"End"|"Home"|"Pause" => code,
|
||||
// All alphabetical keys
|
||||
c if c.starts_with("Key")
|
||||
&& c.len() == 4
|
||||
&& c.chars().nth(3).map_or(false, |c| c.is_ascii_alphabetic()) => &c[3..],
|
||||
// Number row
|
||||
c if c.starts_with("Num")
|
||||
&& c.len() == 4
|
||||
&& c.chars().nth(3).map_or(false, |c| c.is_numeric()) => &c[3..],
|
||||
// All F keys
|
||||
c if c.starts_with("F")
|
||||
&& c.len() == 2
|
||||
&& c.chars().nth(1).map_or(false, |c| c.is_numeric()) => c,
|
||||
// Numpad numbers
|
||||
c if c.starts_with("Kp")
|
||||
&& c.len() == 3
|
||||
&& c.chars().nth(2).map_or(false, |c| c.is_numeric()) => &c[2..],
|
||||
// Individual mappings
|
||||
"Backspace" => "🡐",
|
||||
"CapsLock" => "Caps",
|
||||
"ControlLeft"|"ControlRight" => "Ctrl",
|
||||
"Delete"|"KpDelete" => "Del",
|
||||
"DownArrow" => "⯆",
|
||||
"Escape" => "Esc",
|
||||
"LeftArrow" => "⯇",
|
||||
"MetaLeft"|"MetaRight" => "Super",
|
||||
"PageDown" => "PgDn",
|
||||
"PageUp" => "PgUp",
|
||||
"Return" => "⮠",
|
||||
"RightArrow" => "⯈",
|
||||
"ShiftLeft"|"ShiftRight" => "Shift",
|
||||
// needs an obscure blank character (U+E002D) - css acts weird if its a space or anything the program interprets as one
|
||||
"Space" => "",
|
||||
"Tab" => "⇌",
|
||||
"UpArrow" => "⯅",
|
||||
"PrintScreen" => "PrtSc",
|
||||
"ScrollLock" => "ScrLk",
|
||||
"NumLock" => "Num",
|
||||
"BackQuote" => "`",
|
||||
"Minus" => "-",
|
||||
"Equal" => "=",
|
||||
"LeftBracket" => "(",
|
||||
"RightBracket" => ")",
|
||||
"SemiColon" => ";",
|
||||
"Quote" => "\"",
|
||||
"BackSlash"|"IntlBackslash" => "\\",
|
||||
"Comma" => ",",
|
||||
"Dot" => ".",
|
||||
"Slash"|"KpDivide" => "/",
|
||||
"Insert" => "Ins",
|
||||
"KpReturn" => "↲",
|
||||
"KpPlus" => "+",
|
||||
"KpMultiply" => "*",
|
||||
"Function" => "Fn",
|
||||
fn paste_text(state: State<'_, Mutex<AppState>>, text: String) {
|
||||
println!("{}", text);
|
||||
let mut enigo = Enigo::new(&enigo::Settings::default()).unwrap();
|
||||
|
||||
// Mouse
|
||||
"Left" => "M1",
|
||||
"Right" => "M2",
|
||||
"Middle" => "M3",
|
||||
|
||||
&_ => {
|
||||
println!("Error creating frontend label for keycode: {}, displaying as Unknown", code);
|
||||
"Unknown"
|
||||
},
|
||||
if state.lock().unwrap().config.shift_enter_newline {
|
||||
for c in text.chars() {
|
||||
if c == '\n' {
|
||||
// Press Shift+Enter combination
|
||||
enigo.key(Key::Shift, Press).unwrap();
|
||||
enigo.key(Key::Return, Press).unwrap();
|
||||
enigo.key(Key::Return, Release).unwrap();
|
||||
enigo.key(Key::Shift, Release).unwrap();
|
||||
} else {
|
||||
// Type regular characters normally
|
||||
enigo.text(&c.to_string()).unwrap();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
enigo.text(text.as_str()).unwrap();
|
||||
}
|
||||
}
|
@ -4,5 +4,5 @@
|
||||
mod config;
|
||||
|
||||
fn main() {
|
||||
keydisplay_lib::run()
|
||||
copybot_lib::run()
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "keydisplay",
|
||||
"productName": "COPYBOT",
|
||||
"version": "0.1.0",
|
||||
"identifier": "whatisthisanandroidapp.keydisplay.starlight",
|
||||
"identifier": "whatisthisanandroidapp.copybot.starlight",
|
||||
"build": {
|
||||
"beforeDevCommand": "yarn dev",
|
||||
"devUrl": "http://localhost:1420",
|
||||
@ -12,7 +12,7 @@
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "keydisplay",
|
||||
"title": "copybot",
|
||||
"width": 800,
|
||||
"height": 600
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>keydisplay</title>
|
||||
<title>copybot</title>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
|
@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { invoke, Channel } from "@tauri-apps/api/core";
|
||||
import Key from "./Key.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { watchImmediate } from "@tauri-apps/plugin-fs";
|
||||
@ -20,22 +19,18 @@
|
||||
| { Wheel: { delta_x: number, delta_y: number } }
|
||||
}
|
||||
|
||||
// type with props for the Key component
|
||||
type KeyElement = {
|
||||
label: string,
|
||||
code: string,
|
||||
pressed: boolean
|
||||
}
|
||||
// app state
|
||||
let content_tmp: string = $state("");
|
||||
let content: string = $state("");
|
||||
|
||||
|
||||
// scoping
|
||||
let keys: Array<string>, mouse_buttons: Array<string>;
|
||||
let key_elements: Array<KeyElement>, mouse_button_elements: Array<KeyElement>;
|
||||
let bind: string = $state("");
|
||||
let bind_pressed: boolean;
|
||||
|
||||
let default_config: boolean;
|
||||
let config_path: string;
|
||||
|
||||
let breaks: Set<string>;
|
||||
|
||||
onMount(async () => {
|
||||
// on launch
|
||||
await handle_config();
|
||||
@ -60,7 +55,6 @@
|
||||
// create listener for events
|
||||
const KeyListener = new Channel<KeyEvent>();
|
||||
|
||||
// handle key event by modifying the key_elements and mouse_button_elements arrays
|
||||
KeyListener.onmessage = (message) => {
|
||||
switch (Object.keys(message.event_type)[0]) {
|
||||
case 'KeyPress':
|
||||
@ -71,14 +65,6 @@
|
||||
handleKeyEvent(Object.values(message.event_type)[0], false);
|
||||
break;
|
||||
|
||||
case 'ButtonPress':
|
||||
handleMouseEvent(Object.values(message.event_type)[0], true);
|
||||
break;
|
||||
|
||||
case 'ButtonRelease':
|
||||
handleMouseEvent(Object.values(message.event_type)[0], false);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn("Unhandled event: ", message);
|
||||
}
|
||||
@ -86,28 +72,18 @@
|
||||
};
|
||||
|
||||
async function handleKeyEvent(code: string, isPressed: boolean) {
|
||||
key_elements = key_elements.map(key => {
|
||||
if (key.code === code) {
|
||||
return { ...key, pressed: isPressed}
|
||||
}
|
||||
return key;
|
||||
});
|
||||
}
|
||||
|
||||
async function handleMouseEvent(code: string, isPressed: boolean) {
|
||||
mouse_button_elements = mouse_button_elements.map(button => {
|
||||
if (button.code === code) {
|
||||
return { ...button, pressed: isPressed}
|
||||
}
|
||||
return button;
|
||||
});
|
||||
if(isPressed && !bind_pressed) {
|
||||
bind_pressed = true;
|
||||
console.log(`${code} was pressed, pasting..`)
|
||||
await invoke('paste_text', { text: content });
|
||||
} else if(!isPressed) {
|
||||
bind_pressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
// invoke backend key listener that will send the events
|
||||
await invoke('start_listener', {
|
||||
keys: keys,
|
||||
// named mButtons and not m_buttons because weird naming """conventions""" (WHO CARES)
|
||||
mButtons: mouse_buttons,
|
||||
bind: bind,
|
||||
channel: KeyListener
|
||||
});
|
||||
});
|
||||
@ -134,83 +110,26 @@
|
||||
console.error(`Failed to run get_default from the rust backend: ${error}`);
|
||||
}
|
||||
|
||||
// set display for keypresses
|
||||
// load the bind
|
||||
try {
|
||||
let press_display = await invoke('get_press_display');
|
||||
switch (press_display) {
|
||||
case "instant":
|
||||
case "ease":
|
||||
root?.setAttribute("data-press-display", press_display);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('"press" field in [display] of the config is an invalid value, falling back to "ease"');
|
||||
root?.setAttribute("data-press-display", "ease");
|
||||
}
|
||||
bind = await invoke<string>('get_bind');
|
||||
} catch (error) {
|
||||
console.error(`Failed to run get_press_display from the rust backend: ${error}`);
|
||||
console.error("Failed to process bind: ", error)
|
||||
}
|
||||
}
|
||||
|
||||
// get linebreaks
|
||||
try {
|
||||
breaks = new Set(await invoke<Array<string>>("get_breaks"));
|
||||
} catch (error) {
|
||||
console.error(`Failed to get "break" config field from rust backend: ${error}`);
|
||||
}
|
||||
|
||||
// load the keys/mousebuttons
|
||||
try {
|
||||
keys = await invoke<Array<string>>('get_keys');
|
||||
mouse_buttons = await invoke<Array<string>>('get_mouse_buttons');
|
||||
key_elements = await Promise.all(keys.map(async key => {
|
||||
return {
|
||||
label: await invoke<string>("label_from_keycode", { code: key }),
|
||||
code: key,
|
||||
pressed: false
|
||||
}
|
||||
}));
|
||||
mouse_button_elements = await Promise.all(mouse_buttons.map(async button => {
|
||||
return {
|
||||
label: await invoke<string>("label_from_keycode", { code: button }),
|
||||
code: button,
|
||||
pressed: false
|
||||
};
|
||||
}));
|
||||
|
||||
} catch (error) {
|
||||
console.error("Failed to process keys: ", error)
|
||||
}
|
||||
async function set_text() {
|
||||
content = content_tmp
|
||||
}
|
||||
</script>
|
||||
<link rel="stylesheet" href="/global.css">
|
||||
<main class="container">
|
||||
{#if default_config}
|
||||
<div id="default_config">Docs for configuring this program are at <a href="https://git.stardust.wtf/starlight/keydisplay/src/branch/main/README.md">https://git.stardust.wtf/starlight/keydisplay/src/branch/main/README.md</a><br>You can disable this annoying notice by removing the <code>default = true</code> line from <code>{config_path}</code> :)</div>
|
||||
<div id="default_config">Docs for configuring this program are at <a href="https://git.stardust.wtf/starlight/copybot/src/branch/main/README.md">https://git.stardust.wtf/starlight/copybot/src/branch/main/README.md</a><br>You can disable this annoying notice by removing the <code>default = true</code> line from <code>{config_path}</code> :)</div>
|
||||
{/if}
|
||||
<div id="keys">
|
||||
{#each key_elements as key (key.code)}
|
||||
<Key
|
||||
label={key.label}
|
||||
code={key.code}
|
||||
pressed={key.pressed}
|
||||
>
|
||||
</Key>
|
||||
{#if breaks.has(key.code)}
|
||||
<br>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div id="mouse_buttons">
|
||||
{#each mouse_button_elements as button (button.code)}
|
||||
<Key
|
||||
label={button.label}
|
||||
code={button.code}
|
||||
pressed={button.pressed}
|
||||
>
|
||||
</Key>
|
||||
{#if breaks.has(button.code)}
|
||||
<br>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<h1>copybot</h1>
|
||||
<textarea autocorrect="off" rows="6" bind:value={content_tmp}></textarea>
|
||||
<button id="set_text" onclick={() => content = content_tmp}>Set</button>
|
||||
<p>Current bind: <b>{bind}</b></p>
|
||||
<p>Set to current textarea content: <b>{content == content_tmp}</b></p>
|
||||
</main>
|
@ -1,14 +0,0 @@
|
||||
<!-- generic "key" -->
|
||||
<script>
|
||||
export let label = "";
|
||||
export let code = "";
|
||||
export let pressed = false;
|
||||
|
||||
// Optional: Add press animation
|
||||
import { quintOut } from "svelte/easing";
|
||||
import { crossfade } from "svelte/transition";
|
||||
</script>
|
||||
<link rel="stylesheet" href="/global.css">
|
||||
<div class="key" class:pressed={pressed} data-keycode={code}>
|
||||
{label}
|
||||
</div>
|
@ -32,30 +32,30 @@ img {
|
||||
:root,
|
||||
[data-selected-theme="grey"] {
|
||||
--color-app-background: #2F2F2F;
|
||||
--color-key-background: #363636;
|
||||
--color-key-background-pressed: #212121;
|
||||
--color-app-background-2: #4D4D4D;
|
||||
--color-text: #fff;
|
||||
--color-button-pressed: #212121;
|
||||
--color-accent: #737373;
|
||||
}
|
||||
[data-selected-theme="night"] {
|
||||
--color-app-background: #000;
|
||||
--color-key-background: #0f0f0f;
|
||||
--color-key-background-pressed: #212121;
|
||||
--color-app-background-2: #1B1B1B;
|
||||
--color-text: #eee;
|
||||
--color-button-pressed: #212121;
|
||||
--color-accent: #525151;
|
||||
}
|
||||
[data-selected-theme="day"] {
|
||||
--color-app-background: #e0e0e0;
|
||||
--color-key-background: #fff;
|
||||
--color-key-background-pressed: #eee;
|
||||
--color-app-background-2: #f0f0f0;
|
||||
--color-text: #000;
|
||||
--color-button-pressed: #000;
|
||||
--color-accent: #fff;
|
||||
}
|
||||
[data-selected-theme="catppuccin_mocha"] {
|
||||
--color-app-background: #181825;
|
||||
--color-key-background: #1e1e2e;
|
||||
--color-key-background-pressed: #313244;
|
||||
--color-app-background-2: #1e1e2e;
|
||||
--color-text: #cdd6f4;
|
||||
--color-button-pressed: #313244;
|
||||
--color-accent: #f5e0dc;
|
||||
}
|
||||
|
||||
@ -87,22 +87,28 @@ button {
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-weight: 400;
|
||||
font-family: inherit;
|
||||
color: #0f0f0f;
|
||||
background-color: #ffffff;
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-app-background-2);
|
||||
transition: border-color 0.25s;
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button#set_text {
|
||||
margin-top: 5px;
|
||||
width: 25vw;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border-color: #396cd8;
|
||||
}
|
||||
button:active {
|
||||
border-color: #396cd8;
|
||||
background-color: #e8e8e8;
|
||||
background-color: var(--color-button-pressed);
|
||||
}
|
||||
|
||||
a {
|
||||
@ -125,34 +131,13 @@ div#default_config {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
div#keys,
|
||||
div#mouse_buttons {
|
||||
/* horizontal spacing between keys is dependent on font-size of the parent div? */
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.key {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
margin: 5px;
|
||||
border: 5px solid var(--color-accent);
|
||||
border-radius: 5px;
|
||||
transition: var(--pressed-transition);
|
||||
background-color: var(--color-key-background);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
[data-press-display="ease"] {
|
||||
--pressed-transition: background-color 0.1s ease, transform 0.1s ease;
|
||||
}
|
||||
|
||||
.pressed {
|
||||
background-color: var(--color-key-background-pressed) !important;
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
textarea {
|
||||
background-color: var(--color-app-background-2);
|
||||
border: 3px solid var(--color-accent);
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
width: 80vw;
|
||||
margin-top: 5px;
|
||||
margin-inline: auto;
|
||||
color: var(--color-text);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user