Skip to content

Commit

Permalink
feat:svg2png
Browse files Browse the repository at this point in the history
  • Loading branch information
eggplantiny committed Nov 6, 2024
1 parent 8f22230 commit 1fd59cb
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 105 deletions.
135 changes: 53 additions & 82 deletions src/main/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { join } from 'node:path'
import { electronApp, is, optimizer } from '@electron-toolkit/utils'
import { app, BrowserWindow, dialog, ipcMain, Menu, shell } from 'electron'
import { app, BrowserWindow, dialog, ipcMain, shell } from 'electron'
import ProgressBar from 'electron-progressbar'
import { autoUpdater } from 'electron-updater'
import postcss from 'postcss'
Expand All @@ -10,6 +10,8 @@ import tailwindcss from 'tailwindcss'
import TurndownService from 'turndown'
import icon from '../../resources/icon.png?asset'

let progressBar

function createWindow(): void {
const mainWindow = new BrowserWindow({
titleBarStyle: 'hiddenInset',
Expand All @@ -20,6 +22,7 @@ function createWindow(): void {
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
devTools: is.dev,
},
})

Expand All @@ -34,34 +37,6 @@ function createWindow(): void {
return { action: 'deny' }
})

const menu = Menu.buildFromTemplate([
{
label: 'File',
submenu: [
{
label: 'About eggp-tools',
click: () => {
const version = app.getVersion()
dialog.showMessageBox({
type: 'info',
title: 'About eggp-tools',
message: `eggp-tools version ${version}\nBuilt with Electron and React`,
buttons: ['OK'],
})
},
},
{
label: 'Check for Update',
click: () => {
autoUpdater.checkForUpdates()
},
},
{ role: 'quit' },
],
},
])
Menu.setApplicationMenu(menu)

if (is.dev && process.env.ELECTRON_RENDERER_URL) {
mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL)
}
Expand Down Expand Up @@ -119,61 +94,57 @@ app
createWindow()
})

if (!is.dev) {
autoUpdater.checkForUpdates()

let progressBar

autoUpdater.on('update-available', () => {
dialog
.showMessageBox({
type: 'info',
title: 'Update available',
message:
'A new version of eggp-tools is available. Do you want to update now?',
buttons: ['Later', 'Update'],
})
.then((result) => {
const buttonIndex = result.response

if (buttonIndex === 1)
autoUpdater.downloadUpdate()
})
})

autoUpdater.once('download-progress', (_progressObj) => {
progressBar = new ProgressBar({
text: 'Downloading...',
detail: 'Downloading...',
autoUpdater.on('update-available', () => {
dialog
.showMessageBox({
type: 'info',
title: 'Update available',
message:
'A new version of eggp-tools is available. Do you want to update now?',
buttons: ['Later', 'Update'],
})
.then((result) => {
const buttonIndex = result.response

if (buttonIndex === 1)
autoUpdater.downloadUpdate()
})
})

autoUpdater.once('download-progress', (_progressObj) => {
progressBar = new ProgressBar({
text: 'Downloading...',
detail: 'Downloading...',
}, app)

progressBar
.on('completed', () => {
console.info(`completed...`)
progressBar.detail = 'Task completed. Exiting...'
})
.on('aborted', () => {
console.info(`aborted...`)
})
})

autoUpdater.on('update-downloaded', () => {
progressBar.setCompleted()
dialog
.showMessageBox({
type: 'info',
title: 'Update ready',
message: 'Install & restart now?',
buttons: ['Restart', 'Later'],
})
.then((result) => {
const buttonIndex = result.response

if (buttonIndex === 0)
autoUpdater.quitAndInstall(false, true)
})
})

progressBar
.on('completed', () => {
console.info(`completed...`)
progressBar.detail = 'Task completed. Exiting...'
})
.on('aborted', () => {
console.info(`aborted...`)
})
})

autoUpdater.on('update-downloaded', () => {
progressBar.setCompleted()
dialog
.showMessageBox({
type: 'info',
title: 'Update ready',
message: 'Install & restart now?',
buttons: ['Restart', 'Later'],
})
.then((result) => {
const buttonIndex = result.response

if (buttonIndex === 0)
autoUpdater.quitAndInstall(false, true)
})
})
}
autoUpdater.checkForUpdates()
})

app.on('window-all-closed', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: file:;"
content="default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: file: blob:;"
/>
</head>

Expand Down
9 changes: 9 additions & 0 deletions src/renderer/src/assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,12 @@
}



.electron-draggable {
-webkit-user-select: none;
-webkit-app-region: drag;
}

