mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-11-28 03:00:15 +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-view
|
||||||
%chat-cli
|
%chat-cli
|
||||||
%soto
|
%soto
|
||||||
|
%contact-store
|
||||||
|
%contact-view
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ deft-fish :: default connects
|
++ 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