diff --git a/app/electron/src/main.ts b/app/electron/src/main.ts index a0735f5b..2361fbd8 100644 --- a/app/electron/src/main.ts +++ b/app/electron/src/main.ts @@ -14,11 +14,11 @@ import path from "path"; import { SET_ALWAYS_ON_TOP, SET_FULLSCREEN_BREAK, - SET_MINIMIZE, - SET_CLOSE, + MINIMIZE_WINDOW, + CLOSE_WINDOW, SET_UI_THEME, SET_NATIVE_TITLEBAR, - SET_SHOW, + SHOW_WINDOW, RELEASE_NOTES_LINK, TRAY_ICON_UPDATE, SET_COMPACT_MODE, @@ -396,7 +396,7 @@ ipcMain.on(SET_UI_THEME, (e, { isDarkMode }) => { store.safeSet("isDarkMode", isDarkMode); }); -ipcMain.on(SET_SHOW, () => { +ipcMain.on(SHOW_WINDOW, () => { if (!win?.isVisible()) { win?.show(); } else { @@ -404,7 +404,7 @@ ipcMain.on(SET_SHOW, () => { } }); -ipcMain.on(SET_MINIMIZE, (e, { minimizeToTray }) => { +ipcMain.on(MINIMIZE_WINDOW, (e, { minimizeToTray }) => { if (!minimizeToTray) { win?.minimize(); } else { @@ -415,7 +415,7 @@ ipcMain.on(SET_MINIMIZE, (e, { minimizeToTray }) => { } }); -ipcMain.on(SET_CLOSE, (e, { closeToTray }) => { +ipcMain.on(CLOSE_WINDOW, (e, { closeToTray }) => { if (!closeToTray) { app.exit(); } else { diff --git a/app/renderer/src/components/Layout.tsx b/app/renderer/src/components/Layout.tsx index 5ad23c25..32b78fc3 100644 --- a/app/renderer/src/components/Layout.tsx +++ b/app/renderer/src/components/Layout.tsx @@ -37,10 +37,7 @@ const Layout: React.FC = ({ history, location, children }) => { const registerKey = useCallback( (e: KeyboardEvent) => { - const keyCode = e.keyCode; - const keyChar = String.fromCharCode(keyCode); - - if (e.altKey && e.shiftKey && keyChar === "T") { + if (e.altKey && e.shiftKey && e.code === "KeyT") { if (toggleThemeAction) toggleThemeAction(); } }, diff --git a/app/renderer/src/components/Shortcut.tsx b/app/renderer/src/components/Shortcut.tsx index c72d9e49..eeb8b31b 100644 --- a/app/renderer/src/components/Shortcut.tsx +++ b/app/renderer/src/components/Shortcut.tsx @@ -1,9 +1,10 @@ -import React from "react"; +import React, { useState, type KeyboardEvent } from "react"; import { StyledShortcutWrapper, StyledShortcutName, StyledShortcutKey, } from "styles"; +import { getShortcutFromEvent } from "utils/getShortcutFromEvent"; type Props = { id: string; @@ -12,14 +13,29 @@ type Props = { }; const Shortcut: React.FC = ({ id, name, shortcutKey }) => { + const [shortcut, setShortcut] = useState(() => shortcutKey); + + const handleKeyDown = (e: KeyboardEvent) => { + const shortcut = getShortcutFromEvent(e); + + if (shortcut) { + console.log(shortcut); + + // For now we don't update the UI, as it is not being saved + // TODO: Uncomment when we have a way to save the shortcuts + // setShortcut(shortcut); + } + }; + return ( {name} ); diff --git a/app/renderer/src/contexts/connectors/ElectronConnector.tsx b/app/renderer/src/contexts/connectors/ElectronConnector.tsx index ffbd956c..42e18610 100644 --- a/app/renderer/src/contexts/connectors/ElectronConnector.tsx +++ b/app/renderer/src/contexts/connectors/ElectronConnector.tsx @@ -5,19 +5,18 @@ import { AppStateTypes, SettingTypes } from "../../store"; import { CounterContext } from "../CounterContext"; import { SET_ALWAYS_ON_TOP, - SET_CLOSE, + CLOSE_WINDOW, SET_COMPACT_MODE, SET_FULLSCREEN_BREAK, - SET_MINIMIZE, + MINIMIZE_WINDOW, SET_NATIVE_TITLEBAR, - SET_SHOW, + SHOW_WINDOW, SET_UI_THEME, TRAY_ICON_UPDATE, SET_OPEN_AT_LOGIN, } from "@pomatez/shareables"; -import { encodeSvg } from "../../utils"; -import { TraySVG } from "../../components"; import { InvokeConnector } from "../InvokeConnector"; +import { useTrayIconUpdates } from "hooks/useTrayIconUpdates"; export const ElectronInvokeConnector: InvokeConnector = { send: (event: string, ...payload: any) => { @@ -30,26 +29,22 @@ export const ElectronInvokeConnector: InvokeConnector = { export const ElectronConnectorProvider: React.FC = ({ children }) => { const { electron } = window; - // TODO do logic to switch out the connectors based on the platform - const timer = useSelector((state: AppStateTypes) => state.timer); const settings: SettingTypes = useSelector( (state: AppStateTypes) => state.settings ); - const { count, duration, timerType, shouldFullscreen } = - useContext(CounterContext); - const dashOffset = (duration - count) * (24 / duration); + const { shouldFullscreen } = useContext(CounterContext); const onMinimizeCallback = useCallback(() => { - electron.send(SET_MINIMIZE, { + electron.send(MINIMIZE_WINDOW, { minimizeToTray: settings.minimizeToTray, }); }, [electron, settings.minimizeToTray]); const onExitCallback = useCallback(() => { - electron.send(SET_CLOSE, { + electron.send(CLOSE_WINDOW, { closeToTray: settings.closeToTray, }); }, [electron, settings.closeToTray]); @@ -70,7 +65,7 @@ export const ElectronConnectorProvider: React.FC = ({ children }) => { useEffect(() => { if (!settings.enableFullscreenBreak) { - electron.send(SET_SHOW); + electron.send(SHOW_WINDOW); } }, [electron, timer.timerType, settings.enableFullscreenBreak]); @@ -111,29 +106,9 @@ export const ElectronConnectorProvider: React.FC = ({ children }) => { }); }, [electron, settings.openAtLogin]); - useEffect(() => { - if (timer.playing) { - const canvas = document.createElement("canvas"); - const ctx = canvas.getContext("2d"); - - canvas.width = 16; - canvas.height = 16; - - let svgXML = encodeSvg( - - ); - - const img = new Image(); - img.src = svgXML; - - img.onload = function () { - ctx?.drawImage(img, 0, 0); - const dataUrl = canvas.toDataURL("image/png"); - - electron.send(TRAY_ICON_UPDATE, dataUrl); - }; - } - }, [electron, timer.playing, timerType, dashOffset]); + useTrayIconUpdates((dataUrl) => { + electron.send(TRAY_ICON_UPDATE, { dataUrl }); + }); return ( { @@ -43,11 +42,12 @@ export const TauriConnectorProvider: React.FC = ({ children }) => { (state: AppStateTypes) => state.settings ); + // Prevent webpage behavior (naitive apps shouldn't refresh with F5 or Ctrl+R) useEffect(() => { function handleKeyDown(event: KeyboardEvent) { if ( - (event.ctrlKey && (event.key === "r" || event.key === "R")) || - event.key === "F5" + (event.ctrlKey && event.code === "KeyR") || + event.code === "F5" ) { event.preventDefault(); } @@ -114,23 +114,19 @@ export const TauriConnectorProvider: React.FC = ({ children }) => { }); }, [settings.openAtLogin]); - // TODO do logic to switch out the connectors based on the platform - const timer = useSelector((state: AppStateTypes) => state.timer); - const { count, duration, timerType, shouldFullscreen } = - useContext(CounterContext); - const dashOffset = (duration - count) * (24 / duration); + const { shouldFullscreen } = useContext(CounterContext); const onMinimizeCallback = useCallback(() => { - send(SET_MINIMIZE, { + send(MINIMIZE_WINDOW, { minimizeToTray: settings.minimizeToTray, }); console.log("Minimize callback"); }, [send, settings.minimizeToTray]); const onExitCallback = useCallback(() => { - send(SET_CLOSE, { + send(CLOSE_WINDOW, { closeToTray: settings.closeToTray, }); }, [send, settings.closeToTray]); @@ -142,7 +138,7 @@ export const TauriConnectorProvider: React.FC = ({ children }) => { useEffect(() => { if (!settings.enableFullscreenBreak) { - send(SET_SHOW); + send(SHOW_WINDOW); } }, [send, timer.timerType, settings.enableFullscreenBreak]); @@ -177,29 +173,9 @@ export const TauriConnectorProvider: React.FC = ({ children }) => { }); }, [send, settings.useNativeTitlebar]); - useEffect(() => { - if (timer.playing) { - const canvas = document.createElement("canvas"); - const ctx = canvas.getContext("2d"); - - canvas.width = 16; - canvas.height = 16; - - let svgXML = encodeSvg( - - ); - - const img = new Image(); - img.src = svgXML; - - img.onload = function () { - ctx?.drawImage(img, 0, 0); - const dataUrl = canvas.toDataURL("image/png"); - - send(TRAY_ICON_UPDATE, { dataUrl }); - }; - } - }, [send, timer.playing, timerType, dashOffset]); + useTrayIconUpdates((dataUrl) => { + send(TRAY_ICON_UPDATE, { dataUrl }); + }); // Workaround to make sure it only calls once on mount const checkUpdate = useCallback(() => { diff --git a/app/renderer/src/hooks/useTargetOutside.ts b/app/renderer/src/hooks/useTargetOutside.ts index e5dd3b61..11d60a8e 100644 --- a/app/renderer/src/hooks/useTargetOutside.ts +++ b/app/renderer/src/hooks/useTargetOutside.ts @@ -27,7 +27,7 @@ export const useTargetOutside = ({ ref, eventType }: TargetOutside) => { } function closeOnEscape(e: KeyboardEvent) { - if (e.keyCode === 27) { + if (e.code === "Escape") { setState(false); } } diff --git a/app/renderer/src/hooks/useTrayIconUpdates.tsx b/app/renderer/src/hooks/useTrayIconUpdates.tsx new file mode 100644 index 00000000..49ac8776 --- /dev/null +++ b/app/renderer/src/hooks/useTrayIconUpdates.tsx @@ -0,0 +1,39 @@ +import { CounterContext } from "contexts"; +import { useEffect, useContext } from "react"; +import { useSelector } from "react-redux"; +import type { AppStateTypes } from "store"; +import { TraySVG } from "components"; +import { encodeSvg } from "utils"; + +export const useTrayIconUpdates = ( + onNewIcon: (dataUrl: string) => void +) => { + const timer = useSelector((state: AppStateTypes) => state.timer); + + const { count, duration, timerType } = useContext(CounterContext); + const dashOffset = (duration - count) * (24 / duration); + + useEffect(() => { + if (timer.playing) { + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + + canvas.width = 16; + canvas.height = 16; + + let svgXML = encodeSvg( + + ); + + const img = new Image(); + img.src = svgXML; + + img.onload = function () { + ctx?.drawImage(img, 0, 0); + const dataUrl = canvas.toDataURL("image/png"); + + onNewIcon(dataUrl); + }; + } + }, [onNewIcon, timer.playing, timerType, dashOffset]); +}; diff --git a/app/renderer/src/routes/Config/SpecialField.tsx b/app/renderer/src/routes/Config/SpecialField.tsx index fa5965e3..a394c336 100644 --- a/app/renderer/src/routes/Config/SpecialField.tsx +++ b/app/renderer/src/routes/Config/SpecialField.tsx @@ -139,7 +139,7 @@ const SpecialField: React.FC = ({ useEffect(() => { function registerEscape(e: KeyboardEvent) { - if (e.keyCode === 27) { + if (e.code === "Escape") { setShowSetter(false); } } @@ -161,7 +161,7 @@ const SpecialField: React.FC = ({ } }} onKeyDown={(e) => { - if (e.keyCode === 13) { + if (e.key === "Enter") { setShowSetter(true); } }} diff --git a/app/renderer/src/routes/Tasks/TaskDetails/index.tsx b/app/renderer/src/routes/Tasks/TaskDetails/index.tsx index c38035a3..099fff83 100644 --- a/app/renderer/src/routes/Tasks/TaskDetails/index.tsx +++ b/app/renderer/src/routes/Tasks/TaskDetails/index.tsx @@ -149,7 +149,7 @@ const TaskDetails = React.forwardRef( useEffect(() => { function registerEscape(e: KeyboardEvent) { - if (e.keyCode === 27) { + if (e.key === "Escape") { if (onExit) { onExit(); } diff --git a/app/renderer/src/routes/Tasks/TaskHeader.tsx b/app/renderer/src/routes/Tasks/TaskHeader.tsx index d15e17ee..da98818f 100644 --- a/app/renderer/src/routes/Tasks/TaskHeader.tsx +++ b/app/renderer/src/routes/Tasks/TaskHeader.tsx @@ -52,7 +52,7 @@ const TaskHeader: React.FC = ({ }; inputRef.current.onkeyup = (e: KeyboardEvent) => { - if (e.keyCode === 13 || e.keyCode === 10) { + if (e.key === "Enter" || e.keyCode === 10) { if (inputRef.current) { if (onEditTitle && inputRef.current.value) { onEditTitle(inputRef.current.value); diff --git a/app/renderer/src/routes/Tasks/index.tsx b/app/renderer/src/routes/Tasks/index.tsx index 001b12fc..11aabe4f 100644 --- a/app/renderer/src/routes/Tasks/index.tsx +++ b/app/renderer/src/routes/Tasks/index.tsx @@ -48,13 +48,13 @@ export default function Tasks() { const activeElement = document.activeElement?.tagName; if (activeElement !== "INPUT" && activeElement !== "TEXTAREA") { - if (e.ctrlKey && e.key === "z") { + if (e.ctrlKey && e.code === "KeyZ") { if (tasks.past.length > 0) { dispatch(UndoActionCreator.undo()); } } - if (e.ctrlKey && e.key === "Z") { + if (e.ctrlKey && e.shiftKey && e.code === "KeyZ") { if (tasks.future.length > 0) { dispatch(UndoActionCreator.redo()); } diff --git a/app/renderer/src/styles/components/shortcuts.ts b/app/renderer/src/styles/components/shortcuts.ts index 39ed4312..c093e45e 100644 --- a/app/renderer/src/styles/components/shortcuts.ts +++ b/app/renderer/src/styles/components/shortcuts.ts @@ -42,4 +42,8 @@ export const StyledShortcutKey = styled.input` border: none; border-radius: 0.3rem; background-color: var(--color-bg-secondary); + + &:focus { + outline: 1px solid var(--color-primary); + } `; diff --git a/app/renderer/src/utils/getShortcutFromEvent.ts b/app/renderer/src/utils/getShortcutFromEvent.ts new file mode 100644 index 00000000..8558d33b --- /dev/null +++ b/app/renderer/src/utils/getShortcutFromEvent.ts @@ -0,0 +1,42 @@ +import type { KeyboardEvent } from "react"; + +// TODO - Test this for Tauri + +const ELECTRON_MODIFIERS = Object.freeze({ + Control: "CmdOrCtrl", + Alt: "Alt", + Meta: "Meta", + Shift: "Shift", +}); + +export function getShortcutFromEvent(e: KeyboardEvent) { + // Ignore the event if there is no modifier + if (!e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey) return null; + + const key = e.key.toLowerCase(); + + // Ignore modifiers for the key part in the shortcut + if ( + key === "control" || + key === "alt" || + key === "meta" || + key === "shift" + ) + return null; + + e.preventDefault(); + + let shortcut = ""; + + // Add modifiers to the shortcut + if (e.ctrlKey) shortcut += `${ELECTRON_MODIFIERS.Control}+`; + if (e.altKey) shortcut += `${ELECTRON_MODIFIERS.Alt}+`; + if (e.metaKey) shortcut += `${ELECTRON_MODIFIERS.Meta}+`; + if (e.shiftKey) shortcut += `${ELECTRON_MODIFIERS.Shift}+`; + + // Accept only alphanumeric characters (for now) + if (!key.match(/^[0-9a-z]$/)) return null; + + shortcut += key; + return shortcut; +} diff --git a/app/shareables/src/index.ts b/app/shareables/src/index.ts index d5db765b..cf2e5ece 100644 --- a/app/shareables/src/index.ts +++ b/app/shareables/src/index.ts @@ -6,9 +6,9 @@ export const SET_NATIVE_TITLEBAR = "SET_NATIVE_TITLEBAR"; export const SET_OPEN_AT_LOGIN = "SET_OPEN_AT_LOGIN"; export const TRAY_ICON_UPDATE = "TRAY_ICON_UPDATE"; export const SET_UI_THEME = "SET_UI_THEME"; -export const SET_MINIMIZE = "SET_MINIMIZE"; -export const SET_CLOSE = "SET_CLOSE"; -export const SET_SHOW = "SET_SHOW"; +export const MINIMIZE_WINDOW = "MINIMIZE_WINDOW"; +export const CLOSE_WINDOW = "CLOSE_WINDOW"; +export const SHOW_WINDOW = "SHOW_WINDOW"; export const UPDATE_AVAILABLE = "UPDATE_AVAILABLE"; export const INSTALL_UPDATE = "INSTALL_UPDATE"; @@ -20,9 +20,9 @@ export const TO_MAIN: string[] = [ SET_OPEN_AT_LOGIN, TRAY_ICON_UPDATE, SET_UI_THEME, - SET_MINIMIZE, - SET_CLOSE, - SET_SHOW, + MINIMIZE_WINDOW, + CLOSE_WINDOW, + SHOW_WINDOW, INSTALL_UPDATE, ]; diff --git a/app/tauri/src/commands.rs b/app/tauri/src/commands.rs index fc5504e7..5e1a7a81 100644 --- a/app/tauri/src/commands.rs +++ b/app/tauri/src/commands.rs @@ -31,7 +31,7 @@ static HAS_DECORATIONS: Mutex = Mutex::new(true); static IS_COMPACT: Mutex = Mutex::new(false); #[tauri::command] -fn set_minimize(minimize_to_tray: bool, window: tauri::Window) { +fn minimize_window(minimize_to_tray: bool, window: tauri::Window) { println!("Minimize! {}", minimize_to_tray.to_string().as_str()); if minimize_to_tray { window.hide().unwrap(); @@ -41,7 +41,7 @@ fn set_minimize(minimize_to_tray: bool, window: tauri::Window) { } #[tauri::command] -fn set_close(close_to_tray: bool, window: tauri::Window) { +fn close_window(close_to_tray: bool, window: tauri::Window) { println!("set_close! {}", close_to_tray); if close_to_tray { window.hide().unwrap(); @@ -51,7 +51,7 @@ fn set_close(close_to_tray: bool, window: tauri::Window) { } #[tauri::command] -fn set_show(_window: tauri::Window) { +fn show_window(_window: tauri::Window) { println!("set_show!"); } @@ -177,6 +177,7 @@ fn set_compact_mode(compact_mode: bool, window: tauri::Window) { } } +// TODO - This is doing nothing - Remove or fix #[tauri::command] fn set_ui_theme(is_dark_mode: bool, _window: tauri::Window) { println!("set_ui_theme! {}", is_dark_mode); @@ -232,15 +233,15 @@ pub trait PomatezCommands { impl PomatezCommands for Builder { fn register_pomatez_commands(self) -> tauri::Builder { self.invoke_handler(tauri::generate_handler![ - set_show, + show_window, set_always_on_top, set_fullscreen_break, set_compact_mode, set_ui_theme, set_native_titlebar, system_tray::tray_icon_update, - set_close, - set_minimize, + close_window, + minimize_window, updater::check_for_updates, updater::install_update ]) diff --git a/website/src/hooks/use-target-outside.ts b/website/src/hooks/use-target-outside.ts index ac8af047..fc975403 100644 --- a/website/src/hooks/use-target-outside.ts +++ b/website/src/hooks/use-target-outside.ts @@ -25,7 +25,7 @@ export const useTargetOutside = ({ } function closeOnEscape(e: KeyboardEvent) { - if (e.keyCode === 27) { + if (e.key === "Escape") { setState(false); } }