diff --git a/pkg/arvo/app/settings-store.hoon b/pkg/arvo/app/settings-store.hoon new file mode 100644 index 0000000000..f0df2b0c84 --- /dev/null +++ b/pkg/arvo/app/settings-store.hoon @@ -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)] +-- diff --git a/pkg/arvo/lib/settings.hoon b/pkg/arvo/lib/settings.hoon new file mode 100644 index 0000000000..8730de6e83 --- /dev/null +++ b/pkg/arvo/lib/settings.hoon @@ -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) + -- +-- diff --git a/pkg/arvo/mar/settings/data.hoon b/pkg/arvo/mar/settings/data.hoon new file mode 100644 index 0000000000..a58b017ef3 --- /dev/null +++ b/pkg/arvo/mar/settings/data.hoon @@ -0,0 +1,13 @@ +/+ *settings +|_ dat=data +++ grad %noun +++ grow + |% + ++ noun dat + ++ json (data:enjs dat) + -- +++ grab + |% + ++ noun data + -- +-- diff --git a/pkg/arvo/mar/settings/event.hoon b/pkg/arvo/mar/settings/event.hoon new file mode 100644 index 0000000000..7f03b3139c --- /dev/null +++ b/pkg/arvo/mar/settings/event.hoon @@ -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 + -- +-- diff --git a/pkg/arvo/sur/settings.hoon b/pkg/arvo/sur/settings.hoon new file mode 100644 index 0000000000..c2cf521ce6 --- /dev/null +++ b/pkg/arvo/sur/settings.hoon @@ -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] + == +-- diff --git a/pkg/interface/src/logic/api/global.ts b/pkg/interface/src/logic/api/global.ts index 0a7722a87e..8ed02020b4 100644 --- a/pkg/interface/src/logic/api/global.ts +++ b/pkg/interface/src/logic/api/global.ts @@ -11,6 +11,7 @@ import LaunchApi from './launch'; import GraphApi from './graph'; import S3Api from './s3'; import {HarkApi} from './hark'; +import SettingsApi from './settings'; export default class GlobalApi extends BaseApi { local = new LocalApi(this.ship, this.channel, this.store); @@ -22,6 +23,7 @@ export default class GlobalApi extends BaseApi { s3 = new S3Api(this.ship, this.channel, this.store); graph = new GraphApi(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( public ship: Patp, diff --git a/pkg/interface/src/logic/api/settings.ts b/pkg/interface/src/logic/api/settings.ts new file mode 100644 index 0000000000..7365c434c3 --- /dev/null +++ b/pkg/interface/src/logic/api/settings.ts @@ -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 { + private storeAction(action: SettingsEvent): Promise { + 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, + }}}); + } +} diff --git a/pkg/interface/src/logic/reducers/settings-update.ts b/pkg/interface/src/logic/reducers/settings-update.ts new file mode 100644 index 0000000000..9716cbb85c --- /dev/null +++ b/pkg/interface/src/logic/reducers/settings-update.ts @@ -0,0 +1,77 @@ +import _ from 'lodash'; +import { StoreState } from '../../store/type'; +import { + SettingsUpdate, +} from '~/types/settings'; + +type SettingsState = Pick; + +export default class SettingsReducer{ + 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; + } + } +} diff --git a/pkg/interface/src/logic/store/store.ts b/pkg/interface/src/logic/store/store.ts index 56720d4d97..05bc19770b 100644 --- a/pkg/interface/src/logic/store/store.ts +++ b/pkg/interface/src/logic/store/store.ts @@ -13,6 +13,7 @@ import { HarkReducer } from '../reducers/hark-update'; import GroupReducer from '../reducers/group-update'; import LaunchReducer from '../reducers/launch-update'; import ConnectionReducer from '../reducers/connection'; +import SettingsReducer from '../reducers/settings-update'; import {OrderedMap} from '../lib/OrderedMap'; import { BigIntOrderedMap } from '../lib/BigIntOrderedMap'; @@ -39,6 +40,7 @@ export default class GlobalStore extends BaseStore { groupReducer = new GroupReducer(); launchReducer = new LaunchReducer(); connReducer = new ConnectionReducer(); + settingsReducer = new SettingsReducer(); rehydrate() { this.localReducer.rehydrate(this.state); @@ -89,7 +91,8 @@ export default class GlobalStore extends BaseStore { graph: {}, group: {} }, - notificationsCount: 0 + notificationsCount: 0, + settings: {} }; } @@ -104,5 +107,6 @@ export default class GlobalStore extends BaseStore { this.connReducer.reduce(data, this.state); GraphReducer(data, this.state); HarkReducer(data, this.state); + this.settingsReducer.reduce(data, this.state); } } diff --git a/pkg/interface/src/logic/subscription/global.ts b/pkg/interface/src/logic/subscription/global.ts index ed4a2c94c8..5c88e0f8a7 100644 --- a/pkg/interface/src/logic/subscription/global.ts +++ b/pkg/interface/src/logic/subscription/global.ts @@ -44,6 +44,7 @@ export default class GlobalSubscription extends BaseSubscription { this.subscribe('/updates', 'hark-store'); this.subscribe('/updates', 'hark-graph-hook'); this.subscribe('/updates', 'hark-group-hook'); + this.subscribe('/all', 'settings-store'); } restart() { diff --git a/pkg/interface/src/types/cage.ts b/pkg/interface/src/types/cage.ts index ef6fceacf6..266188543f 100644 --- a/pkg/interface/src/types/cage.ts +++ b/pkg/interface/src/types/cage.ts @@ -5,6 +5,7 @@ import { MetadataUpdate } from "./metadata-update"; import { GroupUpdate } from "./group-update"; import { LaunchUpdate, WeatherState } from "./launch-update"; import { ConnectionStatus } from "./connection"; +import { SettingsUpdate } from "./settings"; interface MarksToTypes { readonly json: any; @@ -14,6 +15,7 @@ interface MarksToTypes { readonly groupUpdate: GroupUpdate; readonly "launch-update": LaunchUpdate; readonly "link-listen-update": LinkListenUpdate; + readonly "settings-event": SettingsUpdate; // not really marks but w/e readonly 'local': LocalUpdate; readonly 'weather': WeatherState | {}; diff --git a/pkg/interface/src/types/settings.ts b/pkg/interface/src/types/settings.ts new file mode 100644 index 0000000000..f0f50df491 --- /dev/null +++ b/pkg/interface/src/types/settings.ts @@ -0,0 +1,55 @@ +export type Key = string; +export type Value = string | boolean | number; +export type Bucket = Map; +export type Settings = Map; + +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; diff --git a/pkg/interface/src/views/App.js b/pkg/interface/src/views/App.js index a5468c6b57..42e2d7d2e4 100644 --- a/pkg/interface/src/views/App.js +++ b/pkg/interface/src/views/App.js @@ -94,6 +94,7 @@ class App extends React.Component { this.updateTheme(this.themeWatcher); }, 500); this.api.local.getBaseHash(); + this.api.settings.getAll(); this.store.rehydrate(); Mousetrap.bindGlobal(['command+/', 'ctrl+/'], (e) => { e.preventDefault();