Merge pull request #4276 from urbit/ixv/settings

settings store
This commit is contained in:
ixv 2021-01-27 11:02:45 -08:00 committed by GitHub
commit 9715c280e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 566 additions and 1 deletions

View File

@ -0,0 +1,167 @@
/- *settings
/+ verb, dbug, default-agent
|%
+$ card card:agent:gall
+$ versioned-state
$% state-0
==
+$ state-0
$: %0
=settings
==
--
=| state-0
=* state -
::
%- agent:dbug
%+ verb |
^- agent:gall
=<
|_ bol=bowl:gall
+* this .
do ~(. +> bol)
def ~(. (default-agent this %|) bol)
::
++ on-init on-init:def
::
++ on-save !>(state)
::
++ on-load
|= =old=vase
^- (quip card _this)
=/ old !<(versioned-state old-vase)
?- -.old
%0 [~ this(state old)]
==
::
++ on-poke
|= [mar=mark vas=vase]
^- (quip card _this)
?> (team:title our.bol src.bol)
?. ?=(%settings-event mar)
(on-poke:def mar vas)
=/ evt=event !<(event vas)
=^ cards state
?- -.evt
%put-bucket (put-bucket:do key.evt bucket.evt)
%del-bucket (del-bucket:do key.evt)
%put-entry (put-entry:do buc.evt key.evt val.evt)
%del-entry (del-entry:do buc.evt key.evt)
==
[cards this]
::
++ on-watch
|= pax=path
^- (quip card _this)
?> (team:title our.bol src.bol)
?+ pax (on-watch:def pax)
[%all ~]
[~ this]
::
[%bucket @ ~]
=* bucket-key i.t.pax
?> (~(has by settings) bucket-key)
[~ this]
::
[%entry @ @ ~]
=* bucket-key i.t.pax
=* entry-key i.t.t.pax
=/ bucket (~(got by settings) bucket-key)
?> (~(has by bucket) entry-key)
[~ this]
==
::
++ on-peek
|= pax=path
^- (unit (unit cage))
?+ pax (on-peek:def pax)
[%x %all ~]
``settings-data+!>(all+settings)
::
[%x %bucket @ ~]
=* buc i.t.t.pax
=/ bucket=(unit bucket) (~(get by settings) buc)
?~ bucket [~ ~]
``settings-data+!>(bucket+u.bucket)
::
[%x %entry @ @ ~]
=* buc i.t.t.pax
=* key i.t.t.t.pax
=/ =bucket (fall (~(get by settings) buc) ~)
=/ entry=(unit val) (~(get by bucket) key)
?~ entry [~ ~]
``settings-data+!>(entry+u.entry)
==
::
++ on-agent on-agent:def
++ on-leave on-leave:def
++ on-arvo on-arvo:def
++ on-fail on-fail:def
--
::
|_ bol=bowl:gall
::
:: +put-bucket: put a bucket in the top level settings map, overwriting if it
:: already exists
::
++ put-bucket
|= [=key =bucket]
^- (quip card _state)
=/ pas=(list path)
:~ /all
/bucket/[key]
==
:- [(give-event pas %put-bucket key bucket)]~
state(settings (~(put by settings) key bucket))
::
:: +del-bucket: delete a bucket from the top level settings map
::
++ del-bucket
|= =key
^- (quip card _state)
=/ pas=(list path)
:~ /all
/bucket/[key]
==
:- [(give-event pas %del-bucket key)]~
state(settings (~(del by settings) key))
::
:: +put-entry: put an entry in a bucket, overwriting if it already exists
:: if bucket does not yet exist, create it
::
++ put-entry
|= [buc=key =key =val]
^- (quip card _state)
=/ pas=(list path)
:~ /all
/bucket/[buc]
/entry/[buc]/[key]
==
=/ =bucket (fall (~(get by settings) buc) ~)
=. bucket (~(put by bucket) key val)
:- [(give-event pas %put-entry buc key val)]~
state(settings (~(put by settings) buc bucket))
::
:: +del-entry: delete an entry from a bucket, fail quietly if bucket does not
:: exist
::
++ del-entry
|= [buc=key =key]
^- (quip card _state)
=/ pas=(list path)
:~ /all
/bucket/[buc]
/entry/[buc]/[key]
==
=/ bucket=(unit bucket) (~(get by settings) buc)
?~ bucket
[~ state]
=. u.bucket (~(del by u.bucket) key)
:- [(give-event pas %del-entry buc key)]~
state(settings (~(put by settings) buc u.bucket))
::
++ give-event
|= [pas=(list path) evt=event]
^- card
[%give %fact pas %settings-event !>(evt)]
--

