diff --git a/assets/.DS_Store b/assets/.DS_Store index b772a23..c358696 100644 Binary files a/assets/.DS_Store and b/assets/.DS_Store differ diff --git a/assets/icons/draw-clear-hover.png b/assets/icons/draw-clear-hover.png new file mode 100644 index 0000000..ef8ede0 Binary files /dev/null and b/assets/icons/draw-clear-hover.png differ diff --git a/assets/icons/draw-clear.png b/assets/icons/draw-clear.png new file mode 100644 index 0000000..b5d227c Binary files /dev/null and b/assets/icons/draw-clear.png differ diff --git a/assets/icons/draw-decrease-hover.png b/assets/icons/draw-decrease-hover.png new file mode 100644 index 0000000..51b0491 Binary files /dev/null and b/assets/icons/draw-decrease-hover.png differ diff --git a/assets/icons/draw-decrease.png b/assets/icons/draw-decrease.png new file mode 100644 index 0000000..c5c600c Binary files /dev/null and b/assets/icons/draw-decrease.png differ diff --git a/assets/icons/draw-increase-hover.png b/assets/icons/draw-increase-hover.png new file mode 100644 index 0000000..11cfc6c Binary files /dev/null and b/assets/icons/draw-increase-hover.png differ diff --git a/assets/icons/draw-increase.png b/assets/icons/draw-increase.png new file mode 100644 index 0000000..6cfe323 Binary files /dev/null and b/assets/icons/draw-increase.png differ diff --git a/components/BinarizeDialog.tsx b/components/BinarizeDialog.tsx index 786f3d3..e76af24 100644 --- a/components/BinarizeDialog.tsx +++ b/components/BinarizeDialog.tsx @@ -21,6 +21,11 @@ const BinarizeDialog: React.FunctionComponent = (props) => { functions.updateTheme(theme, transparency) } initTheme() + const savedValues = async () => { + const savedBinarize = await ipcRenderer.invoke("get-temp", "binarize") + if (savedBinarize) changeState("binarize", Number(savedBinarize)) + } + savedValues() const updateTheme = (event: any, theme: string, transparency: boolean) => { functions.updateTheme(theme, transparency) } @@ -62,6 +67,7 @@ const BinarizeDialog: React.FunctionComponent = (props) => { return {...prev, binarize: value} }) ipcRenderer.invoke("apply-binarize", {...state, binarize: value, realTime: true}) + ipcRenderer.invoke("save-temp", "binarize", String(value)) break } } diff --git a/components/BlurDialog.tsx b/components/BlurDialog.tsx index 52dfa8e..27c49fd 100644 --- a/components/BlurDialog.tsx +++ b/components/BlurDialog.tsx @@ -20,6 +20,13 @@ const BlurDialog: React.FunctionComponent = (props) => { functions.updateTheme(theme, transparency) } initTheme() + const savedValues = async () => { + const savedBlur= await ipcRenderer.invoke("get-temp", "blur") + const savedSharpen = await ipcRenderer.invoke("get-temp", "sharpen") + if (savedBlur) changeState("blur", Number(savedBlur)) + if (savedSharpen) changeState("sharpen", Number(savedSharpen)) + } + savedValues() const updateTheme = (event: any, theme: string, transparency: boolean) => { functions.updateTheme(theme, transparency) } @@ -61,12 +68,14 @@ const BlurDialog: React.FunctionComponent = (props) => { return {...prev, blur: value} }) ipcRenderer.invoke("apply-blur", {...state, blur: value, realTime: true}) + ipcRenderer.invoke("save-temp", "blur", String(value)) break case "sharpen": setState((prev) => { return {...prev, sharpen: value} }) ipcRenderer.invoke("apply-blur", {...state, sharpen: value, realTime: true}) + ipcRenderer.invoke("save-temp", "sharoen", String(value)) break } } diff --git a/components/BrightnessDialog.tsx b/components/BrightnessDialog.tsx index 84bd0db..e0e6cce 100644 --- a/components/BrightnessDialog.tsx +++ b/components/BrightnessDialog.tsx @@ -20,6 +20,13 @@ const BrightnessDialog: React.FunctionComponent = (props) => { functions.updateTheme(theme, transparency) } initTheme() + const savedValues = async () => { + const savedBrightness = await ipcRenderer.invoke("get-temp", "brightness") + const savedContrast = await ipcRenderer.invoke("get-temp", "contrast") + if (savedBrightness) changeState("brightness", Number(savedBrightness)) + if (savedContrast) changeState("contrast", Number(savedContrast)) + } + savedValues() const updateTheme = (event: any, theme: string, transparency: boolean) => { functions.updateTheme(theme, transparency) } @@ -61,12 +68,14 @@ const BrightnessDialog: React.FunctionComponent = (props) => { return {...prev, brightness: value} }) ipcRenderer.invoke("apply-brightness", {...state, brightness: value, realTime: true}) + ipcRenderer.invoke("save-temp", "brightness", String(value)) break case "contrast": setState((prev) => { return {...prev, contrast: value} }) ipcRenderer.invoke("apply-brightness", {...state, contrast: value, realTime: true}) + ipcRenderer.invoke("save-temp", "contrast", String(value)) break } } diff --git a/components/ContextMenu.tsx b/components/ContextMenu.tsx index ceeef03..86ab7f2 100644 --- a/components/ContextMenu.tsx +++ b/components/ContextMenu.tsx @@ -49,6 +49,10 @@ const ContextMenu: React.FunctionComponent = (props) => { ipcRenderer.invoke("copy-address", image) } + const clearCache = () => { + ipcRenderer.invoke("clear-temp") + } + if (visible) { return ( @@ -56,6 +60,7 @@ const ContextMenu: React.FunctionComponent = (props) => { + diff --git a/components/CropDialog.tsx b/components/CropDialog.tsx index 940e67b..f3e0190 100644 --- a/components/CropDialog.tsx +++ b/components/CropDialog.tsx @@ -21,6 +21,17 @@ const CropDialog: React.FunctionComponent = (props) => { functions.updateTheme(theme, transparency) } initTheme() + const savedValues = async () => { + const savedX = await ipcRenderer.invoke("get-temp", "x") + const savedY = await ipcRenderer.invoke("get-temp", "y") + const savedWidth = await ipcRenderer.invoke("get-temp", "width") + const savedHeight = await ipcRenderer.invoke("get-temp", "height") + if (savedX) changeState("x", Number(savedX)) + if (savedY) changeState("y", Number(savedY)) + if (savedWidth) changeState("width", Number(savedWidth)) + if (savedHeight) changeState("height", Number(savedHeight)) + } + savedValues() const updateTheme = (event: any, theme: string, transparency: boolean) => { functions.updateTheme(theme, transparency) } @@ -62,24 +73,28 @@ const CropDialog: React.FunctionComponent = (props) => { return {...prev, x: value} }) ipcRenderer.invoke("apply-crop", {...state, x: value, realTime: true}) + ipcRenderer.invoke("save-temp", "x", String(value)) break case "y": setState((prev) => { return {...prev, y: value} }) ipcRenderer.invoke("apply-crop", {...state, y: value, realTime: true}) + ipcRenderer.invoke("save-temp", "y", String(value)) break case "width": setState((prev) => { return {...prev, width: value} }) ipcRenderer.invoke("apply-crop", {...state, width: value, realTime: true}) + ipcRenderer.invoke("save-temp", "width", String(value)) break case "height": setState((prev) => { return {...prev, height: value} }) ipcRenderer.invoke("apply-crop", {...state, height: value, realTime: true}) + ipcRenderer.invoke("save-temp", "height", String(value)) break } } diff --git a/components/GIFDialog.tsx b/components/GIFDialog.tsx index 4eaa70a..53a0422 100644 --- a/components/GIFDialog.tsx +++ b/components/GIFDialog.tsx @@ -29,6 +29,17 @@ const GIFDialog: React.FunctionComponent = (props) => { functions.updateTheme(theme, transparency) } initTheme() + const savedValues = async () => { + const savedSpeed = await ipcRenderer.invoke("get-temp", "speed") + const savedReverse = await ipcRenderer.invoke("get-temp", "reverse") + const savedTransparency = await ipcRenderer.invoke("get-temp", "transparency") + const savedTransparentColor = await ipcRenderer.invoke("get-temp", "transparentColor") + if (savedSpeed) changeState("speed", Number(savedSpeed)) + if (savedReverse) changeState("reverse", savedReverse === "true") + if (savedTransparency) changeState("transparency", savedTransparency === "true") + if (savedTransparentColor) changeState("transparentColor", savedTransparentColor) + } + savedValues() const updateTheme = (event: any, theme: string, transparency: boolean) => { functions.updateTheme(theme, transparency) } @@ -74,21 +85,25 @@ const GIFDialog: React.FunctionComponent = (props) => { setState((prev) => { return {...prev, speed: value} }) + ipcRenderer.invoke("save-temp", "speed", String(value)) break case "reverse": setState((prev) => { return {...prev, reverse: value} }) + ipcRenderer.invoke("save-temp", "reverse", String(value)) break case "transparency": setState((prev) => { return {...prev, transparency: value} }) + ipcRenderer.invoke("save-temp", "transparency", String(value)) break case "transparentColor": setState((prev) => { return {...prev, transparentColor: value} }) + ipcRenderer.invoke("save-temp", "transparentColor", String(value)) break } } diff --git a/components/HSLDialog.tsx b/components/HSLDialog.tsx index 9a5a399..658a020 100644 --- a/components/HSLDialog.tsx +++ b/components/HSLDialog.tsx @@ -14,6 +14,7 @@ const HSLDialog: React.FunctionComponent = (props) => { const [state, setState] = useState(initialState) const [hover, setHover] = useState(false) + useEffect(() => { const initTheme = async () => { const theme = await ipcRenderer.invoke("get-theme") @@ -21,6 +22,15 @@ const HSLDialog: React.FunctionComponent = (props) => { functions.updateTheme(theme, transparency) } initTheme() + const savedValues = async () => { + const savedHue = await ipcRenderer.invoke("get-temp", "hue") + const savedSaturation = await ipcRenderer.invoke("get-temp", "saturation") + const savedLightness = await ipcRenderer.invoke("get-temp", "lightness") + if (savedHue) changeState("hue", Number(savedHue)) + if (savedSaturation) changeState("saturation", Number(savedSaturation)) + if (savedLightness) changeState("lightness", Number(savedLightness)) + } + savedValues() const updateTheme = (event: any, theme: string, transparency: boolean) => { functions.updateTheme(theme, transparency) } @@ -62,18 +72,21 @@ const HSLDialog: React.FunctionComponent = (props) => { return {...prev, hue: value} }) ipcRenderer.invoke("apply-hsl", {...state, hue: value, realTime: true}) + ipcRenderer.invoke("save-temp", "hue", String(value)) break case "saturation": setState((prev) => { return {...prev, saturation: value} }) ipcRenderer.invoke("apply-hsl", {...state, saturation: value, realTime: true}) + ipcRenderer.invoke("save-temp", "saturation", String(value)) break case "lightness": setState((prev) => { return {...prev, lightness: value} }) ipcRenderer.invoke("apply-hsl", {...state, lightness: value, realTime: true}) + ipcRenderer.invoke("save-temp", "lightness", String(value)) break } } diff --git a/components/PhotoViewer.tsx b/components/PhotoViewer.tsx index 03fdb3b..90b0ed2 100644 --- a/components/PhotoViewer.tsx +++ b/components/PhotoViewer.tsx @@ -164,7 +164,9 @@ const PhotoViewer: React.FunctionComponent = (props) => { ipcRenderer.on("bulk-process", bulkProcess) ipcRenderer.on("draw", draw) ipcRenderer.on("draw-undo", undoDraw) - ipcRenderer.on("draw-invert", invertDraw) + ipcRenderer.on("draw-clear", clearDraw) + ipcRenderer.on("draw-increase-size", increaseBrushSize) + ipcRenderer.on("draw-decrease-size", decreaseBrushSize) document.addEventListener("dblclick", doubleClick) return () => { ipcRenderer.removeListener("open-file", openFile) @@ -185,12 +187,21 @@ const PhotoViewer: React.FunctionComponent = (props) => { ipcRenderer.removeListener("bulk-process", bulkProcess) ipcRenderer.removeListener("draw", draw) ipcRenderer.removeListener("draw-undo", undoDraw) - ipcRenderer.removeListener("draw-invert", invertDraw) + ipcRenderer.removeListener("draw-clear", clearDraw) + ipcRenderer.removeListener("draw-increase-size", increaseBrushSize) + ipcRenderer.removeListener("draw-decrease-size", decreaseBrushSize) document.removeEventListener("dblclick", doubleClick) ipcRenderer.removeListener("apply-crop", bulkCrop) } }, []) + useEffect(() => { + ipcRenderer.on("draw-invert", invertDraw) + return () => { + ipcRenderer.removeListener("draw-invert", invertDraw) + } + }, [brushColor]) + useEffect(() => { const triggerUndo = () => { if (drawing) return undoDraw() @@ -323,6 +334,7 @@ const PhotoViewer: React.FunctionComponent = (props) => { document.documentElement.style.setProperty("cursor", "grab", "important") } if (event.key === "r") { + if (drawing) return if (rotateEnabled) { const selection = document.querySelector(".ReactCrop__crop-selection") as HTMLDivElement if (selection?.style.opacity === "1") { @@ -389,6 +401,7 @@ const PhotoViewer: React.FunctionComponent = (props) => { setRotateEnabled(false) } const wheel = (event: WheelEvent) => { + /* // @ts-ignore const trackPad = event.wheelDeltaY ? event.wheelDeltaY === -3 * event.deltaY : event.deltaMode === 0 if (event.deltaY < 0) { @@ -403,7 +416,7 @@ const PhotoViewer: React.FunctionComponent = (props) => { } else { increaseBrushSize() } - } + }*/ } ipcRenderer.on("accept-action-response", acceptActionResponse) document.addEventListener("wheel", wheel) @@ -682,6 +695,10 @@ const PhotoViewer: React.FunctionComponent = (props) => { } } + const clearDraw = () => { + drawRef.current.clear() + } + const undoDraw = () => { drawRef.current.undo() } @@ -755,16 +772,16 @@ const PhotoViewer: React.FunctionComponent = (props) => { save()} width={30} height={30} onMouseEnter={() => setSaveHover(true)} onMouseLeave={() => setSaveHover(false)}/> reset()} width={30} height={30} onMouseEnter={() => setResetHover(true)} onMouseLeave={() => setResetHover(false)}/> - setZoomScale(ref.state.scale)} disabled={drawing} wheel={{step: 0.1}} pinch={{disabled: true}} zoomAnimation={{size: 0}} + setZoomScale(ref.state.scale)} + onZoomStop={(ref) => setZoomScale(ref.state.scale)} panning={{allowLeftClickPan: !drawing, allowRightClickPan: false}} wheel={{step: 0.1}} pinch={{disabled: true}} zoomAnimation={{size: 0}} alignmentAnimation={{disabled: true}} doubleClick={{mode: "reset", animationTime: 0}}>
{bulk ? :
{drawing ? : + catenaryColor="rgba(0, 0, 0, 0)" hideGrid={true} canvasWidth={width} canvasHeight={height} imgSrc={image} erase={erasing} + loadTimeOffset={0} eraseColor="#000000" zoom={zoomScale} style={{transform: `rotate(${-rotateDegrees}deg)`}}/> : setCropState(percentCrop as any)} disabled={!cropEnabled} keepSelection={true}/>}
} diff --git a/components/PixelateDialog.tsx b/components/PixelateDialog.tsx index 0124fd1..b8dc90a 100644 --- a/components/PixelateDialog.tsx +++ b/components/PixelateDialog.tsx @@ -19,6 +19,11 @@ const PixelateDialog: React.FunctionComponent = (props) => { functions.updateTheme(theme, transparency) } initTheme() + const savedValues = async () => { + const savedPixelate = await ipcRenderer.invoke("get-temp", "pixelate") + if (savedPixelate) changeState("strength", Number(savedPixelate)) + } + savedValues() const updateTheme = (event: any, theme: string, transparency: boolean) => { functions.updateTheme(theme, transparency) } @@ -60,6 +65,7 @@ const PixelateDialog: React.FunctionComponent = (props) => { return {...prev, strength: value} }) ipcRenderer.invoke("apply-pixelate", {...state, strength: value, realTime: true}) + ipcRenderer.invoke("save-temp", "pixelate", String(value)) break } } diff --git a/components/ResizeDialog.tsx b/components/ResizeDialog.tsx index f85d01a..8f0fe7a 100644 --- a/components/ResizeDialog.tsx +++ b/components/ResizeDialog.tsx @@ -39,6 +39,11 @@ const ResizeDialog: React.FunctionComponent = (props) => { functions.updateTheme(theme, transparency) } initTheme() + const savedValues = async () => { + const savedResize = await ipcRenderer.invoke("get-temp", "resize") + if (savedResize) changeState("resize", JSON.parse(savedResize)) + } + savedValues() const updateTheme = (event: any, theme: string, transparency: boolean) => { functions.updateTheme(theme, transparency) } @@ -80,6 +85,7 @@ const ResizeDialog: React.FunctionComponent = (props) => { return {...prev, width: newState.width, height: newState.height} }) ipcRenderer.invoke("apply-resize", {...state, width: newState.width, height: newState.height, percent: state.percent, realTime: true}) + ipcRenderer.invoke("save-temp", "resize", JSON.stringify(newState)) break } } diff --git a/components/RotateDialog.tsx b/components/RotateDialog.tsx index a1a2572..fe51eba 100644 --- a/components/RotateDialog.tsx +++ b/components/RotateDialog.tsx @@ -31,6 +31,11 @@ const RotateDialog: React.FunctionComponent = (props) => { functions.updateTheme(theme, transparency) } initTheme() + const savedValues = async () => { + const savedRotation = await ipcRenderer.invoke("get-temp", "rotation") + if (savedRotation) changeState("degrees", Number(savedRotation)) + } + savedValues() const updateTheme = (event: any, theme: string, transparency: boolean) => { functions.updateTheme(theme, transparency) } @@ -72,6 +77,7 @@ const RotateDialog: React.FunctionComponent = (props) => { return {...prev, degrees: value} }) ipcRenderer.invoke("apply-rotate", {...state, degrees: value, realTime: true}) + ipcRenderer.invoke("get-temp", "rotation", String(value)) break } } diff --git a/components/TintDialog.tsx b/components/TintDialog.tsx index 86f1c5a..8efba05 100644 --- a/components/TintDialog.tsx +++ b/components/TintDialog.tsx @@ -20,6 +20,11 @@ const TintDialog: React.FunctionComponent = (props) => { functions.updateTheme(theme, transparency) } initTheme() + const savedValues = async () => { + const savedTint = await ipcRenderer.invoke("get-temp", "tint") + if (savedTint) changeState("tint", Number(savedTint)) + } + savedValues() const updateTheme = (event: any, theme: string, transparency: boolean) => { functions.updateTheme(theme, transparency) } @@ -61,6 +66,7 @@ const TintDialog: React.FunctionComponent = (props) => { return {...prev, tint: value} }) ipcRenderer.invoke("apply-tint", {...state, tint: value, realTime: true}) + ipcRenderer.invoke("save-temp", "tint", String(value)) break } } diff --git a/components/TitleBar.tsx b/components/TitleBar.tsx index 0150676..e2d63b6 100644 --- a/components/TitleBar.tsx +++ b/components/TitleBar.tsx @@ -45,6 +45,12 @@ import squareButton from "../assets/icons/square.png" import squareButtonHover from "../assets/icons/square-hover.png" import transparentButton from "../assets/icons/transparent.png" import transparentButtonHover from "../assets/icons/transparent-hover.png" +import increaseSizeButton from "../assets/icons/draw-increase.png" +import increaseSizeButtonHover from "../assets/icons/draw-increase-hover.png" +import decreaseSizeButton from "../assets/icons/draw-decrease.png" +import decreaseSizeButtonHover from "../assets/icons/draw-decrease-hover.png" +import clearButton from "../assets/icons/draw-clear.png" +import clearButtonHover from "../assets/icons/draw-clear-hover.png" import pack from "../package.json" import "../styles/titlebar.less" import functions from "../structures/functions" @@ -71,6 +77,9 @@ const TitleBar: React.FunctionComponent = (props) => { const [hoverUndo, setHoverUndo] = useState(false) const [hoverInvert, setHoverInvert] = useState(false) const [hoverTransparent, setHoverTransparent] = useState(false) + const [hoverIncreaseSize, setHoverIncreaseSize] = useState(false) + const [hoverDecreaseSize, setHoverDecreaseSize] = useState(false) + const [hoverClear, setHoverClear] = useState(false) const [hoverHundred, setHoverHundred] = useState(false) const [hoverBulk, setHoverBulk] = useState(false) const [theme, setTheme] = useState("light") @@ -213,6 +222,19 @@ const TitleBar: React.FunctionComponent = (props) => { ipcRenderer.invoke("draw-invert") } + const clear = () => { + ipcRenderer.invoke("draw-clear") + } + + const increaseSize = () => { + ipcRenderer.invoke("draw-increase-size") + } + + const decreaseSize = () => { + ipcRenderer.invoke("draw-decrease-size") + } + + const resetBounds = () => { ipcRenderer.invoke("reset-bounds") } @@ -235,8 +257,11 @@ const TitleBar: React.FunctionComponent = (props) => {
{acceptAction === "draw" ? <> - setHoverInvert(true)} onMouseLeave={() => setHoverInvert(false)}/> - setHoverUndo(true)} onMouseLeave={() => setHoverUndo(false)}/> + setHoverInvert(true)} onMouseLeave={() => setHoverInvert(false)}/> + {/* setHoverClear(true)} onMouseLeave={() => setHoverClear(false)}/> */} + setHoverUndo(true)} onMouseLeave={() => setHoverUndo(false)}/> + setHoverIncreaseSize(true)} onMouseLeave={() => setHoverIncreaseSize(false)}/> + setHoverDecreaseSize(true)} onMouseLeave={() => setHoverDecreaseSize(false)}/> setBrushColor(event.target.value)} value={brushColor}> triggerAction("cancel")} onMouseEnter={() => setHoverCancel(true)} onMouseLeave={() => setHoverCancel(false)}/> triggerAction("accept")} onMouseEnter={() => setHoverAccept(true)} onMouseLeave={() => setHoverAccept(false)}/> diff --git a/main.ts b/main.ts index a86b9c5..4bfeac8 100644 --- a/main.ts +++ b/main.ts @@ -16,6 +16,7 @@ let window: Electron.BrowserWindow | null let currentDialog: Electron.BrowserWindow | null autoUpdater.autoDownload = false const store = new Store() +let tempStore = {} as any let filePath = "" let originalImages = null as any @@ -49,6 +50,18 @@ const saveImage = async (image: any, savePath: string) => { } } +ipcMain.handle("clear-temp", (event, key: string) => { + tempStore = {} +}) + +ipcMain.handle("get-temp", (event, key: string) => { + return tempStore[key] +}) + +ipcMain.handle("save-temp", (event, key: string, value: string) => { + tempStore[key] = value +}) + const getDimensions = async (image: any) => { const metadata = await sharp(image).metadata() return {width: metadata.width ?? 0, height: metadata.height ?? 0} @@ -129,7 +142,7 @@ ipcMain.handle("show-bulk-save-dialog", async (event) => { if (currentDialog.type === "bulk-save") return } const bounds = window?.getBounds()! - currentDialog = new BrowserWindow({width: 230, height: 170, x: Math.floor(bounds.width/2) + 200, y: Math.floor(bounds.height/2), resizable: false, show: false, frame: false, backgroundColor: "#3177f5", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) + currentDialog = new BrowserWindow({width: 230, height: 170, x: Math.floor(bounds.width/2) + 200, y: Math.floor(bounds.height/2), resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) currentDialog.loadFile(path.join(__dirname, "bulksavedialog.html")) currentDialog.removeMenu() currentDialog.setAlwaysOnTop(true) @@ -165,6 +178,18 @@ ipcMain.handle("draw-invert", () => { window?.webContents.send("draw-invert") }) +ipcMain.handle("draw-clear", () => { + window?.webContents.send("draw-clear") +}) + +ipcMain.handle("draw-increase-size", () => { + window?.webContents.send("draw-increase-size") +}) + +ipcMain.handle("draw-decrease-size", () => { + window?.webContents.send("draw-decrease-size") +}) + ipcMain.handle("get-info", (event: any, image: string) => { window?.webContents.send("close-all-dialogs", "info") window?.webContents.send("show-info-dialog", image) @@ -221,7 +246,7 @@ ipcMain.handle("show-gif-dialog", async (event) => { if (currentDialog.type === "gif") return } const bounds = window?.getBounds()! - currentDialog = new BrowserWindow({width: 190, height: 175, x: bounds.x + bounds.width - 190 - 170, y: bounds.y + 60, resizable: false, show: false, transparent: true, frame: false, backgroundColor: "#3177f5", roundedCorners: false, webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) + currentDialog = new BrowserWindow({width: 190, height: 175, x: bounds.x + bounds.width - 190 - 170, y: bounds.y + 60, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", roundedCorners: false, webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) currentDialog.loadFile(path.join(__dirname, "gifdialog.html")) currentDialog.removeMenu() currentDialog.setAlwaysOnTop(true) @@ -395,7 +420,7 @@ ipcMain.handle("show-crop-dialog", async (event) => { if (currentDialog.type === "crop") return } const bounds = window?.getBounds()! - currentDialog = new BrowserWindow({width: 190, height: 220, x: bounds.x + 70, y: bounds.y + 400, resizable: false, show: false, frame: false, backgroundColor: "#3177f5", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) + currentDialog = new BrowserWindow({width: 190, height: 220, x: bounds.x + 70, y: bounds.y + 400, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) currentDialog.loadFile(path.join(__dirname, "cropdialog.html")) currentDialog.removeMenu() currentDialog.setAlwaysOnTop(true) @@ -466,7 +491,7 @@ ipcMain.handle("show-rotate-dialog", async (event) => { if (currentDialog.type === "rotate") return } const bounds = window?.getBounds()! - currentDialog = new BrowserWindow({width: 230, height: 170, x: bounds.x + bounds.width - 230 - 70, y: bounds.y + 60, resizable: false, show: false, frame: false, backgroundColor: "#3177f5", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) + currentDialog = new BrowserWindow({width: 230, height: 170, x: bounds.x + bounds.width - 230 - 70, y: bounds.y + 60, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) currentDialog.loadFile(path.join(__dirname, "rotatedialog.html")) currentDialog.removeMenu() currentDialog.setAlwaysOnTop(true) @@ -538,7 +563,7 @@ ipcMain.handle("show-resize-dialog", async (event) => { if (currentDialog.type === "resize") return } const bounds = window?.getBounds()! - currentDialog = new BrowserWindow({width: 230, height: 150, x: bounds.x + bounds.width - 230 - 70, y: bounds.y + 40, resizable: false, show: false, frame: false, backgroundColor: "#3177f5", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) + currentDialog = new BrowserWindow({width: 230, height: 150, x: bounds.x + bounds.width - 230 - 70, y: bounds.y + 40, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) currentDialog.loadFile(path.join(__dirname, "resizedialog.html")) currentDialog.removeMenu() currentDialog.setAlwaysOnTop(true) @@ -607,7 +632,7 @@ ipcMain.handle("show-binarize-dialog", async (event) => { if (currentDialog.type === "binarize") return } const bounds = window?.getBounds()! - currentDialog = new BrowserWindow({width: 250, height: 130, x: bounds.x + 70, y: bounds.y + 450, resizable: false, show: false, frame: false, backgroundColor: "#3177f5", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) + currentDialog = new BrowserWindow({width: 250, height: 130, x: bounds.x + 70, y: bounds.y + 450, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) currentDialog.loadFile(path.join(__dirname, "binarizedialog.html")) currentDialog.removeMenu() currentDialog.setAlwaysOnTop(true) @@ -684,7 +709,7 @@ ipcMain.handle("show-pixelate-dialog", async (event) => { if (currentDialog.type === "pixelate") return } const bounds = window?.getBounds()! - currentDialog = new BrowserWindow({width: 250, height: 130, x: bounds.x + 70, y: bounds.y + 330, resizable: false, show: false, frame: false, backgroundColor: "#3177f5", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) + currentDialog = new BrowserWindow({width: 250, height: 130, x: bounds.x + 70, y: bounds.y + 330, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) currentDialog.loadFile(path.join(__dirname, "pixelatedialog.html")) currentDialog.removeMenu() currentDialog.setAlwaysOnTop(true) @@ -755,7 +780,7 @@ ipcMain.handle("show-blur-dialog", async (event) => { if (currentDialog.type === "blur") return } const bounds = window?.getBounds()! - currentDialog = new BrowserWindow({width: 250, height: 155, x: bounds.x + 70, y: bounds.y + 190, resizable: false, show: false, frame: false, backgroundColor: "#3177f5", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) + currentDialog = new BrowserWindow({width: 250, height: 155, x: bounds.x + 70, y: bounds.y + 190, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) currentDialog.loadFile(path.join(__dirname, "blurdialog.html")) currentDialog.removeMenu() currentDialog.setAlwaysOnTop(true) @@ -824,7 +849,7 @@ ipcMain.handle("show-tint-dialog", async (event) => { if (currentDialog.type === "tint") return } const bounds = window?.getBounds()! - currentDialog = new BrowserWindow({width: 180, height: 135, x: bounds.x + 70, y: bounds.y + 130, resizable: false, show: false, frame: false, backgroundColor: "#3177f5", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) + currentDialog = new BrowserWindow({width: 180, height: 135, x: bounds.x + 70, y: bounds.y + 130, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) currentDialog.loadFile(path.join(__dirname, "tintdialog.html")) currentDialog.removeMenu() currentDialog.setAlwaysOnTop(true) @@ -893,7 +918,7 @@ ipcMain.handle("show-hsl-dialog", async (event) => { if (currentDialog.type === "hsl") return } const bounds = window?.getBounds()! - currentDialog = new BrowserWindow({width: 250, height: 180, x: bounds.x + 70, y: bounds.y + 50, resizable: false, show: false, frame: false, backgroundColor: "#3177f5", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) + currentDialog = new BrowserWindow({width: 250, height: 180, x: bounds.x + 70, y: bounds.y + 50, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) currentDialog.loadFile(path.join(__dirname, "hsldialog.html")) currentDialog.removeMenu() currentDialog.setAlwaysOnTop(true) @@ -963,9 +988,8 @@ ipcMain.handle("show-brightness-dialog", async (event) => { // @ts-expect-error if (currentDialog.type === "brightness") return } - const backgroundColor = "#3177f5" const bounds = window?.getBounds()! - currentDialog = new BrowserWindow({width: 250, height: 150, x: bounds.x + 70, y: bounds.y + 40, resizable: false, show: false, frame: false, backgroundColor, webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) + currentDialog = new BrowserWindow({width: 250, height: 150, x: bounds.x + 70, y: bounds.y + 40, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}}) currentDialog.loadFile(path.join(__dirname, "brightnessdialog.html")) currentDialog.removeMenu() currentDialog.setAlwaysOnTop(true) @@ -1304,7 +1328,7 @@ if (!singleLock) { app.on("ready", () => { app.commandLine.appendSwitch('disable-features', 'DarkMode'); - window = new BrowserWindow({width: 900, height: 650, minWidth: 520, minHeight: 250, show: false, transparent: true, frame: false, hasShadow: false, backgroundColor: "#00000000", center: true, roundedCorners: false, webPreferences: {nodeIntegration: true, contextIsolation: false, enableRemoteModule: true, webSecurity: false}}) + window = new BrowserWindow({width: 900, height: 650, minWidth: 520, minHeight: 250, show: false,frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", center: true, roundedCorners: false, webPreferences: {nodeIntegration: true, contextIsolation: false, enableRemoteModule: true, webSecurity: false}}) window.loadFile(path.join(__dirname, "index.html")) window.removeMenu() openFile() diff --git a/package-lock.json b/package-lock.json index 1e65957..acdf2ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "react-draggable": "^4.4.3", "react-dropzone": "^11.3.4", "react-image-crop": "^9.0.4", - "react-zoom-pan-pinch": "^2.1.3", + "react-zoom-pan-pinch": "^3.6.1", "sharp": "^0.33.5", "unzipper": "^0.10.11" }, @@ -7583,16 +7583,16 @@ } }, "node_modules/react-zoom-pan-pinch": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-2.1.3.tgz", - "integrity": "sha512-a5AChOWhjo0RmxsNZXGQIlNh3e3nLU6m4V6M+6dlbPNk5d+MtMxgKWyA5zpR06Lp3OZkZVF9nR8JeWSvKwck9g==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-3.6.1.tgz", + "integrity": "sha512-SdPqdk7QDSV7u/WulkFOi+cnza8rEZ0XX4ZpeH7vx3UZEg7DoyuAy3MCmm+BWv/idPQL2Oe73VoC0EhfCN+sZQ==", "engines": { "node": ">=8", "npm": ">=5" }, "peerDependencies": { - "react": "^17.0.2", - "react-dom": "^17.0.2" + "react": "*", + "react-dom": "*" } }, "node_modules/read-config-file": { @@ -15503,9 +15503,9 @@ } }, "react-zoom-pan-pinch": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-2.1.3.tgz", - "integrity": "sha512-a5AChOWhjo0RmxsNZXGQIlNh3e3nLU6m4V6M+6dlbPNk5d+MtMxgKWyA5zpR06Lp3OZkZVF9nR8JeWSvKwck9g==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-3.6.1.tgz", + "integrity": "sha512-SdPqdk7QDSV7u/WulkFOi+cnza8rEZ0XX4ZpeH7vx3UZEg7DoyuAy3MCmm+BWv/idPQL2Oe73VoC0EhfCN+sZQ==", "requires": {} }, "read-config-file": { diff --git a/package.json b/package.json index 609681e..009fc69 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "photo-viewer", - "version": "0.2.4", + "version": "0.2.5", "description": "An image/GIF viewer that can apply various resizing and color effects.", "main": "dist/main.js", "scripts": { @@ -169,7 +169,7 @@ "react-draggable": "^4.4.3", "react-dropzone": "^11.3.4", "react-image-crop": "^9.0.4", - "react-zoom-pan-pinch": "^2.1.3", + "react-zoom-pan-pinch": "^3.6.1", "sharp": "^0.33.5", "unzipper": "^0.10.11" } diff --git a/structures/CanvasDraw.js b/structures/CanvasDraw.js index d7c6ae3..2af5040 100644 --- a/structures/CanvasDraw.js +++ b/structures/CanvasDraw.js @@ -111,6 +111,8 @@ export default class extends PureComponent { hideInterface: PropTypes.bool, erase: PropTypes.bool, eraseColor: PropTypes.string, + zoom: PropTypes.number, + spin: PropTypes.number }; static defaultProps = { @@ -131,7 +133,9 @@ export default class extends PureComponent { immediateLoading: false, hideInterface: false, erase: false, - eraseColor: "#000000" + eraseColor: "#00000000", + zoom: 1, + spin: 0 }; constructor(props) { @@ -211,6 +215,10 @@ export default class extends PureComponent { if (prevProps.imgSrc !== this.props.imgSrc) { this.drawImage(); } + + if (prevProps.zoom !== this.props.zoom) { + this.loop(); + } } componentWillUnmount = () => { @@ -393,6 +401,10 @@ export default class extends PureComponent { handleDrawStart = e => { e.preventDefault(); + if (e.button === 1 || e.button === 2) { + return; + } + // Start drawing this.isPressing = true; @@ -522,21 +534,26 @@ export default class extends PureComponent { let p1 = points[0]; let p2 = points[1]; - this.ctx.temp.moveTo(p2.x, p2.y); + let newP1 = this.modifyCoordinates(p1); + let newP2 = this.modifyCoordinates(p2); + + this.ctx.temp.moveTo(newP2.x, newP2.y); this.ctx.temp.beginPath(); for (var i = 1, len = points.length; i < len; i++) { // we pick the point between pi+1 & pi+2 as the // end point and p1 as our control point var midPoint = midPointBtw(p1, p2); - this.ctx.temp.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); + newP1 = this.modifyCoordinates(p1); + midPoint = this.modifyCoordinates(midPoint); + this.ctx.temp.quadraticCurveTo(newP1.x, newP1.y, midPoint.x, midPoint.y); p1 = points[i]; p2 = points[i + 1]; } // Draw last line as a straight line while // we wait for the next point to be able to calculate // the bezier control point - this.ctx.temp.lineTo(p1.x, p1.y); + this.ctx.temp.lineTo(newP1.x, newP1.y); this.ctx.temp.stroke(); }; @@ -637,6 +654,13 @@ export default class extends PureComponent { ctx.stroke(); }; + modifyCoordinates = (point) => { + if (!point) return; + let x = point.x / this.props.zoom; + let y = point.y / this.props.zoom; + return {x, y} + } + drawInterface = (ctx, pointer, brush) => { if (this.props.hideInterface) return; @@ -644,17 +668,21 @@ export default class extends PureComponent { // Color brush preview according to erase prop const brushColor = this.props.erase ? this.props.eraseColor : this.props.brushColor; - + let brushX = brush.x / this.props.zoom; + let brushY = brush.y / this.props.zoom; + let pointerX = pointer.x / this.props.zoom; + let pointerY = pointer.y / this.props.zoom; + // Draw brush preview ctx.beginPath(); ctx.fillStyle = brushColor; - ctx.arc(brush.x, brush.y, this.props.brushRadius, 0, Math.PI * 2, true); + ctx.arc(brushX, brushY, this.props.brushRadius, 0, Math.PI * 2, true); ctx.fill(); // Draw mouse point (the one directly at the cursor) ctx.beginPath(); ctx.fillStyle = this.props.catenaryColor; - ctx.arc(pointer.x, pointer.y, 4, 0, Math.PI * 2, true); + ctx.arc(pointerX, pointerY, 4, 0, Math.PI * 2, true); ctx.fill(); // Draw catenary @@ -676,7 +704,7 @@ export default class extends PureComponent { // Draw brush point (the one in the middle of the brush preview) ctx.beginPath(); ctx.fillStyle = this.props.catenaryColor; - ctx.arc(brush.x, brush.y, 2, 0, Math.PI * 2, true); + ctx.arc(brushX, brushY, 2, 0, Math.PI * 2, true); ctx.fill(); };