Add font settings
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "opvault-web",
|
"name": "opvault-web",
|
||||||
"version": "1.0.0",
|
"version": "1.0.220221",
|
||||||
"main": "dist/main/index.js",
|
"main": "dist/main/index.js",
|
||||||
"author": "proteria",
|
"author": "proteria",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
@ -14,9 +14,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/css": "^11.7.1",
|
"@emotion/css": "^11.7.1",
|
||||||
"@emotion/react": "^11.7.1",
|
"@emotion/react": "^11.8.1",
|
||||||
"@emotion/styled": "^11.6.0",
|
"@emotion/styled": "^11.8.1",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
@ -24,22 +25,23 @@
|
|||||||
"react-idle-timer": "4.6.4"
|
"react-idle-timer": "4.6.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.16.7",
|
"@babel/core": "^7.17.5",
|
||||||
"@emotion/babel-plugin": "^11.7.2",
|
"@emotion/babel-plugin": "^11.7.2",
|
||||||
"@rollup/plugin-yaml": "^3.1.0",
|
"@rollup/plugin-yaml": "^3.1.0",
|
||||||
"@types/react": "^17.0.37",
|
|
||||||
"@types/react-dom": "^17.0.11",
|
|
||||||
"@vitejs/plugin-react": "^1.1.3",
|
|
||||||
"@types/babel__core": "^7.1.18",
|
"@types/babel__core": "^7.1.18",
|
||||||
"concurrently": "^6.5.1",
|
"@types/lodash-es": "^4.17.6",
|
||||||
"electron": "^16.0.5",
|
"@types/react": "^17.0.39",
|
||||||
"electron-builder": "^22.14.5",
|
"@types/react-dom": "^17.0.11",
|
||||||
"esbuild": "^0.14.5",
|
"@vitejs/plugin-react": "^1.2.0",
|
||||||
|
"concurrently": "^7.0.0",
|
||||||
|
"electron": "^17.0.1",
|
||||||
|
"electron-builder": "^22.14.13",
|
||||||
|
"esbuild": "^0.14.23",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"opvault.js": "*",
|
"opvault.js": "*",
|
||||||
"sass": "^1.45.0",
|
"sass": "^1.49.8",
|
||||||
"typescript": "^4.5.4",
|
"typescript": "^4.5.5",
|
||||||
"vite": "^2.7.3"
|
"vite": "^2.8.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
./scripts/update-version.js
|
||||||
./scripts/build-i18n-yml-typedef.js
|
./scripts/build-i18n-yml-typedef.js
|
||||||
./scripts/build-third-party-license.js
|
./scripts/build-third-party-license-info.js
|
||||||
./scripts/build-package-json.js
|
./scripts/build-package-json.js
|
||||||
npx vite build
|
npx vite build
|
||||||
NODE_ENV=production ./esbuild.js
|
NODE_ENV=production ./esbuild.js
|
||||||
|
19
packages/web/scripts/update-version.js
Executable file
19
packages/web/scripts/update-version.js
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
const fs = require("fs")
|
||||||
|
const { resolve } = require("path")
|
||||||
|
|
||||||
|
const json = require("../package.json")
|
||||||
|
const date = new Date()
|
||||||
|
json.version = json.version
|
||||||
|
.split(".")
|
||||||
|
.slice(0, 2)
|
||||||
|
.concat(
|
||||||
|
[
|
||||||
|
date.getUTCFullYear() - 2000,
|
||||||
|
(date.getUTCMonth() + 1).toString().padStart(2, "0"),
|
||||||
|
date.getUTCDate().toString().padStart(2, "0"),
|
||||||
|
].join("")
|
||||||
|
)
|
||||||
|
.join(".")
|
||||||
|
|
||||||
|
fs.writeFileSync(resolve(__dirname, "../package.json"), JSON.stringify(json, null, 2))
|
@ -1,5 +1,11 @@
|
|||||||
import { useEffect, memo } from "react"
|
import { useEffect, memo } from "react"
|
||||||
|
import { debounce } from "lodash-es"
|
||||||
import { useLocaleContext, useTranslate } from "./i18n"
|
import { useLocaleContext, useTranslate } from "./i18n"
|
||||||
|
import { Key, useStorage } from "./utils/localStorage"
|
||||||
|
|
||||||
|
const updateCSS = debounce((name: string, value: string) => {
|
||||||
|
document.body.style.setProperty(name, value || null)
|
||||||
|
}, 500)
|
||||||
|
|
||||||
export const SideEffect = memo(() => {
|
export const SideEffect = memo(() => {
|
||||||
const { locale } = useLocaleContext()
|
const { locale } = useLocaleContext()
|
||||||
@ -10,5 +16,16 @@ export const SideEffect = memo(() => {
|
|||||||
document.title = t.label.app_name
|
document.title = t.label.app_name
|
||||||
}, [locale])
|
}, [locale])
|
||||||
|
|
||||||
|
const [uiFont] = useStorage(Key.UI_FONT)
|
||||||
|
const [monoFont] = useStorage(Key.MONOSPACE_FONT)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateCSS("--sans-serif", uiFont)
|
||||||
|
}, [uiFont])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateCSS("--monospace", monoFont)
|
||||||
|
}, [monoFont])
|
||||||
|
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
@ -21,7 +21,7 @@ const Header = styled.h2`
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
`
|
`
|
||||||
const Pre = styled.pre`
|
const Pre = styled.pre`
|
||||||
font-size: 15px;
|
font-size: 1rem;
|
||||||
line-height: 1.3em;
|
line-height: 1.3em;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ const Separator = styled.div`
|
|||||||
|
|
||||||
const Item = styled.div`
|
const Item = styled.div`
|
||||||
cursor: default;
|
cursor: default;
|
||||||
font-size: 14px;
|
font-size: 0.875rem;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 2.3em;
|
height: 2.3em;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import styled from "@emotion/styled"
|
import styled from "@emotion/styled"
|
||||||
import { useCallback } from "react"
|
import { useCallback, useEffect, useRef } from "react"
|
||||||
|
import { useEventListener } from "../utils/useEvent"
|
||||||
|
|
||||||
const ModalBackground = styled.div`
|
const ModalBackground = styled.div`
|
||||||
background: rgba(0, 0, 0, 0.6);
|
background: rgba(0, 0, 0, 0.6);
|
||||||
@ -21,11 +22,25 @@ const ModalBackground2 = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
`
|
`
|
||||||
const ModalContainer = styled.div`
|
const ModalContainer = styled.dialog`
|
||||||
background: var(--page-background);
|
background: var(--page-background);
|
||||||
box-shadow: rgba(0, 0, 0, 0.25) 0px 14px 28px, rgba(0, 0, 0, 0.22) 0px 10px 10px;
|
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
border: inherit;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.25) 0px 14px 28px, rgba(0, 0, 0, 0.22) 0px 10px 10px;
|
||||||
|
color: inherit;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&::backdrop {
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
backdrop-filter: blur(1px);
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
const ModalTitle = styled.div`
|
const ModalTitle = styled.div`
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
@ -37,12 +52,15 @@ const ModalContent = styled.div`
|
|||||||
padding: 15px 20px;
|
padding: 15px 20px;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
document.createElement("dialog")
|
||||||
|
|
||||||
export const Modal: React.FC<{
|
export const Modal: React.FC<{
|
||||||
show: boolean
|
show: boolean
|
||||||
title: string
|
title: string
|
||||||
maxWidth?: number
|
maxWidth?: number
|
||||||
onClose(): void
|
onClose(): void
|
||||||
}> = ({ show, children, title, maxWidth = 700, onClose }) => {
|
}> = ({ show, children, title, maxWidth = 700, onClose }) => {
|
||||||
|
const dialogRef = useRef<HTMLDialogElement>(null)
|
||||||
const onBackgroundClick = useCallback(
|
const onBackgroundClick = useCallback(
|
||||||
e => {
|
e => {
|
||||||
if (e.currentTarget === e.target) {
|
if (e.currentTarget === e.target) {
|
||||||
@ -53,6 +71,15 @@ export const Modal: React.FC<{
|
|||||||
[onClose]
|
[onClose]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useEventListener(document.body, "keyup").on(
|
||||||
|
e => {
|
||||||
|
if (show && e.key === "Escape") {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[show]
|
||||||
|
)
|
||||||
|
|
||||||
if (!show) {
|
if (!show) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -61,7 +88,7 @@ export const Modal: React.FC<{
|
|||||||
<>
|
<>
|
||||||
<ModalBackground />
|
<ModalBackground />
|
||||||
<ModalBackground2 onClick={onBackgroundClick}>
|
<ModalBackground2 onClick={onBackgroundClick}>
|
||||||
<ModalContainer style={{ maxWidth }}>
|
<ModalContainer open ref={dialogRef} style={{ maxWidth }}>
|
||||||
<ModalTitle>{title}</ModalTitle>
|
<ModalTitle>{title}</ModalTitle>
|
||||||
<ModalContent>{children}</ModalContent>
|
<ModalContent>{children}</ModalContent>
|
||||||
</ModalContainer>
|
</ModalContainer>
|
||||||
|
@ -176,6 +176,16 @@ options:
|
|||||||
fr: Verrouillage automatique
|
fr: Verrouillage automatique
|
||||||
ja: 自動ロック
|
ja: 自動ロック
|
||||||
|
|
||||||
|
ui_font:
|
||||||
|
en: Interface font
|
||||||
|
fr: Police de l’interface
|
||||||
|
ja: フォント
|
||||||
|
|
||||||
|
monospace:
|
||||||
|
en: Monospace font
|
||||||
|
fr: Police monospace
|
||||||
|
ja: 等幅フォント
|
||||||
|
|
||||||
noun:
|
noun:
|
||||||
vault:
|
vault:
|
||||||
en: vault
|
en: vault
|
||||||
|
@ -10,8 +10,7 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, system-ui, "Roboto", "Oxygen",
|
font-family: var(--sans-serif);
|
||||||
"Cantarell", "Droid Sans", "Helvetica Neue", "Noto Sans CJK JP", sans-serif;
|
|
||||||
}
|
}
|
||||||
:root {
|
:root {
|
||||||
--page-background: #fff;
|
--page-background: #fff;
|
||||||
@ -22,6 +21,8 @@ body {
|
|||||||
--selected-background: #d5d5d5;
|
--selected-background: #d5d5d5;
|
||||||
--hover-background: #ddd;
|
--hover-background: #ddd;
|
||||||
--border-color: #e3e3e3;
|
--border-color: #e3e3e3;
|
||||||
|
--sans-serif: -apple-system, BlinkMacSystemFont, system-ui, "Roboto", "Oxygen",
|
||||||
|
"Cantarell", "Droid Sans", "Helvetica Neue", "Noto Sans CJK JP", sans-serif;
|
||||||
--monospace: D2Coding, "source-code-pro", Menlo, Monaco, Consolas, "Courier New",
|
--monospace: D2Coding, "source-code-pro", Menlo, Monaco, Consolas, "Courier New",
|
||||||
monospace;
|
monospace;
|
||||||
}
|
}
|
||||||
@ -78,7 +79,7 @@ input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
input[type="search"],
|
input[type="search"],
|
||||||
input[type="input"],
|
input[type="text"],
|
||||||
input[type="number"],
|
input[type="number"],
|
||||||
input[type="password"] {
|
input[type="password"] {
|
||||||
@include input;
|
@include input;
|
||||||
@ -100,7 +101,7 @@ input[type="checkbox" i] {
|
|||||||
position: relative;
|
position: relative;
|
||||||
&:checked:after {
|
&:checked:after {
|
||||||
content: "\2714";
|
content: "\2714";
|
||||||
font-size: 15px;
|
font-size: 1rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
left: 3px;
|
left: 3px;
|
||||||
@ -125,7 +126,6 @@ select,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
font-size: 16px;
|
|
||||||
padding: 8px 15px;
|
padding: 8px 15px;
|
||||||
box-shadow: rgb(0 0 0 / 7%) 0px 1px 2px;
|
box-shadow: rgb(0 0 0 / 7%) 0px 1px 2px;
|
||||||
transition: 0.1s;
|
transition: 0.1s;
|
||||||
|
@ -36,7 +36,7 @@ const TabButton = styled.button<{ active?: boolean }>`
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
font-size: 22px;
|
font-size: 1.4666em;
|
||||||
padding: 10px 14px;
|
padding: 10px 14px;
|
||||||
${p => p.active && "&:hover { background: var(--selected-background); }"}
|
${p => p.active && "&:hover { background: var(--selected-background); }"}
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
|
@ -25,6 +25,9 @@ const Checkbox = styled.input`
|
|||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
`
|
`
|
||||||
|
const Input = styled.input`
|
||||||
|
width: 100%;
|
||||||
|
`
|
||||||
|
|
||||||
const GhostLabel = styled.div`
|
const GhostLabel = styled.div`
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
@ -42,6 +45,8 @@ export const Settings: React.FC<{
|
|||||||
|
|
||||||
const [enableAutoLock, setEnableAutoLock] = useStorage(Key.ENABLE_AUTO_LOCK)
|
const [enableAutoLock, setEnableAutoLock] = useStorage(Key.ENABLE_AUTO_LOCK)
|
||||||
const [autolockAfter, setAutolockAfter] = useStorage(Key.AUTO_LOCK_AFTER)
|
const [autolockAfter, setAutolockAfter] = useStorage(Key.AUTO_LOCK_AFTER)
|
||||||
|
const [uiFont, setUIFont] = useStorage(Key.UI_FONT)
|
||||||
|
const [monoFont, setMonoFont] = useStorage(Key.MONOSPACE_FONT)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal show={show} title={t.label.settings} onClose={onHide}>
|
<Modal show={show} title={t.label.settings} onClose={onHide}>
|
||||||
@ -83,6 +88,30 @@ export const Settings: React.FC<{
|
|||||||
</GhostLabel>
|
</GhostLabel>
|
||||||
</FormValue>
|
</FormValue>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t.options.ui_font}</FormLabel>
|
||||||
|
<FormValue>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={uiFont}
|
||||||
|
onChange={e => setUIFont(e.target.value)}
|
||||||
|
spellCheck={false}
|
||||||
|
/>
|
||||||
|
</FormValue>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t.options.monospace}</FormLabel>
|
||||||
|
<FormValue>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={monoFont}
|
||||||
|
onChange={e => setMonoFont(e.target.value)}
|
||||||
|
spellCheck={false}
|
||||||
|
/>
|
||||||
|
</FormValue>
|
||||||
|
</FormItem>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ export enum Key {
|
|||||||
PREFERRED_LOCALE = "app.config.locale",
|
PREFERRED_LOCALE = "app.config.locale",
|
||||||
ENABLE_AUTO_LOCK = "app.config.enable_auto_lock",
|
ENABLE_AUTO_LOCK = "app.config.enable_auto_lock",
|
||||||
AUTO_LOCK_AFTER = "app.config.auto_lock_after",
|
AUTO_LOCK_AFTER = "app.config.auto_lock_after",
|
||||||
|
UI_FONT = "app.config.font.ui",
|
||||||
|
MONOSPACE_FONT = "app.config.font.monospace",
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StoredData {
|
interface StoredData {
|
||||||
@ -14,6 +16,8 @@ interface StoredData {
|
|||||||
[Key.PREFERRED_LOCALE]: string
|
[Key.PREFERRED_LOCALE]: string
|
||||||
[Key.ENABLE_AUTO_LOCK]: boolean
|
[Key.ENABLE_AUTO_LOCK]: boolean
|
||||||
[Key.AUTO_LOCK_AFTER]: number
|
[Key.AUTO_LOCK_AFTER]: number
|
||||||
|
[Key.UI_FONT]: string
|
||||||
|
[Key.MONOSPACE_FONT]: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const events = new Map(Object.values(Key).map(key => [key, new Set()])) as {
|
const events = new Map(Object.values(Key).map(key => [key, new Set()])) as {
|
||||||
@ -74,3 +78,5 @@ const defaults: typeof set = (key, value) => {
|
|||||||
defaults(Key.ENABLE_AUTO_LOCK, true)
|
defaults(Key.ENABLE_AUTO_LOCK, true)
|
||||||
defaults(Key.AUTO_LOCK_AFTER, 180)
|
defaults(Key.AUTO_LOCK_AFTER, 180)
|
||||||
defaults(Key.RECENTLY_OPENED_VAULTS, [])
|
defaults(Key.RECENTLY_OPENED_VAULTS, [])
|
||||||
|
defaults(Key.UI_FONT, "")
|
||||||
|
defaults(Key.MONOSPACE_FONT, "")
|
||||||
|
51
packages/web/src/utils/useEvent.ts
Normal file
51
packages/web/src/utils/useEvent.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { useEffect } from "react"
|
||||||
|
|
||||||
|
type UseEventListenerOptions = boolean | AddEventListenerOptions
|
||||||
|
|
||||||
|
export { useEventListener }
|
||||||
|
|
||||||
|
type On<This, Ev> = {
|
||||||
|
on(listener: (this: This, ev: Ev) => void, deps?: any[]): void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseEventListener {
|
||||||
|
<K extends keyof MediaQueryListEventMap>(
|
||||||
|
mediaQueryList: MediaQueryList,
|
||||||
|
type: K,
|
||||||
|
options?: UseEventListenerOptions
|
||||||
|
): On<MediaQueryList, MediaQueryListEventMap[K]>
|
||||||
|
<K extends keyof WindowEventMap>(
|
||||||
|
window: Window,
|
||||||
|
type: K,
|
||||||
|
options?: UseEventListenerOptions
|
||||||
|
): On<Window, WindowEventMap[K]>
|
||||||
|
<K extends keyof DocumentEventMap>(
|
||||||
|
document: Document,
|
||||||
|
type: K,
|
||||||
|
options?: UseEventListenerOptions
|
||||||
|
): On<Document, DocumentEventMap[K]>
|
||||||
|
<K extends keyof HTMLElementEventMap>(
|
||||||
|
element: HTMLElement,
|
||||||
|
type: K,
|
||||||
|
options?: UseEventListenerOptions
|
||||||
|
): On<HTMLElement, HTMLElementEventMap[K]>
|
||||||
|
}
|
||||||
|
|
||||||
|
const useEventListener: UseEventListener = function useEventListener(
|
||||||
|
element: EventTarget,
|
||||||
|
type: string,
|
||||||
|
options?: UseEventListenerOptions
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
on(listener: (ev: any) => any, deps?: any[]) {
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
element.addEventListener(type, listener, options)
|
||||||
|
return () => element.removeEventListener(type, listener, options)
|
||||||
|
},
|
||||||
|
deps ? [element, type, ...deps] : undefined
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
854
pnpm-lock.yaml
generated
854
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user