132
pkg/arvo/lib/settings.hoon Normal file
View File

@ -0,0 +1,132 @@
/- *settings
|%
++ enjs
=, enjs:format
|%
++ data
|= dat=^data
^- json
%+ frond -.dat
?- -.dat
%all (settings +.dat)
%bucket (bucket +.dat)
%entry (value +.dat)
==
::
++ settings
|= s=^settings
^- json
[%o (~(run by s) bucket)]
::
++ event
|= evt=^event
^- json
%+ frond -.evt
?- -.evt
%put-bucket (put-bucket +.evt)
%del-bucket (del-bucket +.evt)
%put-entry (put-entry +.evt)
%del-entry (del-entry +.evt)
==
::
++ put-bucket
|= [k=key b=^bucket]
^- json
%- pairs
:~ bucket-key+s+k
bucket+(bucket b)
==
::
++ del-bucket
|= k=key
^- json
%- pairs
:~ bucket-key+s+k
==
::
++ put-entry
|= [b=key k=key v=val]
^- json
%- pairs
:~ bucket-key+s+b
entry-key+s+k
value+(val v)
==
::
++ del-entry
|= [buc=key =key]
^- json
%- pairs
:~ bucket-key+s+buc
entry-key+s+key
==
::
++ value
|= =val
^- json
?- -.val
%s val
%b val
%n (numb p.val)
==
::
++ bucket
|= b=^bucket
^- json
[%o (~(run by b) value)]
--
::
++ dejs
=, dejs:format
|%
++ event
|= jon=json
^- ^event
%. jon
%- of
:~ put-bucket+put-bucket
del-bucket+del-bucket
put-entry+put-entry
del-entry+del-entry
==
::
++ put-bucket
%- ot
:~ bucket-key+so
bucket+bucket
==
::
++ del-bucket
%- ot
:~ bucket-key+so
==
::
++ put-entry
%- ot
:~ bucket-key+so
entry-key+so
value+val
==
::
++ del-entry
%- ot
:~ bucket-key+so
entry-key+so
==
::
++ value
|= jon=json
^- val
?+ -.jon !!
%s jon
%b jon
%n [%n (rash p.jon dem)]
==
::
++ bucket
|= jon=json
^- ^bucket
?> ?=([%o *] jon)
(~(run by p.jon) value)
--
--

View File

@ -0,0 +1,13 @@
/+ *settings
|_ dat=data
++ grad %noun
++ grow
|%
++ noun dat
++ json (data:enjs dat)
--
++ grab
|%
++ noun data
--
--

View File

@ -0,0 +1,16 @@
/+ *settings
|_ evt=event
++ grad %noun
++ grow
|%
++ noun evt
++ json
%+ frond:enjs:format %settings-event
(event:enjs evt)
--
++ grab
|%
++ noun event
++ json event:dejs
--
--

View File

@ -0,0 +1,21 @@
|%
+$ settings (map key bucket)
+$ bucket (map key val)
+$ key term
+$ val
$% [%s p=@t]
[%b p=?]
[%n p=@]
==
+$ 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

