feat: add d'n'd listener to open files on drop

chore: apply eslint-config
This commit is contained in:
andres
2023-10-14 12:13:24 +02:00
parent 0665b2bf32
commit 5e229bf646
22 changed files with 1317 additions and 134 deletions

View File

@@ -1,9 +1,3 @@
module.exports = {
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'@electron-toolkit/eslint-config-ts/recommended',
'@electron-toolkit/eslint-config-prettier'
]
}
extends: '@it-incubator/eslint-config',
}

15
.idea/git_toolbox_prj.xml generated Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxProjectSettings">
<option name="commitMessageIssueKeyValidationOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
<option name="commitMessageValidationEnabledOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
</component>
</project>

1
.idea/prettier.xml generated
View File

@@ -3,5 +3,6 @@
<component name="PrettierConfiguration">
<option name="myConfigurationMode" value="AUTOMATIC" />
<option name="myRunOnSave" value="true" />
<option name="myFilesPattern" value="{**/*,*}.{js,ts,jsx,tsx,vue,astro,css,cjs,mjs,json,scss}" />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

3
.prettierrc.cjs Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
...require('@it-incubator/prettier-config'),
}

View File

@@ -1,4 +0,0 @@
singleQuote: true
semi: false
printWidth: 100
trailingComma: none

3
.stylelintrc.cjs Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
extends: '@it-incubator/stylelint-config',
}

View File

@@ -1,11 +1,10 @@
import { resolve } from 'path'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import react from '@vitejs/plugin-react'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
export default defineConfig({
main: {
// plugins: [externalizeDepsPlugin({ exclude: ['@it-incubator/md-bundler', 'builtin-modules'] })],
plugins: [externalizeDepsPlugin({ exclude: ['rehype-slug'] })],
build: {
rollupOptions: {
// external: ['builtin-modules'],
@@ -22,18 +21,20 @@ export default defineConfig({
// }
// }
// }
}
}
},
},
// plugins: [externalizeDepsPlugin({ exclude: ['@it-incubator/md-bundler', 'builtin-modules'] })],
plugins: [externalizeDepsPlugin({ exclude: ['rehype-slug'] })],
},
preload: {
plugins: [externalizeDepsPlugin()]
plugins: [externalizeDepsPlugin()],
},
renderer: {
plugins: [react()],
resolve: {
alias: {
'@renderer': resolve('src/renderer/src')
}
'@renderer': resolve('src/renderer/src'),
},
},
plugins: [react()]
}
},
})

View File

@@ -23,6 +23,7 @@
"@electron-toolkit/preload": "^2.0.0",
"@electron-toolkit/utils": "^2.0.0",
"@fontsource/roboto": "^5.0.8",
"@it-incubator/mdx-components": "^0.0.2",
"@it-incubator/ui-kit": "^0.2.7",
"builtin-modules": "^3.3.0",
"chokidar": "^3.5.3",
@@ -38,6 +39,9 @@
"@electron-toolkit/eslint-config-prettier": "^1.0.1",
"@electron-toolkit/eslint-config-ts": "^1.0.0",
"@electron-toolkit/tsconfig": "^1.0.1",
"@it-incubator/eslint-config": "^1.0.1",
"@it-incubator/prettier-config": "^0.1.2",
"@it-incubator/stylelint-config": "^0.1.5",
"@types/node": "^18.17.5",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",

