This commit is contained in:
Galen Wolfe-Pauly 2015-08-19 12:38:25 -07:00
parent 9081b3a278
commit 743b0a632e
13 changed files with 2089 additions and 0 deletions

33
pub/work/fab/hymn.hook Normal file
View File

@ -0,0 +1,33 @@
::
::
:::: /hook/hymn/fab/talk/pub/
::
|%
++ cdnj
|= a=wall ^- marl
%+ turn a
|= lib=tape
;script(type "text/javascript", src "//cdnjs.cloudflare.com/ajax/libs/{lib}");
--
::
::::
::
^- manx
;html
;head
;meta(charset "utf-8");
;* %- cdnj :~
"jquery/2.1.1/jquery.js"
"lodash.js/2.4.1/lodash.min.js"
"react/0.13.1/react.js"
==
;meta(name "viewport", content "width=device-width, height=device-height, ".
"initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0");
;link(type "text/css", rel "stylesheet", href "/home/pub/work/src/css/main.css");
;title: Talk
==
;body
;div#c;
;script(type "text/javascript", src "/home/pub/work/src/js/main.js");
==
==

View File

@ -0,0 +1,84 @@
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau.woff");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-italic.woff");
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-medium.woff");
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-mediumitalic.woff");
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-bold.woff");
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-bolditalic.woff");
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-super.woff");
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-superitalic.woff");
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-extralight.woff");
font-weight: 200;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-light.woff");
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-regular.woff");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-medium.woff");
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-bold.woff");
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-black.woff");
font-weight: 700;
font-style: normal;
}

235
pub/work/src/css/main.css Normal file
View File

@ -0,0 +1,235 @@
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau.woff");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-italic.woff");
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-medium.woff");
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-mediumitalic.woff");
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-bold.woff");
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-bolditalic.woff");
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-super.woff");
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: "bau";
src: url("http://storage.googleapis.com/urbit-extra/bau-superitalic.woff");
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-extralight.woff");
font-weight: 200;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-light.woff");
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-regular.woff");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-medium.woff");
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-bold.woff");
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: "scp";
src: url("http://storage.googleapis.com/urbit-extra/scp-black.woff");
font-weight: 700;
font-style: normal;
}
html,
body {
font-family: "bau";
font-size: 18px;
}
#c {
position: absolute;
top: 0rem;
left: 50%;
width: 34rem;
margin-left: -17rem;
margin-bottom: 12rem;
}
h1 {
font-weight: 500;
}
.items {
margin-top: 4rem;
}
.item {
display: block;
max-height: 3rem;
margin-bottom: 1.5rem;
width: 36rem;
overflow: hidden;
background-color: #fff;
transition: max-height 200ms linear;
}
.item .comments {
height: 0;
}
.item.expand {
max-height: 16rem;
transition: max-height 200ms linear;
}
.item .expand {
margin-left: 2rem;
cursor: pointer;
transform-origin: 3px 12px;
transition: transform 200ms linear;
}
.item.expand .expand {
transform: rotate(90deg);
transition: transform 200ms linear;
}
.item .sort,
.item .title,
.item .date,
.item .tags,
.item .comment {
line-height: 2rem;
}
.item .sort,
.item .date {
font-family: 'scp';
}
.item .audience,
.item .date {
font-size: 0.7rem;
}
.item .audience,
.item .sort {
color: #ccc;
}
.item .audience {
text-transform: uppercase;
height: 1rem;
letter-spacing: 0.07rem;
margin-left: 2.2rem;
}
.item .done {
width: 0.7rem;
height: 0.7rem;
margin-top: 0.5rem;
border: 0.2rem solid #ccc;
}
.item .sort {
font-size: 0.6rem;
width: 2rem;
text-align: center;
}
.item .title {
min-width: 16rem;
margin-left: 0.5rem;
}
.item .date {
min-width: 6rem;
}
.item .tags {
min-width: 6rem;
}
.item .description,
.item .discussion {
line-height: 2rem;
margin: 0.5rem 0 0.5rem 2.3rem;
}
.item .description textarea {
min-width: 20rem;
min-height: 6rem;
}
.item .hr {
height: 0.2rem;
width: 6rem;
}
.item .comp {
width: 3rem;
opacity: 0;
}
.item .comp .a {
display: block;
font-size: 0.7rem;
font-weight: 500;
line-height: 0.9rem;
}
.item:hover .comp {
opacity: 1;
}
/* global */
.top {
vertical-align: top;
}
.ib {
display: inline-block;
}
.hidden {
display: none;
}
.a {
display: inline;
cursor: pointer;
text-decoration: underline;
}
.input {
outline: none;
display: inline-block;
padding: 0 0.3rem;
background-color: #f9f9f9;
border: 0;
font: inherit;
resize: none;
}
.input:focus {
background-color: #e6e6e6;
}
.caret.left {
border-left: 6px solid #000;
border-top: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid transparent;
margin-top: 0.4rem;
}