@ -11,6 +11,7 @@ import LaunchApi from './launch';
import GraphApi from './graph'; import GraphApi from './graph';
import S3Api from './s3'; import S3Api from './s3';
import {HarkApi} from './hark'; import {HarkApi} from './hark';
import SettingsApi from './settings';
export default class GlobalApi extends BaseApi<StoreState> { export default class GlobalApi extends BaseApi<StoreState> {
local = new LocalApi(this.ship, this.channel, this.store); local = new LocalApi(this.ship, this.channel, this.store);
@ -22,6 +23,7 @@ export default class GlobalApi extends BaseApi<StoreState> {
s3 = new S3Api(this.ship, this.channel, this.store); s3 = new S3Api(this.ship, this.channel, this.store);
graph = new GraphApi(this.ship, this.channel, this.store); graph = new GraphApi(this.ship, this.channel, this.store);
hark = new HarkApi(this.ship, this.channel, this.store); hark = new HarkApi(this.ship, this.channel, this.store);
settings = new SettingsApi(this.ship, this.channel, this.store);
constructor( constructor(
public ship: Patp, public ship: Patp,

View File

@ -0,0 +1,74 @@
import BaseApi from './base';
import { StoreState } from '../store/type';
import {
SettingsUpdate,
SettingsData,
Key,
Value,
Bucket,
} from '~/types/settings';
export default class SettingsApi extends BaseApi<StoreState> {
private storeAction(action: SettingsEvent): Promise<any> {
return this.action('settings-store', 'settings-event', action);
}
putBucket(key: Key, bucket: Bucket) {
this.storeAction({
"put-bucket": {
"bucket-key": key,
"bucket": bucket,
}
});
}
delBucket(key: Key) {
this.storeAction({
"del-bucket": {
"bucket-key": key,
}
});
}
putEntry(buc: Key, key: Key, val: Value) {
this.storeAction({
"put-entry": {
"bucket-key": buc,
"entry-key": key,
"value": val,
}
});
}
delEntry(buc: Key, key: Key) {
this.storeAction({
"put-entry": {
"bucket-key": buc,
"entry-key": key,
}
});
}
async getAll() {
const data = await this.scry("settings-store", "/all");
this.store.handleEvent({data: {"settings-data": data.all}});
}
async getBucket(bucket: Key) {
const data = await this.scry('settings-store', `/bucket/${bucket}`);
this.store.handleEvent({data: {"settings-data": {
"bucket-key": bucket,
"bucket": data.bucket,
}}});
}
async getEntry(bucket: Key, entry: Key) {
const data = await this.scry('settings-store', `/entry/${bucket}/${entry}`);
this.store.handleEvent({data: {"settings-data": {
"bucket-key": bucket,
"entry-key": entry,
"entry": data.entry,
}}});
}
}

View File

@ -0,0 +1,77 @@
import _ from 'lodash';
import { StoreState } from '../../store/type';
import {
SettingsUpdate,
} from '~/types/settings';
type SettingsState = Pick<StoreState, 'settings'>;
export default class SettingsReducer<S extends SettingsState>{
reduce(json: Cage, state: S) {
let data = json["settings-event"];
if (data) {
this.putBucket(data, state);
this.delBucket(data, state);
this.putEntry(data, state);
this.delEntry(data, state);
}
data = json["settings-data"];
if (data) {
this.getAll(data, state);
this.getBucket(data, state);
this.getEntry(data, state);
}
}
putBucket(json: SettingsUpdate, state: S) {
const data = _.get(json, 'put-bucket', false);
if (data) {
state.settings[data["bucket-key"]] = data.bucket;
}
}
delBucket(json: SettingsUpdate, state: S) {
const data = _.get(json, 'del-bucket', false);
if (data) {
delete state.settings[data["bucket-key"]];
}
}
putEntry(json: SettingsUpdate, state: S) {
const data = _.get(json, 'put-entry', false);
if (data) {
if (!state.settings[data["bucket-key"]]) {
state.settings[data["bucket-key"]] = {};
}
state.settings[data["bucket-key"]][data["entry-key"]] = data.value;
}
}
delEntry(json: SettingsUpdate, state: S) {
const data = _.get(json, 'del-entry', false);
if (data) {
delete state.settings[data["bucket-key"]][data["entry-key"]];
}
}
getAll(json: any, state: S) {
state.settings = json;
}
getBucket(json: any, state: S) {
const key = _.get(json, 'bucket-key', false);
const bucket = _.get(json, 'bucket', false);
if (key && bucket) {
state.settings[key] = bucket;
}
}
getEntry(json: any, state: S) {
const bucketKey = _.get(json, 'bucket-key', false);
const entryKey = _.get(json, 'entry-key', false);
const entry = _.get(json, 'entry', false);
if (bucketKey && entryKey && entry) {
state.settings[bucketKey][entryKey] = entry;
}
}
}

View File

@ -13,6 +13,7 @@ import { HarkReducer } from '../reducers/hark-update';
import GroupReducer from '../reducers/group-update'; import GroupReducer from '../reducers/group-update';
import LaunchReducer from '../reducers/launch-update'; import LaunchReducer from '../reducers/launch-update';
import ConnectionReducer from '../reducers/connection'; import ConnectionReducer from '../reducers/connection';
import SettingsReducer from '../reducers/settings-update';
import {OrderedMap} from '../lib/OrderedMap'; import {OrderedMap} from '../lib/OrderedMap';
import { BigIntOrderedMap } from '../lib/BigIntOrderedMap'; import { BigIntOrderedMap } from '../lib/BigIntOrderedMap';
@ -39,6 +40,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
groupReducer = new GroupReducer(); groupReducer = new GroupReducer();
launchReducer = new LaunchReducer(); launchReducer = new LaunchReducer();
connReducer = new ConnectionReducer(); connReducer = new ConnectionReducer();
settingsReducer = new SettingsReducer();
rehydrate() { rehydrate() {
this.localReducer.rehydrate(this.state); this.localReducer.rehydrate(this.state);
@ -89,7 +91,8 @@ export default class GlobalStore extends BaseStore<StoreState> {
graph: {}, graph: {},
group: {} group: {}
}, },
notificationsCount: 0 notificationsCount: 0,
settings: {}
}; };
} }
@ -104,5 +107,6 @@ export default class GlobalStore extends BaseStore<StoreState> {
this.connReducer.reduce(data, this.state); this.connReducer.reduce(data, this.state);
GraphReducer(data, this.state); GraphReducer(data, this.state);
HarkReducer(data, this.state); HarkReducer(data, this.state);
this.settingsReducer.reduce(data, this.state);
} }
} }

