diff --git a/biome.json b/biome.json index 1a4ae51..f188466 100644 --- a/biome.json +++ b/biome.json @@ -1,7 +1,8 @@ { "$schema": "https://biomejs.dev/schemas/1.6.4/schema.json", "files": { - "ignoreUnknown": true + "ignoreUnknown": true, + "ignore": [".next/", "**/node_modules/"] }, "formatter": { "enabled": true, diff --git a/src/components/modals/SettingsPopover.tsx b/src/components/modals/SettingsPopover.tsx index 925bcb4..a7c4f12 100644 --- a/src/components/modals/SettingsPopover.tsx +++ b/src/components/modals/SettingsPopover.tsx @@ -1,4 +1,5 @@ import useLanguage from '@/hooks/useLanguage'; +import useServerURL from '@/hooks/useServerURL'; import useTheme from '@/hooks/useTheme'; import useThemeValues from '@/hooks/useThemeValues'; import useLanguageStore from '@/store/language'; @@ -6,11 +7,16 @@ import type { SettingPopoverProps } from '@/types/Components.ts'; import { Box, Button, + Center, + CloseButton, Flex, FormControl, FormLabel, Heading, Icon, + Input, + InputGroup, + InputRightElement, Popover, PopoverBody, PopoverCloseButton, @@ -23,10 +29,11 @@ import { Spacer, Stack, Text, + useBreakpointValue, useDisclosure } from '@chakra-ui/react'; -import type { ReactElement } from 'react'; -import { MdCheckCircle, MdFlag, MdKeyboardArrowDown } from 'react-icons/md'; +import { type ReactElement, useState } from 'react'; +import { MdAdd, MdCheckCircle, MdFlag, MdKeyboardArrowDown, MdPalette } from 'react-icons/md'; import SelectModal from './SelectModal'; const SettingsPopover = ({ trigger }: SettingPopoverProps): ReactElement => { @@ -34,8 +41,29 @@ const SettingsPopover = ({ trigger }: SettingPopoverProps): ReactElement => { const [languageId, languages] = useLanguage(); const { setLanguageId } = useLanguageStore(); const [themeId, setThemeId, themes] = useTheme(); + const [serverURL, setServerURL] = useServerURL(); + const { isOpen: isLangOpen, onClose: onLangClose, onOpen: onLangOpen } = useDisclosure(); + const { isOpen: isThemeOpen, onClose: onThemeClose, onOpen: onThemeOpen } = useDisclosure(); + + const [serverURLInput, setServerURLInput] = useState(serverURL ?? ''); + const [isServerURLInputFocused, setIsServerURLInputFocused] = useState(false); + + let serverInputError: string | null = null; + + if (serverURLInput) { + try { + const u = new URL(serverURLInput); + + if (u.protocol !== 'http:' && u.protocol !== 'https:') { + serverInputError = 'Please provide a valid server URL'; + } + } catch {} + } + + const quickThemeLimit = useBreakpointValue([3, 4]) ?? 4; + return ( <> { onPreview={setLanguageId} onSelect={setLanguageId} /> + ({ + id, + name, + details: themeId === id ? 'Recently used' : 'Set theme', + icon: + }))} + initialSelectedId={themeId} + onPreview={setThemeId} + onSelect={setThemeId} + /> {trigger} @@ -64,7 +105,12 @@ const SettingsPopover = ({ trigger }: SettingPopoverProps): ReactElement => { Language - @@ -72,16 +118,20 @@ const SettingsPopover = ({ trigger }: SettingPopoverProps): ReactElement => { Theme selector - - {themes.map((theme, i) => ( - + {themes.slice(0, quickThemeLimit + 1).map((theme, i) => ( +
{ _hover={{ outline: '3px solid', outlineOffset: '-3px', - outlineColor: theme.values.lowTransparency + outlineColor: + i === quickThemeLimit + ? getThemeValue('lowTransparency') + : theme.values.lowTransparency }} - onClick={() => setThemeId(theme.id)} + onClick={() => + i === quickThemeLimit ? onThemeOpen() : setThemeId(theme.id) + } > { { - {theme.name} + {i === quickThemeLimit ? ( + + More + + + ) : ( + theme.name + )} - {(theme.id === themeId || (!themeId && i === 0)) && ( + {((theme.id === themeId && i !== quickThemeLimit) || + (!themeId && i === 0)) && ( { - +
))}
+ + + + Server URL + {!serverInputError && isServerURLInputFocused && ( + + + Make sure you trust the server you are adding. + + + )} + {serverInputError && ( + + + {serverInputError} + + + )} + + + + { + setServerURLInput(e.target.value); + }} + focusBorderColor={getThemeValue('primary')} + placeholder='https://jspaste.eu/api/v2/documents' + onBlur={() => { + setIsServerURLInputFocused(false); + + if (!serverInputError) setServerURL(serverURLInput); + }} + onFocus={() => setIsServerURLInputFocused(true)} + /> + + + setServerURLInput('')} /> + + + + diff --git a/src/hooks/useServerURL.ts b/src/hooks/useServerURL.ts new file mode 100644 index 0000000..25934fc --- /dev/null +++ b/src/hooks/useServerURL.ts @@ -0,0 +1,7 @@ +import useServerURLStore from '@/store/serverURL'; + +export default function useServerURL() { + const { serverURL, setServerURL } = useServerURLStore(); + + return [serverURL, setServerURL] as const; +} diff --git a/src/hooks/useTheme.ts b/src/hooks/useTheme.ts index 647a4b2..c391b55 100644 --- a/src/hooks/useTheme.ts +++ b/src/hooks/useTheme.ts @@ -8,7 +8,7 @@ export default function useTheme() { return [ themeId, - (themeId: string) => { + (themeId?: string) => { const selectedTheme = themes.find((theme) => theme.id === themeId); if (!selectedTheme) return; diff --git a/src/store/serverURL.ts b/src/store/serverURL.ts new file mode 100644 index 0000000..4201fff --- /dev/null +++ b/src/store/serverURL.ts @@ -0,0 +1,19 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +const serverURLStore = create( + persist<{ + serverURL: string | undefined; + setServerURL: (serverURL: string) => void; + }>( + (set) => ({ + serverURL: undefined, + setServerURL: (serverURL) => set({ serverURL }) + }), + { + name: 'server-store' + } + ) +); + +export default serverURLStore; diff --git a/src/themes/monaco/amoled.json b/src/themes/monaco/amoled.json new file mode 100644 index 0000000..5b0e88a --- /dev/null +++ b/src/themes/monaco/amoled.json @@ -0,0 +1,8 @@ +{ + "base": "vs-dark", + "inherit": true, + "rules": [], + "colors": { + "editor.background": "#000000" + } +} diff --git a/src/themes/monaco/midnight.json b/src/themes/monaco/midnight.json index 64adf62..a9f77b2 100644 --- a/src/themes/monaco/midnight.json +++ b/src/themes/monaco/midnight.json @@ -3,6 +3,6 @@ "inherit": true, "rules": [], "colors": { - "editor.background": "#1C082E00" + "editor.background": "#04010700" } } diff --git a/src/themes/ui/themes.ts b/src/themes/ui/themes.ts index 916aba3..b061b19 100644 --- a/src/themes/ui/themes.ts +++ b/src/themes/ui/themes.ts @@ -15,8 +15,8 @@ export const themes: Theme[] = [ editor: '#2E2E2E', tooltip: '#202020', popup: '#292929', - text: '#CCC', - textMuted: '#AAA', + text: '#D6D6D6', + textMuted: '#BBBBBB', highTransparency: '#FFFFFF20', midTransparency: '#FFFFFF30', lowTransparency: '#FFFFFF40', @@ -33,8 +33,8 @@ export const themes: Theme[] = [ isCustomMonacoTheme: false, values: { primary: '#D3D3D3', - primaryDisplay: '#2E2E2E', - information: '#2E2E2E', + primaryDisplay: '#222222', + information: '#181818', controls: '#222222', editor: '#2E2E2E', tooltip: '#464646', @@ -82,11 +82,35 @@ export const themes: Theme[] = [ values: { primary: '#7D76DD', primaryDisplay: '#5C51F7', - information: '#180827', - controls: '#180827', - editor: '#0D031D', + information: '#05010C', + controls: '#05010C', + editor: '#0A0216', tooltip: '#18082C', - popup: '#18082C', + popup: '#140922', + text: '#EBEBEB', + textMuted: '#949494', + highTransparency: '#ffffff20', + midTransparency: '#ffffff30', + lowTransparency: '#ffffff40', + highAltTransparency: '#00000020', + midAltTransparency: '#00000030', + lowAltTransparency: '#00000040' + } + }, + { + id: 'amoled', + name: 'Amoled', + baseTheme: 'dark', + monacoTheme: 'amoled', + isCustomMonacoTheme: true, + values: { + primary: '#D3D3D3', + primaryDisplay: '#1B1B1B', + information: '#070707', + controls: '#070707', + editor: '#1A1A1A', + tooltip: '#161616', + popup: '#070707', text: '#EBEBEB', textMuted: '#949494', highTransparency: '#ffffff20',