diff --git a/electron.vite.config.mjs b/electron.vite.config.mjs index f5f2935..cac99ab 100644 --- a/electron.vite.config.mjs +++ b/electron.vite.config.mjs @@ -11,6 +11,9 @@ export default defineConfig({ plugins: [externalizeDepsPlugin()] }, renderer: { + server: { + hmr: false, + }, resolve: { alias: { '@renderer': resolve('src/renderer/src') diff --git a/src/renderer/src/App.jsx b/src/renderer/src/App.jsx index 689c15a..065bf12 100644 --- a/src/renderer/src/App.jsx +++ b/src/renderer/src/App.jsx @@ -29,10 +29,12 @@ import { } from './Imports' import { createSignal } from 'solid-js' import "./styles/index.css" +import "./styles/animations.css" import MainToolbar from './components/MainToolbar' import MainToolbarSideContainer from './components/MainToolbarSideContainer' import BottomSidebar from './components/BottomSidebar' import ScreenSidebar from './components/ScreenSidebar' +import PenOverlays from './overlays/PenOverlays' function App() { @@ -44,7 +46,7 @@ function App() { const [toolbarItems, setToolbarItems] = createSignal([ {type: 'button', icon: iconCursor, solidIcon: iconSolidCursor, name: "鼠標模式", action: "mouse"}, - {type: 'button', icon: iconPen, solidIcon: iconSolidPen, name: "批註模式", action: "pen"}, + {type: 'button', icon: iconPen, solidIcon: iconSolidPen, name: "批註模式", action: "pen", overlay: PenOverlays}, {type: 'button', icon: iconEraser, name: "橡皮擦模式", solidIcon:iconSolidEraser, action: "eraser"}, {type: 'button', icon: iconTrash, name: "清空畫布", action: "clear"}, {type: "separator"}, diff --git a/src/renderer/src/components/ToolBarButton.jsx b/src/renderer/src/components/ToolBarButton.jsx index a9ad23d..1ce5afe 100644 --- a/src/renderer/src/components/ToolBarButton.jsx +++ b/src/renderer/src/components/ToolBarButton.jsx @@ -1,64 +1,130 @@ // noinspection ES6UnusedImports import gest from "../utils/gest" +import { createEffect, createSignal, on, Show, splitProps } from 'solid-js' +import { Dynamic } from 'solid-js/web' /** * 定義單個工具欄按鈕的Solid組件 * @param props{{ * item: { * name:string, icon, solidIcon, - * type: "button" | "separator" | "pagination-indicator", + * type: "button" | "separator" | "pagination-indicator"| "pagination-indicator-sidebar", * action: string, * }, toolbarSignal, setToolbarSignal, * }} 傳入工具欄按鈕的相關選項。 * */ export default function ToolBarButton(props) { - const v = props.item; - const toolbarSignal = props.toolbarSignal; - const setToolbarSignal = props.setToolbarSignal; + let toolButtonRef; + const [boundingRect, updateBoundingRect] = createSignal({x:0,y:0}); + const [isOverlayDisplay, setOverlayDisplay] = createSignal(false); + const [isAnimating, setAnimating] = createSignal(false); + const [className, setClassName] = createSignal({}) + const [timerID, setTimerID] = createSignal(0); + const [{item:v,toolbarSignal,setToolbarSignal},_] = splitProps(props,["item","toolbarSignal","setToolbarSignal"]) + + const overlayVisibleFalseTimerFn = ()=>{ + setOverlayDisplay(false) + setAnimating(false); + }; + + /** + * 用於animate Overlay的顯示與隱藏效果 + * @param isVisible{boolean} 手動指定是要顯示還是隱藏Overlay, + * 未指定該param時,根據當前Signal切換狀態 + * */ + const changeOverlayVisibility = function(isVisible) { + // 防抖處理 + if (isAnimating()===true) return 0; + // 移除現有的Timer + clearTimeout(timerID()); + setTimerID(0); + // 清空className,移除所有animation + setClassName("") + // isVisible===true + if (isVisible!==undefined&&isVisible!==null&&isVisible===true) { + if (isOverlayDisplay()&&isOverlayDisplay()===true) return 0; + setAnimating(true); + setClassName("animate-fade-in"); + setOverlayDisplay(true); + } else if (isVisible!==undefined&&isVisible!==null&&isVisible===false) { + if (isOverlayDisplay()&&isOverlayDisplay()===false) return 0; + setClassName("animate-fade-out"); + setTimerID(setTimeout(overlayVisibleFalseTimerFn,90)) + } else { + // no isVisible param + if (isOverlayDisplay()===true) { + // to false + setAnimating(true); + setClassName("animate-fade-out"); + setTimerID(setTimeout(overlayVisibleFalseTimerFn,90)) + } else if (isOverlayDisplay()===false) { + setClassName("animate-fade-in"); + setOverlayDisplay(true); + } + } + } + + createEffect(on(toolbarSignal,(tbsignal)=>{ + if (tbsignal.activeTool!==v.action) changeOverlayVisibility(false) + })) + // noinspection HtmlUnknownAttribute return ( -
{ - if (v.action === "drawer") { - setToolbarSignal({ - ...toolbarSignal(), - isDrawerOpen: !toolbarSignal().isDrawerOpen - }); - } else if (!(["clear"].includes(v.action))&&v.action){ - setToolbarSignal({ - ...toolbarSignal(), - activeTool: v.action, - }) - } - }} - classList={{ - "toolbar-item": v.type !== "pagination-indicator"&&v.type!=="pagination-indicator-sidebar", - "toolbar-item-separator": v.type === "separator", - "toolbar-item-pagination-indicator": v.type === "pagination-indicator"||v.type==="pagination-indicator-sidebar", - "toolbar-item-active": toolbarSignal().activeTool===v.action - }} use:gest={null}> - {v.type === "separator" ?
: v.type === 'button' ? ( - <> - {v.action === "drawer" && toolbarSignal().isDrawerOpen === true ? ( -
- {v.name}/ -
- ) : <> - {v.name}/ - {v.name}/ - } -
- - ) : v.type === "pagination-indicator" ? <> -
-
123
-
/500
-
- : v.type === "pagination-indicator-sidebar" ? <> -
-
123
-
500
-
- : void 0} -
+ <> +
{ + if (v.action === "drawer") { + setToolbarSignal({ + ...toolbarSignal(), + isDrawerOpen: !toolbarSignal().isDrawerOpen + }); + } else if (v.action&&v.action===toolbarSignal().activeTool&&v.overlay) { + // 傳入BoundingRect讓Overlay更新位置 + updateBoundingRect(JSON.parse(JSON.stringify(toolButtonRef.getBoundingClientRect()))) + // 切換Overlay的顯示 + changeOverlayVisibility(); + } else if (!(["clear"].includes(v.action))&&v.action){ + setToolbarSignal({ + ...toolbarSignal(), + activeTool: v.action, + }) + } + }} ref={toolButtonRef} + classList={{ + "toolbar-item": v.type !== "pagination-indicator"&&v.type!=="pagination-indicator-sidebar", + "toolbar-item-separator": v.type === "separator", + "toolbar-item-pagination-indicator": v.type === "pagination-indicator"||v.type==="pagination-indicator-sidebar", + "toolbar-item-active": toolbarSignal().activeTool===v.action + }} use:gest={null}> + {v.type === "separator" ?
: v.type === 'button' ? ( + <> + {v.action === "drawer" && toolbarSignal().isDrawerOpen === true ? ( +
+ {v.name}/ +
+ ) : <> + {v.name}/ + {v.name}/ + } +
+ + ) : v.type === "pagination-indicator" ? <> +
+
123
+
/500
+
+ : v.type === "pagination-indicator-sidebar" ? <> +
+
123
+
500
+
+ : void 0} +
+ + + + ) } \ No newline at end of file diff --git a/src/renderer/src/i18n/manifest.js b/src/renderer/src/i18n/manifest.js new file mode 100644 index 0000000..e69de29 diff --git a/src/renderer/src/overlays/PenOverlays.jsx b/src/renderer/src/overlays/PenOverlays.jsx new file mode 100644 index 0000000..5bd63a3 --- /dev/null +++ b/src/renderer/src/overlays/PenOverlays.jsx @@ -0,0 +1,18 @@ +import { Portal } from 'solid-js/web' +import { splitProps } from 'solid-js' + +export default function PenOverlays(props) { + const [{toolbarSignal,setToolbarSignal,boundingRect,animateClass},_] = splitProps(props,["toolbarSignal","setToolbarSignal","boundingRect","animateClass"]); + return ( + <> + +
+
+
+
+ + ) +} \ No newline at end of file diff --git a/src/renderer/src/styles/animations.css b/src/renderer/src/styles/animations.css new file mode 100644 index 0000000..130a45c --- /dev/null +++ b/src/renderer/src/styles/animations.css @@ -0,0 +1,17 @@ +@keyframes fade-in { + from {opacity: 0} + to {opacity: 1} +} +@keyframes fade-out { + from {opacity: 1} + to {opacity: 0} +} +.animate-fade-in { + animation-name: fade-in !important; + animation-duration: 100ms !important; +} +.animate-fade-out { + animation-name: fade-out !important; + animation-duration: 100ms !important; + animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1); +} \ No newline at end of file diff --git a/src/renderer/src/styles/index.css b/src/renderer/src/styles/index.css index 4b8919e..f279050 100644 --- a/src/renderer/src/styles/index.css +++ b/src/renderer/src/styles/index.css @@ -13,10 +13,10 @@ user-select: none; } .zph-container.window-border { - @apply ring-1 ring-inset ring-red-600 ; + @apply "ring-1 ring-inset ring-red-600"; } .zph-container.window-black { - background-color: black !important; + background-color: white !important; } /*Main Toolbar*/ @@ -31,7 +31,7 @@ align-items: center; justify-content: center; z-index: 50; - @apply bottom-1; + @apply "bottom-1"; } .main-toolbar-container>div { flex-shrink: 0 !important; @@ -39,17 +39,17 @@ pointer-events: auto; height: 100vh; position: relative; - @apply rounded-md shadow-md ring-1 ring-inset ring-zinc-700; transition-duration: 200ms; transition-property: max-height; transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1); + @apply "rounded-md shadow-md ring-1 ring-inset ring-zinc-700"; } .main-toolbar-container[data-mode="default"]>div { - @apply max-h-9; + @apply "max-h-9"; } .main-toolbar-container[data-mode="drawer"]>div { - @apply max-h-48; + @apply "max-h-48"; } .absolute-toolbar-space { position: absolute; @@ -60,7 +60,7 @@ flex-direction: column; justify-content: end; align-items: center; - @apply h-48; + @apply "h-48"; } .absolute-toolbar-space .toolbar-buttons { display: flex; @@ -71,13 +71,13 @@ transition-duration: 200ms; transition-property: padding-bottom; transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1); - @apply h-9 w-fit px-1; + @apply "h-9 w-fit px-1"; } .main-toolbar-container[data-mode="default"] .absolute-toolbar-space .toolbar-buttons { - @apply pb-0; + @apply "pb-0"; } .main-toolbar-container[data-mode="drawer"] .absolute-toolbar-space .toolbar-buttons { - @apply pb-1.5; + @apply "pb-1.5"; } .toolbar-item { display: flex; @@ -85,20 +85,20 @@ justify-content: center; background-color: transparent; position: relative; - @apply h-9 w-8 ; + @apply "h-9 w-8"; } .toolbar-item>img , .toolbar-item img { -webkit-user-drag: none !important; - @apply h-5 w-5; + @apply "h-5 w-5"; } .toolbar-item-separator { display: flex; align-items: center; justify-content: center; - @apply h-9 w-1.5; + @apply "h-9 w-1.5"; } .toolbar-item-separator>div { - @apply h-5 w-px bg-white/50 + @apply "h-5 w-px bg-white/50"; } /*Drawer*/ @@ -110,7 +110,7 @@ justify-content: center; padding: 0.75rem; width: 100%; - @apply mb-1 grow; + @apply "mb-1 grow"; } .drawer-bottom-separator { position: absolute; @@ -129,38 +129,38 @@ transition-duration: 200ms; transition-property: width; transition-timing-function: cubic-bezier(0.33, 1, 0.68, 1); - @apply bg-zinc-500 rounded-full ; + @apply "bg-zinc-500 rounded-full"; } .drawer-open-button-state { - @apply h-8 w-8 bg-blue-500/50 rounded-md flex items-center justify-center; + @apply "h-8 w-8 bg-blue-500/50 rounded-md flex items-center justify-center"; } .main-toolbar-container[data-mode="default"] .drawer-bottom-separator>div { - @apply w-0; + @apply "w-0"; } .main-toolbar-container[data-mode="drawer"] .drawer-bottom-separator>div { - @apply w-100%; + @apply "w-100%"; } .drawer-items { display: grid; grid-template-columns:repeat(5, minmax(0, 1fr)); width: 100%; height: 100%; - @apply p-2 gap-2; + @apply "p-2 gap-2"; } .drawer-item { display: flex; flex-direction: column; align-items: center; justify-content: center; - @apply h-full w-full; + @apply "h-full w-full"; } .drawer-item img { -webkit-user-drag: none !important; } .drawer-item .drawer-item-text { font-size: 0.72rem; - @apply text-white mt-1; + @apply "text-white mt-1"; } /*Toolbar Left Right Containers*/ @@ -175,10 +175,10 @@ width: 100vw; z-index: 50; pointer-events: none; - @apply bottom-1; + @apply "bottom-1"; } .toolbar-right-container:nth-child(0) , .toolbar-left-container:last-child { - @apply shrink-0 h-9 bg-transparent; + @apply "shrink-0 h-9 bg-transparent"; } .end-slideshow-button *,.mainmenu-button * { -webkit-user-drag: none !important; @@ -190,7 +190,7 @@ align-items: center; justify-content: center; flex-shrink: 0; - @apply h-9 w-9 rounded-md bg-red-700/75 ring-1 ring-inset ring-red-500 shadow-md; + @apply "h-9 w-9 rounded-md bg-red-700/75 ring-1 ring-inset ring-red-500 shadow-md"; } .mainmenu-button { pointer-events: auto; @@ -199,7 +199,7 @@ align-items: center; justify-content: center; flex-shrink: 0; - @apply h-9 w-9 rounded-md shadow-md ring-1 ring-inset ring-zinc-700; + @apply "h-9 w-9 rounded-md shadow-md ring-1 ring-inset ring-zinc-700"; } /*BottomSidebar*/ @@ -212,7 +212,7 @@ display: flex; align-items: center; z-index: 50; - @apply bottom-1 + @apply "bottom-1"; } .bottom-sidebar-container._left { justify-content: start; @@ -227,10 +227,10 @@ overflow: hidden; pointer-events: auto; position: relative; - @apply h-9 rounded-md shadow-md ring-1 ring-inset ring-zinc-700; transition-duration: 200ms; transition-property: max-height; transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1); + @apply "h-9 rounded-md shadow-md ring-1 ring-inset ring-zinc-700"; } .absolute-bottom-sidebar-space { position: absolute; @@ -241,7 +241,7 @@ flex-direction: column; justify-content: end; align-items: center; - @apply h-9; + @apply "h-9"; } .absolute-bottom-sidebar-space .toolbar-buttons { display: flex; @@ -252,7 +252,7 @@ transition-duration: 200ms; transition-property: padding-bottom; transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1); - @apply h-9 w-fit px-1; + @apply "h-9 w-fit px-1"; } /*Pagination Indicator*/ @@ -261,13 +261,13 @@ align-items: center; justify-content: center; width: 4.5rem; - @apply h-9; + @apply "h-9"; } .pagination-indicator .now-page-text { - @apply text-white text-3.5 font-bold leading-none mr-0.5; + @apply "text-white text-3.5 font-bold leading-none mr-0.5"; } .pagination-indicator .total-page-text { - @apply text-white text-3 leading-none; + @apply "text-white text-3 leading-none"; } /*Toolbar Item Animation*/ @@ -280,19 +280,19 @@ transition-property: all; z-index: 0; transition-timing-function: cubic-bezier(0.85, 0, 0.15, 1); !important; - @apply duration-100 scale-75 opacity-0; + @apply "duration-100 scale-75 opacity-0"; } .toolbar-item>img.icon-solid , .toolbar-item img.icon-solid { display: none; } .toolbar-item .tb-animate-bg>div { - @apply h-7.5 w-7.5 bg-zinc-300/12 rounded-0.275rem; + @apply "h-7.5 w-7.5 bg-zinc-300/12 rounded-0.275rem"; } .toolbar-item.toolbar-item-active .tb-animate-bg>div { - @apply bg-blue-300/15 ring-1 ring-inset ring-blue-400/50 + @apply "bg-blue-300/15 ring-1 ring-inset ring-blue-400/50"; } .toolbar-item.gest-pointer-down .tb-animate-bg , .toolbar-item.toolbar-item-active .tb-animate-bg{ - @apply scale-100 opacity-100; + @apply "scale-100 opacity-100"; } .toolbar-item.gest-pointer-down>img.icon-solid , .toolbar-item.gest-pointer-down img.icon-solid , .toolbar-item.toolbar-item-active>img.icon-solid , .toolbar-item.toolbar-item-active img.icon-solid{ @@ -314,7 +314,7 @@ display: flex; align-items: center; z-index: 50; - @apply bottom-35vh + @apply "bottom-35vh"; } .screen-sidebar-container._left { justify-content: start; @@ -329,7 +329,7 @@ overflow: hidden; pointer-events: auto; position: relative; - @apply w-9 rounded-md shadow-md ring-1 ring-inset ring-zinc-700; + @apply "w-9 rounded-md shadow-md ring-1 ring-inset ring-zinc-700"; } .absolute-screen-sidebar-space { position: absolute; @@ -340,7 +340,7 @@ flex-direction: column; justify-content: center; align-items: center; - @apply w-9; + @apply "w-9"; } .absolute-screen-sidebar-space .toolbar-buttons { display: flex; @@ -351,7 +351,7 @@ transition-duration: 200ms; transition-property: padding-bottom; transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1); - @apply w-9 h-fit py-1; + @apply "w-9 h-fit py-1"; } .absolute-screen-sidebar-space .toolbar-item { @@ -360,16 +360,16 @@ justify-content: center; background-color: transparent; position: relative; - @apply h-8 w-9 ; + @apply "h-8 w-9"; } .absolute-screen-sidebar-space .toolbar-item-separator { display: flex; align-items: center; justify-content: center; - @apply h-1.5 w-9; + @apply "h-1.5 w-9"; } .absolute-screen-sidebar-space .toolbar-item-separator>div { - @apply h-px w-5 bg-white/50 + @apply "h-px w-5 bg-white/50"; } .absolute-screen-sidebar-space .pagination-indicator { @@ -377,20 +377,34 @@ flex-direction: column; align-items: center; justify-content: center; - @apply w-9 h-10; + @apply "w-9 h-10"; } .absolute-screen-sidebar-space .pagination-indicator .now-page-text { - @apply text-white text-3.35 font-normal leading-none; + @apply "text-white text-3.35 font-normal leading-none"; } .absolute-screen-sidebar-space .pagination-indicator .total-page-text { - @apply text-white/75 mt-1 text-2.25 leading-none; + @apply "text-white/75 mt-1 text-2.25 leading-none"; } - /*Watermark*/ .brand-watermark { position: absolute; top: 0.25rem; left: 0.25rem; - @apply h-6; + @apply "h-6"; +} + +/*Pen Overlays*/ +.pen-overlays { + z-index: 60; + position: fixed; + display: flex; + align-items: center; + flex-direction: column; + overflow: clip; + @apply "w-56 h-48 mb-2.5 bg-zinc-900/80 rounded-md ring-1 ring-zinc-700"; +} +.pen-overlays * { + user-select: none !important; + -webkit-user-drag: none !important; } \ No newline at end of file