add option to change primary color

This commit is contained in:
hzrd149 2023-04-12 01:19:50 -05:00
parent a209b9d2fe
commit 66c6b4d73f
15 changed files with 160 additions and 72 deletions

View File

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Add option to change primary color for theme

View File

@ -1,9 +1,7 @@
import {
Badge,
Box,
Button,
Flex,
Highlight,
IconButton,
Input,
InputGroup,
@ -17,13 +15,13 @@ import {
ModalHeader,
ModalOverlay,
ModalProps,
Text,
useDisclosure,
} from "@chakra-ui/react";
import { useState } from "react";
import { useAsync } from "react-use";
import { unique } from "../helpers/array";
import { RelayIcon, SearchIcon } from "./icons";
import { safeRelayUrl } from "../helpers/url";
function RelayPickerModal({
onSelect,
@ -60,9 +58,8 @@ function RelayPickerModal({
</InputGroup>
<Flex gap="2" direction="column">
{filteredRelays.map((url) => (
<Flex gap="2" alignItems="center">
<Flex key={url} gap="2" alignItems="center">
<Button
key={url}
value={url}
onClick={() => {
onSelect(url);

View File

@ -0,0 +1,31 @@
import { useCallback } from "react";
import appSettings, { replaceSettings } from "../services/app-settings";
import useSubject from "./use-subject";
import { AppSettings } from "../services/user-app-settings";
import { useToast } from "@chakra-ui/react";
export default function useAppSettings() {
const settings = useSubject(appSettings);
const toast = useToast();
const updateSettings = useCallback(
(newSettings: Partial<AppSettings>) => {
try {
return replaceSettings({ ...settings, ...newSettings });
} catch (e) {
if (e instanceof Error) {
toast({
status: "error",
description: e.message,
});
}
}
},
[settings]
);
return {
...settings,
updateSettings,
};
}

View File

@ -1,10 +1,16 @@
import React from "react";
import React, { useMemo } from "react";
import { ChakraProvider, localStorageManager } from "@chakra-ui/react";
import theme from "../theme";
import { SigningProvider } from "./signing-provider";
import createTheme from "../theme";
import useAppSettings from "../hooks/use-app-settings";
export const Providers = ({ children }: { children: React.ReactNode }) => (
<ChakraProvider theme={theme} colorModeManager={localStorageManager}>
<SigningProvider>{children}</SigningProvider>
</ChakraProvider>
);
export const Providers = ({ children }: { children: React.ReactNode }) => {
const { primaryColor } = useAppSettings();
const theme = useMemo(() => createTheme(primaryColor), [primaryColor]);
return (
<ChakraProvider theme={theme} colorModeManager={localStorageManager}>
<SigningProvider>{children}</SigningProvider>
</ChakraProvider>
);
};

View File

@ -6,27 +6,27 @@ import signingService from "./signing";
import { nostrPostAction } from "../classes/nostr-post-action";
export let appSettings = new PersistentSubject(defaultSettings);
export async function updateSettings(settings: Partial<AppSettings>) {
try {
const account = accountService.current.value;
if (!account) return;
const json: AppSettings = { ...appSettings.value, ...settings };
export async function replaceSettings(newSettings: AppSettings) {
const account = accountService.current.value;
if (!account) return;
if (account.readonly) {
accountService.updateAccountLocalSettings(account.pubkey, json);
appSettings.next(json);
} else {
const draft = userAppSettings.buildAppSettingsEvent({ ...appSettings.value, ...settings });
const event = await signingService.requestSignature(draft, account);
userAppSettings.receiveEvent(event);
await nostrPostAction(clientRelaysService.getWriteUrls(), event).onComplete;
}
} catch (e) {}
if (account.readonly) {
accountService.updateAccountLocalSettings(account.pubkey, newSettings);
appSettings.next(newSettings);
} else {
const draft = userAppSettings.buildAppSettingsEvent(newSettings);
const event = await signingService.requestSignature(draft, account);
userAppSettings.receiveEvent(event);
await nostrPostAction(clientRelaysService.getWriteUrls(), event).onComplete;
}
}
export async function loadSettings() {
const account = accountService.current.value;
if (!account) return;
if (!account) {
appSettings.next(defaultSettings);
return;
}
appSettings.disconnectAll();

View File

@ -46,7 +46,11 @@ const db = await openDB<SchemaV2>(dbName, version, {
const v1 = db as unknown as IDBPDatabase<SchemaV1>;
const v2 = db as unknown as IDBPDatabase<SchemaV2>;
v1.deleteObjectStore("settings");
// rename the old settings object store to misc
const oldSettings = transaction.objectStore("settings");
oldSettings.name = "misc";
// create new settings object store
const settings = v2.createObjectStore("settings", {
keyPath: "pubkey",
});

View File

@ -56,4 +56,8 @@ export interface SchemaV2 extends SchemaV1 {
value: NostrEvent;
indexes: { created_at: number };
};
misc: {
key: string;
value: any;
};
}

View File

@ -7,12 +7,12 @@ const decryptedKeys = new Map<string, string>();
class SigningService {
private async getSalt() {
let salt = await db.get("settings", "salt");
let salt = await db.get("misc", "salt");
if (salt) {
return salt as Uint8Array;
} else {
const newSalt = window.crypto.getRandomValues(new Uint8Array(16));
await db.put("settings", newSalt, "salt");
await db.put("misc", newSalt, "salt");
return newSalt;
}
}

View File

@ -24,6 +24,7 @@ export type AppSettings = {
showSignatureVerification: boolean;
lightningPayMode: LightningPayMode;
zapAmounts: number[];
primaryColor: string;
};
export const defaultSettings: AppSettings = {
@ -35,6 +36,7 @@ export const defaultSettings: AppSettings = {
showSignatureVerification: false,
lightningPayMode: LightningPayMode.Prompt,
zapAmounts: [50, 200, 500, 1000],
primaryColor: "#8DB600",
};
function parseAppSettings(event: NostrEvent): AppSettings {

View File

@ -1,37 +1,24 @@
import { extendTheme } from "@chakra-ui/react";
import { containerTheme } from "./container";
const theme = extendTheme({
colors: {
// https://hihayk.github.io/scale/#5/4/60/50/0/0/20/-25/8DB600/141/182/0/white
// brand: {
// 50: "#334009",
// 100: "#44550A",
// 200: "#556C09",
// 300: "#678307",
// 400: "#7A9C04",
// 500: "#8DB600",
// 600: "#9DC320",
// 700: "#ADCF40",
// 800: "#BCDA60",
// 900: "#CBE480",
// },
brand: {
50: "#8DB600",
100: "#8DB600",
200: "#8DB600",
300: "#8DB600",
400: "#8DB600",
500: "#8DB600",
600: "#8DB600",
700: "#8DB600",
800: "#8DB600",
900: "#8DB600",
export default function createTheme(primaryColor: string = "#8DB600") {
return extendTheme({
colors: {
brand: {
50: primaryColor,
100: primaryColor,
200: primaryColor,
300: primaryColor,
400: primaryColor,
500: primaryColor,
600: primaryColor,
700: primaryColor,
800: primaryColor,
900: primaryColor,
},
},
},
components: {
Container: containerTheme,
},
});
export default theme;
components: {
Container: containerTheme,
},
});
}

View File

@ -23,6 +23,7 @@ export default function HomeView() {
isLazy
index={activeTab}
onChange={(v) => navigate(tabs[v].path)}
colorScheme="brand"
>
<TabList>
{tabs.map(({ label }) => (

View File

@ -9,12 +9,42 @@ import {
Box,
AccordionIcon,
FormHelperText,
Input,
InputProps,
} from "@chakra-ui/react";
import useSubject from "../../hooks/use-subject";
import appSettings, { updateSettings } from "../../services/app-settings";
import { useEffect, useRef, useState } from "react";
import useAppSettings from "../../hooks/use-app-settings";
function ColorPicker({ value, onPickColor, ...props }: { onPickColor?: (color: string) => void } & InputProps) {
const [tmpColor, setTmpColor] = useState(value);
const ref = useRef<HTMLInputElement>();
useEffect(() => setTmpColor(value), [value]);
useEffect(() => {
if (ref.current) {
ref.current.onchange = () => {
if (onPickColor && ref.current?.value) {
onPickColor(ref.current.value);
}
};
}
});
return (
<Input
{...props}
ref={ref}
value={tmpColor}
onChange={(e) => {
setTmpColor(e.target.value);
if (props.onChange) props.onChange(e);
}}
/>
);
}
export default function DisplaySettings() {
const { blurImages, colorMode } = useSubject(appSettings);
const { blurImages, colorMode, primaryColor, updateSettings } = useAppSettings();
return (
<AccordionItem>
@ -40,7 +70,25 @@ export default function DisplaySettings() {
/>
</Flex>
<FormHelperText>
<span>Enabled: hacker mode</span>
<span>Enables hacker mode</span>
</FormHelperText>
</FormControl>
<FormControl>
<Flex alignItems="center">
<FormLabel htmlFor="primary-color" mb="0">
Primary Color
</FormLabel>
<ColorPicker
id="primary-color"
type="color"
value={primaryColor}
onPickColor={(color) => updateSettings({ primaryColor: color })}
maxW="120"
size="sm"
/>
</Flex>
<FormHelperText>
<span>The primary color of the theme</span>
</FormHelperText>
</FormControl>
<FormControl>

View File

@ -12,13 +12,14 @@ import {
Select,
} from "@chakra-ui/react";
import { useEffect, useState } from "react";
import appSettings, { updateSettings } from "../../services/app-settings";
import appSettings, { replaceSettings } from "../../services/app-settings";
import useSubject from "../../hooks/use-subject";
import { LightningIcon } from "../../components/icons";
import { LightningPayMode } from "../../services/user-app-settings";
import useAppSettings from "../../hooks/use-app-settings";
export default function LightningSettings() {
const { lightningPayMode, zapAmounts } = useSubject(appSettings);
const { lightningPayMode, zapAmounts, updateSettings } = useAppSettings();
const [zapInput, setZapInput] = useState(zapAmounts.join(","));
useEffect(() => setZapInput(zapAmounts.join(",")), [zapAmounts.join(",")]);

View File

@ -10,11 +10,12 @@ import {
AccordionIcon,
FormHelperText,
} from "@chakra-ui/react";
import appSettings, { updateSettings } from "../../services/app-settings";
import appSettings, { replaceSettings } from "../../services/app-settings";
import useSubject from "../../hooks/use-subject";
import useAppSettings from "../../hooks/use-app-settings";
export default function PerformanceSettings() {
const { autoShowMedia, proxyUserMedia, showReactions, showSignatureVerification } = useSubject(appSettings);
const { autoShowMedia, proxyUserMedia, showReactions, showSignatureVerification, updateSettings } = useAppSettings();
return (
<AccordionItem>

View File

@ -62,6 +62,7 @@ const UserView = () => {
isLazy
index={activeTab}
onChange={(v) => navigate(tabs[v].path)}
colorScheme="brand"
>
<TabList overflowX="auto" overflowY="hidden" flexShrink={0}>
{tabs.map(({ label }) => (