1092
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,8 +9,9 @@ import { BundledMdx } from './types'
export const bundleMdx = async (source: string): Promise<BundledMdx> => {
return await bundleMDX({
source,
globals: {},
globals: {
components: '@it-incubator/mdx-components',
},
mdxOptions(options) {
options.rehypePlugins = [
...(options.rehypePlugins ?? []),
@@ -19,7 +20,13 @@ export const bundleMdx = async (source: string): Promise<BundledMdx> => {
[
rehypePrettyCode,
{
theme,
filterMetaString: (meta: string) => meta.replace(CODE_BLOCK_FILENAME_REGEX, ''),
onVisitHighlightedChars(node: any) {
node.properties.className = ['highlighted']
},
onVisitHighlightedLine(node: any) {
node.properties.className.push('highlighted')
},
onVisitLine(node: any) {
// Prevent lines from collapsing in `display: grid` mode, and
// allow empty lines to be copy/pasted
@@ -27,19 +34,14 @@ export const bundleMdx = async (source: string): Promise<BundledMdx> => {
node.children = [{ type: 'text', value: ' ' }]
}
},
onVisitHighlightedLine(node: any) {
node.properties.className.push('highlighted')
},
onVisitHighlightedChars(node: any) {
node.properties.className = ['highlighted']
},
filterMetaString: (meta: string) => meta.replace(CODE_BLOCK_FILENAME_REGEX, '')
}
theme,
},
],
attachMeta
attachMeta,
]
return options
}
},
source,
})
}

View File

