mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-07 03:18:02 +02:00
add option to change primary color
This commit is contained in:
parent
a209b9d2fe
commit
66c6b4d73f
5
.changeset/chilled-impalas-wonder.md
Normal file
5
.changeset/chilled-impalas-wonder.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Add option to change primary color for theme
|
@ -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);
|
||||
|
31
src/hooks/use-app-settings.ts
Normal file
31
src/hooks/use-app-settings.ts
Normal 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,
|
||||
};
|
||||
}
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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",
|
||||
});
|
||||
|
@ -56,4 +56,8 @@ export interface SchemaV2 extends SchemaV1 {
|
||||
value: NostrEvent;
|
||||
indexes: { created_at: number };
|
||||
};
|
||||
misc: {
|
||||
key: string;
|
||||
value: any;
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ export default function HomeView() {
|
||||
isLazy
|
||||
index={activeTab}
|
||||
onChange={(v) => navigate(tabs[v].path)}
|
||||
colorScheme="brand"
|
||||
>
|
||||
<TabList>
|
||||
{tabs.map(({ label }) => (
|
||||
|
@ -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>
|
||||
|
@ -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(",")]);
|
||||
|
@ -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>
|
||||
|
@ -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 }) => (
|
||||
|
Loading…
x
Reference in New Issue
Block a user