Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 83 additions & 12 deletions src/main/ipc/handlers/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,20 @@ const THEME_TEMPLATE: ThemeFile = {
author: '',
type: 'light',
colors: {
'color-primary': 'hsl(277, 22%, 57%)',
'color-bg': 'hsl(35, 67%, 96%)',
'color-fg': 'hsl(245, 18%, 40%)',
'color-text': 'hsl(245, 18%, 40%)',
'color-text-muted': 'hsl(270, 10%, 53%)',
'color-border': 'hsl(30, 24%, 88%)',
'color-button': 'hsl(34, 52%, 91%)',
'color-button-hover': 'hsl(34, 42%, 88%)',
'color-list-selection': 'hsl(34, 38%, 89%)',
'color-list-selection-fg': 'hsl(245, 18%, 40%)',
'color-scrollbar': 'hsla(270, 14%, 73%, 0.5)',
'primary': 'hsl(277, 22%, 57%)',
'primary-foreground': 'hsl(0, 0%, 100%)',
'background': 'hsl(35, 67%, 96%)',
'foreground': 'hsl(245, 18%, 40%)',
'accent': 'hsl(34, 38%, 89%)',
'accent-hover': 'hsl(34, 42%, 92%)',
'accent-foreground': 'hsl(245, 18%, 40%)',
'muted': 'hsl(34, 52%, 91%)',
'muted-foreground': 'hsl(270, 10%, 53%)',
'card': 'hsl(35, 50%, 94%)',
'popover': 'hsl(35, 67%, 96%)',
'popover-foreground': 'hsl(245, 18%, 40%)',
'border': 'hsl(30, 24%, 88%)',
'scrollbar': 'hsla(270, 14%, 73%, 0.5)',
},
editorColors: {
'editor-keyword': 'hsl(277, 22%, 57%)',
Expand All @@ -55,6 +58,21 @@ const THEME_TEMPLATE: ThemeFile = {
},
}

const TOKEN_MIGRATION_MAP: Record<string, string> = {
'color-primary': 'primary',
'color-bg': 'background',
'color-fg': 'foreground',
'color-text': 'foreground',
'color-text-muted': 'muted-foreground',
'color-border': 'border',
'color-button': 'muted',
'color-list-selection': 'accent',
'color-list-selection-fg': 'accent-foreground',
'color-scrollbar': 'scrollbar',
}

const DROPPED_TOKENS = new Set(['color-button-hover'])

let themeWatcher: FSWatcher | null = null
let themeWatcherTimer: NodeJS.Timeout | null = null
let watchedThemesDir: string | null = null
Expand Down Expand Up @@ -224,15 +242,68 @@ function resolveThemeFilePath(id: string): string | null {
return filePath
}

function migrateThemeColors(colors: Record<string, string>): {
migrated: Record<string, string>
changed: boolean
} {
const result: Record<string, string> = {}
let changed = false

for (const [key, value] of Object.entries(colors)) {
if (DROPPED_TOKENS.has(key)) {
changed = true
continue
}

const newKey = TOKEN_MIGRATION_MAP[key]

if (newKey) {
result[newKey] = value
changed = true
}
else {
result[key] = value
}
}

return { migrated: result, changed }
}

function readThemeFromFile(
filePath: string,
fileName: string,
): ThemeFile | null {
try {
const content = readFileSync(filePath, 'utf8')
const parsed = JSON.parse(content) as unknown
const theme = parseThemeFile(parsed, fileName)

if (!theme) {
return null
}

if (theme.colors) {
const { migrated, changed } = migrateThemeColors(theme.colors)

if (changed) {
theme.colors = migrated

try {
const raw = parsed as Record<string, unknown>
raw.colors = migrated
writeFileSync(filePath, `${JSON.stringify(raw, null, 2)}\n`, 'utf8')
console.warn(`[theme] Migrated ${fileName} to new token format`)
}
catch (writeError) {
console.warn(
`[theme] Failed to write migrated theme ${fileName}`,
writeError,
)
}
}
}

return parseThemeFile(parsed, fileName)
return theme
}
catch (error) {
reportThemeIssue(fileName, 'Failed to read or parse JSON', error)
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/ui/input/variants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { VariantProps } from 'class-variance-authority'
import { cva } from 'class-variance-authority'

export const variants = cva(
'w-full rounded-md focus:outline-none placeholder:text-muted-foreground py-0.5 px-2 border bg-background dark:bg-black/20 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none',
'w-full rounded-md focus:outline-none placeholder:text-muted-foreground py-0.5 px-2 border bg-[color-mix(in_oklch,var(--foreground)_2%,var(--background))] [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none',
{
variants: {
variant: {
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/components/ui/menu/FormSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ const props = withDefaults(defineProps<Props>(), {
cn(
'rounded-lg border p-5',
props.variant === 'danger'
? 'border-destructive/20 bg-destructive/5'
: 'border-border bg-card',
? 'border-destructive/20 bg-[color-mix(in_oklch,var(--destructive)_5%,var(--background))]'
: 'border-border bg-[color-mix(in_oklch,var(--foreground)_4%,var(--background))]',
)
"
>
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/ui/shadcn/button/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const buttonVariants = cva(
destructive:
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
outline:
'border bg-background shadow-xs hover:bg-accent-hover hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
'border bg-[color-mix(in_oklch,var(--foreground)_2%,var(--background))] shadow-xs hover:bg-[color-mix(in_oklch,var(--foreground)_5%,var(--background))] hover:text-accent-foreground',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost:
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/ui/shadcn/select/SelectTrigger.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const forwardedProps = useForwardProps(delegatedProps)
v-bind="forwardedProps"
:class="
cn(
'border-input bg-background data-[placeholder]:text-muted-foreground [&_svg:not([class*=\'text-\'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex h-7 w-fit items-center justify-between gap-2 rounded-md border px-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 [&>span]:truncate',
'border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*=\'text-\'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive flex h-7 w-fit items-center justify-between gap-2 rounded-md border bg-[color-mix(in_oklch,var(--foreground)_2%,var(--background))] px-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none hover:bg-[color-mix(in_oklch,var(--foreground)_5%,var(--background))] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 [&>span]:truncate',
props.class,
)
"
Expand Down
27 changes: 25 additions & 2 deletions src/renderer/composables/useTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ const CUSTOM_STYLE_ID = 'masscode-custom-theme'
const LIGHT_EDITOR_THEME = 'neo'
const DARK_EDITOR_THEME = 'oceanic-next'

const TOKEN_MIGRATION_MAP: Record<string, string> = {
'color-primary': 'primary',
'color-bg': 'background',
'color-fg': 'foreground',
'color-text': 'foreground',
'color-text-muted': 'muted-foreground',
'color-border': 'border',
'color-button': 'muted',
'color-list-selection': 'accent',
'color-list-selection-fg': 'accent-foreground',
'color-scrollbar': 'scrollbar',
}

const DROPPED_TOKENS = new Set(['color-button-hover'])

const storedThemeId = String(store.preferences.get('theme') || 'auto')

const colorMode = useColorMode()
Expand Down Expand Up @@ -85,8 +100,16 @@ function buildThemeCss(theme: ThemeFile): string {

if (theme.colors) {
const colorVars = Object.entries(theme.colors)
.filter(([key, value]) => isValidCssToken(key) && Boolean(value.trim()))
.map(([key, value]) => ` --${key}: ${value};`)
.filter(
([key, value]) =>
!DROPPED_TOKENS.has(key)
&& isValidCssToken(key)
&& Boolean(value.trim()),
)
.map(([key, value]) => {
const resolvedKey = TOKEN_MIGRATION_MAP[key] ?? key
return ` --${resolvedKey}: ${value};`
})

if (colorVars.length) {
chunks.push(`${themeSelector} {`)
Expand Down
12 changes: 6 additions & 6 deletions src/renderer/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
/* Core */
--background: oklch(100% 0 0);
--foreground: oklch(20% 0 0);
--card: oklch(97% 0 0);
--card: color-mix(in oklch, var(--foreground) 4%, var(--background));
--card-foreground: oklch(20% 0 0);
--popover: oklch(100% 0 0);
--popover: var(--background);
--popover-foreground: oklch(20% 0 0);
--primary: oklch(50% 0.19 260);
--primary-foreground: oklch(100% 0 0);
Expand All @@ -22,7 +22,7 @@
--muted: oklch(97% 0 0);
--muted-foreground: oklch(60% 0 0);
--accent: oklch(92% 0 0);
--accent-hover: oklch(96% 0 0);
--accent-hover: color-mix(in oklch, var(--accent) 50%, var(--background));
--accent-foreground: oklch(20% 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(90% 0 0);
Expand Down Expand Up @@ -55,9 +55,9 @@
/* Core */
--background: oklch(24.78% 0 0);
--foreground: oklch(75% 0 0);
--card: oklch(22% 0 0);
--card: color-mix(in oklch, var(--foreground) 4%, var(--background));
--card-foreground: oklch(75% 0 0);
--popover: oklch(24.78% 0 0);
--popover: var(--background);
--popover-foreground: oklch(75% 0 0);
--primary: oklch(50% 0.19 260);
--primary-foreground: oklch(100% 0 0);
Expand All @@ -66,7 +66,7 @@
--muted: oklch(27% 0 0);
--muted-foreground: oklch(60% 0 0);
--accent: oklch(32% 0 0);
--accent-hover: oklch(28% 0 0);
--accent-hover: color-mix(in oklch, var(--accent) 50%, var(--background));
--accent-foreground: oklch(95% 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(30% 0 0);
Expand Down