@@ -1,33 +1,37 @@
import { app, shell, BrowserWindow } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
import path from 'node:path'
import fs from 'fs'
import path from 'node:path'
import { join } from 'path'
import { electronApp, is, optimizer } from '@electron-toolkit/utils'
import { BrowserWindow, app, ipcMain, shell } from 'electron'
import icon from '../../resources/icon.png?asset'
import { bundleMdx } from './bundle-mdx'
const chokidar = require('chokidar')
let mainWindow: BrowserWindow | null = null
function createWindow(): void {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 900,
autoHideMenuBar: true,
height: 670,
show: false,
autoHideMenuBar: true,
width: 900,
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false
}
sandbox: false,
},
})
mainWindow.on('ready-to-show', () => {
mainWindow?.show()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
mainWindow.webContents.setWindowOpenHandler(details => {
shell.openExternal(details.url)
return { action: 'deny' }
})
@@ -59,7 +63,9 @@ app.whenReady().then(() => {
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
@@ -71,32 +77,57 @@ app.on('window-all-closed', () => {
app.quit()
}
})
console.log('dirname', __dirname)
// In this file you can include the rest of your app"s specific main process
// code. You can also put them in separate files and require them here.
const watcher = chokidar.watch(path.resolve(__dirname, '../../../hello.md'), {
ignored: /(^|[\/\\])\../, // ignore dotfiles
persistent: true
})
// Something to use when events are received.
const log = console.log.bind(console)
// Add event listeners.
watcher
.on('add', (path) => log(`File ${path} has been added`))
.on('change', (path) => {
let watcher: any = null
function setupWatcher(filePath: string) {
// Close the existing watcher if it exists
if (watcher) {
watcher.close()
}
watcher = chokidar.watch(filePath, {
ignored: /(^|[/\\])\../, // ignore dotfiles
persistent: true,
})
const bundleAndSend = async (path: string) => {
fs.readFile(path, 'utf8', async (err, content) => {
if (err) {
console.error('Error reading the file:', err)
return
}
console.log('content', content)
// Send file content to renderer
if (mainWindow && !mainWindow.isDestroyed()) {
const bundled = await bundleMdx(content)
// const toc = generateToc(content, {})
mainWindow.webContents.send('file-changed', bundled)
mainWindow.webContents.send('file-changed', { ...bundled, fileName: path })
}
})
})
.on('unlink', (path) => log(`File ${path} has been removed`))
await shell.openPath(path)
}
// Add your event listeners
watcher
.on('add', async (path: string) => {
await bundleAndSend(path)
console.warn(`File ${path} has been added`)
})
.on('change', bundleAndSend)
.on('unlink', (path: string) => console.warn(`File ${path} has been removed`))
}
ipcMain.on('dropped-file', (event, arg) => {
console.warn('Dropped File(s):', arg)
event.returnValue = `Received ${arg.length} paths.` // Synchronous reply
// Assuming the user only dropped one file, update the watcher to watch that file.
if (arg.length > 0) {
setupWatcher(arg[0])
}
})
// Initially setup watcher for 'hello.md'
setupWatcher(path.resolve(__dirname, '../../../hello.md'))

View File

@@ -15,8 +15,8 @@ function visit(node, tagNames, handler) {
export const parseMeta =
({ defaultShowCopyCode }) =>
(tree) => {
visit(tree, ['pre'], (preEl) => {
tree => {
visit(tree, ['pre'], preEl => {
const [codeEl] = preEl.children
// Add default language `text` for code-blocks without languages
@@ -31,8 +31,8 @@ export const parseMeta =
})
}
export const attachMeta = () => (tree) => {
visit(tree, ['div', 'pre'], (node) => {
export const attachMeta = () => tree => {
visit(tree, ['div', 'pre'], node => {
if ('data-rehype-pretty-code-fragment' in node.properties) {
// remove <div data-rehype-pretty-code-fragment /> element that wraps <pre /> element
// because we'll wrap with our own <div />

View File

@@ -2,7 +2,7 @@ import { ElectronAPI } from '@electron-toolkit/preload'
declare global {
interface Window {
electron: ElectronAPI
api: unknown
electron: ElectronAPI
}
}

View File

@@ -1,5 +1,5 @@
import { contextBridge } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
import { contextBridge } from 'electron'
// Custom APIs for renderer
const api = {}

View File

@@ -1,50 +1,64 @@
import { ComponentProps, ReactElement, useEffect, useState } from 'react'
import { IpcRendererListener } from '@electron-toolkit/preload'
import * as components from '@it-incubator/mdx-components'
import { ImagePreview, Typography } from '@it-incubator/ui-kit'
import { getMDXComponent } from 'mdx-bundler/client'
import s from './view.module.scss'
import { Pre } from './components/pre'
import { ImagePreview } from '@it-incubator/ui-kit'
function App() {
const [code, setCode] = useState<any>()
const [code, setCode] = useState<string>('')
const [fileName, setFileName] = useState<string>('')
const [srcPreview, setSrcPreview] = useState<string>('')
console.log(code)
useEffect(() => {
const listener = (event, content) => {
console.log('file-changed', event, content)
const listener: IpcRendererListener = (_event, content) => {
setCode(content.code)
setFileName(content.fileName)
}
console.log(window.electron)
window.electron.ipcRenderer.on('file-changed', listener)
return () => {
window.electron.ipcRenderer.removeAllListeners('file-changed')
}
}, [])
if (!code) return null
const Component = getMDXComponent(code)
if (!code) {
return null
}
const Component = getMDXComponent(code, { components: components })
return (
<article className={s.root}>
<ImagePreview open={!!srcPreview} src={srcPreview} onClose={() => setSrcPreview('')} />
<Component
components={{
code: Code,
pre: Pre,
img: (props) => (
<img
{...props}
onClick={() => setSrcPreview(props.src || '')}
style={{ cursor: 'pointer' }}
/>
)
}}
/>
</article>
<div>
<Typography.H1>{fileName}</Typography.H1>
<article className={s.root}>
<ImagePreview onClose={() => setSrcPreview('')} open={!!srcPreview} src={srcPreview} />
<Component
components={{
code: Code,
img: props => (
<img
{...props}
onClick={() => setSrcPreview(props.src || '')}
style={{ cursor: 'pointer' }}
/>
),
pre: Pre,
}}
/>
</article>
</div>
)
}
export default App
const Code = ({ children, ...props }: ComponentProps<'code'>): ReactElement => {
return (
<code className={s.inline} dir="ltr" {...props}>
<code className={s.inline} dir={'ltr'} {...props}>
{children}
</code>
)

View File

@@ -1,14 +1,14 @@
import { useState } from 'react'
function Versions(): JSX.Element {
function Versions() {
const [versions] = useState(window.electron.process.versions)
return (
<ul className="versions">
<li className="electron-version">Electron v{versions.electron}</li>
<li className="chrome-version">Chromium v{versions.chrome}</li>
<li className="node-version">Node v{versions.node}</li>
<li className="v8-version">V8 v{versions.v8}</li>
<ul className={'versions'}>
<li className={'electron-version'}>Electron v{versions.electron}</li>
<li className={'chrome-version'}>Chromium v{versions.chrome}</li>
<li className={'node-version'}>Node v{versions.node}</li>
<li className={'v8-version'}>V8 v{versions.v8}</li>
</ul>
)
}

View File

@@ -2,11 +2,13 @@ import type { ComponentProps, ReactElement } from 'react'
export function CheckIcon(props: ComponentProps<'svg'>): ReactElement {
return (
<svg viewBox="0 0 20 20" width="1em" height="1em" fill="currentColor" {...props}>
<svg fill={'currentColor'} height={'1em'} viewBox={'0 0 20 20'} width={'1em'} {...props}>
<path
fillRule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clipRule="evenodd"
clipRule={'evenodd'}
d={
'M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z'
}
fillRule={'evenodd'}
/>
</svg>
)

View File

@@ -1,11 +1,11 @@
import type { ComponentProps, ReactElement } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { ComponentProps, ReactElement, useCallback, useEffect, useState } from 'react'
import { clsx } from 'clsx'
import s from './copy-to-clipboard.module.scss'
import { CheckIcon } from './check'
import { CopyIcon } from './copy'
import s from './copy-to-clipboard.module.scss'
export const CopyToClipboard = ({
getValue,
@@ -16,7 +16,9 @@ export const CopyToClipboard = ({
const [isCopied, setCopied] = useState(false)
useEffect(() => {
if (!isCopied) return
if (!isCopied) {
return
}
const timerId = setTimeout(() => {
setCopied(false)
}, 2000)
@@ -41,7 +43,7 @@ export const CopyToClipboard = ({
const IconToUse = isCopied ? CheckIcon : CopyIcon
return (
<button onClick={handleClick} className={s.button} title="Copy code" tabIndex={0} {...props}>
<button className={s.button} onClick={handleClick} tabIndex={0} title={'Copy code'} {...props}>
<IconToUse className={clsx('nextra-copy-icon', s.root)} />
</button>
)

View File

@@ -3,29 +3,31 @@ import type { ComponentProps, ReactElement } from 'react'
export function CopyIcon(props: ComponentProps<'svg'>): ReactElement {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
stroke="currentColor"
fill={'none'}
height={'24'}
stroke={'currentColor'}
viewBox={'0 0 24 24'}
width={'24'}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<rect
x="9"
y="9"
width="13"
height="13"
rx="2"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
height={'13'}
rx={'2'}
strokeLinecap={'round'}
strokeLinejoin={'round'}
strokeWidth={'2'}
width={'13'}
x={'9'}
y={'9'}
/>
<path
d="M5 15H4C2.89543 15 2 14.1046 2 13V4C2 2.89543 2.89543 2 4 2H13C14.1046 2 15 2.89543 15 4V5"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
d={
'M5 15H4C2.89543 15 2 14.1046 2 13V4C2 2.89543 2.89543 2 4 2H13C14.1046 2 15 2.89543 15 4V5'
}
strokeLinecap={'round'}
strokeLinejoin={'round'}
strokeWidth={'2'}
/>
</svg>
)

View File

@@ -3,8 +3,9 @@ import { ComponentProps, ReactElement, useRef } from 'react'
import { Scrollbar } from '@it-incubator/ui-kit'
import { clsx } from 'clsx'
import { CopyToClipboard } from './copy-to-clipboard'
import styles from './pre.module.scss'
import { CopyToClipboard } from './copy-to-clipboard'
export const Pre = ({
children,
className,

View File

@@ -1,12 +1,34 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import './assets/index.css'
import '@fontsource/roboto/400.css'
import '@fontsource/roboto/500.css'
import '@fontsource/roboto/700.css'
import '@it-incubator/mdx-components/dist/style.css'
import '@it-incubator/ui-kit/dist/style.css'
import './assets/index.css'
import App from './App'
document.addEventListener('dragover', e => {
e.preventDefault()
e.stopPropagation()
})
document.addEventListener('drop', event => {
event.preventDefault()
event.stopPropagation()
if (!event?.dataTransfer?.files.length) {
return
}
const pathArr: string[] = []
for (const f of event.dataTransfer.files) {
pathArr.push(f.path) // assemble array for main.js
}
window.electron.ipcRenderer.sendSync('dropped-file', pathArr)
})
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<App />