158
pub/work/src/css/main.styl Normal file
View File

@ -0,0 +1,158 @@
//
// fonts first
//
@import 'fonts'
html
body
font-family "bau"
font-size 18px
#c
position absolute
top 0rem
left 50%
width 34rem
margin-left -17rem
margin-bottom 12rem
h1
font-weight 500
.items
margin-top 4rem
.item
display block
max-height 3rem
margin-bottom 1.5rem
width 36rem
overflow hidden
background-color #fff
transition max-height 200ms linear
.item .comments
height 0
.item.expand
max-height 16rem
transition max-height 200ms linear
.item .expand
margin-left 2rem
cursor pointer
transform-origin 3px 12px
transition transform 200ms linear
.item.expand .expand
transform rotate(90deg)
transition transform 200ms linear
.item .sort
.item .title
.item .date
.item .tags
.item .comment
line-height 2rem
.item .sort
.item .date
font-family 'scp'
.item .audience
.item .date
font-size .7rem
.item .audience
.item .sort
color #ccc
.item .audience
text-transform uppercase
height 1rem
letter-spacing .07rem
margin-left 2.2rem
.item .done
width .7rem
height .7rem
margin-top .5rem
border .2rem solid #ccc
.item .sort
font-size .6rem
width 2rem
text-align center
.item .title
min-width 16rem
margin-left .5rem
.item .date
min-width 6rem
.item .tags
min-width 6rem
.item .description
.item .discussion
line-height 2rem
margin .5rem 0 .5rem 2.3rem
.item .description textarea
min-width 20rem
min-height 6rem
.item .hr
height .2rem
width 6rem
.item .comp
width 3rem
opacity 0
.item .comp .a
display block
font-size .7rem
font-weight 500
line-height .9rem
.item:hover .comp
opacity 1
/* global */
.top
vertical-align top
.ib
display inline-block
.hidden
display none
.a
display inline
cursor pointer
text-decoration underline
.input
outline none
display inline-block
padding 0 .3rem
background-color #f9f9f9
border 0
font inherit
resize none
.input:focus
background-color #e6e6e6
.caret.left
border-left 6px solid #000
border-top 6px solid transparent
border-right 6px solid transparent
border-bottom 6px solid transparent
margin-top .4rem

View File

@ -0,0 +1,28 @@
Dispatcher = require '../dispatcher/Dispatcher.coffee'
module.exports =
newItem: (index,list) ->
Dispatcher.handleViewAction
type:'newItem'
index:index
list:list
swapItems: (to,from,list) ->
Dispatcher.handleViewAction
type:'swapItem'
from:from
list:list
to:to
removeItem: (index,list) ->
Dispatcher.handleViewAction
type:'removeItem'
index:index
list:list
addItem: (index,item,list) ->
Dispatcher.handleViewAction
type:'addItem'
list:list
index:index
item:item

View File

