Skip to content

Commit

Permalink
added ability to set custom hotkey
Browse files Browse the repository at this point in the history
  • Loading branch information
0PandaDEV committed Sep 2, 2024
1 parent 79dd783 commit 5bb6f22
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 201 deletions.
151 changes: 117 additions & 34 deletions assets/css/keybind.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,50 +17,133 @@ $mutedtext: #78756F;
outline: none;
}

.back {
position: absolute;
top: 16px;
left: 16px;
display: flex;
gap: 8px;
align-items: center;

img{
background-color: $divider;
border-radius: 6px;
padding: 8px 6px;
}

p {
color: $text2;
}
}

.keybind-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 20px;
}
height: 100vh;
gap: 6px;

.title {
font-size: 20px;
font-weight: 800;
}

.keybind-input {
padding: 6px;
border: 1px solid $divider;
color: $text2;
display: flex;
border-radius: 13px;
outline: none;
gap: 6px;

h2 {
margin-bottom: 20px;
.key {
color: $text2;
font-family: SFRoundedMedium;
background-color: $divider;
padding: 6px 8px;
border-radius: 8px;
}
}

.keybind-input:focus {
border: 1px solid rgba(255, 255, 255, 0.2);
}
}

.keybind-input {
width: 300px;
height: 50px;
border: 2px solid $accent;
border-radius: 5px;
.bottom-bar {
height: 40px;
width: calc(100vw - 2px);
backdrop-filter: blur(18px);
background-color: hsla(40, 3%, 16%, 0.8);
position: fixed;
bottom: 1px;
left: 1px;
z-index: 100;
border-radius: 0 0 12px 12px;
display: flex;
flex-direction: row;
justify-content: space-between;
padding-inline: 12px;
padding-right: 6px;
padding-top: 1px;
align-items: center;
justify-content: center;
font-size: 18px;
cursor: pointer;
margin-bottom: 20px;
background-color: rgba($accent, 0.1);
user-select: none;
}
font-size: 14px;
border-top: 1px solid $divider;

.keybind-input:focus {
outline: none;
box-shadow: 0 0 0 2px rgba($accent, 0.5);
}
p {
color: $text2;
}

button {
padding: 10px 20px;
background-color: $accent;
color: $primary;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
}
.left {
display: flex;
align-items: center;
gap: 8px;

button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.logo {
width: 18px;
height: 18px;
}
}

.right {
display: flex;
align-items: center;

.actions div {
display: flex;
align-items: center;
gap: 2px;
}

.divider {
width: 2px;
height: 12px;
background-color: $divider;
margin-left: 8px;
margin-right: 4px;
transition: all .2s;
}

.actions {
padding: 4px;
padding-left: 8px;
display: flex;
align-items: center;
gap: 8px;
border-radius: 7px;
background-color: transparent;
transition: all .2s;
cursor: pointer;
}

.actions:hover {
background-color: $divider;
}

&:hover .actions:hover~.divider {
opacity: 0;
}
}
}
2 changes: 1 addition & 1 deletion pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,6 @@ onMounted(async () => {
</script>

<style lang="scss">
<style scoped lang="scss">
@import '~/assets/css/index.scss';
</style>
134 changes: 84 additions & 50 deletions pages/keybind.vue
Original file line number Diff line number Diff line change
@@ -1,23 +1,52 @@
<template>
<div class="bg">
<div class="back">
<img @click="router.push('/')" src="../public/back_arrow.svg">
<p>Back</p>
</div>
<div class="bottom-bar">
<div class="left">
<img alt="" class="logo" src="../public/logo.png" width="18px">
<p>Qopy</p>
</div>
<div class="right">
<div @click="saveKeybind" class="actions">
<p>Save</p>
<div>
<img alt="" src="../public/ctrl.svg" v-if="os === 'windows' || os === 'linux'">
<img alt="" src="../public/cmd.svg" v-if="os === 'macos'">
<img alt="" src="../public/enter.svg">
</div>
</div>
</div>
</div>
<div class="keybind-container">
<h2>Set New Keybind</h2>
<div class="keybind-input" tabindex="0" @keydown="onKeyDown" @keyup="onKeyUp" @focus="onFocus" ref="keybindInput">
{{ currentKeybind || 'Click here, then press your desired key combination' }}
<h2 class="title">Record a new Hotkey</h2>
<div @blur="onBlur" @focus="onFocus" @keydown="onKeyDown" @keyup="onKeyUp" class="keybind-input"
ref="keybindInput" tabindex="0">
<span class="key" v-if="currentKeybind.length === 0">Click here</span>
<template v-else>
<span :key="index" class="key" v-for="(key, index) in currentKeybind">{{ keyToDisplay(key) }}</span>
</template>
</div>
<button @click="saveKeybind" :disabled="!currentKeybind">Save Keybind</button>
</div>
</div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { invoke } from '@tauri-apps/api/core';
import { platform } from '@tauri-apps/plugin-os';
import { onMounted, onUnmounted, ref } from 'vue';
import { useRouter } from 'vue-router';
const currentKeybind = ref('');
const activeModifiers = ref<Set<string>>(new Set());
const currentKeybind = ref<string[]>([]);
const isKeybindInputFocused = ref(false);
const keybindInput = ref<HTMLElement | null>(null);
const keys = ref<Set<string>>(new Set());
const recording = ref(false);
const lastNonModifier = ref('');
const os = ref('');
const router = useRouter();
const lastBlurTime = ref(0);
const keyToDisplayMap: Record<string, string> = {
" ": "Space",
Expand All @@ -28,78 +57,83 @@ const keyToDisplayMap: Record<string, string> = {
ArrowUp: "",
Control: "Ctrl",
Enter: "",
Escape: "Esc",
Meta: "Meta",
Shift: "",
};
const modifierKeySet = new Set(["Alt", "Control", "Meta", "Shift"]);
function keyCodeToKey(keyCode: string): string {
if (keyCode.startsWith("Key")) return keyCode.slice(3);
if (keyCode.endsWith("Left")) return keyCode.slice(0, -4);
if (keyCode.startsWith("Digit")) return keyCode.slice(5);
if (keyCode.endsWith("Right")) return keyCode.slice(0, -5);
return keyCode;
function keyToDisplay(key: string): string {
return keyToDisplayMap[key] || key.toUpperCase();
}
function keyToDisplay(keyCode: string): string {
const key = keyCodeToKey(keyCode);
return keyToDisplayMap[key] || key;
function updateCurrentKeybind() {
const modifiers = Array.from(activeModifiers.value);
currentKeybind.value = lastNonModifier.value ? [...modifiers, lastNonModifier.value] : modifiers;
}
function keyCombToDisplay(keyComb: Set<string>): string {
return Array.from(keyComb).map(keyToDisplay).join("+");
}
const onBlur = () => {
isKeybindInputFocused.value = false;
lastBlurTime.value = Date.now();
};
function mapKeyToTauriKey(key: string): string {
return key === "Meta" ? "Command" : key;
}
const onFocus = () => {
isKeybindInputFocused.value = true;
activeModifiers.value.clear();
lastNonModifier.value = '';
updateCurrentKeybind();
};
const onKeyDown = (event: KeyboardEvent) => {
event.preventDefault();
const key = keyCodeToKey(event.code);
const key = event.key;
if (modifierKeySet.has(key) && !keys.value.has(key)) {
keys.value = new Set(Array.from(keys.value).filter(k => modifierKeySet.has(k)));
if (key === "Escape") {
if (keybindInput.value) {
keybindInput.value.blur();
}
return;
}
keys.value.add(key);
if (modifierKeySet.has(key)) {
activeModifiers.value.add(key);
} else {
lastNonModifier.value = key;
}
updateCurrentKeybind();
};
const onKeyUp = (event: KeyboardEvent) => {
event.preventDefault();
const key = keyCodeToKey(event.code);
if (!modifierKeySet.has(key)) {
recording.value = false;
updateCurrentKeybind();
}
};
const onFocus = () => {
resetKeybind();
};
const updateCurrentKeybind = () => {
currentKeybind.value = keyCombToDisplay(keys.value);
const saveKeybind = async () => {
console.log("New:", currentKeybind.value);
console.log("Old: " + new Array(await invoke("get_keybind")));
await invoke("save_keybind", { keybind: currentKeybind.value})
};
const resetKeybind = () => {
keys.value.clear();
currentKeybind.value = '';
recording.value = true;
const handleGlobalKeyDown = (event: KeyboardEvent) => {
const now = Date.now();
if ((os.value === 'macos' ? event.metaKey : event.ctrlKey) && event.key === 'Enter' && !isKeybindInputFocused.value) {
event.preventDefault();
saveKeybind();
} else if (event.key === 'Escape' && !isKeybindInputFocused.value && now - lastBlurTime.value > 100) {
event.preventDefault();
router.push('/');
}
};
const saveKeybind = async () => {
console.log(await invoke("get_keybind"));
};
onMounted(() => {
os.value = platform();
window.addEventListener('keydown', handleGlobalKeyDown);
});
const startCapture = () => {
resetKeybind();
};
onUnmounted(() => {
window.removeEventListener('keydown', handleGlobalKeyDown);
});
</script>

<style lang="scss">
<style scoped lang="scss">
@import '~/assets/css/keybind.scss';
</style>
7 changes: 7 additions & 0 deletions public/back_arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/icon.icns
Binary file not shown.
3 changes: 1 addition & 2 deletions src-tauri/src/api/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,7 @@ pub async fn save_keybind(
keybind: Vec<String>,
pool: State<'_, SqlitePool>,
) -> Result<(), String> {
let setting = KeybindSetting { keybind };
let json = serde_json::to_string(&setting).map_err(|e| e.to_string())?;
let json = serde_json::to_string(&keybind).map_err(|e| e.to_string())?;

sqlx::query(
"INSERT OR REPLACE INTO settings (key, value) VALUES ('keybind', ?)"
Expand Down
Loading

0 comments on commit 5bb6f22

Please sign in to comment.