View File

@ -44,6 +44,7 @@ export default class GlobalSubscription extends BaseSubscription<StoreState> {
this.subscribe('/updates', 'hark-store'); this.subscribe('/updates', 'hark-store');
this.subscribe('/updates', 'hark-graph-hook'); this.subscribe('/updates', 'hark-graph-hook');
this.subscribe('/updates', 'hark-group-hook'); this.subscribe('/updates', 'hark-group-hook');
this.subscribe('/all', 'settings-store');
} }
restart() { restart() {

View File

@ -5,6 +5,7 @@ import { MetadataUpdate } from "./metadata-update";
import { GroupUpdate } from "./group-update"; import { GroupUpdate } from "./group-update";
import { LaunchUpdate, WeatherState } from "./launch-update"; import { LaunchUpdate, WeatherState } from "./launch-update";
import { ConnectionStatus } from "./connection"; import { ConnectionStatus } from "./connection";
import { SettingsUpdate } from "./settings";
interface MarksToTypes { interface MarksToTypes {
readonly json: any; readonly json: any;
@ -14,6 +15,7 @@ interface MarksToTypes {
readonly groupUpdate: GroupUpdate; readonly groupUpdate: GroupUpdate;
readonly "launch-update": LaunchUpdate; readonly "launch-update": LaunchUpdate;
readonly "link-listen-update": LinkListenUpdate; readonly "link-listen-update": LinkListenUpdate;
readonly "settings-event": SettingsUpdate;
// not really marks but w/e // not really marks but w/e
readonly 'local': LocalUpdate; readonly 'local': LocalUpdate;
readonly 'weather': WeatherState | {}; readonly 'weather': WeatherState | {};

View File

@ -0,0 +1,55 @@
export type Key = string;
export type Value = string | boolean | number;
export type Bucket = Map<string, Value>;
export type Settings = Map<string, Bucket>;
interface PutBucket {
"put-bucket": {
"bucket-key": Key;
"bucket": Bucket;
};
}
interface DelBucket {
"del-bucket": {
"bucket-key": Key;
};
}
interface PutEntry {
"put-entry": {
"bucket-key": Key;
"entry-key": Key;
"value": Value;
};
}
interface DelEntry {
"del-entry": {
"bucket-key": Key;
"entry-key": Key;
};
}
interface AllData {
"all": Settings;
}
interface BucketData {
"bucket": Bucket;
}
interface EntryData {
"entry": Value;
}
export type SettingsUpdate =
| PutBucket
| DelBucket
| PutEntry
| DelEntry;
export type SettingsData =
| AllData
| BucketData
| EntryData;

View File

@ -94,6 +94,7 @@ class App extends React.Component {
this.updateTheme(this.themeWatcher); this.updateTheme(this.themeWatcher);
}, 500); }, 500);
this.api.local.getBaseHash(); this.api.local.getBaseHash();
this.api.settings.getAll();
this.store.rehydrate(); this.store.rehydrate();
Mousetrap.bindGlobal(['command+/', 'ctrl+/'], (e) => { Mousetrap.bindGlobal(['command+/', 'ctrl+/'], (e) => {
e.preventDefault(); e.preventDefault();