@ -0,0 +1,98 @@
recl = React.createClass
[div,textarea] = [React.DOM.div,React.DOM.textarea]
WorkActions = require '../actions/WorkActions.coffee'
module.exports = recl
_dragStart: (e) ->
$t = $(e.target)
@dragged = $t.closest('.item')
e.dataTransfer.effectAllowed = 'move'
e.dataTransfer.setData 'text/html',e.currentTarget
@props._dragStart e,@
_dragEnd: (e) -> @props._dragEnd e,@
_keyDown: (e) ->
@props._keyDown e,@
kc = e.keyCode
switch kc
# tab - expand
when 9
if @state.expand is false
@setState {expand:true}
# esc - collapse
when 27
@setState {expand:false}
if (kc is 9 and @state.expand is false) or (kc is 27)
e.preventDefault()
return
_focus: (e) -> @props._focus e,@
formatDate: (d) ->
"#{d.getDate()}-#{(d.getMonth()+1)}-#{d.getFullYear()}"
getInitialState: -> {expand:false}
render: ->
itemClass = 'item'
if @state.expand then itemClass += ' expand'
(div {
className:itemClass
draggable:true
'data-index':@props.index
onDragStart:@_dragStart
onDragEnd:@_dragEnd
'data-index':@props.index
}, [
(div {className:'audience'},@props.item.audience.join(" "))
(div {className:'sort ib top'},@props.index)
(div {className:'done ib'},'')
(div {className:'title ib top'},[
(div {
contentEditable:true
onFocus:@_focus
onKeyDown:@_keyDown
className:'input'
},@props.item.title)
])
(div {className:'date ib top'}, [
(div {
contentEditable:true
className:'input'
},@formatDate(@props.item['date-created']))
])
(div {className:'tags ib top'},[
(div {
contentEditable:true
className:'input'
},@props.item.tags.join(" "))
])
(div {
className:'expand ib',
onClick: (e) =>
@setState {expand:!@state.expand}
},[
(div {className:'caret left'},"")
])
(div {className:"description"},[
(textarea {
className:'input'
},@props.item.description)
])
(div {className:"hr"},"")
(div {className:"discussion"},[
(div {className:"comments"}, @props.item.discussion.map (slug) ->
(div {className:'slug'}, slug)
),
(div {
contentEditable:true
className:'input comment'
},"")
])
])

View File

@ -0,0 +1,121 @@
recl = React.createClass
rece = React.createElement
[div,h1,input,textarea] = [React.DOM.div,React.DOM.h1,React.DOM.input,React.DOM.textarea]
WorkStore = require '../stores/WorkStore.coffee'
WorkActions = require '../actions/WorkActions.coffee'
ItemComponent = require './ItemComponent.coffee'
module.exports = recl
stateFromStore: -> {
list:WorkStore.getList @props.list
expand:false
}
getInitialState: -> @stateFromStore()
_onChangeStore: -> @setState @stateFromStore()
alias: ->
@$el = $ @getDOMNode()
@$items = @$el.find('.items').children()
_focus: (e,i) -> @setState {selected:Number(i.props.index)}
_dragStart: (e,i) -> @dragged = i.dragged
_dragEnd: (e,i) ->
from = Number @dragged.attr('data-index')
to = Number @over.attr('data-index')
if from<to then to--
if @drop is 'after' then to++
WorkActions.swapItems to,from,@props.list
@dragged.removeClass 'hidden'
@placeholder.remove()
_dragOver: (e,i) ->
e.preventDefault()
$t = $(e.target).closest('.item')
if $t.hasClass 'placeholder' then return
if $t.length is 0 then return
@over = $t
if not @dragged.hasClass('hidden') then @dragged.addClass 'hidden'
if (e.clientY - $t[0].offsetTop) < ($t[0].offsetHeight / 2)
@drop = 'before'
@placeholder.insertBefore $t
else
@drop = 'after'
@placeholder.insertAfter $t
_keyDown: (e) ->
kc = e.keyCode
switch kc
# enter - add new
when 13
if window.getSelection().getRangeAt(0).endOffset is 0
ins = @state.selected
else
ins = @state.selected+1
@setState {selected:ins,select:true}
WorkActions.newItem ins,@props.list
# backspace - remove if at 0
when 8
if window.getSelection().getRangeAt(0).endOffset is 0 and
e.target.innerText.length is 0
if @state.selected isnt 0
@setState {selected:@state.selected-1,select:"end"}
WorkActions.removeItem @state.selected,@props.list
e.preventDefault()
# up
when 38
last = @state.selected-1
if last<0 then last = @state.list.length-1
@$items.eq(last).find('.title .input').focus()
@setState {select:"end"}
# down
when 40
next = @state.selected+1
if next is @state.list.length then next = 0
@$items.eq(next).find('.title .input').focus()
@setState {select:"end"}
# cancel these
if (kc is 13) or (kc is 38) or (kc is 40) then e.preventDefault()
componentDidMount: ->
@placeholder = $ "<div class='item placeholder'><div class='sort'>x</div></div>"
WorkStore.addChangeListener @_onChangeStore
@alias()
componentDidUpdate: ->
@alias()
if @state.selected isnt undefined or @state.select
$title = @$items.eq(@state.selected).find('.title .input')
if @state.selected isnt undefined and @state.select
$title.focus()
if @state.select is "end"
r = window.getSelection().getRangeAt(0)
r.setStart $title[0],1
r.setEnd $title[0],1
s = window.getSelection()
s.removeAllRanges()
s.addRange r
if @state.select
@setState {select:false}
render: ->
(div {}, [
(div {
className:'items'
onDragOver:@_dragOver
}, [
_.map @state.list,(item,index) =>
rece(ItemComponent,{
item
index
@_focus
@_keyDown
@_dragStart
@_dragEnd})
])
])

