Merge pull request #5211 from urbit/lf/garden-settings

settings-store: move to garden, namespace
This commit is contained in:
Liam Fitzgerald 2021-09-10 10:16:27 +10:00 committed by GitHub
commit 03a7bdda09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 386 additions and 147 deletions

View File

@ -0,0 +1,44 @@
/+ *mip
|%
::
++ settings-0
=< settings
|%
+$ settings (map key bucket)
+$ bucket (map key val)
+$ val
$% [%s p=@t]
[%b p=?]
[%n p=@]
==
--
::
++ settings-1
=< settings
|%
+$ settings (map key bucket)
--
+$ bucket (map key val)
+$ key term
+$ val
$~ [%n 0]
$% [%s p=@t]
[%b p=?]
[%n p=@]
[%a p=(list val)]
==
::
+$ settings (mip desk key bucket)
+$ event
$% [%put-bucket =desk =key =bucket]
[%del-bucket =desk =key]
[%put-entry =desk buc=key =key =val]
[%del-entry =desk buc=key =key]
==
+$ data
$% [%all =settings]
[%bucket =bucket]
[%desk desk=(map key bucket)]
[%entry =val]
==
--

View File

@ -5,11 +5,13 @@
+$ versioned-state +$ versioned-state
$% state-0 $% state-0
state-1 state-1
state-2
== ==
+$ state-0 [%0 settings=settings-0] +$ state-0 [%0 settings=settings-0]
+$ state-1 [%1 =settings] +$ state-1 [%1 settings=settings-1]
+$ state-2 [%2 =settings]
-- --
=| state-1 =| state-2
=* state - =* state -
:: ::
%- agent:dbug %- agent:dbug
@ -25,9 +27,8 @@
++ on-init ++ on-init
^- (quip card _this) ^- (quip card _this)
=^ cards state =^ cards state
(put-entry:do %tutorial %seen b+|) (put-entry:do q.byk.bol %tutorial %seen b+|)
[cards this] [cards this]
:: ::
++ on-save !>(state) ++ on-save !>(state)
:: ::
@ -38,7 +39,8 @@
|- |-
?- -.old ?- -.old
%0 $(old [%1 +.old]) %0 $(old [%1 +.old])
%1 [~ this(state old)] %1 $(old [%2 (~(put by *^settings) q.byk.bol settings.old)])
%2 `this(state old)
== ==
:: ::
++ on-poke ++ on-poke
@ -50,10 +52,10 @@
=/ evt=event !<(event vas) =/ evt=event !<(event vas)
=^ cards state =^ cards state
?- -.evt ?- -.evt
%put-bucket (put-bucket:do key.evt bucket.evt) %put-bucket (put-bucket:do [desk key bucket]:evt)
%del-bucket (del-bucket:do key.evt) %del-bucket (del-bucket:do [desk key]:evt)
%put-entry (put-entry:do buc.evt key.evt val.evt) %put-entry (put-entry:do [desk buc key val]:evt)
%del-entry (del-entry:do buc.evt key.evt) %del-entry (del-entry:do [desk buc key]:evt)
== ==
[cards this] [cards this]
:: ::
@ -65,15 +67,22 @@
[%all ~] [%all ~]
[~ this] [~ this]
:: ::
[%bucket @ ~] [%desk @ ~]
=* bucket-key i.t.pax =* desk i.t.pax
?> (~(has by settings) bucket-key) ?> (~(has by settings) desk)
[~ this] [~ this]
:: ::
[%entry @ @ ~] [%bucket @ @ ~]
=* bucket-key i.t.pax =* desk i.t.pax
=* entry-key i.t.t.pax =* bucket-key i.t.t.pax
=/ bucket (~(got by settings) bucket-key) ?> (~(has bi settings) desk bucket-key)
[~ this]
::
[%entry @ @ @ ~]
=* desk i.t.pax
=* bucket-key i.t.t.pax
=* entry-key i.t.t.t.pax
=/ bucket (~(got bi settings) desk bucket-key)
?> (~(has by bucket) entry-key) ?> (~(has by bucket) entry-key)
[~ this] [~ this]
== ==
@ -85,29 +94,38 @@
[%x %all ~] [%x %all ~]
``settings-data+!>(`data`all+settings) ``settings-data+!>(`data`all+settings)
:: ::
[%x %bucket @ ~] [%x %desk @ ~]
=* buc i.t.t.pax =* desk i.t.t.pax
=/ bucket=(unit bucket) (~(get by settings) buc) ?~ desk-settings=(~(get by settings) desk) [~ ~]
``settings-data+!>(desk+u.desk-settings)
::
[%x %bucket @ @ ~]
=* desk i.t.t.pax
=* buc i.t.t.t.pax
=/ bucket=(unit bucket) (~(get bi settings) desk buc)
?~ bucket [~ ~] ?~ bucket [~ ~]
``settings-data+!>(`data`bucket+u.bucket) ``settings-data+!>(`data`bucket+u.bucket)
:: ::
[%x %entry @ @ ~] [%x %entry @ @ @ ~]
=* buc i.t.t.pax =* desk i.t.t.pax
=* key i.t.t.t.pax =* buc i.t.t.t.pax
=/ =bucket (fall (~(get by settings) buc) ~) =* key i.t.t.t.t.pax
=/ =bucket (~(gut bi settings) desk buc *bucket)
=/ entry=(unit val) (~(get by bucket) key) =/ entry=(unit val) (~(get by bucket) key)
?~ entry [~ ~] ?~ entry [~ ~]
``settings-data+!>(`data`entry+u.entry) ``settings-data+!>(`data`entry+u.entry)
:: ::
[%x %has-bucket @ ~] [%x %has-bucket @ @ ~]
=* buc i.t.t.pax =/ desk i.t.t.pax
=/ has-bucket=? (~(has by settings) buc) =/ buc i.t.t.t.pax
=/ has-bucket=? (~(has bi settings) desk buc)
``noun+!>(`?`has-bucket) ``noun+!>(`?`has-bucket)
:: ::
[%x %has-entry @ @ ~] [%x %has-entry @ @ @ ~]
=* buc i.t.t.pax =* desk i.t.t.pax
=* key i.t.t.t.pax =* buc i.t.t.t.pax
=/ =bucket (fall (~(get by settings) buc) ~) =* key i.t.t.t.t.pax
=/ =bucket (~(gut bi settings) desk buc *bucket)
=/ has-entry=? (~(has by bucket) key) =/ has-entry=? (~(has by bucket) key)
``noun+!>(`?`has-entry) ``noun+!>(`?`has-entry)
== ==
@ -124,60 +142,63 @@
:: already exists :: already exists
:: ::
++ put-bucket ++ put-bucket
|= [=key =bucket] |= [=desk =key =bucket]
^- (quip card _state) ^- (quip card _state)
=/ pas=(list path) =/ pas=(list path)
:~ /all :~ /all
/bucket/[key] /desk/[desk]
/bucket/[desk]/[key]
== ==
:- [(give-event pas %put-bucket key bucket)]~ :- [(give-event pas %put-bucket desk key bucket)]~
state(settings (~(put by settings) key bucket)) state(settings (~(put bi settings) desk key bucket))
:: ::
:: +del-bucket: delete a bucket from the top level settings map :: +del-bucket: delete a bucket from the top level settings map
:: ::
++ del-bucket ++ del-bucket
|= =key |= [=desk =key]
^- (quip card _state) ^- (quip card _state)
=/ pas=(list path) =/ pas=(list path)
:~ /all :~ /all
/desk/[desk]
/bucket/[key] /bucket/[key]
== ==
:- [(give-event pas %del-bucket key)]~ :- [(give-event pas %del-bucket desk key)]~
state(settings (~(del by settings) key)) state(settings (~(del bi settings) desk key))
:: ::
:: +put-entry: put an entry in a bucket, overwriting if it already exists :: +put-entry: put an entry in a bucket, overwriting if it already exists
:: if bucket does not yet exist, create it :: if bucket does not yet exist, create it
:: ::
++ put-entry ++ put-entry
|= [buc=key =key =val] |= [=desk buc=key =key =val]
^- (quip card _state) ^- (quip card _state)
=/ pas=(list path) =/ pas=(list path)
:~ /all :~ /all
/bucket/[buc] /desk/[desk]
/entry/[buc]/[key] /bucket/[desk]/[buc]
/entry/[desk]/[buc]/[key]
== ==
=/ =bucket (fall (~(get by settings) buc) ~) =/ =bucket (~(put by (~(gut bi settings) desk buc *bucket)) key val)
=. bucket (~(put by bucket) key val) :- [(give-event pas %put-entry desk buc key val)]~
:- [(give-event pas %put-entry buc key val)]~ state(settings (~(put bi settings) desk key bucket))
state(settings (~(put by settings) buc bucket))
:: ::
:: +del-entry: delete an entry from a bucket, fail quietly if bucket does not :: +del-entry: delete an entry from a bucket, fail quietly if bucket does not
:: exist :: exist
:: ::
++ del-entry ++ del-entry
|= [buc=key =key] |= [=desk buc=key =key]
^- (quip card _state) ^- (quip card _state)
=/ pas=(list path) =/ pas=(list path)
:~ /all :~ /all
/bucket/[buc] /desk/[desk]
/entry/[buc]/[key] /bucket/[desk]/[buc]
/entry/[desk]/[buc]/[key]
== ==
=/ bucket=(unit bucket) (~(get by settings) buc) =/ bucket=(unit bucket) (~(get bi settings) desk buc)
?~ bucket ?~ bucket
[~ state] [~ state]
=. u.bucket (~(del by u.bucket) key) =. u.bucket (~(del by u.bucket) key)
:- [(give-event pas %del-entry buc key)]~ :- [(give-event pas %del-entry desk buc key)]~
state(settings (~(put by settings) buc u.bucket)) state(settings (~(put bi settings) desk buc u.bucket))
:: ::
++ give-event ++ give-event
|= [pas=(list path) evt=event] |= [pas=(list path) evt=event]

View File

@ -1,6 +1,7 @@
:~ :- %apes :~ :- %apes
:~ %docket :~ %docket
%treaty %treaty
%settings-store
== ==
:- %fish ~ :- %fish ~
== ==

View File

@ -1,8 +1,8 @@
:~ title+'Garden' :~ title+'Garden'
info+'An app launcher for Urbit.' info+'An app launcher for Urbit.'
color+0xee.5432 color+0xee.5432
::glob-http+'https://bootstrap.urbit.org/glob-0v6.t43bu.cpl0b.bsisc.sqr4d.dckpn.glob' glob-http+'https://bootstrap.urbit.org/glob-0v6.t43bu.cpl0b.bsisc.sqr4d.dckpn.glob'
glob-ames+~zod ::glob-ames+~zod
base+'grid' base+'grid'
version+[0 0 1] version+[0 0 1]
website+'https://tlon.io' website+'https://tlon.io'

55
pkg/garden/lib/mip.hoon Normal file
View File

@ -0,0 +1,55 @@
|%
++ mip :: map of maps
|$ [kex key value]
(map kex (map key value))
::
++ bi :: mip engine
=| a=(map * (map))
|@
++ del
|* [b=* c=*]
=+ d=(~(gut by a) b ~)
=+ e=(~(del by d) c)
?~ e
(~(del by a) b)
(~(put by a) b e)
::
++ get
|* [b=* c=*]
=> .(b `_?>(?=(^ a) p.n.a)`b, c `_?>(?=(^ a) ?>(?=(^ q.n.a) p.n.q.n.a))`c)
^- (unit _?>(?=(^ a) ?>(?=(^ q.n.a) q.n.q.n.a)))
(~(get by (~(gut by a) b ~)) c)
::
++ got
|* [b=* c=*]
(need (get b c))
::
++ gut
|* [b=* c=* d=*]
(~(gut by (~(gut by a) b ~)) c d)
::
++ has
|* [b=* c=*]
!=(~ (get b c))
::
++ key
|* b=*
~(key by (~(gut by a) b ~))
::
++ put
|* [b=* c=* d=*]
%+ ~(put by a) b
%. [c d]
%~ put by
(~(gut by a) b ~)
::
++ tap
::NOTE naive turn-based implementation find-errors ):
=< $
=+ b=`_?>(?=(^ a) *(list [x=_p.n.a _?>(?=(^ q.n.a) [y=p v=q]:n.q.n.a)]))`~
|. ^+ b
?~ a
b
$(a r.a, b (welp (turn ~(tap by q.n.a) (lead p.n.a)) $(a l.a)))
--
--

View File

@ -11,11 +11,16 @@
%all (settings +.dat) %all (settings +.dat)
%bucket (bucket +.dat) %bucket (bucket +.dat)
%entry (value +.dat) %entry (value +.dat)
%desk (desk-settings +.dat)
== ==
:: ::
++ settings ++ settings
|= s=^settings |= s=^settings
^- json ^- json
[%o (~(run by s) desk-settings)]
::
++ desk-settings
|= s=(map key ^bucket)
[%o (~(run by s) bucket)] [%o (~(run by s) bucket)]
:: ::
++ event ++ event
@ -30,35 +35,39 @@
== ==
:: ::
++ put-bucket ++ put-bucket
|= [k=key b=^bucket] |= [d=desk k=key b=^bucket]
^- json ^- json
%- pairs %- pairs
:~ bucket-key+s+k :~ bucket-key+s+k
bucket+(bucket b) bucket+(bucket b)
desk+s+d
== ==
:: ::
++ del-bucket ++ del-bucket
|= k=key |= [d=desk k=key]
^- json ^- json
%- pairs %- pairs
:~ bucket-key+s+k :~ bucket-key+s+k
desk+s+d
== ==
:: ::
++ put-entry ++ put-entry
|= [b=key k=key v=val] |= [d=desk b=key k=key v=val]
^- json ^- json
%- pairs %- pairs
:~ bucket-key+s+b :~ bucket-key+s+b
entry-key+s+k entry-key+s+k
value+(value v) value+(value v)
desk+s+d
== ==
:: ::
++ del-entry ++ del-entry
|= [buc=key =key] |= [d=desk buc=key =key]
^- json ^- json
%- pairs %- pairs
:~ bucket-key+s+buc :~ bucket-key+s+buc
entry-key+s+key entry-key+s+key
desk+s+d
== ==
:: ::
++ value ++ value
@ -93,25 +102,29 @@
:: ::
++ put-bucket ++ put-bucket
%- ot %- ot
:~ bucket-key+so :~ desk+so
bucket-key+so
bucket+bucket bucket+bucket
== ==
:: ::
++ del-bucket ++ del-bucket
%- ot %- ot
:~ bucket-key+so :~ desk+so
bucket-key+so
== ==
:: ::
++ put-entry ++ put-entry
%- ot %- ot
:~ bucket-key+so :~ desk+so
bucket-key+so
entry-key+so entry-key+so
value+value value+value
== ==
:: ::
++ del-entry ++ del-entry
%- ot %- ot
:~ bucket-key+so :~ desk+so
bucket-key+so
entry-key+so entry-key+so
== ==
:: ::

View File

@ -0,0 +1 @@
../../garden-dev/sur/settings.hoon

View File

@ -1,15 +1,23 @@
import React from 'react'; import React from 'react';
import { Setting } from '../../components/Setting'; import { Setting } from '../../components/Setting';
import { useSettingsState, SettingsState } from '../../state/settings';
import { usePreferencesStore } from './usePreferencesStore'; import { usePreferencesStore } from './usePreferencesStore';
const selDnd = (s: SettingsState) => s.display.doNotDisturb;
async function toggleDnd() {
const state = useSettingsState.getState();
await state.putEntry('display', 'doNotDisturb', !selDnd(state));
}
export const NotificationPrefs = () => { export const NotificationPrefs = () => {
const { doNotDisturb, mentions, toggleDoNotDisturb, toggleMentions } = usePreferencesStore(); const { mentions, toggleMentions } = usePreferencesStore();
const doNotDisturb = useSettingsState(selDnd);
return ( return (
<> <>
<h2 className="h3 mb-7">Notifications</h2> <h2 className="h3 mb-7">Notifications</h2>
<div className="space-y-3"> <div className="space-y-3">
<Setting on={doNotDisturb} toggle={toggleDoNotDisturb} name="Do Not Disturb"> <Setting on={doNotDisturb} toggle={toggleDnd} name="Do Not Disturb">
<p> <p>
Block visual desktop notifications whenever Urbit software produces an in-Landscape Block visual desktop notifications whenever Urbit software produces an in-Landscape
notification badge. notification badge.

View File

@ -7,6 +7,7 @@ import { persist } from 'zustand/middleware';
import Urbit, { SubscriptionRequestInterface } from '@urbit/http-api'; import Urbit, { SubscriptionRequestInterface } from '@urbit/http-api';
import { Poke } from '@urbit/api'; import { Poke } from '@urbit/api';
import api from './api'; import api from './api';
import { useMockData } from './util';
setAutoFreeze(false); setAutoFreeze(false);
enablePatches(); enablePatches();
@ -181,7 +182,7 @@ export async function pokeOptimisticallyN<A, S extends Record<string, unknown>>(
let num: string | undefined; let num: string | undefined;
try { try {
num = optReduceState(state, poke.json, reduce); num = optReduceState(state, poke.json, reduce);
await api.poke(poke); await (useMockData ? new Promise((res) => setTimeout(res, 500)) : api.poke(poke));
state.getState().removePatch(num); state.getState().removePatch(num);
} catch (e) { } catch (e) {
console.error(e); console.error(e);

View File

@ -0,0 +1,98 @@
/* eslint-disable no-param-reassign */
import {
SettingsUpdate,
Value,
putEntry as doPutEntry,
getDeskSettings,
DeskData
} from '@urbit/api/settings';
import _ from 'lodash';
import {
BaseState,
createState,
createSubscription,
pokeOptimisticallyN,
reduceStateN
} from './base';
import api from './api';
interface BaseSettingsState {
display: {
theme: 'light' | 'dark' | 'automatic';
doNotDisturb: boolean;
};
putEntry: (bucket: string, key: string, value: Value) => Promise<void>;
[ref: string]: unknown;
}
export type SettingsState = BaseSettingsState & BaseState<BaseSettingsState>;
function putBucket(json: SettingsUpdate, state: SettingsState): SettingsState {
const data = _.get(json, 'put-bucket', false);
if (data) {
state[data['bucket-key']] = data.bucket;
}
return state;
}
function delBucket(json: SettingsUpdate, state: SettingsState): SettingsState {
const data = _.get(json, 'del-bucket', false);
if (data) {
delete state[data['bucket-key']];
}
return state;
}
function putEntry(json: SettingsUpdate, state: any): SettingsState {
const data: Record<string, string> = _.get(json, 'put-entry', false);
if (data) {
if (!state[data['bucket-key']]) {
state[data['bucket-key']] = {};
}
state[data['bucket-key']][data['entry-key']] = data.value;
}
return state;
}
function delEntry(json: SettingsUpdate, state: any): SettingsState {
const data = _.get(json, 'del-entry', false);
if (data) {
delete state[data['bucket-key']][data['entry-key']];
}
return state;
}
export const reduceUpdate = [putBucket, delBucket, putEntry, delEntry];
export const useSettingsState = createState<BaseSettingsState>(
'Settings',
(set, get) => ({
display: {
theme: 'automatic',
doNotDisturb: true
},
loaded: false,
putEntry: async (bucket, key, val) => {
const poke = doPutEntry(window.desk, bucket, key, val);
await pokeOptimisticallyN(useSettingsState, poke, reduceUpdate);
},
fetchAll: async () => {
const result = (await api.scry<DeskData>(getDeskSettings(window.desk))).desk;
const newState = {
loaded: true,
..._.mergeWith(get(), result, (obj, src) => (_.isArray(src) ? src : undefined))
};
set(newState);
}
}),
[],
[
(set, get) =>
createSubscription('settings-store', `/desk/${window.desk}`, (e) => {
const data = _.get(e, 'settings-event', false);
if (data) {
reduceStateN(get(), data, reduceUpdate);
}
})
]
);

View File

@ -40,8 +40,8 @@ function delEntry(json: SettingsUpdate, state: any): SettingsState {
return state; return state;
} }
function getAll(json: any, state: SettingsState): SettingsState { function getDesk(json: any, state: SettingsState): SettingsState {
const data = _.get(json, 'all'); const data = _.get(json, 'desk');
if(data) { if(data) {
_.mergeWith(state, data, (obj, src) => _.isArray(src) ? src : undefined); _.mergeWith(state, data, (obj, src) => _.isArray(src) ? src : undefined);
} }
@ -75,7 +75,7 @@ export const reduceUpdate = [
]; ];
export const reduceScry = [ export const reduceScry = [
getAll, getDesk,
getBucket, getBucket,
getEntry getEntry
]; ];

View File

@ -16,7 +16,7 @@ import {
import { useCallback } from 'react'; import { useCallback } from 'react';
import { reduceUpdate } from '../reducers/settings-update'; import { reduceUpdate } from '../reducers/settings-update';
import airlock from '~/logic/api'; import airlock from '~/logic/api';
import { getAll, Value } from '@urbit/api'; import { getDeskSettings, Value } from '@urbit/api';
import { putEntry } from '@urbit/api/settings'; import { putEntry } from '@urbit/api/settings';
export interface ShortcutMapping { export interface ShortcutMapping {
@ -45,7 +45,7 @@ export interface SettingsState {
keyboard: ShortcutMapping; keyboard: ShortcutMapping;
remoteContentPolicy: RemoteContentPolicy; remoteContentPolicy: RemoteContentPolicy;
getAll: () => Promise<void>; getAll: () => Promise<void>;
putEntry: (bucket: string, key: string, value: Value) => void; putEntry: (bucket: string, key: string, value: Value) => Promise<void>;
leap: { leap: {
categories: LeapCategories[]; categories: LeapCategories[];
}; };
@ -101,20 +101,22 @@ const useSettingsState = createState<SettingsState>(
readGroup: 'shift+Escape' readGroup: 'shift+Escape'
}, },
getAll: async () => { getAll: async () => {
const { all } = await airlock.scry(getAll); const { desk } = await airlock.scry(getDeskSettings((window as any).desk));
get().set((s) => { get().set((s) => {
Object.assign(s, all); for(const bucket in desk) {
s[bucket] = { ...(s[bucket] || {}), ...desk[bucket] };
}
}); });
}, },
putEntry: (bucket: string, entry: string, value: Value) => { putEntry: async (bucket: string, entry: string, value: Value) => {
const poke = putEntry(bucket, entry, value); const poke = putEntry((window as any).desk, bucket, entry, value);
pokeOptimisticallyN(useSettingsState, poke, reduceUpdate); pokeOptimisticallyN(useSettingsState, poke, reduceUpdate);
} }
}), }),
[], [],
[ [
(set, get) => (set, get) =>
createSubscription('settings-store', '/all', (e) => { createSubscription('settings-store', `/desk/${(window as any).desk}`, (e) => {
const data = _.get(e, 'settings-event', false); const data = _.get(e, 'settings-event', false);
if (data) { if (data) {
reduceStateN(get(), data, reduceUpdate); reduceStateN(get(), data, reduceUpdate);

View File

@ -32,7 +32,6 @@ import Tiles from './components/tiles';
import Tile from './components/tiles/tile'; import Tile from './components/tiles/tile';
import './css/custom.css'; import './css/custom.css';
import { join } from '@urbit/api/groups'; import { join } from '@urbit/api/groups';
import { putEntry } from '@urbit/api/settings';
import { joinGraph } from '@urbit/api/graph'; import { joinGraph } from '@urbit/api/graph';
import airlock from '~/logic/api'; import airlock from '~/logic/api';
@ -103,15 +102,17 @@ export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
maxWidth: '350px', maxWidth: '350px',
modal: function modal(dismiss) { modal: function modal(dismiss) {
const onDismiss = (e) => { const onDismiss = (e) => {
const { putEntry } = useSettingsState.getState();
e.stopPropagation(); e.stopPropagation();
airlock.poke(putEntry('tutorial', 'seen', true)); putEntry('tutorial', 'seen', true);
dismiss(); dismiss();
}; };
const onContinue = async (e) => { const onContinue = async (e) => {
const { putEntry } = useSettingsState.getState();
e.stopPropagation(); e.stopPropagation();
if (!hasTutorialGroup({ associations })) { if (!hasTutorialGroup({ associations })) {
await airlock.poke(join(TUTORIAL_HOST, TUTORIAL_GROUP)); await airlock.poke(join(TUTORIAL_HOST, TUTORIAL_GROUP));
await airlock.poke(putEntry('tutorial', 'joined', Date.now())); await putEntry('tutorial', 'joined', Date.now());
await waiter(hasTutorialGroup); await waiter(hasTutorialGroup);
await Promise.all( await Promise.all(
[TUTORIAL_BOOK, TUTORIAL_CHAT, TUTORIAL_LINKS].map(graph => airlock.thread(joinGraph(TUTORIAL_HOST, graph)))); [TUTORIAL_BOOK, TUTORIAL_CHAT, TUTORIAL_LINKS].map(graph => airlock.thread(joinGraph(TUTORIAL_HOST, graph))));

View File

@ -4,7 +4,6 @@ import {
ManagedCheckboxField, Text ManagedCheckboxField, Text
} from '@tlon/indigo-react'; } from '@tlon/indigo-react';
import { Form, useFormikContext } from 'formik'; import { Form, useFormikContext } from 'formik';
import { putEntry } from '@urbit/api/settings';
import _ from 'lodash'; import _ from 'lodash';
import React from 'react'; import React from 'react';
import useSettingsState, { selectSettingsState } from '~/logic/state/settings'; import useSettingsState, { selectSettingsState } from '~/logic/state/settings';
@ -15,7 +14,6 @@ import {
import { FormikOnBlur } from '~/views/components/FormikOnBlur'; import { FormikOnBlur } from '~/views/components/FormikOnBlur';
import { ShuffleFields } from '~/views/components/ShuffleFields'; import { ShuffleFields } from '~/views/components/ShuffleFields';
import { BackButton } from './BackButton'; import { BackButton } from './BackButton';
import airlock from '~/logic/api';
const labels: Record<LeapCategories, string> = { const labels: Record<LeapCategories, string> = {
mychannel: 'My Channels', mychannel: 'My Channels',
@ -60,11 +58,12 @@ export function LeapSettings() {
}; };
const onSubmit = async (values: FormSchema) => { const onSubmit = async (values: FormSchema) => {
const { putEntry } = useSettingsState.getState();
const result = values.categories.reduce( const result = values.categories.reduce(
(acc, { display, category }) => (display ? [...acc, category] : acc), (acc, { display, category }) => (display ? [...acc, category] : acc),
[] as LeapCategories[] [] as LeapCategories[]
); );
await airlock.poke(putEntry('leap', 'categories', result)); await putEntry('leap', 'categories', result);
}; };
return ( return (

View File

@ -3,7 +3,6 @@ import _ from 'lodash';
import { Box, Col, Text } from '@tlon/indigo-react'; import { Box, Col, Text } from '@tlon/indigo-react';
import { Formik, Form, useField } from 'formik'; import { Formik, Form, useField } from 'formik';
import { putEntry } from '@urbit/api/settings';
import { getChord } from '~/logic/lib/util'; import { getChord } from '~/logic/lib/util';
import useSettingsState, { import useSettingsState, {
@ -12,7 +11,6 @@ import useSettingsState, {
} from '~/logic/state/settings'; } from '~/logic/state/settings';
import { AsyncButton } from '~/views/components/AsyncButton'; import { AsyncButton } from '~/views/components/AsyncButton';
import { BackButton } from './BackButton'; import { BackButton } from './BackButton';
import airlock from '~/logic/api';
const settingsSel = selectSettingsState(['keyboard']); const settingsSel = selectSettingsState(['keyboard']);
@ -69,9 +67,10 @@ export default function ShortcutSettings() {
initialValues={keyboard} initialValues={keyboard}
onSubmit={async (values: ShortcutMapping, actions) => { onSubmit={async (values: ShortcutMapping, actions) => {
const promises = _.map(values, (value, key) => { const promises = _.map(values, (value, key) => {
const { putEntry } = useSettingsState.getState();
return keyboard[key] !== value return keyboard[key] !== value
? airlock.poke(putEntry('keyboard', key, value)) ? putEntry('keyboard', key, value)
: Promise.resolve(0); : Promise.resolve();
}); });
await Promise.all(promises); await Promise.all(promises);
actions.setStatus({ success: null }); actions.setStatus({ success: null });

View File

@ -4,7 +4,7 @@ import {
ManagedTextInputField as Input, Row, ManagedTextInputField as Input, Row,
Text Text
} from '@tlon/indigo-react'; } from '@tlon/indigo-react';
import { join, MetadataUpdatePreview, putEntry } from '@urbit/api'; import { join, MetadataUpdatePreview } from '@urbit/api';
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
import _ from 'lodash'; import _ from 'lodash';
import React, { ReactElement, useCallback, useEffect, useState } from 'react'; import React, { ReactElement, useCallback, useEffect, useState } from 'react';
@ -22,6 +22,7 @@ import { FormError } from '~/views/components/FormError';
import { StatelessAsyncButton } from '~/views/components/StatelessAsyncButton'; import { StatelessAsyncButton } from '~/views/components/StatelessAsyncButton';
import { GroupSummary } from './GroupSummary'; import { GroupSummary } from './GroupSummary';
import airlock from '~/logic/api'; import airlock from '~/logic/api';
import useSettingsState from '~/logic/state/settings';
const formSchema = Yup.object({ const formSchema = Yup.object({
group: Yup.string() group: Yup.string()
@ -73,8 +74,9 @@ export function JoinGroup(props: JoinGroupProps): ReactElement {
const onConfirm = useCallback(async (group: string) => { const onConfirm = useCallback(async (group: string) => {
const [,,ship,name] = group.split('/'); const [,,ship,name] = group.split('/');
const { putEntry } = useSettingsState.getState();
if(group === TUTORIAL_GROUP_RESOURCE) { if(group === TUTORIAL_GROUP_RESOURCE) {
await airlock.poke(putEntry('tutorial', 'joined', Date.now())); await putEntry('tutorial', 'joined', Date.now());
} }
if (group in groups) { if (group in groups) {
return history.push(`/~landscape${group}`); return history.push(`/~landscape${group}`);

View File

@ -1,5 +1,5 @@
import { Box, Button, Col, Icon, Row, Text } from '@tlon/indigo-react'; import { Box, Button, Col, Icon, Row, Text } from '@tlon/indigo-react';
import { leaveGroup, putEntry } from '@urbit/api'; import { leaveGroup } from '@urbit/api';
import _ from 'lodash'; import _ from 'lodash';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
@ -16,6 +16,7 @@ import { Portal } from '~/views/components/Portal';
import { StatelessAsyncButton } from '~/views/components/StatelessAsyncButton'; import { StatelessAsyncButton } from '~/views/components/StatelessAsyncButton';
import { Triangle } from '~/views/components/Triangle'; import { Triangle } from '~/views/components/Triangle';
import airlock from '~/logic/api'; import airlock from '~/logic/api';
import useSettingsState from '~/logic/state/settings';
const localSelector = selectLocalState([ const localSelector = selectLocalState([
'tutorialProgress', 'tutorialProgress',
@ -94,7 +95,8 @@ export function TutorialModal() {
const dismiss = useCallback(async () => { const dismiss = useCallback(async () => {
setPaused(false); setPaused(false);
hideTutorial(); hideTutorial();
await airlock.poke(putEntry('tutorial', 'seen', true)); const { putEntry } = useSettingsState.getState();
await putEntry('tutorial', 'seen', true);
}, [hideTutorial]); }, [hideTutorial]);
const bailExit = useCallback(() => { const bailExit = useCallback(() => {

View File

@ -35,7 +35,6 @@
%observe-hook %observe-hook
%s3-store %s3-store
%sane %sane
%settings-store
%weather %weather
== ==
:- %fish :- %fish

View File

@ -1,7 +1,7 @@
:~ title+'Landscape' :~ title+'Landscape'
info+'A suite of applications to communicate on Urbit' info+'A suite of applications to communicate on Urbit'
color+0xee.5432 color+0xee.5432
glob+'https://bootstrap.urbit.org/glob-0v4.0k6hb.4s38v.su79d.10vd5.7c8lu.glob' glob-http+'https://bootstrap.urbit.org/glob-0v4.0k6hb.4s38v.su79d.10vd5.7c8lu.glob'
base+'landscape' base+'landscape'
version+[0 0 1] version+[0 0 1]
website+'https://tlon.io' website+'https://tlon.io'

View File

@ -1,31 +0,0 @@
|%
+$ settings-0 (map key bucket-0)
+$ bucket-0 (map key val-0)
+$ val-0
$% [%s p=@t]
[%b p=?]
[%n p=@]
==
::
+$ settings (map key bucket)
+$ bucket (map key val)
+$ key term
+$ val
$~ [%n 0]
$% [%s p=@t]
[%b p=?]
[%n p=@]
[%a p=(list val)]
==
+$ event
$% [%put-bucket =key =bucket]
[%del-bucket =key]
[%put-entry buc=key =key =val]
[%del-entry buc=key =key]
==
+$ data
$% [%all =settings]
[%bucket =bucket]
[%entry =val]
==
--

View File

@ -0,0 +1 @@
../../garden-dev/sur/settings.hoon

View File

@ -44,15 +44,16 @@
++ read-setting ++ read-setting
|= key=term |= key=term
=/ m (strand @t) ^- form:m =/ m (strand @t) ^- form:m
;< =bowl:spider bind:m get-bowl:strandio
;< has=? bind:m ;< has=? bind:m
%+ scry:strandio ? %+ scry:strandio ?
/gx/settings-store/has-entry/gcp-store/[key]/noun /gx/settings-store/has-entry/[q.byk.bowl]/gcp-store/[key]/noun
?. has ?. has
(strand-fail:strandio (rap 3 %gcp-missing- key ~) ~) (strand-fail:strandio (rap 3 %gcp-missing- key ~) ~)
;< =data:settings bind:m ;< =data:settings bind:m
%+ scry:strandio %+ scry:strandio
data:settings data:settings
/gx/settings-store/entry/gcp-store/[key]/settings-data /gx/settings-store/entry/[q.byk.bowl]/gcp-store/[key]/settings-data
?> ?=([%entry %s @] data) ?> ?=([%entry %s @] data)
(pure:m p.val.data) (pure:m p.val.data)
:: ::

View File

@ -40,9 +40,10 @@ b+has
|= key=@tas |= key=@tas
=/ m (strand ?) =/ m (strand ?)
^- form:m ^- form:m
;< =bowl:spider bind:m get-bowl:strandio
;< has=? bind:m ;< has=? bind:m
%+ scry:strandio ? %+ scry:strandio ?
/gx/settings-store/has-entry/gcp-store/[key]/noun /gx/settings-store/has-entry/[q.byk.bowl]/gcp-store/[key]/noun
(pure:m has) (pure:m has)
:: ::
-- --

View File

@ -1,4 +1,4 @@
import { Poke, Scry } from "../lib"; import { Poke, Scry } from '../lib';
import { PutBucket, Key, Bucket, DelBucket, Value, PutEntry, DelEntry, SettingsUpdate } from './types'; import { PutBucket, Key, Bucket, DelBucket, Value, PutEntry, DelEntry, SettingsUpdate } from './types';
export const action = <T extends SettingsUpdate>(data: T): Poke<T> => ({ export const action = <T extends SettingsUpdate>(data: T): Poke<T> => ({
@ -8,29 +8,35 @@ export const action = <T extends SettingsUpdate>(data: T): Poke<T> => ({
}); });
export const putBucket = ( export const putBucket = (
desk: string,
key: Key, key: Key,
bucket: Bucket bucket: Bucket
): Poke<PutBucket> => action({ ): Poke<PutBucket> => action({
'put-bucket': { 'put-bucket': {
desk,
'bucket-key': key, 'bucket-key': key,
'bucket': bucket 'bucket': bucket
} }
}); });
export const delBucket = ( export const delBucket = (
desk: string,
key: Key key: Key
): Poke<DelBucket> => action({ ): Poke<DelBucket> => action({
'del-bucket': { 'del-bucket': {
desk,
'bucket-key': key 'bucket-key': key
} }
}); });
export const putEntry = ( export const putEntry = (
desk: string,
bucket: Key, bucket: Key,
key: Key, key: Key,
value: Value value: Value
): Poke<PutEntry> => action({ ): Poke<PutEntry> => action({
'put-entry': { 'put-entry': {
desk,
'bucket-key': bucket, 'bucket-key': bucket,
'entry-key': key, 'entry-key': key,
value: value value: value
@ -38,10 +44,12 @@ export const putEntry = (
}); });
export const delEntry = ( export const delEntry = (
desk: string,
bucket: Key, bucket: Key,
key: Key key: Key
): Poke<DelEntry> => action({ ): Poke<DelEntry> => action({
'del-entry': { 'del-entry': {
desk,
'bucket-key': bucket, 'bucket-key': bucket,
'entry-key': key 'entry-key': key
} }
@ -50,17 +58,21 @@ export const delEntry = (
export const getAll: Scry = { export const getAll: Scry = {
app: 'settings-store', app: 'settings-store',
path: '/all' path: '/all'
} };
export const getBucket = (bucket: string) => ({ export const getBucket = (desk: string, bucket: string) => ({
app: 'settings-store', app: 'settings-store',
path: `/bucket/${bucket}` path: `/bucket/${bucket}`
}); });
export const getEntry = (bucket: string, entry: string) => ({ export const getEntry = (desk: string, bucket: string, entry: string) => ({
app: 'settings-store', app: 'settings-store',
path: `/entry/${bucket}/${entry}` path: `/entry/${desk}/${bucket}/${entry}`
});
export const getDeskSettings = (desk: string) => ({
app: 'settings-store',
path: `/desk/${desk}`
}); });
export * from './types'; export * from './types';

View File

@ -1,46 +1,54 @@
export type Key = string; export type Key = string;
export type Value = string | string[] | boolean | number; export type Value = string | string[] | boolean | number;
export type Bucket = Map<string, Value>; export type Bucket = { [key: string]: Value; };
export type Settings = Map<string, Bucket>; export type DeskSettings = { [bucket: string]: Bucket; };
export type Settings = { [desk: string]: Settings; }
export interface PutBucket { export interface PutBucket {
"put-bucket": { 'put-bucket': {
"bucket-key": Key; desk: string;
"bucket": Bucket; 'bucket-key': Key;
'bucket': Bucket;
}; };
} }
export interface DelBucket { export interface DelBucket {
"del-bucket": { 'del-bucket': {
"bucket-key": Key; desk: string;
'bucket-key': Key;
}; };
} }
export interface PutEntry { export interface PutEntry {
"put-entry": { 'put-entry': {
"bucket-key": Key; 'bucket-key': Key;
"entry-key": Key; 'entry-key': Key;
"value"?: Value; 'value'?: Value;
}; };
} }
export interface DelEntry { export interface DelEntry {
"del-entry": { 'del-entry': {
"bucket-key": Key; desk: string;
"entry-key": Key; 'bucket-key': Key;
'entry-key': Key;
}; };
} }
export interface AllData { export interface AllData {
"all": Settings; 'all': Settings;
}
export interface DeskData {
desk: DeskSettings;
} }
export interface BucketData { export interface BucketData {
"bucket": Bucket; 'bucket': Bucket;
} }
export interface EntryData { export interface EntryData {
"entry": Value; 'entry': Value;
} }
export type SettingsUpdate = export type SettingsUpdate =
@ -52,4 +60,5 @@ export type SettingsUpdate =
export type SettingsData = export type SettingsData =
| AllData | AllData
| BucketData | BucketData
| EntryData; | EntryData
| DeskData;