mirror of
https://github.com/urbit/shrub.git
synced 2024-11-28 13:54:20 +03:00
prefs: hooking up system updates and cleaning up theme handling
This commit is contained in:
parent
8dd9113843
commit
8027ab9d50
@ -5,23 +5,24 @@ import { Grid } from './pages/Grid';
|
||||
import useDocketState from './state/docket';
|
||||
import { PermalinkRoutes } from './pages/PermalinkRoutes';
|
||||
import useKilnState from './state/kiln';
|
||||
import { usePreferencesStore } from './nav/preferences/usePreferencesStore';
|
||||
import useContactState from './state/contact';
|
||||
import api from './state/api';
|
||||
import { useHarkStore } from './state/hark';
|
||||
import { useTheme } from './state/settings';
|
||||
import { useLocalState } from './state/local';
|
||||
|
||||
const AppRoutes = () => {
|
||||
const { push } = useHistory();
|
||||
const theme = usePreferencesStore((s) => s.theme);
|
||||
const theme = useTheme();
|
||||
|
||||
const updateThemeClass = useCallback(
|
||||
(e: MediaQueryListEvent) => {
|
||||
if ((e.matches && theme === 'automatic') || theme === 'dark') {
|
||||
document.body.classList.add('dark');
|
||||
usePreferencesStore.setState({ currentTheme: 'dark' });
|
||||
useLocalState.setState({ currentTheme: 'dark' });
|
||||
} else {
|
||||
document.body.classList.remove('dark');
|
||||
usePreferencesStore.setState({ currentTheme: 'light' });
|
||||
useLocalState.setState({ currentTheme: 'light' });
|
||||
}
|
||||
},
|
||||
[theme]
|
||||
|
@ -27,7 +27,7 @@ export const Setting: FC<SettingsProps> = ({ name, on, toggle, className, childr
|
||||
onPressedChange={call}
|
||||
className="flex-none self-start text-blue-400"
|
||||
/>
|
||||
<div className="flex-1 space-y-6">{children}</div>
|
||||
<div className="flex-1 flex flex-col justify-center space-y-6">{children}</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
@ -34,9 +34,7 @@ export function InterfacePrefs() {
|
||||
<>
|
||||
<h2 className="h3 mb-7">Interface Settings</h2>
|
||||
<Setting on={protocolHandling} toggle={toggleProtoHandling} name="Handle Urbit links">
|
||||
<p className="flex flex-col justify-center h-full">
|
||||
Automatically open urbit links with this urbit
|
||||
</p>
|
||||
<p>Automatically open urbit links with this urbit</p>
|
||||
</Setting>
|
||||
</>
|
||||
);
|
||||
|
@ -36,14 +36,7 @@ export const NotificationPrefs = () => {
|
||||
</p>
|
||||
</Setting>
|
||||
<Setting on={mentions} toggle={toggleMentions} name="Mentions">
|
||||
<p>
|
||||
[PLACEHOLDER] Block visual desktop notifications whenever Urbit software produces an
|
||||
in-Landscape notification badge.
|
||||
</p>
|
||||
<p>
|
||||
Turning this "off" will prompt your browser to ask if you'd like to
|
||||
enable notifications
|
||||
</p>
|
||||
<p>Notify me if someone mentions my @p in a channel I've joined</p>
|
||||
</Setting>
|
||||
</div>
|
||||
</>
|
||||
|
@ -1,19 +1,30 @@
|
||||
import _ from 'lodash';
|
||||
import React, { ChangeEvent, FormEvent, useCallback, useEffect, useState } from 'react';
|
||||
import { Button } from '../../components/Button';
|
||||
import { Setting } from '../../components/Setting';
|
||||
import { ShipName } from '../../components/ShipName';
|
||||
import { Spinner } from '../../components/Spinner';
|
||||
import { useAsyncCall } from '../../logic/useAsyncCall';
|
||||
import { usePreferencesStore } from './usePreferencesStore';
|
||||
import useKilnState, { useVat } from '../../state/kiln';
|
||||
|
||||
export const SystemUpdatePrefs = () => {
|
||||
const { otasEnabled, otaSource, toggleOTAs, setOTASource } = usePreferencesStore();
|
||||
const [source, setSource] = useState(otaSource);
|
||||
const { changeOTASource, toggleOTAs } = useKilnState((s) =>
|
||||
_.pick(s, ['toggleOTAs', 'changeOTASource'])
|
||||
);
|
||||
const base = useVat('base');
|
||||
const otasEnabled = base && !base.arak.paused;
|
||||
const otaSource = base?.arak.ship;
|
||||
|
||||
const toggleBase = useCallback((on: boolean) => toggleOTAs('base', on), [toggleOTAs]);
|
||||
|
||||
const [source, setSource] = useState('');
|
||||
const sourceDirty = source !== otaSource;
|
||||
const { status: sourceStatus, call: setOTA } = useAsyncCall(setOTASource);
|
||||
const { status: sourceStatus, call: setOTA } = useAsyncCall(changeOTASource);
|
||||
|
||||
useEffect(() => {
|
||||
setSource(otaSource);
|
||||
if (otaSource) {
|
||||
setSource(otaSource);
|
||||
}
|
||||
}, [otaSource]);
|
||||
|
||||
const handleSourceChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
@ -34,11 +45,13 @@ export const SystemUpdatePrefs = () => {
|
||||
<>
|
||||
<h2 className="h3 mb-7">System Updates</h2>
|
||||
<div className="space-y-3">
|
||||
<Setting on={otasEnabled} toggle={toggleOTAs} name="Enable Automatic Urbit OTAs">
|
||||
<Setting on={!!otasEnabled} toggle={toggleBase} name="Enable Automatic Urbit OTAs">
|
||||
<p>Automatically download and apply system updates to keep your Urbit up to date.</p>
|
||||
<p>
|
||||
OTA Source: <ShipName name={otaSource} className="font-semibold font-mono" />
|
||||
</p>
|
||||
{otaSource && (
|
||||
<p>
|
||||
OTA Source: <ShipName name={otaSource} className="font-semibold font-mono" />
|
||||
</p>
|
||||
)}
|
||||
</Setting>
|
||||
<form className="inner-section relative" onSubmit={onSubmit}>
|
||||
<label htmlFor="ota-source" className="h4 mb-3">
|
||||
|
@ -1,55 +0,0 @@
|
||||
import create from 'zustand';
|
||||
import { fakeRequest } from '../../state/util';
|
||||
|
||||
const useMockData = import.meta.env.MODE === 'mock';
|
||||
|
||||
interface PreferencesStore {
|
||||
theme: 'light' | 'dark' | 'automatic';
|
||||
currentTheme: 'light' | 'dark';
|
||||
otasEnabled: boolean;
|
||||
otaSource: string;
|
||||
doNotDisturb: boolean;
|
||||
mentions: boolean;
|
||||
setOTASource: (source: string) => Promise<void>;
|
||||
toggleOTAs: () => Promise<void>;
|
||||
toggleDoNotDisturb: () => Promise<void>;
|
||||
toggleMentions: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const usePreferencesStore = create<PreferencesStore>((set) => ({
|
||||
theme: 'automatic',
|
||||
currentTheme: 'light',
|
||||
otasEnabled: true,
|
||||
otaSource: useMockData ? '~sabbus' : '',
|
||||
doNotDisturb: false,
|
||||
mentions: true,
|
||||
/**
|
||||
* a lot of these are repetitive, we may do better with a map of settings
|
||||
* and some generic way to update them through pokes. That way, we could
|
||||
* just have toggleSetting(key) and run a similar op for all
|
||||
*/
|
||||
toggleOTAs: async () => {
|
||||
if (useMockData) {
|
||||
await fakeRequest({});
|
||||
set((state) => ({ otasEnabled: !state.otasEnabled }));
|
||||
}
|
||||
},
|
||||
setOTASource: async (source: string) => {
|
||||
if (useMockData) {
|
||||
await fakeRequest({});
|
||||
set({ otaSource: source });
|
||||
}
|
||||
},
|
||||
toggleDoNotDisturb: async () => {
|
||||
if (useMockData) {
|
||||
await fakeRequest({});
|
||||
set((state) => ({ doNotDisturb: !state.doNotDisturb }));
|
||||
}
|
||||
},
|
||||
toggleMentions: async () => {
|
||||
if (useMockData) {
|
||||
await fakeRequest({});
|
||||
set((state) => ({ mentions: !state.mentions }));
|
||||
}
|
||||
}
|
||||
}));
|
@ -1,4 +1,5 @@
|
||||
import Urbit from '@urbit/http-api';
|
||||
import { useMockData } from './util';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@ -6,17 +7,18 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const api =
|
||||
import.meta.env.MODE === 'mock'
|
||||
? ({
|
||||
poke: async () => {},
|
||||
subscribe: async () => {},
|
||||
subscribeOnce: async () => {},
|
||||
ship: '',
|
||||
scry: async () => {}
|
||||
} as unknown as Urbit)
|
||||
: new Urbit('', '');
|
||||
|
||||
api.ship = import.meta.env.MODE === 'mock' ? 'dopzod' : window.ship;
|
||||
const api = useMockData
|
||||
? ({
|
||||
poke: async () => {},
|
||||
subscribe: async () => {},
|
||||
subscribeOnce: async () => {},
|
||||
ship: '',
|
||||
scry: async () => {}
|
||||
} as unknown as Urbit)
|
||||
: new Urbit('', '');
|
||||
if (useMockData) {
|
||||
api.verbose = true;
|
||||
}
|
||||
api.ship = useMockData ? 'dopzod' : window.ship;
|
||||
|
||||
export default api;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { getVats, Vats, scryLag, getBlockers, Vat } from '@urbit/api';
|
||||
import { getVats, Vats, scryLag, getBlockers, Vat, kilnInstall } from '@urbit/api';
|
||||
import { kilnPause, kilnResume } from '@urbit/api/hood';
|
||||
import create from 'zustand';
|
||||
import produce from 'immer';
|
||||
import { useCallback } from 'react';
|
||||
@ -12,6 +13,8 @@ interface KilnState {
|
||||
fetchVats: () => Promise<void>;
|
||||
lag: boolean;
|
||||
fetchLag: () => Promise<void>;
|
||||
changeOTASource: (ship: string) => Promise<void>;
|
||||
toggleOTAs: (desk: string, on: boolean) => Promise<void>;
|
||||
set: (s: KilnState) => void;
|
||||
}
|
||||
const useKilnState = create<KilnState>((set) => ({
|
||||
@ -31,6 +34,39 @@ const useKilnState = create<KilnState>((set) => ({
|
||||
const lag = await api.scry<boolean>(scryLag);
|
||||
set({ lag });
|
||||
},
|
||||
changeOTASource: async (ship: string) => {
|
||||
if (useMockData) {
|
||||
await fakeRequest('');
|
||||
set(
|
||||
produce((draft: KilnState) => {
|
||||
draft.vats.base.arak.ship = ship;
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await api.poke(kilnInstall(ship, '%kids', 'base'));
|
||||
},
|
||||
toggleOTAs: async (desk: string, on: boolean) => {
|
||||
if (useMockData) {
|
||||
await fakeRequest('');
|
||||
set(
|
||||
produce((draft: KilnState) => {
|
||||
const baseArak = draft.vats.base.arak;
|
||||
|
||||
if (on) {
|
||||
baseArak.paused = false;
|
||||
} else {
|
||||
baseArak.paused = true;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await api.poke(on ? kilnResume(desk) : kilnPause(desk));
|
||||
},
|
||||
set: produce(set)
|
||||
}));
|
||||
|
||||
|
@ -4,6 +4,7 @@ import produce from 'immer';
|
||||
|
||||
interface LocalState {
|
||||
protocolHandling: boolean;
|
||||
currentTheme: 'light' | 'dark';
|
||||
set: (f: (s: LocalState) => void) => void;
|
||||
}
|
||||
|
||||
@ -11,6 +12,7 @@ export const useLocalState = create<LocalState>(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
set: (f) => set(produce(get(), f)),
|
||||
currentTheme: 'light',
|
||||
protocolHandling: false
|
||||
}),
|
||||
{
|
||||
@ -24,4 +26,9 @@ export function useProtocolHandling() {
|
||||
return useLocalState(selProtocolHandling);
|
||||
}
|
||||
|
||||
const selCurrentTheme = (s: LocalState) => s.currentTheme;
|
||||
export function useCurrentTheme() {
|
||||
return useLocalState(selCurrentTheme);
|
||||
}
|
||||
|
||||
export const setLocalState = (f: (s: LocalState) => void) => useLocalState.getState().set(f);
|
||||
|
@ -96,3 +96,8 @@ export const useSettingsState = createState<BaseSettingsState>(
|
||||
})
|
||||
]
|
||||
);
|
||||
|
||||
const selTheme = (s: SettingsState) => s.display.theme;
|
||||
export function useTheme() {
|
||||
return useSettingsState(selTheme);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { hsla, parseToHsla } from 'color2k';
|
||||
import { usePreferencesStore } from '../nav/preferences/usePreferencesStore';
|
||||
import { useCurrentTheme } from '../state/local';
|
||||
|
||||
function getDarkColor(color: string): string {
|
||||
const hslaColor = parseToHsla(color);
|
||||
@ -7,7 +7,7 @@ function getDarkColor(color: string): string {
|
||||
}
|
||||
|
||||
export const useTileColor = (color: string) => {
|
||||
const theme = usePreferencesStore((s) => s.currentTheme);
|
||||
const theme = useCurrentTheme();
|
||||
|
||||
return {
|
||||
theme,
|
||||
|
@ -37,6 +37,7 @@ export interface Arak {
|
||||
aeon: number;
|
||||
next: Woof[];
|
||||
rein: Rein;
|
||||
paused: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user