View File

@ -0,0 +1,11 @@
recl = React.createClass
rece = React.createElement
[div,input,textarea] = [React.DOM.div,React.DOM.input,React.DOM.textarea]
ListComponent = require './ListComponent.coffee'
module.exports = recl
render: ->
(div {}, [
(rece(ListComponent,{list:'upcoming'}))
])

View File

@ -0,0 +1,13 @@
Dispatcher = require('flux').Dispatcher
module.exports = _.merge new Dispatcher(), {
handleServerAction: (action) ->
@dispatch
source: 'server'
action: action
handleViewAction: (action) ->
@dispatch
source: 'view'
action: action
}

View File

@ -0,0 +1,4 @@
WorkComponent = require './components/WorkComponent.coffee'
$ ->
React.render React.createElement(WorkComponent),$('#c')[0]

1192
pub/work/src/js/main.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
{
"name": "urbit-work",
"version": "0.0.0",
"repository": {
"type":"git",
"url":"https://github.com/urbit/urbit"
},
"description": "urbit work frontend",
"main": "main.js",
"dependencies": {
"coffeeify": "~0.7.0",
"flux": "~2.0.1",
"lodash": "~2.4.1",
"moment-timezone": "~0.2.4",
"object-assign": "^1.0.0"
}
}

View File

@ -0,0 +1,95 @@
EventEmitter = require('events').EventEmitter
assign = require 'object-assign'
Dispatcher = require '../dispatcher/Dispatcher.coffee'
_upcoming = [
id:0
sort:0
"date-created":new Date('2015-8-18')
"date-modifed":new Date('2015-8-18')
"date-due":null
owner:"~talsur-todres"
audience:["doznec/urbit-meta","doznec/tlon"]
status:"working"
tags:['food','office']
title:'get groceries'
description:'first go out the door, \n then walk down the block.'
discussion:[]
,
id:1
sort:1
"date-created":new Date('2015-8-18')
"date-modifed":new Date('2015-8-18')
"date-due":null
owner:"~talsur-todres"
audience:["doznec/tlon"]
status:"working"
tags:['home','office']
title:'eat'
description:'dont forget about lunch.'
discussion:[]
,
id:2
sort:2
"date-created":new Date('2015-8-18')
"date-modifed":new Date('2015-8-18')
"date-due":null
owner:"~talsur-todres"
audience:["doznec/tlon"]
status:"working"
tags:['home']
title:'sleep'
description:'go get some sleep.'
discussion:[]
]
_following = {}
_incoming = {}
lists =
'upcoming':_upcoming
'following':_following
'incoming':_incoming
WorkStore = assign {},EventEmitter.prototype,{
emitChange: -> @emit 'change'
addChangeListener: (cb) -> @on 'change', cb
removeChangeListener: (cb) -> @removeListener "change", cb
getList: (key) -> lists[key]
newItem: ({index,list}) ->
list = lists[list]
item =
id:index
sort:index
"date-created":new Date()
"date-modifed":new Date()
"date-due":null
owner:"~talsur-todres"
status:null
tags:[]
title:''
description:''
discussion:[]
list.splice index,0,item
swapItem: ({to,from,list}) ->
list = lists[list]
list.splice to,0,list.splice(from,1)[0]
removeItem: ({index,list}) ->
list = lists[list]
list.splice index,1
}
WorkStore.setMaxListeners 100
WorkStore.dispatchToken = Dispatcher.register (p) ->
a = p.action
if WorkStore[a.type]
WorkStore[a.type] a
WorkStore.emitChange()
module.exports = WorkStore