mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-11-30 19:11:59 +03:00
contacts: added store, view, marks, ui
This commit is contained in:
parent
524bdbd8c1
commit
47c1745074
179
pkg/arvo/app/contact-store.hoon
Normal file
179
pkg/arvo/app/contact-store.hoon
Normal file
@ -0,0 +1,179 @@
|
||||
/+ *contact-json
|
||||
|%
|
||||
+$ move [bone card]
|
||||
::
|
||||
+$ card
|
||||
$% [%diff [%contact-update contact-update]]
|
||||
[%quit ~]
|
||||
==
|
||||
::
|
||||
+$ state
|
||||
$% state-zero
|
||||
==
|
||||
::
|
||||
+$ state-zero
|
||||
$: %0
|
||||
=rolodex
|
||||
==
|
||||
--
|
||||
::
|
||||
|_ [bol=bowl:gall state-zero]
|
||||
::
|
||||
++ this .
|
||||
::
|
||||
++ prep
|
||||
|= old=(unit state)
|
||||
^- (quip move _this)
|
||||
:- ~
|
||||
?~ old this
|
||||
?- -.old
|
||||
%0 this(+<+ u.old)
|
||||
==
|
||||
::
|
||||
++ peek-x-all
|
||||
|= pax=path
|
||||
^- (unit (unit [%noun (map path contacts)]))
|
||||
[~ ~ %noun rolodex]
|
||||
::
|
||||
++ peek-x-contacts
|
||||
|= pax=path
|
||||
^- (unit (unit [%noun (unit contacts)]))
|
||||
?~ pax
|
||||
~
|
||||
=/ contacts=(unit contacts) (~(get by rolodex) pax)
|
||||
[~ ~ %noun contacts]
|
||||
::
|
||||
++ peek-x-contact
|
||||
|= pax=path
|
||||
^- (unit (unit [%noun (unit contact)]))
|
||||
:: /:path/:ship
|
||||
=/ pas (flop pax)
|
||||
?~ pas
|
||||
~
|
||||
=/ =ship (slav %p i.pas)
|
||||
=. pax (scag (dec (lent pax)) `(list @ta)`pax)
|
||||
=/ contacts=(unit contacts) (~(get by rolodex) pax)
|
||||
?~ contacts
|
||||
~
|
||||
=/ contact=(unit contact) (~(get by u.contacts) ship)
|
||||
[~ ~ %noun contact]
|
||||
::
|
||||
++ peer-all
|
||||
|= pax=path
|
||||
^- (quip move _this)
|
||||
?> (team:title our.bol src.bol)
|
||||
:: send all updates from now on
|
||||
:_ this
|
||||
[ost.bol %diff %contact-update [%rolodex rolodex]]~
|
||||
::
|
||||
++ peer-updates
|
||||
|= pax=path
|
||||
^- (quip move _this)
|
||||
?> (team:title our.bol src.bol)
|
||||
:: send all updates from now on
|
||||
[~ this]
|
||||
::
|
||||
++ peer-contacts
|
||||
|= pax=path
|
||||
^- (quip move _this)
|
||||
?> (team:title our.bol src.bol)
|
||||
:_ this
|
||||
[ost.bol %diff %contact-update [%contacts pax (~(got by rolodex) pax)]]~
|
||||
::
|
||||
::++ poke-json
|
||||
:: |= =json
|
||||
:: ^- (quip move _this)
|
||||
:: ?> (team:title our.bol src.bol)
|
||||
:: (poke-contact-action (json-to-action json))
|
||||
::
|
||||
++ poke-contact-action
|
||||
|= action=contact-action
|
||||
^- (quip move _this)
|
||||
?> (team:title our.bol src.bol)
|
||||
?- -.action
|
||||
%create (handle-create action)
|
||||
%delete (handle-delete action)
|
||||
%add (handle-add action)
|
||||
%remove (handle-remove action)
|
||||
%edit (handle-edit action)
|
||||
==
|
||||
::
|
||||
++ handle-create
|
||||
|= act=contact-action
|
||||
^- (quip move _this)
|
||||
?> ?=(%create -.act)
|
||||
?: (~(has by rolodex) path.act)
|
||||
[~ this]
|
||||
:- (send-diff path.act act)
|
||||
this(rolodex (~(put by rolodex) path.act *contacts))
|
||||
::
|
||||
++ handle-delete
|
||||
|= act=contact-action
|
||||
^- (quip move _this)
|
||||
?> ?=(%delete -.act)
|
||||
?. (~(has by rolodex) path.act)
|
||||
[~ this]
|
||||
:- (send-diff path.act act)
|
||||
this(rolodex (~(del by rolodex) path.act))
|
||||
::
|
||||
++ handle-add
|
||||
|= act=contact-action
|
||||
^- (quip move _this)
|
||||
?> ?=(%add -.act)
|
||||
=/ contacts (~(got by rolodex) path.act)
|
||||
?> (~(has by contacts) ship.act)
|
||||
=. contacts (~(put by contacts) ship.act contact.act)
|
||||
:- (send-diff path.act act)
|
||||
this(rolodex (~(put by rolodex) path.act contacts))
|
||||
::
|
||||
++ handle-remove
|
||||
|= act=contact-action
|
||||
^- (quip move _this)
|
||||
?> ?=(%remove -.act)
|
||||
=/ contacts (~(got by rolodex) path.act)
|
||||
?< (~(has by contacts) ship.act)
|
||||
=. contacts (~(del by contacts) ship.act)
|
||||
:- (send-diff path.act act)
|
||||
this(rolodex (~(put by rolodex) path.act contacts))
|
||||
::
|
||||
++ handle-edit
|
||||
|= act=contact-action
|
||||
^- (quip move _this)
|
||||
?> ?=(%edit -.act)
|
||||
=/ contacts (~(got by rolodex) path.act)
|
||||
=/ contact (~(got by contacts) ship.act)
|
||||
=. contact (edit-contact contact edit-field.act)
|
||||
=. contacts (~(put by contacts) ship.act contact)
|
||||
:- (send-diff path.act act)
|
||||
this(rolodex (~(put by rolodex) path.act contacts))
|
||||
::
|
||||
++ edit-contact
|
||||
|= [con=contact edit=edit-field]
|
||||
^- contact
|
||||
?- -.edit
|
||||
%nickname con(nickname nickname.edit)
|
||||
%email con(email email.edit)
|
||||
%phone con(phone phone.edit)
|
||||
%website con(website website.edit)
|
||||
%notes con(notes notes.edit)
|
||||
%color con(color color.edit)
|
||||
%avatar con(avatar avatar.edit)
|
||||
==
|
||||
::
|
||||
++ update-subscribers
|
||||
|= [pax=path upd=contact-update]
|
||||
^- (list move)
|
||||
%+ turn (prey:pubsub:userlib pax bol)
|
||||
|= [=bone *]
|
||||
[bone %diff %contact-update upd]
|
||||
::
|
||||
++ send-diff
|
||||
|= [pax=path upd=contact-update]
|
||||
^- (list move)
|
||||
%- zing
|
||||
:~ (update-subscribers /all upd)
|
||||
(update-subscribers /updates upd)
|
||||
(update-subscribers [%contacts pax] upd)
|
||||
==
|
||||
::
|
||||
--
|
122
pkg/arvo/app/contact-view.hoon
Normal file
122
pkg/arvo/app/contact-view.hoon
Normal file
@ -0,0 +1,122 @@
|
||||
:: contact-view: sets up contact JS client and combines commands
|
||||
:: into semantic actions for the UI
|
||||
::
|
||||
/+ *server, *contact-json
|
||||
/= index
|
||||
/^ octs
|
||||
/; as-octs:mimes:html
|
||||
/: /===/app/contacts/index
|
||||
/| /html/
|
||||
/~ ~
|
||||
==
|
||||
/= tile-js
|
||||
/^ octs
|
||||
/; as-octs:mimes:html
|
||||
/: /===/app/contacts/js/tile
|
||||
/| /js/
|
||||
/~ ~
|
||||
==
|
||||
/= script
|
||||
/^ octs
|
||||
/; as-octs:mimes:html
|
||||
/: /===/app/contacts/js/index
|
||||
/| /js/
|
||||
/~ ~
|
||||
==
|
||||
/= style
|
||||
/^ octs
|
||||
/; as-octs:mimes:html
|
||||
/: /===/app/contacts/css/index
|
||||
/| /css/
|
||||
/~ ~
|
||||
==
|
||||
/= contact-png
|
||||
/^ (map knot @)
|
||||
/: /===/app/contacts/img /_ /png/
|
||||
::
|
||||
|%
|
||||
::
|
||||
+$ move [bone card]
|
||||
::
|
||||
+$ card
|
||||
$% [%http-response =http-event:http]
|
||||
[%connect wire binding:eyre term]
|
||||
[%quit ~]
|
||||
[%poke wire dock poke]
|
||||
==
|
||||
::
|
||||
+$ poke
|
||||
$% [%launch-action [@tas path @t]]
|
||||
==
|
||||
--
|
||||
::
|
||||
|_ [bol=bowl:gall ?]
|
||||
::
|
||||
++ this .
|
||||
::
|
||||
++ prep
|
||||
|= old=(unit ?)
|
||||
^- (quip move _this)
|
||||
?~ old
|
||||
:_ this
|
||||
:~ [ost.bol %connect / [~ /'~contact'] %contact-view]
|
||||
(launch-poke [/configs '/~contact/js/tile.js'])
|
||||
==
|
||||
[~ this(+<+ u.old)]
|
||||
::
|
||||
++ bound
|
||||
|= [wir=wire success=? binding=binding:eyre]
|
||||
^- (quip move _this)
|
||||
[~ this]
|
||||
::
|
||||
++ poke-handle-http-request
|
||||
%- (require-authorization:app ost.bol move this)
|
||||
|= =inbound-request:eyre
|
||||
^- (quip move _this)
|
||||
::
|
||||
=+ url=(parse-request-line url.request.inbound-request)
|
||||
=/ name=@t
|
||||
=+ back-path=(flop site.url)
|
||||
?~ back-path
|
||||
''
|
||||
i.back-path
|
||||
?: =(name 'tile')
|
||||
[[ost.bol %http-response (js-response:app tile-js)]~ this]
|
||||
?+ site.url
|
||||
:_ this
|
||||
[ost.bol %http-response not-found:app]~
|
||||
::
|
||||
:: styling
|
||||
::
|
||||
[%'~contacts' %css %index ~]
|
||||
:_ this
|
||||
[ost.bol %http-response (css-response:app style)]~
|
||||
::
|
||||
:: javascript
|
||||
::
|
||||
[%'~contacts' %js %index ~]
|
||||
:_ this
|
||||
[ost.bol %http-response (js-response:app script)]~
|
||||
::
|
||||
:: images
|
||||
::
|
||||
[%'~contacts' %img *]
|
||||
=/ img (as-octs:mimes:html (~(got by contact-png) `@ta`name))
|
||||
:_ this
|
||||
[ost.bol %http-response (png-response:app img)]~
|
||||
::
|
||||
:: main page
|
||||
::
|
||||
[%'~contacts' *]
|
||||
:_ this
|
||||
[ost.bol %http-response (html-response:app index)]~
|
||||
==
|
||||
::
|
||||
::
|
||||
:: +utilities
|
||||
::
|
||||
++ launch-poke
|
||||
|= [pax=path =cord]
|
||||
^- move
|
||||
[ost.bol %poke / [our.bol %launch] [%launch-action [%contact-view pax cord]]]
|
||||
--
|
2
pkg/arvo/app/contacts/css/index.css
Normal file
2
pkg/arvo/app/contacts/css/index.css
Normal file
File diff suppressed because one or more lines are too long
1
pkg/arvo/app/contacts/js/index.js
Normal file
1
pkg/arvo/app/contacts/js/index.js
Normal file
File diff suppressed because one or more lines are too long
1
pkg/arvo/app/contacts/js/tile.js
Normal file
1
pkg/arvo/app/contacts/js/tile.js
Normal file
File diff suppressed because one or more lines are too long
167
pkg/arvo/lib/contact-json.hoon
Normal file
167
pkg/arvo/lib/contact-json.hoon
Normal file
@ -0,0 +1,167 @@
|
||||
/- *contact-store
|
||||
|%
|
||||
++ nu :: parse number as hex
|
||||
|= jon/json
|
||||
?> ?=({$n *} jon)
|
||||
(rash p.jon hex)
|
||||
::
|
||||
++ rolodex-to-json
|
||||
|= rolo=rolodex
|
||||
=, enjs:format
|
||||
^- json
|
||||
%+ frond %contact-initial
|
||||
%- pairs
|
||||
%+ turn ~(tap by rolo)
|
||||
|= [pax=^path =contacts]
|
||||
^- [cord json]
|
||||
:- (spat pax)
|
||||
(pairs [%contacts (contacts-to-json contacts)]~)
|
||||
::
|
||||
++ contacts-to-json
|
||||
|= con=contacts
|
||||
^- json
|
||||
=, enjs:format
|
||||
%- pairs
|
||||
%+ turn ~(tap by con)
|
||||
|= [shp=^ship =contact]
|
||||
^- [cord json]
|
||||
:- (crip (slag 1 (scow %p shp)))
|
||||
(contact-to-json contact)
|
||||
::
|
||||
++ contact-to-json
|
||||
|= con=contact
|
||||
^- json
|
||||
=, enjs:format
|
||||
%- pairs
|
||||
:~ [%nickname s+nickname.con]
|
||||
[%email s+email.con]
|
||||
[%phone s+phone.con]
|
||||
[%website s+website.con]
|
||||
[%notes s+notes.con]
|
||||
[%color s+(scot %ux color.con)]
|
||||
[%avatar s+'TODO']
|
||||
==
|
||||
::
|
||||
++ edit-to-json
|
||||
|= edit=edit-field
|
||||
^- json
|
||||
?- -.edit
|
||||
%nickname s+nickname.edit
|
||||
%email s+email.edit
|
||||
%phone s+phone.edit
|
||||
%website s+website.edit
|
||||
%notes s+notes.edit
|
||||
%color s+(scot %ux color.edit)
|
||||
%avatar s+'TODO'
|
||||
==
|
||||
::
|
||||
++ update-to-json
|
||||
|= upd=contact-update
|
||||
=, enjs:format
|
||||
^- json
|
||||
%+ frond %contact-update
|
||||
%- pairs
|
||||
:~
|
||||
?: ?=(%create -.upd)
|
||||
[%create (pairs [%path (path path.upd)]~)]
|
||||
?: ?=(%delete -.upd)
|
||||
[%delete (pairs [%path (path path.upd)]~)]
|
||||
?: ?=(%add -.upd)
|
||||
:- %add
|
||||
%- pairs
|
||||
:~ [%path (path path.upd)]
|
||||
[%ship (ship ship.upd)]
|
||||
[%contact (contact-to-json contact.upd)]
|
||||
==
|
||||
?: ?=(%remove -.upd)
|
||||
:- %remove
|
||||
%- pairs
|
||||
:~ [%path (path path.upd)]
|
||||
[%ship (ship ship.upd)]
|
||||
==
|
||||
?: ?=(%edit -.upd)
|
||||
:- %edit
|
||||
%- pairs
|
||||
:~ [%path (path path.upd)]
|
||||
[%ship (ship ship.upd)]
|
||||
[%edit-field (edit-to-json edit-field.upd)]
|
||||
==
|
||||
[*@t *^json]
|
||||
==
|
||||
::
|
||||
++ json-to-action
|
||||
|= jon=json
|
||||
^- contact-action
|
||||
=, dejs:format
|
||||
=< (parse-json jon)
|
||||
|%
|
||||
++ parse-json
|
||||
%- of
|
||||
:~ [%create create]
|
||||
[%delete delete]
|
||||
[%add add]
|
||||
[%remove remove]
|
||||
[%edit edit]
|
||||
==
|
||||
::
|
||||
++ create
|
||||
(ot [%path pa]~)
|
||||
::
|
||||
++ delete
|
||||
(ot [%path pa]~)
|
||||
::
|
||||
++ add
|
||||
%- ot
|
||||
:~ [%path pa]
|
||||
[%ship (su ;~(pfix sig fed:ag))]
|
||||
[%contact cont]
|
||||
==
|
||||
::
|
||||
++ remove
|
||||
%- ot
|
||||
:~ [%path pa]
|
||||
[%ship (su ;~(pfix sig fed:ag))]
|
||||
==
|
||||
::
|
||||
++ edit
|
||||
%- ot
|
||||
:~ [%path pa]
|
||||
[%ship (su ;~(pfix sig fed:ag))]
|
||||
[%edit-field edit-fi]
|
||||
==
|
||||
--
|
||||
::
|
||||
++ octet
|
||||
%- ot:dejs:format
|
||||
:~ [%p ni:dejs:format]
|
||||
[%q so:dejs:format]
|
||||
==
|
||||
::
|
||||
++ avat
|
||||
%- ot:dejs:format
|
||||
:~ [%content-type so:dejs:format]
|
||||
[%octs octet]
|
||||
==
|
||||
::
|
||||
++ cont
|
||||
%- ot:dejs:format
|
||||
:~ [%nickname so:dejs:format]
|
||||
[%email so:dejs:format]
|
||||
[%phone so:dejs:format]
|
||||
[%website so:dejs:format]
|
||||
[%notes so:dejs:format]
|
||||
[%color nu]
|
||||
[%avatar (mu:dejs:format avat)]
|
||||
==
|
||||
::
|
||||
++ edit-fi
|
||||
%- of:dejs:format
|
||||
:~ [%nickname so:dejs:format]
|
||||
[%email so:dejs:format]
|
||||
[%phone so:dejs:format]
|
||||
[%website so:dejs:format]
|
||||
[%notes so:dejs:format]
|
||||
[%color nu]
|
||||
[%avatar (mu:dejs:format avat)]
|
||||
==
|
||||
--
|
@ -109,6 +109,8 @@
|
||||
%chat-view
|
||||
%chat-cli
|
||||
%soto
|
||||
%contact-store
|
||||
%contact-view
|
||||
==
|
||||
::
|
||||
++ deft-fish :: default connects
|
||||
|
42
pkg/arvo/sur/contact-store.hoon
Normal file
42
pkg/arvo/sur/contact-store.hoon
Normal file
@ -0,0 +1,42 @@
|
||||
/- *identity
|
||||
|%
|
||||
+$ rolodex (map path contacts)
|
||||
::
|
||||
+$ contacts (map ship contact)
|
||||
::
|
||||
+$ avatar [content-type=@t octs=[p=@ud q=@t]]
|
||||
::
|
||||
+$ contact
|
||||
$: nickname=@t
|
||||
email=@t
|
||||
phone=@t
|
||||
website=@t
|
||||
notes=@t
|
||||
color=@ux
|
||||
avatar=(unit avatar)
|
||||
==
|
||||
::
|
||||
+$ edit-field
|
||||
$% [%nickname nickname=@t]
|
||||
[%email email=@t]
|
||||
[%phone phone=@t]
|
||||
[%website website=@t]
|
||||
[%notes notes=@t]
|
||||
[%color color=@ux]
|
||||
[%avatar avatar=(unit avatar)]
|
||||
==
|
||||
::
|
||||
+$ contact-action
|
||||
$% [%create =path]
|
||||
[%delete =path]
|
||||
[%add =path =ship =contact]
|
||||
[%remove =path =ship]
|
||||
[%edit =path =ship =edit-field]
|
||||
==
|
||||
::
|
||||
+$ contact-update
|
||||
$% [%rolodex =rolodex]
|
||||
[%contacts =path =contacts]
|
||||
contact-action
|
||||
==
|
||||
--
|
183
pkg/interface/contacts/gulpfile.js
Normal file
183
pkg/interface/contacts/gulpfile.js
Normal file
@ -0,0 +1,183 @@
|
||||
var gulp = require('gulp');
|
||||
var cssimport = require('gulp-cssimport');
|
||||
var rollup = require('gulp-better-rollup');
|
||||
var cssnano = require('cssnano');
|
||||
var postcss = require('gulp-postcss');
|
||||
var sucrase = require('@sucrase/gulp-plugin');
|
||||
var minify = require('gulp-minify');
|
||||
var rename = require('gulp-rename');
|
||||
var del = require('del');
|
||||
|
||||
var resolve = require('rollup-plugin-node-resolve');
|
||||
var commonjs = require('rollup-plugin-commonjs');
|
||||
var rootImport = require('rollup-plugin-root-import');
|
||||
var globals = require('rollup-plugin-node-globals');
|
||||
|
||||
/***
|
||||
Main config options
|
||||
***/
|
||||
|
||||
var urbitrc = require('../urbitrc');
|
||||
|
||||
/***
|
||||
End main config options
|
||||
***/
|
||||
|
||||
gulp.task('css-bundle', function() {
|
||||
let plugins = [
|
||||
cssnano()
|
||||
];
|
||||
return gulp
|
||||
.src('src/index.css')
|
||||
.pipe(cssimport())
|
||||
.pipe(postcss(plugins))
|
||||
.pipe(gulp.dest('../../arvo/app/contacts/css'));
|
||||
});
|
||||
|
||||
gulp.task('jsx-transform', function(cb) {
|
||||
return gulp.src('src/**/*.js')
|
||||
.pipe(sucrase({
|
||||
transforms: ['jsx']
|
||||
}))
|
||||
.pipe(gulp.dest('dist'));
|
||||
});
|
||||
|
||||
gulp.task('tile-jsx-transform', function(cb) {
|
||||
return gulp.src('tile/**/*.js')
|
||||
.pipe(sucrase({
|
||||
transforms: ['jsx']
|
||||
}))
|
||||
.pipe(gulp.dest('dist'));
|
||||
});
|
||||
|
||||
gulp.task('js-imports', function(cb) {
|
||||
return gulp.src('dist/index.js')
|
||||
.pipe(rollup({
|
||||
plugins: [
|
||||
commonjs({
|
||||
namedExports: {
|
||||
'node_modules/react/index.js': [ 'Component' ],
|
||||
'node_modules/react-is/index.js': [ 'isValidElementType' ],
|
||||
}
|
||||
}),
|
||||
rootImport({
|
||||
root: `${__dirname}/dist/js`,
|
||||
useEntry: 'prepend',
|
||||
extensions: '.js'
|
||||
}),
|
||||
globals(),
|
||||
resolve()
|
||||
]
|
||||
}, 'umd'))
|
||||
.on('error', function(e){
|
||||
console.log(e);
|
||||
cb();
|
||||
})
|
||||
.pipe(gulp.dest('../../arvo/app/contacts/js/'))
|
||||
.on('end', cb);
|
||||
});
|
||||
|
||||
gulp.task('tile-js-imports', function(cb) {
|
||||
return gulp.src('dist/tile.js')
|
||||
.pipe(rollup({
|
||||
plugins: [
|
||||
commonjs({
|
||||
namedExports: {
|
||||
'node_modules/react/index.js': [ 'Component' ],
|
||||
}
|
||||
}),
|
||||
rootImport({
|
||||
root: `${__dirname}/dist/js`,
|
||||
useEntry: 'prepend',
|
||||
extensions: '.js'
|
||||
}),
|
||||
globals(),
|
||||
resolve()
|
||||
]
|
||||
}, 'umd'))
|
||||
.on('error', function(e){
|
||||
console.log(e);
|
||||
cb();
|
||||
})
|
||||
.pipe(gulp.dest('../../arvo/app/contacts/js/'))
|
||||
.on('end', cb);
|
||||
});
|
||||
|
||||
gulp.task('js-minify', function () {
|
||||
return gulp.src('../../arvo/app/contacts/js/index.js')
|
||||
.pipe(minify())
|
||||
.pipe(gulp.dest('../../arvo/app/contacts/js/'));
|
||||
});
|
||||
|
||||
gulp.task('tile-js-minify', function () {
|
||||
return gulp.src('../../arvo/app/contacts/js/tile.js')
|
||||
.pipe(minify())
|
||||
.pipe(gulp.dest('../../arvo/app/contacts/js/'));
|
||||
});
|
||||
|
||||
gulp.task('rename-index-min', function() {
|
||||
return gulp.src('../../arvo/app/contacts/js/index-min.js')
|
||||
.pipe(rename('index.js'))
|
||||
.pipe(gulp.dest('../../arvo/app/contacts/js/'))
|
||||
});
|
||||
|
||||
gulp.task('rename-tile-min', function() {
|
||||
return gulp.src('../../arvo/app/contacts/js/tile-min.js')
|
||||
.pipe(rename('tile.js'))
|
||||
.pipe(gulp.dest('../../arvo/app/contacts/js/'))});
|
||||
|
||||
gulp.task('clean-min', function() {
|
||||
return del(['../../arvo/app/contacts/js/index-min.js', '../../arvo/app/contacts/js/tile-min.js'], {force: true})
|
||||
});
|
||||
|
||||
gulp.task('urbit-copy', function () {
|
||||
let ret = gulp.src('../../arvo/**/*');
|
||||
|
||||
urbitrc.URBIT_PIERS.forEach(function(pier) {
|
||||
ret = ret.pipe(gulp.dest(pier));
|
||||
});
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
gulp.task('js-bundle-dev', gulp.series('jsx-transform', 'js-imports'));
|
||||
gulp.task('tile-js-bundle-dev', gulp.series('tile-jsx-transform', 'tile-js-imports'));
|
||||
gulp.task('js-bundle-prod', gulp.series('jsx-transform', 'js-imports', 'js-minify'))
|
||||
gulp.task('tile-js-bundle-prod',
|
||||
gulp.series('tile-jsx-transform', 'tile-js-imports', 'tile-js-minify'));
|
||||
|
||||
gulp.task('bundle-dev',
|
||||
gulp.series(
|
||||
gulp.parallel(
|
||||
'css-bundle',
|
||||
'js-bundle-dev',
|
||||
'tile-js-bundle-dev'
|
||||
),
|
||||
'urbit-copy'
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task('bundle-prod',
|
||||
gulp.series(
|
||||
gulp.parallel(
|
||||
'css-bundle',
|
||||
'js-bundle-prod',
|
||||
'tile-js-bundle-prod',
|
||||
),
|
||||
'rename-index-min',
|
||||
'rename-tile-min',
|
||||
'clean-min',
|
||||
'urbit-copy'
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task('default', gulp.series('bundle-dev'));
|
||||
|
||||
gulp.task('watch', gulp.series('default', function() {
|
||||
gulp.watch('tile/**/*.js', gulp.parallel('tile-js-bundle-dev'));
|
||||
|
||||
gulp.watch('src/**/*.js', gulp.parallel('js-bundle-dev'));
|
||||
gulp.watch('src/**/*.css', gulp.parallel('css-bundle'));
|
||||
|
||||
gulp.watch('../../arvo/**/*', gulp.parallel('urbit-copy'));
|
||||
}));
|
6529
pkg/interface/contacts/package-lock.json
generated
Normal file
6529
pkg/interface/contacts/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
42
pkg/interface/contacts/package.json
Normal file
42
pkg/interface/contacts/package.json
Normal file
@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "urbit-apps",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@sucrase/gulp-plugin": "^2.0.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-better-rollup": "^4.0.1",
|
||||
"gulp-cssimport": "^7.0.0",
|
||||
"gulp-minify": "^3.1.0",
|
||||
"gulp-postcss": "^8.0.0",
|
||||
"gulp-rename": "^1.4.0",
|
||||
"rollup": "^1.6.0",
|
||||
"rollup-plugin-commonjs": "^9.2.0",
|
||||
"rollup-plugin-node-globals": "^1.4.0",
|
||||
"rollup-plugin-node-resolve": "^4.0.0",
|
||||
"rollup-plugin-root-import": "^0.2.3",
|
||||
"sucrase": "^3.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.6",
|
||||
"del": "^5.1.0",
|
||||
"lodash": "^4.17.11",
|
||||
"moment": "^2.20.1",
|
||||
"mousetrap": "^1.6.3",
|
||||
"react": "^16.5.2",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"urbit-ob": "^4.1.2",
|
||||
"urbit-sigil-js": "^1.3.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"natives": "1.1.3"
|
||||
}
|
||||
}
|
147
pkg/interface/contacts/src/css/custom.css
Normal file
147
pkg/interface/contacts/src/css/custom.css
Normal file
@ -0,0 +1,147 @@
|
||||
p, h1, h2, h3, h4, h5, h6, a, input, textarea, button {
|
||||
margin-block-end: unset;
|
||||
margin-block-start: unset;
|
||||
-webkit-margin-before: unset;
|
||||
-webkit-margin-after: unset;
|
||||
font-family: Inter, sans-serif;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
textarea, input, button {
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
border: none;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000 !important;
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 32px;
|
||||
line-height: 48px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.body-regular {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.body-large {
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.body-medium {
|
||||
font-size: 16px;
|
||||
line-height: 19px;
|
||||
}
|
||||
|
||||
.body-small {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: #7f7f7f;
|
||||
}
|
||||
|
||||
.label-regular {
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.label-small {
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.label-small-mono {
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
font-family: "Source Code Pro", monospace;
|
||||
}
|
||||
|
||||
.body-regular-400 {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.plus-font {
|
||||
font-size: 32px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.btn-font {
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.fw-normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.fw-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.fs-italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.td-underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.bg-v-light-gray {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.nice-green {
|
||||
color: #2AA779 !important;
|
||||
}
|
||||
|
||||
.bg-nice-green {
|
||||
background: #2ED196;
|
||||
}
|
||||
|
||||
.nice-red {
|
||||
color: #EE5432 !important;
|
||||
}
|
||||
|
||||
.inter {
|
||||
font-family: Inter, sans-serif;
|
||||
}
|
||||
|
||||
.clamp-3 {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.clamp-message {
|
||||
max-width: calc(100% - 36px - 1.5rem);
|
||||
}
|
||||
|
||||
.clamp-attachment {
|
||||
overflow: scroll;
|
||||
max-height: 10em;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.lh-16 {
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: "Source Code Pro", monospace;
|
||||
}
|
||||
|
||||
.label-small-mono.list-ship {
|
||||
line-height: 29px;
|
||||
}
|
63
pkg/interface/contacts/src/css/fonts.css
Normal file
63
pkg/interface/contacts/src/css/fonts.css
Normal file
@ -0,0 +1,63 @@
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url("https://media.urbit.org/fonts/Inter-Regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: url("https://media.urbit.org/fonts/Inter-Italic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url("https://media.urbit.org/fonts/Inter-Bold.woff2") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: url("https://media.urbit.org/fonts/Inter-BoldItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-extralight.woff");
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-light.woff");
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-regular.woff");
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-medium.woff");
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-semibold.woff");
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-bold.woff");
|
||||
font-weight: 700;
|
||||
}
|
||||
|
38
pkg/interface/contacts/src/css/spinner.css
Normal file
38
pkg/interface/contacts/src/css/spinner.css
Normal file
@ -0,0 +1,38 @@
|
||||
.spinner-pending {
|
||||
position: relative;
|
||||
content: "";
|
||||
border-radius: 100%;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
|
||||
background-color: rgba(255,255,255,1);
|
||||
}
|
||||
|
||||
.spinner-pending::after {
|
||||
content: "";
|
||||
background-color: rgba(128,128,128,1);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
border-radius: 100%;
|
||||
clip: rect(0, 16px, 16px, 8px);
|
||||
|
||||
animation: spin 1s cubic-bezier(0.745, 0.045, 0.355, 1.000) infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {transform:rotate(0deg)}
|
||||
25% {transform:rotate(90deg)}
|
||||
50% {transform:rotate(180deg)}
|
||||
75% {transform:rotate(270deg)}
|
||||
100% {transform:rotate(360deg)}
|
||||
}
|
||||
|
||||
.spinner-nostart {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 100%;
|
||||
content:'';
|
||||
background-color: black;
|
||||
}
|
||||
|
2
pkg/interface/contacts/src/css/tachyons.css
Normal file
2
pkg/interface/contacts/src/css/tachyons.css
Normal file
File diff suppressed because one or more lines are too long
5
pkg/interface/contacts/src/index.css
Normal file
5
pkg/interface/contacts/src/index.css
Normal file
@ -0,0 +1,5 @@
|
||||
@import "css/tachyons.css";
|
||||
@import "css/fonts.css";
|
||||
@import "css/spinner.css";
|
||||
@import "css/custom.css";
|
||||
|
16
pkg/interface/contacts/src/index.js
Normal file
16
pkg/interface/contacts/src/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Root } from '/components/root';
|
||||
import { api } from '/api';
|
||||
import { store } from '/store';
|
||||
import { subscription } from "/subscription";
|
||||
|
||||
api.setAuthTokens({
|
||||
ship: window.ship
|
||||
});
|
||||
|
||||
subscription.start();
|
||||
|
||||
ReactDOM.render((
|
||||
<Root />
|
||||
), document.querySelectorAll("#root")[0]);
|
195
pkg/interface/contacts/src/js/api.js
Normal file
195
pkg/interface/contacts/src/js/api.js
Normal file
@ -0,0 +1,195 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import _ from 'lodash';
|
||||
import { uuid } from '/lib/util';
|
||||
import { store } from '/store';
|
||||
|
||||
|
||||
class UrbitApi {
|
||||
setAuthTokens(authTokens) {
|
||||
this.authTokens = authTokens;
|
||||
this.bindPaths = [];
|
||||
|
||||
this.groups = {
|
||||
add: this.groupAdd.bind(this),
|
||||
remove: this.groupRemove.bind(this)
|
||||
};
|
||||
|
||||
this.contacts = {
|
||||
create: this.contactCreate.bind(this),
|
||||
delete: this.contactDelete.bind(this),
|
||||
add: this.contactAdd.bind(this),
|
||||
remove: this.contactRemove.bind(this),
|
||||
edit: this.contactEdit.bind(this)
|
||||
};
|
||||
|
||||
this.invite = {
|
||||
accept: this.inviteAccept.bind(this),
|
||||
decline: this.inviteDecline.bind(this),
|
||||
invite: this.inviteInvite.bind(this)
|
||||
};
|
||||
}
|
||||
|
||||
bind(path, method, ship = this.authTokens.ship, app, success, fail, quit) {
|
||||
this.bindPaths = _.uniq([...this.bindPaths, path]);
|
||||
|
||||
window.subscriptionId = window.urb.subscribe(ship, app, path,
|
||||
(err) => {
|
||||
fail(err);
|
||||
},
|
||||
(event) => {
|
||||
success({
|
||||
data: event,
|
||||
from: {
|
||||
ship,
|
||||
path
|
||||
}
|
||||
});
|
||||
},
|
||||
(qui) => {
|
||||
quit(qui);
|
||||
});
|
||||
}
|
||||
|
||||
action(appl, mark, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
window.urb.poke(ship, appl, mark, data,
|
||||
(json) => {
|
||||
resolve(json);
|
||||
},
|
||||
(err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addPendingMessage(msg) {
|
||||
if (store.state.pendingMessages.has(msg.path)) {
|
||||
store.state.pendingMessages.get(msg.path).push(msg.envelope);
|
||||
} else {
|
||||
store.state.pendingMessages.set(msg.path, [msg.envelope]);
|
||||
}
|
||||
|
||||
store.setState({
|
||||
pendingMessages: store.state.pendingMessages
|
||||
});
|
||||
}
|
||||
|
||||
groupsAction(data) {
|
||||
this.action("group-store", "group-action", data);
|
||||
}
|
||||
|
||||
groupAdd(members, path) {
|
||||
this.groupsAction({
|
||||
add: {
|
||||
members, path
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
groupRemove(members, path) {
|
||||
this.groupsAction({
|
||||
remove: {
|
||||
members, path
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
contactAction(data) {
|
||||
this.action("contact-store", "json", data);
|
||||
}
|
||||
|
||||
contactCreate(path) {
|
||||
this.contactAction({ create: { path }});
|
||||
}
|
||||
|
||||
contactDelete(path) {
|
||||
this.contactAction({ delete: { path }});
|
||||
}
|
||||
|
||||
contactAdd(path, ship, contact = {
|
||||
nickname: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
website: '',
|
||||
notes: '',
|
||||
color: '0x000000',
|
||||
avatar: null
|
||||
}) {
|
||||
this.contactAction({
|
||||
add: {
|
||||
path, ship, contact
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
contactRemove(path, ship) {
|
||||
this.contactAction({
|
||||
remove: {
|
||||
path, ship
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
contactEdit(path, ship, editField) {
|
||||
/* editField can be...
|
||||
{nickname: ''}
|
||||
{email: ''}
|
||||
{phone: ''}
|
||||
{website: ''}
|
||||
{notes: ''}
|
||||
{color: '0xfff'}
|
||||
{avatar: null}
|
||||
{avatar: {p: length, q: bytestream}}
|
||||
*/
|
||||
this.contactAction({
|
||||
edit: {
|
||||
path, ship, 'edit-field': editField
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inviteAction(data) {
|
||||
this.action("invite-store", "json", data);
|
||||
}
|
||||
|
||||
inviteInvite(path, ship) {
|
||||
this.action("invite-hook", "json",
|
||||
{
|
||||
invite: {
|
||||
path: '/chat',
|
||||
invite: {
|
||||
path,
|
||||
ship: `~${window.ship}`,
|
||||
recipient: ship,
|
||||
app: 'chat-hook',
|
||||
text: `You have been invited to /${window.ship}${path}`,
|
||||
},
|
||||
uid: uuid()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
inviteAccept(uid) {
|
||||
this.inviteAction({
|
||||
accept: {
|
||||
path: '/chat',
|
||||
uid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inviteDecline(uid) {
|
||||
this.inviteAction({
|
||||
decline: {
|
||||
path: '/chat',
|
||||
uid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export let api = new UrbitApi();
|
||||
window.api = api;
|
50
pkg/interface/contacts/src/js/components/root.js
Normal file
50
pkg/interface/contacts/src/js/components/root.js
Normal file
@ -0,0 +1,50 @@
|
||||
import React, { Component } from 'react';
|
||||
import { BrowserRouter, Route, Link } from "react-router-dom";
|
||||
import classnames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { api } from '/api';
|
||||
import { subscription } from '/subscription';
|
||||
import { store } from '/store';
|
||||
import { Skeleton } from '/components/skeleton';
|
||||
|
||||
|
||||
export class Root extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = store.state;
|
||||
store.setStateHandler(this.setState.bind(this));
|
||||
this.setSpinner = this.setSpinner.bind(this);
|
||||
}
|
||||
|
||||
setSpinner(spinner) {
|
||||
this.setState({
|
||||
spinner
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<div>
|
||||
<Route exact path="/~contacts"
|
||||
render={ (props) => {
|
||||
return (
|
||||
<Skeleton>
|
||||
<div className="h-100 w-100 overflow-x-hidden flex flex-column">
|
||||
<div className="pl3 pr3 pt2 pb3">
|
||||
<h2>Home</h2>
|
||||
</div>
|
||||
</div>
|
||||
</Skeleton>
|
||||
);
|
||||
}} />
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
22
pkg/interface/contacts/src/js/components/skeleton.js
Normal file
22
pkg/interface/contacts/src/js/components/skeleton.js
Normal file
@ -0,0 +1,22 @@
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
|
||||
export class Skeleton extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="h-100 w-100 absolute">
|
||||
<div className="cf w-100 absolute flex"
|
||||
style={{
|
||||
height: 'calc(100% - 48px)'
|
||||
}}>
|
||||
<div className="h-100 w-100" style={{
|
||||
flexGrow: 1,
|
||||
}}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
63
pkg/interface/contacts/src/js/lib/util.js
Normal file
63
pkg/interface/contacts/src/js/lib/util.js
Normal file
@ -0,0 +1,63 @@
|
||||
import _ from 'lodash';
|
||||
import classnames from 'classnames';
|
||||
|
||||
|
||||
export function uuid() {
|
||||
let str = "0v"
|
||||
str += Math.ceil(Math.random()*8)+"."
|
||||
for (var i = 0; i < 5; i++) {
|
||||
let _str = Math.ceil(Math.random()*10000000).toString(32);
|
||||
_str = ("00000"+_str).substr(-5,5);
|
||||
str += _str+".";
|
||||
}
|
||||
|
||||
return str.slice(0,-1);
|
||||
}
|
||||
|
||||
export function isPatTa(str) {
|
||||
const r = /^[a-z,0-9,\-,\.,_,~]+$/.exec(str)
|
||||
return !!r;
|
||||
}
|
||||
|
||||
/*
|
||||
Goes from:
|
||||
~2018.7.17..23.15.09..5be5 // urbit @da
|
||||
To:
|
||||
(javascript Date object)
|
||||
*/
|
||||
export function daToDate(st) {
|
||||
var dub = function(n) {
|
||||
return parseInt(n) < 10 ? "0" + parseInt(n) : n.toString();
|
||||
};
|
||||
var da = st.split('..');
|
||||
var bigEnd = da[0].split('.');
|
||||
var lilEnd = da[1].split('.');
|
||||
var ds = `${bigEnd[0].slice(1)}-${dub(bigEnd[1])}-${dub(bigEnd[2])}T${dub(lilEnd[0])}:${dub(lilEnd[1])}:${dub(lilEnd[2])}Z`;
|
||||
return new Date(ds);
|
||||
}
|
||||
|
||||
/*
|
||||
Goes from:
|
||||
(javascript Date object)
|
||||
To:
|
||||
~2018.7.17..23.15.09..5be5 // urbit @da
|
||||
*/
|
||||
|
||||
export function dateToDa(d, mil) {
|
||||
var fil = function(n) {
|
||||
return n >= 10 ? n : "0" + n;
|
||||
};
|
||||
return (
|
||||
`~${d.getUTCFullYear()}.` +
|
||||
`${(d.getUTCMonth() + 1)}.` +
|
||||
`${fil(d.getUTCDate())}..` +
|
||||
`${fil(d.getUTCHours())}.` +
|
||||
`${fil(d.getUTCMinutes())}.` +
|
||||
`${fil(d.getUTCSeconds())}` +
|
||||
`${mil ? "..0000" : ""}`
|
||||
);
|
||||
}
|
||||
|
||||
export function deSig(ship) {
|
||||
return ship.replace('~', '');
|
||||
}
|
29
pkg/interface/contacts/src/js/reducers/contact-update.js
Normal file
29
pkg/interface/contacts/src/js/reducers/contact-update.js
Normal file
@ -0,0 +1,29 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
export class ContactUpdateReducer {
|
||||
reduce(json, state) {
|
||||
let data = _.get(json, 'contact-update', false);
|
||||
if (data) {
|
||||
this.create(data, state);
|
||||
this.delete(data, state);
|
||||
this.delete(data, state);
|
||||
}
|
||||
}
|
||||
|
||||
create(json, state) {
|
||||
let data = _.get(json, 'create', false);
|
||||
if (data) {
|
||||
state.contacts[data.path] = {};
|
||||
}
|
||||
}
|
||||
|
||||
delete(json, state) {
|
||||
let data = _.get(json, 'delete', false);
|
||||
if (data) {
|
||||
delete state.contacts[data.path];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
65
pkg/interface/contacts/src/js/reducers/group-update.js
Normal file
65
pkg/interface/contacts/src/js/reducers/group-update.js
Normal file
@ -0,0 +1,65 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
export class GroupUpdateReducer {
|
||||
reduce(json, state) {
|
||||
let data = _.get(json, 'group-update', false);
|
||||
if (data) {
|
||||
this.add(data, state);
|
||||
this.remove(data, state);
|
||||
this.bundle(data, state);
|
||||
this.unbundle(data, state);
|
||||
this.keys(data, state);
|
||||
this.path(data, state);
|
||||
}
|
||||
}
|
||||
|
||||
add(json, state) {
|
||||
let data = _.get(json, 'add', false);
|
||||
if (data) {
|
||||
for (let member of data.members) {
|
||||
state.groups[data.path].add(member);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remove(json, state) {
|
||||
let data = _.get(json, 'remove', false);
|
||||
if (data) {
|
||||
for (let member of data.members) {
|
||||
state.groups[data.path].delete(member);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bundle(json, state) {
|
||||
|
||||
let data = _.get(json, 'bundle', false);
|
||||
if (data) {
|
||||
state.groups[data.path] = new Set();
|
||||
}
|
||||
}
|
||||
|
||||
unbundle(json, state) {
|
||||
let data = _.get(json, 'unbundle', false);
|
||||
if (data) {
|
||||
delete state.groups[data.path];
|
||||
}
|
||||
}
|
||||
|
||||
keys(json, state) {
|
||||
let data = _.get(json, 'keys', false);
|
||||
if (data) {
|
||||
state.groupKeys = new Set(data.keys);
|
||||
}
|
||||
}
|
||||
|
||||
path(json, state) {
|
||||
let data = _.get(json, 'path', false);
|
||||
if (data) {
|
||||
state.groups[data.path] = new Set([data.members]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
34
pkg/interface/contacts/src/js/reducers/initial.js
Normal file
34
pkg/interface/contacts/src/js/reducers/initial.js
Normal file
@ -0,0 +1,34 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
export class InitialReducer {
|
||||
reduce(json, state) {
|
||||
let data = _.get(json, 'chat-initial', false);
|
||||
if (data) {
|
||||
state.inbox = data;
|
||||
}
|
||||
|
||||
data = _.get(json, 'group-initial', false);
|
||||
if (data) {
|
||||
for (let group in data) {
|
||||
state.groups[group] = new Set(data[group]);
|
||||
}
|
||||
}
|
||||
|
||||
data = _.get(json, 'permission-initial', false);
|
||||
if (data) {
|
||||
for (let perm in data) {
|
||||
state.permissions[perm] = {
|
||||
who: new Set(data[perm].who),
|
||||
kind: data[perm].kind
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data = _.get(json, 'invite-initial', false);
|
||||
if (data) {
|
||||
state.invites = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
53
pkg/interface/contacts/src/js/reducers/invite-update.js
Normal file
53
pkg/interface/contacts/src/js/reducers/invite-update.js
Normal file
@ -0,0 +1,53 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
export class InviteUpdateReducer {
|
||||
reduce(json, state) {
|
||||
let data = _.get(json, 'invite-update', false);
|
||||
if (data) {
|
||||
this.create(data, state);
|
||||
this.delete(data, state);
|
||||
this.invite(data, state);
|
||||
this.accepted(data, state);
|
||||
this.decline(data, state);
|
||||
}
|
||||
}
|
||||
|
||||
create(json, state) {
|
||||
let data = _.get(json, 'create', false);
|
||||
if (data) {
|
||||
state.invites[data.path] = {};
|
||||
}
|
||||
}
|
||||
|
||||
delete(json, state) {
|
||||
let data = _.get(json, 'delete', false);
|
||||
if (data) {
|
||||
delete state.invites[data.path];
|
||||
}
|
||||
}
|
||||
|
||||
invite(json, state) {
|
||||
let data = _.get(json, 'invite', false);
|
||||
if (data) {
|
||||
state.invites[data.path][data.uid] = data.invite;
|
||||
}
|
||||
}
|
||||
|
||||
accepted(json, state) {
|
||||
let data = _.get(json, 'accepted', false);
|
||||
if (data) {
|
||||
console.log(data);
|
||||
delete state.invites[data.path][data.uid];
|
||||
}
|
||||
}
|
||||
|
||||
decline(json, state) {
|
||||
let data = _.get(json, 'decline', false);
|
||||
if (data) {
|
||||
delete state.invites[data.path][data.uid];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
51
pkg/interface/contacts/src/js/reducers/permission-update.js
Normal file
51
pkg/interface/contacts/src/js/reducers/permission-update.js
Normal file
@ -0,0 +1,51 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
export class PermissionUpdateReducer {
|
||||
reduce(json, state) {
|
||||
let data = _.get(json, 'permission-update', false);
|
||||
if (data) {
|
||||
this.create(data, state);
|
||||
this.delete(data, state);
|
||||
this.add(data, state);
|
||||
this.remove(data, state);
|
||||
}
|
||||
}
|
||||
|
||||
create(json, state) {
|
||||
let data = _.get(json, 'create', false);
|
||||
if (data) {
|
||||
state.permissions[data.path] = {
|
||||
kind: data.kind,
|
||||
who: new Set(data.who)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
delete(json, state) {
|
||||
let data = _.get(json, 'delete', false);
|
||||
if (data) {
|
||||
delete state.permissions[data.path];
|
||||
}
|
||||
}
|
||||
|
||||
add(json, state) {
|
||||
let data = _.get(json, 'add', false);
|
||||
if (data) {
|
||||
for (let member of data.who) {
|
||||
state.permissions[data.path].who.add(member);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remove(json, state) {
|
||||
let data = _.get(json, 'remove', false);
|
||||
if (data) {
|
||||
for (let member of data.who) {
|
||||
state.permissions[data.path].who.delete(member);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
45
pkg/interface/contacts/src/js/store.js
Normal file
45
pkg/interface/contacts/src/js/store.js
Normal file
@ -0,0 +1,45 @@
|
||||
import { InitialReducer } from '/reducers/initial';
|
||||
import { ContactUpdateReducer } from '/reducers/contact-update';
|
||||
import { GroupUpdateReducer } from '/reducers/group-update';
|
||||
import { InviteUpdateReducer } from '/reducers/invite-update';
|
||||
import { PermissionUpdateReducer } from '/reducers/permission-update';
|
||||
|
||||
|
||||
class Store {
|
||||
constructor() {
|
||||
this.state = {
|
||||
contacts: {},
|
||||
groups: {},
|
||||
permissions: {},
|
||||
invites: {},
|
||||
spinner: false
|
||||
};
|
||||
|
||||
this.initialReducer = new InitialReducer();
|
||||
this.groupUpdateReducer = new GroupUpdateReducer();
|
||||
this.permissionUpdateReducer = new PermissionUpdateReducer();
|
||||
this.contactUpdateReducer = new ContactUpdateReducer();
|
||||
this.inviteUpdateReducer = new InviteUpdateReducer();
|
||||
this.setState = () => {};
|
||||
}
|
||||
|
||||
setStateHandler(setState) {
|
||||
this.setState = setState;
|
||||
}
|
||||
|
||||
handleEvent(data) {
|
||||
let json = data.data;
|
||||
|
||||
console.log(json);
|
||||
this.initialReducer.reduce(json, this.state);
|
||||
this.groupUpdateReducer.reduce(json, this.state);
|
||||
this.permissionUpdateReducer.reduce(json, this.state);
|
||||
this.contactUpdateReducer.reduce(json, this.state);
|
||||
this.inviteUpdateReducer.reduce(json, this.state);
|
||||
|
||||
this.setState(this.state);
|
||||
}
|
||||
}
|
||||
|
||||
export let store = new Store();
|
||||
window.store = store;
|
64
pkg/interface/contacts/src/js/subscription.js
Normal file
64
pkg/interface/contacts/src/js/subscription.js
Normal file
@ -0,0 +1,64 @@
|
||||
import { api } from '/api';
|
||||
import { store } from '/store';
|
||||
|
||||
import urbitOb from 'urbit-ob';
|
||||
|
||||
|
||||
export class Subscription {
|
||||
start() {
|
||||
if (api.authTokens) {
|
||||
this.initializeChat();
|
||||
} else {
|
||||
console.error("~~~ ERROR: Must set api.authTokens before operation ~~~");
|
||||
}
|
||||
}
|
||||
|
||||
initializeChat() {
|
||||
api.bind('/primary', 'PUT', api.authTokens.ship, 'chat-view',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this),
|
||||
this.handleQuitAndResubscribe.bind(this));
|
||||
api.bind('/primary', 'PUT', api.authTokens.ship, 'invite-view',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this),
|
||||
this.handleQuitAndResubscribe.bind(this));
|
||||
api.bind('/all', 'PUT', api.authTokens.ship, 'group-store',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this),
|
||||
this.handleQuitAndResubscribe.bind(this));
|
||||
api.bind('/all', 'PUT', api.authTokens.ship, 'permission-store',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this),
|
||||
this.handleQuitAndResubscribe.bind(this));
|
||||
}
|
||||
|
||||
handleEvent(diff) {
|
||||
store.handleEvent(diff);
|
||||
}
|
||||
|
||||
handleError(err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
handleQuitSilently(quit) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
handleQuitAndResubscribe(quit) {
|
||||
// TODO: resubscribe
|
||||
}
|
||||
|
||||
fetchMessages(start, end, path) {
|
||||
console.log(start, end, path);
|
||||
fetch(`/~chat/paginate/${start}/${end}${path}`)
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
store.handleEvent({
|
||||
data: json
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export let subscription = new Subscription();
|
31
pkg/interface/contacts/tile/tile.js
Normal file
31
pkg/interface/contacts/tile/tile.js
Normal file
@ -0,0 +1,31 @@
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
export default class ContactTile extends Component {
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
return (
|
||||
<div className="w-100 h-100 relative" style={{ background: '#1a1a1a' }}>
|
||||
<a className="w-100 h-100 db pa2 no-underline" href="/~chat">
|
||||
<p className="gray label-regular b absolute"
|
||||
style={{left: 8, top: 4}}>
|
||||
Contacts
|
||||
</p>
|
||||
<img
|
||||
className="absolute"
|
||||
style={{ left: 68, top: 65 }}
|
||||
src="/~chat/img/Tile.png"
|
||||
width={106}
|
||||
height={98} />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
window['contact-viewTile'] = ContactTile;
|
Loading…
Reference in New Issue
Block a user