.electron-draggable-release {
-webkit-app-region: no-drag;
}
2 changes: 1 addition & 1 deletion src/renderer/src/components/layout/app-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function AppHeader() {

return (
<>
<header className={cn('relative flex justify-center items-center p-2 gap-2')}>
<header className={cn('electron-draggable relative flex justify-center items-center p-2 gap-2')}>
{
title && (
<h1 className={cn('!text-sm text-left font-bold')}>{title}</h1>)
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/src/components/nav-main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ export interface MenuItemType {

export function NavMain({ items }: { items: MenuItemType[] }) {
return (
<SidebarGroup className="mt-6">
<SidebarGroup className="mt-6 electron-draggable">
<SidebarGroupLabel>
Tools
</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenu className="electron-draggable-release">
{items.map(item => (
<SidebarMenuItem
key={item.url}
Expand All @@ -35,7 +35,7 @@ export function NavMain({ items }: { items: MenuItemType[] }) {
>
<Link to={item.url}>
{item.icon && <item.icon />}
<span>{item.title}</span>
<span className="whitespace-nowrap">{item.title}</span>
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
</Link>
</SidebarMenuButton>
Expand Down
3 changes: 2 additions & 1 deletion src/renderer/src/pages/color-tool-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,11 @@ export function ColorToolPage() {
<PenTool />
</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverContent className={cn('w-64')}>
<div className={cn('flex flex-col gap-4')}>
{ colorCommands.map(({ label, leftAction, rightAction }) => (
<CommandController
key={label}
label={label}
leftAction={() => syncColors(leftAction(chroma.color(pickerColor)))}
rightAction={() => syncColors(rightAction(chroma.color(pickerColor)))}
Expand Down
95 changes: 78 additions & 17 deletions src/renderer/src/pages/svg-to-png-page.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { Input } from '@/components/ui/input'
import { PageRoot } from '@/components/ui/layout'
import { Label } from '@/components/ui/label'
import { PageRootWithSplit } from '@/components/ui/layout'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import usePageMeta from '@/hooks/use-page-meta'
import { useSessionStorage } from '@/hooks/use-storage'
import { svgToPng } from '@/lib/image'
import { useState } from 'react'
import { cn } from '@/lib/utils'
import { useEffect, useState } from 'react'

export function SvgToPngPage() {
usePageMeta({
title: 'SVG to PNG',
})

const [_svg, setSvg] = useSessionStorage<string>('svg-to-png:svg', '')
const [svg, setSvg] = useState<string>('')
const [image, setImage] = useState<string>()

const [width, setWidth] = useState(256)
const [height, setHeight] = useState(256)

function onInput(event: React.ChangeEvent<HTMLInputElement>) {
const file = event.target.files?.[0]
if (!file)
Expand All @@ -21,24 +26,80 @@ export function SvgToPngPage() {
reader.onload = async (event) => {
const svg = event.target?.result as string
setSvg(svg)

const _image = await svgToPng(svg, 256, 256)
setImage(_image)
}
reader.readAsText(file)
}

useEffect(() => {
if (!svg)
return
svgToPng(svg, width, height).then(setImage)
}, [svg, width, height])

return (
<PageRoot>
<Input
type="file"
accept="image/svg+xml"
onInput={onInput}
/>

{image && (
<img src={image} alt="SVG to PNG" className="w-64 h-64" />
<PageRootWithSplit
left={(
<div className={cn('grid grid-cols-12 gap-4')}>
<Label className={cn('col-span-12')}>
SVG File
<Input
type="file"
accept="image/svg+xml"
onInput={onInput}
className={cn('mt-1')}
/>
</Label>

<Label className={cn('col-span-6')}>
Width
<Input
type="number"
value={width}
onInput={event => setWidth(Number(event.currentTarget.value))}
className={cn('mt-1')}
/>
</Label>

<Label className={cn('col-span-6')}>
Height
<Input
type="number"
value={height}
onInput={event => setHeight(Number(event.currentTarget.value))}
className={cn('mt-1')}
/>
</Label>
</div>
)}
</PageRoot>
right={(
<>
{image && (
<div className={cn('grid grid-cols-12 gap-4')}>
<div className={cn('col-span-12')}>
<Tooltip>
<TooltipTrigger>
<a href={image} download="image.png">
<img
src={image}
alt="SVG to PNG"
style={{
width: `${width}px`,
height: `${height}px`,
}}
/>
</a>
</TooltipTrigger>
<TooltipContent>
Download
</TooltipContent>
</Tooltip>
</div>
</div>
) }
</>
)}
>

</PageRootWithSplit>
)
}

0 comments on commit 1fd59cb

Please sign in to comment.