This commit is contained in:
Galen Wolfe-Pauly 2015-02-18 14:25:58 -08:00
parent d10ee595fa
commit c1fcc47513
22 changed files with 21336 additions and 161 deletions

View File

@ -9,19 +9,15 @@
;script(type "text/javascript", src "//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.js");
;script(type "text/javascript", src "//cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js");
;script(type "text/javascript", src "//cdnjs.cloudflare.com/ajax/libs/react/0.12.1/react.js");
;script(type "text/javascript", src "/gen/main/pub/talk/src/dep/director.js");
;script(type "text/javascript", src "/gen/main/pub/talk/src/js/dep/director.js");
;meta(name "viewport", content "width=432, initial-scale=1");
;script(type "text/javascript", src "//use.typekit.net/fkv0sjk.js");
;script:'''
try{Typekit.load();}catch(e){}
'''
;link(type "text/css", rel "stylesheet", href "/gen/main/pub/talk/src/main.css");
;link(type "text/css", rel "stylesheet", href "/gen/main/pub/talk/src/css/main.css");
;script(type "text/javascript", src "/gop/hart.js");
;title: Radio
==
;body
;div#c;
;script(type "text/javascript", src "/gen/main/lib/urb.js");
;script(type "text/javascript", src "/gen/main/pub/talk/src/main.js");
;script(type "text/javascript", src "/gen/main/pub/talk/src/js/main.js");
==
==

View File

@ -0,0 +1,72 @@
@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;
}

View File

@ -1,44 +1,80 @@
@font-face {
font-family: "bau";
src: url("http://office-for.com/lib/etc/bau/bauregular.otf");
src: url("http://storage.googleapis.com/urbit-extra/bau.woff");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: "bau";
src: url("http://office-for.com/lib/etc/bau/bauregularitalic.otf");
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://office-for.com/lib/etc/bau/baumedium.otf");
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://office-for.com/lib/etc/bau/baumediumitalic.otf");
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://office-for.com/lib/etc/bau/baubold.otf");
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://office-for.com/lib/etc/bau/baubolditalic.otf");
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;
}
.iden,
.audi,
.time,
#length {
font-family: "source-code-pro";
font-family: "scp";
}
.join-ctrl input {
font-family: "bau";
@ -93,11 +129,11 @@ body {
top: 0;
left: 50%;
width: 28rem;
max-height: 2.9rem;
max-height: 2.6rem;
overflow: hidden;
margin-left: -14rem;
padding-top: 1rem;
background-color: #fff;
background-color: #f5f5f5;
border-bottom: 3px solid #ededed;
transition: max-height 0.15s ease-out;
}
@ -119,7 +155,6 @@ body {
}
#station > div {
display: inline-block;
float: left;
}
#station-meta {
margin-right: 1rem;
@ -127,6 +162,8 @@ body {
}
#sources-container {
width: 6rem;
float: right;
margin: 1rem -6rem 0 0;
}
#members {
margin-left: 2rem;
@ -146,10 +183,12 @@ body {
width: 24rem;
}
.station .name {
padding: 0.3rem;
border-bottom: 6px solid transparent;
}
.station .name:hover {
border-bottom: 6px solid #000;
background-color: #f5f5f5;
border-bottom: 6px solid #ededed;
}
.station > div {
display: inline-block;
@ -174,7 +213,7 @@ body {
display: inline;
}
#sources-container .station {
font-family: "source-code-pro";
font-family: "scp";
font-weight: 500;
text-transform: lowercase;
}
@ -195,7 +234,6 @@ body {
}
.message {
padding-top: 0.3rem;
margin-bottom: 0.6rem;
}
#messages .message:last-child {
margin-bottom: 1rem;
@ -232,8 +270,11 @@ body {
#length {
vertical-align: top;
}
.attr {
color: #d7d7d7;
}
.attr .iden {
margin-left: 1rem;
color: #000;
}
.attr > div {
margin-right: 0.3rem;
@ -262,6 +303,7 @@ body {
#writing-container {
bottom: 4rem;
margin-bottom: 1rem;
float: left;
}
.writing {
padding-top: 0.3rem;
@ -273,11 +315,12 @@ body {
}
#writing {
font-size: 0.9rem;
min-height: 1.3rem;
min-height: 1.6rem;
line-height: 1.6rem;
min-width: 1.3rem;
padding: 0;
outline: none;
background-color: #20c567;
background-color: #eee;
}
#writing:focus {
background-color: #fff;
@ -296,7 +339,7 @@ input.join {
width: 24rem;
}
#station input.join {
font-family: "source-code-pro";
font-family: "scp";
font-size: 0.7rem;
line-height: 1rem;
width: 12rem;
@ -318,12 +361,12 @@ a.up {
}
.arow-up {
display: inline-block;
margin-right: 1rem;
margin: 0 0.5rem 0 0.5rem;
width: 0;
height: 0;
border-left: 9px solid transparent;
border-right: 9px solid transparent;
border-bottom: 9px solid #ff6b00;
border-bottom: 9px solid #000;
}
#scrolling {
display: none;

View File

@ -0,0 +1,379 @@
@import 'fonts'
.iden
.audi
.time
#length
font-family "scp"
.join-ctrl input
font-family "bau"
input
-webkit-appearance none
border-radius 0
html
body
height 100%
font-family "bau"
#length
.time
font-size .6rem
letter-spacing 0
font-weight 200
input.join
.iden
.audi
#station
font-size .7rem
html
body
font-size 18px
body
background-color #fefefe
padding 0
margin 0
.hidden
display none
#c
top 0
background-color #fff
#stations-container
position absolute
top 1rem
left 50%
width 24rem
margin-left -12rem
font-size 4rem
#station-container
position fixed
top 0
left 50%
width 28rem
max-height 2.6rem
overflow hidden
margin-left -14rem
padding-top 1rem
background-color #f5f5f5
border-bottom 3px solid #ededed
transition max-height 0.15s ease-out
#station-container:hover
max-height 12rem
transition max-height 0.25s ease-in
#stations-container
#messages-container
vertical-align top
#messaging-container
position absolute
top 4rem
left 50%
width 24rem
margin-left -12rem
margin-bottom 4rem
#station > div
display inline-block
#station-meta
margin-right 1rem
margin-bottom 1rem
#sources-container
width 6rem
float right
margin 1rem -6rem 0 0
#members
margin-left 2rem
#station .iden
display block
.station
display inline-block
width 9rem
margin-bottom .3rem
cursor pointer
font-weight 200
#stations .station
display block
width 24rem
.station .name
padding .3rem
border-bottom 6px solid transparent
.station .name:hover
background-color #f5f5f5
border-bottom 6px solid #ededed
.station > div
display inline-block
.toggle
width .4rem
height .4rem
border 2px solid #000
margin-right .6rem
.toggle.active
background-color #000
.station .remove
display none
float right
margin-left 1rem
font-weight 600
color #ff0000
.station:hover .remove
display inline
#sources-container .station
font-family "scp"
font-weight 500
text-transform lowercase
// hate this.
.sour-ctrl
margin-bottom .16rem
.join-ctrl input
.sour-ctrl input
border none
font-weight 400
.sour-ctrl input::-webkit-input-placeholder
font-family "bau"
font-size 1rem
font-weight 200
margin-left .6rem
color #0003FF
.message
padding-top .3rem
#messages .message:last-child
margin-bottom 1rem
#messages .message .time
opacity 0
#messages .message:last-child .time
#messages .message:hover .time
opacity 1
.time
margin-right .6rem
.member
width 12rem
margin .3rem .6rem .3rem 0
#messages
height auto
.mess
.iden
.attr > div
#station .member div
#writing
display inline-block
.iden > div
display inline
.mess
#writing
#length
vertical-align top
.attr
color #D7D7D7
.attr .iden
color #000
.attr > div
margin-right .3rem
.mess
font-size .9rem
line-height 1.6rem
letter-spacing .03rem
word-wrap break-word
max-width 31rem
.ship
font-weight 600
.ship.talk:before
content "..."
margin-left -1.3rem
margin-right .3rem
width 1rem
margin-top -.4rem
vertical-align middle
display inline-block
line-height .2rem
letter-spacing -.1rem
#writing-container
bottom 4rem
margin-bottom 1rem
float left
.writing
padding-top .3rem
.writing #length
display inline
margin-left 1rem
margin-top 1.2rem
#writing
font-size .9rem
min-height 1.6rem
line-height 1.6rem
min-width 1.3rem
padding 0
outline none
background-color #eee
#writing:focus
background-color #fff
#station h1
display inline-block
margin 0
font-weight 200
font-size 2rem
text-transform lowercase
input.join
font-size 4rem
background-color #fff
outline none
width 24rem
#station input.join
font-family "scp"
font-size .7rem
line-height 1rem
width 12rem
input.join::-webkit-input-placeholder
color #0003FF
input.join:focus::-webkit-input-placeholder
color #fff
.pending
color #ccc
a.up
height 2rem
margin-top .6rem
vertical-align middle
display inline-block
.arow-up
display inline-block
margin 0 .5rem 0 .5rem
width 0
height 0
border-left 9px solid transparent
border-right 9px solid transparent
border-bottom 9px solid #000
#scrolling
display none
.scrolling #scrolling
position fixed
bottom 3rem
left 2rem
height 1rem
padding 1rem
height 2rem
background-color #f9f9f9
font-weight 500
font-size .8rem
text-transform uppercase
@media (max-width: 40rem)
#c
left 0
margin-left 0
width 24rem
#messages-container
#writing-container
margin-left 1rem
#stations-container
#station-container
position relative
float left
#stations-container
width 8rem
#station-container
left auto
.station
width 5rem
.attr
display block
text-align left
width 2rem
margin-right 1rem
.message
height 1.6rem
.stations
.iden
#station
font-size .5rem
.station .remove
display inline
font-size .6rem
line-height .6rem
.ship.talk:before
margin-left -.3rem
margin-right 0
.attr
width 4rem
.iden > div
display block
.attr > .time
display none
.mess
max-width 12rem
margin-bottom 1rem
#writing
max-width 12rem

View File

@ -0,0 +1,63 @@
MessageDispatcher = require '../dispatcher/Dispatcher.coffee'
# hm
module.exports =
loadMessages: (grams,get) ->
MessageDispatcher.handleServerAction
type:"messages-load"
messages:grams.tele
last:grams.num
get:get
listenStation: (station,date) ->
if not date then date = window.urb.util.toDate(new Date())
window.chat.MessagePersistence.listenStation station,date
listeningStation: (station) ->
MessageDispatcher.handleViewAction
type:"messages-listen"
station:station
setTyping: (state) ->
MessageDispatcher.handleViewAction
type:"messages-typing"
state:state
getMore: (station,start,end) ->
MessageDispatcher.handleViewAction
type:"messages-fetch"
window.chat.MessagePersistence.get station,start,end
sendMessage: (station,message,audience) ->
serial = window.util.uuid32()
if station[0] isnt "~" then station = "~"+window.urb.ship+"/"+station
if audience.length is 0 then audience.push station
_audi = {}
for k,v of audience
_audi[v] =
envelope:
visible:true
sender:null
delivery:"pending"
_message =
ship:window.urb.ship
thought:
serial:serial
audience:_audi
statement:
bouquet:[]
speech:
lin:
say:false
txt:message
date: Date.now()
MessageDispatcher.handleViewAction
type:"message-send"
message:_message
window.chat.MessagePersistence.sendMessage _message.thought

View File

@ -0,0 +1,66 @@
StationDispatcher = require '../dispatcher/Dispatcher.coffee'
module.exports =
loadConfig: (station,config) ->
StationDispatcher.handleServerAction
type: "config-load"
station:station
config:config
switchStation: (station) ->
StationDispatcher.handleViewAction
type:"station-switch"
station:station
setAudience: (audience) ->
StationDispatcher.handleViewAction
type:"station-set-audience"
audience:audience
toggleAudience: (station) ->
StationDispatcher.handleViewAction
type:"station-audience-toggle"
station:station
removeStation: (station) ->
window.chat.StationPersistence.removeStation station
setSources: (station,sources) ->
window.chat.StationPersistence.setSources station,window.urb.ship,sources
createStation: (name) ->
window.chat.StationPersistence.createStation name
listenStation: (station) ->
window.chat.StationPersistence.listenStation station
listeningStation: (station) ->
StationDispatcher.handleViewAction
type:"station-listen"
station:station
setTyping: (station,state) ->
StationDispatcher.handleViewAction
type:"typing-set"
station:station
state:state
ping: (_ping) ->
window.chat.StationPersistence.ping _ping
loadStations: (stations) ->
StationDispatcher.handleServerAction
type:"stations-load"
stations:stations
loadMembers: (station,members) ->
StationDispatcher.handleServerAction
type:"members-load"
members:members
station:station
createStation: (station) ->
StationDispatcher.handleViewAction
type: "station-create"
station: station
window.chat.StationPersistence.createStation station

View File

@ -0,0 +1,11 @@
recl = React.createClass
[div,input,textarea] = [React.DOM.div,React.DOM.input,React.DOM.textarea]
module.exports = recl
render: ->
if @props.ship[0] isnt "~" then @props.ship = "~"+@props.ship
k = "ship"
k+= " #{@props.presence}" if @props.presence
div {className:"iden"}, [
div {className:k}, @props.ship
]

View File

@ -0,0 +1,126 @@
moment = require 'moment-timezone'
recl = React.createClass
[div,input,textarea] = [React.DOM.div,React.DOM.input,React.DOM.textarea]
MessageStore = require '../stores/MessageStore.coffee'
StationStore = require '../stores/StationStore.coffee'
MessageActions = require '../actions/MessageActions.coffee'
StationActions = require '../actions/StationActions.coffee'
Member = require './MemberComponent.coffee'
Message = recl
lz: (n) -> if n<10 then "0#{n}" else "#{n}"
convTime: (time) ->
d = new Date time
h = @lz d.getHours()
m = @lz d.getMinutes()
s = @lz d.getSeconds()
"~#{h}.#{m}.#{s}"
render: ->
# pendingClass = if @props.pending isnt "received" then "pending" else ""
delivery = _.uniq _.pluck @props.thought.audience, "delivery"
pendingClass = if delivery.indexOf("received") isnt -1 then "received" else "pending"
if pendingClass is "pending"
console.log @props.thought
console.log delivery
name = if @props.name then @props.name else ""
audi = _.remove _.keys(@props.thought.audience), (stat) =>
stat isnt "~"+window.urb.ship+"/"+@props.station
audi = audi.join " "
div {className:"message "+pendingClass}, [
(div {className:"attr"}, [
(Member {ship:@props.ship}, "")
div {className:"audi"}, "#{audi}"
div {className:"time"}, @convTime @props.thought.statement.date
])
div {className:"mess"}, @props.thought.statement.speech.lin.txt
]
module.exports = recl
pageSize: 50
paddingTop: 100
stateFromStore: -> {
messages:MessageStore.getAll()
last:MessageStore.getLast()
fetching:MessageStore.getFetching()
listening:MessageStore.getListening()
station:StationStore.getStation()
stations:StationStore.getStations()
configs:StationStore.getConfigs()
typing:MessageStore.getTyping()
}
getInitialState: -> @stateFromStore()
checkMore: ->
if $(window).scrollTop() < @paddingTop &&
@state.fetching is false &&
this.state.last &&
this.state.last > 0
end = @state.last-@pageSize
end = 0 if end < 0
@lastLength = @length
MessageActions.getMore @state.station,(@state.last+1),end
setAudience: ->
return if not @last
if _.keys(@last.thought.audience).length > 0 and @state.typing is false and
_.difference(_.keys(@last.thought.audience),@state.audi).length is 0
StationActions.setAudience _.keys(@last.thought.audience)
componentDidMount: ->
MessageStore.addChangeListener @_onChangeStore
StationStore.addChangeListener @_onChangeStore
if @state.station and @state.listening.indexOf(@state.station) is -1
MessageActions.listenStation @state.station
checkMore = @checkMore
$(window).on 'scroll', checkMore
window.util.setScroll()
componentDidUpdate: ->
$window = $(window)
if @lastLength
h = $('.message').height() * (@length-@lastLength)
st = $window.scrollTop()
$window.scrollTop st+h
@lastLength = null
else
if $('#writing-container').length > 0
window.util.setScroll()
componentWillUnmount: ->
MessageStore.removeChangeListener @_onChangeStore
StationStore.removeChangeListener @_onChangeStore
_onChangeStore: -> @setState @stateFromStore()
render: ->
station = @state.station
_station = "~"+window.urb.ship+"/"+station
sources = _.clone @state.configs[@state.station]?.sources ? []
sources.push _station
_messages = _.filter @state.messages, (_message) ->
audience = _.keys(_message.thought.audience)
_.intersection(sources,audience).length > 0
_messages = _.sortBy _messages, (_message) ->
_message.pending = _message.thought.audience[station]
_message.thought.statement.time
@last = _messages[_messages.length-1]
@length = _messages.length
setTimeout =>
@checkMore() if length < @pageSize
, 1
messages = _messages.map (_message) =>
_message.station = @state.station
Message _message, ""
div {id: "messages"}, messages

View File

@ -0,0 +1,15 @@
recl = React.createClass
[div,input,textarea] = [React.DOM.div,React.DOM.input,React.DOM.textarea]
StationComponent = require './StationComponent.coffee'
MessagesComponent = require './MessagesComponent.coffee'
WritingComponent = require './WritingComponent.coffee'
module.exports = recl
render: ->
div {id:"d"}, "asdf"
# div {id:"d"}, [
# (div {id:'station-container'}, (StationComponent {}, ""))
# (div {id:'messages-container'}, (MessagesComponent {}, ""))
# (div {id:'writing-container'}, (WritingComponent {}, ""))
# ]

View File

@ -0,0 +1,101 @@
recl = React.createClass
[div,input,textarea,h1,a] = [
React.DOM.div,
React.DOM.input,
React.DOM.textarea,
React.DOM.h1,
React.DOM.a
]
StationStore = require '../stores/StationStore.coffee'
StationActions = require '../actions/StationActions.coffee'
Member = require './MemberComponent.coffee'
module.exports = recl
stateFromStore: -> {
audi:StationStore.getAudience()
members:StationStore.getMembers()
station:StationStore.getStation()
stations:StationStore.getStations()
configs:StationStore.getConfigs()
typing:StationStore.getTyping()
listening:StationStore.getListening()
}
getInitialState: -> @stateFromStore()
componentDidMount: ->
@$el = $(@getDOMNode())
@$input = @$el.find('input')
StationStore.addChangeListener @_onChangeStore
if @state.listening.indexOf(@state.station) is -1
StationActions.listenStation @state.station
componentWillUnmount: ->
StationStore.removeChangeListener @_onChangeStore
_toggleAudi: (e) ->
$e = $(e.target).closest('.station')
station = $e.find('.path').text()
StationActions.toggleAudience station
_onChangeStore: ->
@setState @stateFromStore()
_keyUp: (e) ->
if e.keyCode is 13
v = @$input.val()
if @state.configs[@state.station].sources.indexOf(v) is -1
_sources = _.clone @state.configs[@state.station].sources
_sources.push v
StationActions.setSources @state.station,_sources
@$input.val('')
_remove: (e) ->
e.stopPropagation()
e.preventDefault()
_station = $(e.target).attr "data-station"
_sources = _.clone @state.configs[@state.station].sources
_sources.slice _sources.indexOf(_station),1
StationActions.setSources @state.station,_sources
render: ->
parts = []
members = []
if @state.station and @state.members[@state.station]
members = _.map @state.members[@state.station], (state,member) ->
Member {ship:member,presence:state.presence}
else
members = ""
sourceInput = [(input {className:"join",onKeyUp:@_keyUp,placeholder:"+"}, "")]
sourceCtrl = div {className:"sour-ctrl"}, sourceInput
sources = []
if @state.station and @state.configs[@state.station]
_remove = @_remove
_sources = _.clone @state.configs[@state.station].sources
_sources.push "twitter/hoontap"
sources = _.map _sources,(source) =>
toggleClass = "toggle "
if @state.audi.indexOf(source) isnt -1 then toggleClass += "active"
(div {className:"station",onClick:@_toggleAudi}, [
(div {className:toggleClass})
(div {className:"path"}, source),
(div {className:"remove",onClick:_remove,"data-station":source},"×")
])
else
sources = ""
station = []
station.push (a {className:"up",href:"\#/"}, [(div {className:"arow-up"}, "")])
station.push (h1 {},@state.station)
station.push (div {id:"members"},members)
parts.push (div {id:"station-container"}, (div {id:"station-meta"},station))
parts.push (div {id:"sources-container"}, [(div {class:"sources-list"},sources),sourceCtrl])
div {id:"station"},parts

View File

@ -0,0 +1,61 @@
recl = React.createClass
[div,input] = [React.DOM.div,React.DOM.input]
StationStore = require '../stores/StationStore.coffee'
StationActions = require '../actions/StationActions.coffee'
module.exports = recl
stateFromStore: -> {
stations: StationStore.getStations()
station: StationStore.getStation()
}
getInitialState: -> @stateFromStore()
componentDidMount: ->
@$el = $(@getDOMNode())
@$add = $('#stations .add')
@$input = @$el.find('input')
StationStore.addChangeListener @_onChangeStore
componentWillUnmount: ->
StationStore.removeChangeListener @_onChangeStore
_onChangeStore: -> @setState @stateFromStore()
_click: (e) ->
s = $(e.target).closest('.station').find('.name').text()
window.location.hash = "/#{s.toLowerCase()}"
_keyUp: (e) ->
if e.keyCode is 13
v = @$input.val().toLowerCase()
if @state.stations.indexOf(v) is -1
StationActions.createStation v
@$input.val('')
@$input.blur()
_remove: (e) ->
_station = $(e.target).parent().find('.name').text()
_stations = _.without @state.stations,_station
StationActions.removeStation _station,_stations
e.stopPropagation()
e.preventDefault()
render: ->
station = @state.station
_click = @_click
_remove = @_remove
stations = @state.stations.map (_station) ->
k = "station"
parts = [(div {className:"name"}, _station.name)]
if _station.name isnt window.util.mainStation()
parts.push (div {className:"remove",onClick:_remove,dataStation:_station.name},"×")
div {className:k,onClick:_click},parts
div {id:"stations"}, [
div {className:"stations"},stations
div {className:"join-ctrl"}, [
input {className:"join",onKeyUp:@_keyUp,placeholder:"+"}, ""
]
]

View File

@ -0,0 +1,130 @@
recl = React.createClass
[div,input,textarea] = [React.DOM.div,React.DOM.input,React.DOM.textarea]
MessageActions = require '../actions/MessageActions.coffee'
StationActions = require '../actions/StationActions.coffee'
StationStore = require '../stores/StationStore.coffee'
Member = require './MemberComponent.coffee'
module.exports = recl
set: ->
if window.localStorage and @$writing then window.localStorage.setItem 'writing', @$writing.text()
get: ->
if window.localStorage then window.localStorage.getItem 'writing'
stateFromStore: -> {
audi:StationStore.getAudience()
members:StationStore.getMembers()
typing:StationStore.getTyping()
station:StationStore.getStation()
}
getInitialState: -> @stateFromStore()
typing: (state) ->
if @state.typing[@state.station] isnt state
StationActions.setTyping @state.station,state
_blur: ->
MessageActions.setTyping false
@typing false
_focus: ->
MessageActions.setTyping true
@typing true
sendMessage: ->
MessageActions.sendMessage @state.station,@$writing.text(),@state.audi
@$length.text "0/69"
@$writing.text('')
@set()
@typing false
_keyDown: (e) ->
if e.keyCode is 13
e.preventDefault()
@sendMessage()
return false
@_input()
@set()
_input: (e) ->
text = @$writing.text()
length = text.length
geturl = new RegExp "(^|[ \t\r\n])((ftp|http|https|gopher|mailto|news|nntp|telnet|wais|file|prospero|aim|webcal):(([A-Za-z0-9$_.+!*(),;/?:@&~=-])|%[A-Fa-f0-9]{2}){2,}(#([a-zA-Z0-9][a-zA-Z0-9$_.+!*(),;/?:@&~=%-]*))?([A-Za-z0-9$_+!*();/?:~-]))", "g"
urls = text.match(geturl)
if urls isnt null and urls.length > 0
for url in urls
length -= url.length
length += 10
@$length.text "#{length}/69"
if length >= 69
@$writing.text(@$writing.text().substr(0,69))
@cursorAtEnd()
e.preventDefault() if e
return false
_setFocus: -> @$writing.focus()
getTime: ->
d = new Date()
seconds = d.getSeconds()
if seconds < 10
seconds = "0" + seconds
"~"+d.getHours() + "." + d.getMinutes() + "." + seconds
cursorAtEnd: ->
range = document.createRange()
range.selectNodeContents @$writing[0]
range.collapse(false)
selection = window.getSelection()
selection.removeAllRanges()
selection.addRange(range)
componentDidMount: ->
window.util.sendMessage = @sendMessage
StationStore.addChangeListener @_onChangeStore
@$el = $ @getDOMNode()
@$length = $('#length')
@$writing = $('#writing')
@$writing.focus()
if @get()
@$writing.text @get()
@_input()
@interval = setInterval =>
@$el.find('.time').text @getTime()
, 1000
componentWillUnmount: ->
StationStore.removeChangeListener @_onChangeStore
clearInterval @interval
_onChangeStore: -> @setState @stateFromStore()
render: ->
user = "~"+window.urb.user
iden = StationStore.getMember(user)
ship = if iden then iden.ship else user
name = if iden then iden.name else ""
k = "writing"
k+= " hidden" if not @state?.station
div {className:k,onClick:@_setFocus}, [
(div {className:"attr"}, [
(Member iden, "")
(div {className:"time"}, @getTime())
])
(div {
id:"writing"
contentEditable:true
onFocus: @_focus
onBlur: @_blur
onInput: @_input
onPaste: @_input
onKeyDown: @_keyDown
onFocus: @cursorAtEnd
}, "")
div {id:"length"}, "0/69"
]

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,111 @@
$(() ->
StationActions = require './actions/StationActions.coffee'
rend = React.render
window.chat = {}
window.chat.MessagePersistence = require './persistence/MessagePersistence.coffee'
window.chat.StationPersistence = require './persistence/StationPersistence.coffee'
window.util =
mainStation: ->
switch window.urb.user.length
when 3
return "court"
when 5
return "floor"
when 13
return "porch"
create: (name) ->
window.chat.StationPersistence.createStation name, (err,res) ->
subscribe: (name) ->
window.chat.StationPersistence.addSource "main",window.urb.ship,["~zod/#{name}"]
uuid32: ->
str = "0v"
str += Math.ceil(Math.random()*8)+"."
for i in [0..5]
_str = Math.ceil(Math.random()*10000000).toString(32)
_str = ("00000"+_str).substr(-5,5)
str += _str+"."
str.slice(0,-1)
populate: (station,number) ->
c = 0
send = ->
if c < number
c++
else
console.log 'done'
return true
_audi = {}
_audi[station] = "pending"
_message =
serial:window.util.uuid32()
audience:_audi
statement:
speech:
say:"Message "+c
time: Date.now()
now: Date.now()
window.chat.MessagePersistence.sendMessage _message,send
send()
getScroll: ->
@writingPosition = $('#messaging-container').outerHeight(true)+$('#messaging-container').offset().top-$(window).height()
#@writingPosition = $('#writing-container').position().top-$(window).height()+$('#writing-container').outerHeight(true)
setScroll: ->
window.util.getScroll()
$(window).scrollTop(window.util.writingPosition)
checkScroll: ->
if not window.util.writingPosition
window.util.getScroll()
if $(window).scrollTop() < window.util.writingPosition
$('body').addClass 'scrolling'
else
$('body').removeClass 'scrolling'
$(window).on 'scroll', window.util.checkScroll
window.chat.StationPersistence.listen()
StationComponent = require './components/StationComponent.coffee'
StationsComponent = require './components/StationsComponent.coffee'
MessagesComponent = require './components/MessagesComponent.coffee'
WritingComponent = require './components/WritingComponent.coffee'
$c = $('#c')
clean = ->
React.unmountComponentAtNode $('#stations-container')[0]
React.unmountComponentAtNode $('#station-parts-container')[0]
React.unmountComponentAtNode $('#writing-container')[0]
React.unmountComponentAtNode $('#messages-container')[0]
routes =
'': ->
clean()
$c.html "<div id='stations-container'></div>"
rend (StationsComponent {}, ""),$('#stations-container')[0]
'/:station': (station) ->
clean()
StationActions.switchStation station
$c.html ""
$c.append("<div id='messaging-container'></div>")
$d = $('#messaging-container')
$d.append("<div id='messages-container'></div>")
$d.append("<div id='writing-container'></div>")
$d.append("<div id='station-parts-container'></div>")
$c.append("<div id='scrolling'>BOTTOM</div>")
rend (StationComponent {}, ""),$('#station-parts-container')[0]
rend (MessagesComponent {}, ""),$('#messages-container')[0]
rend (WritingComponent {}, ""),$('#writing-container')[0]
router = Router routes
if not window.location.hash then window.location.hash = "/"
router.init()
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,12 @@
{
"name": "urbit-radio",
"version": "0.0.0",
"description": "urbit radio frontend",
"main": "main.js",
"dependencies": {
"coffeeify": "~0.7.0",
"flux": "~2.0.1",
"lodash": "~2.4.1",
"moment-timezone": "~0.2.4"
}
}

View File

@ -0,0 +1,43 @@
MessageActions = require '../actions/MessageActions.coffee'
module.exports =
listenStation: (station,since) ->
window.urb.subscribe {
appl:"rodeo"
path:"/f/#{station}/#{since}"
}, (err,res) ->
console.log('m subscription updates')
console.log(res.data)
if res.data.ok is true
MessageActions.listeningStation station
if res.data?.grams?.tele
MessageActions.loadMessages res.data.grams
get: (station,start,end) ->
window.urb.subscribe {
appl:"rodeo"
path:"/f/#{station}/#{end}/#{start}"
}, (err,res) ->
console.log 'get'
console.log res
if res.data?.grams?.tele
MessageActions.loadMessages res.data.grams,true
window.urb.unsubscribe {
appl:"rodeo"
path:"/f/#{station}/#{end}/#{start}"
}, (err,res) ->
console.log 'done'
console.log res
sendMessage: (message,cb) ->
window.urb.send {
appl:"rodeo"
mark:"rodeo-command"
data:
publish: [
message
]
}, (err,res) ->
console.log 'sent'
console.log arguments
cb(err,res) if cb

View File

@ -0,0 +1,74 @@
StationActions = require '../actions/StationActions.coffee'
module.exports =
createStation: (name,cb) ->
window.urb.send {
appl:"rodeo"
mark:"rodeo-command"
data:
design:
party:name
config:
sources:[]
caption:""
cordon:{posture:"white", list:[]}
}, cb
removeStation: (name,cb) ->
window.urb.send {
appl:"rodeo"
mark:"rodeo-command"
data:
design:
party:name
config:null
}, cb
setSources: (station,ship,sources) ->
send =
appl:"rodeo"
mark:"rodeo-command"
data:
design:
party:station
config:
sources:sources
caption:""
cordon:{posture:"white", list:[]}
window.urb.send send, (err,res) ->
console.log 'add source updates'
console.log arguments
members: ->
window.urb.subscribe {
appl:"rodeo"
path:"/a/court"
}, (err,res) ->
console.log 'membership updates'
console.log res.data
if res.data?.group?.global
StationActions.loadMembers res.data.group.global
listen: ->
window.urb.subscribe {
appl:"rodeo"
path:"/"
}, (err,res) ->
console.log 'house updates'
console.log res.data
if res.data.house
StationActions.loadStations res.data.house
listenStation: (station) ->
window.urb.subscribe {
appl:"rodeo"
path:"/ax/#{station}"
}, (err,res) ->
console.log('station subscription updates')
console.log(res.data)
if res.data.ok is true
StationActions.listeningStation station
if res.data.group?.local
StationActions.loadMembers station,res.data.group.local
if res.data.config
StationActions.loadConfig station,res.data.config

View File

@ -0,0 +1,101 @@
moment = require 'moment-timezone'
EventEmitter = require('events').EventEmitter
MessageDispatcher = require '../dispatcher/Dispatcher.coffee'
_messages = {}
_fetching = false
_last = null
_station = null
_listening = []
_typing = false
MessageStore = _.merge new EventEmitter,{
removeChangeListener: (cb) -> @removeListener "change", cb
emitChange: -> @emit 'change'
addChangeListener: (cb) -> @on 'change', cb
leadingZero: (str) ->
if Number(str) < 10 then "0"+str else str
convertDate: (time) ->
time = time.substr(1).split(".")
time[1] = @leadingZero time[1]
time[2] = @leadingZero time[2]
d = new moment "#{time[0]}-#{time[1]}-#{time[2]}T#{time[4]}:#{time[5]}:#{time[6]}Z"
d.tz "Europe/London"
d
getListening: -> _listening
getTyping: -> _typing
setTyping: (state) -> _typing = state
setListening: (station) ->
if _listening.indexOf(station) isnt -1
console.log 'already listening on that station (somehow).'
else
_listening.push station
setStation: (station) -> _station = station
sendMessage: (message) ->
_messages[message.thought.serial] = message
loadMessages: (messages,last,get) ->
for k,v of messages
serial = v.thought.serial
v.key = serial
# always overwrite with new
_messages[serial] = v
_last = last if last < _last or _last is null or get is true
_fetching = false
getAll: -> _.values _messages
getFetching: -> _fetching
setFetching: (state) -> _fetching = state
getLast: -> _last
}
MessageStore.setMaxListeners 100
MessageStore.dispatchToken = MessageDispatcher.register (payload) ->
action = payload.action
switch action.type
when 'station-switch'
MessageStore.setStation action.station
break
when 'messages-listen'
MessageStore.setListening action.station
MessageStore.emitChange()
break
when 'messages-typing'
MessageStore.setTyping action.state
MessageStore.emitChange()
break
when 'messages-fetch'
MessageStore.setFetching true
MessageStore.emitChange()
break
when 'messages-load'
MessageStore.loadMessages action.messages,action.last,action.get
MessageStore.emitChange()
break
when 'message-load'
MessageStore.loadMessage action.time,action.message,action.author
MessageStore.emitChange()
break
when 'message-send'
MessageStore.sendMessage action.message
MessageStore.emitChange()
break
module.exports = MessageStore

View File

@ -0,0 +1,132 @@
EventEmitter = require('events').EventEmitter
StationDispatcher = require '../dispatcher/Dispatcher.coffee'
_audience = []
_members = {}
_stations = []
_listening = []
_station = null
_config = {}
_typing = {}
StationStore = _.merge new EventEmitter,{
removeChangeListener: (cb) -> @removeListener "change", cb
emitChange: -> @emit 'change'
addChangeListener: (cb) -> @on 'change', cb
getAudience: -> _audience
setAudience: (audience) -> _audience = audience
toggleAudience: (station) ->
if _audience.indexOf(station) isnt -1
_audience.splice _audience.indexOf(station), 1
else
_audience.push station
loadConfig: (station,config) -> _config[station] = config
getConfigs: -> _config
getConfig: (station) -> _config[station]
getMember: (ship) -> {ship:ship}
changeMember: (dir,name,ship) ->
if dir is "out"
_members = _.filter _members, (_member) ->
return (_member.ship isnt ship)
if dir is "in"
_members.push {name:name, ship:ship}
loadMembers: (station,members) -> _members[station] = members
getMembers: -> _members
getListening: -> _listening
setListening: (station) ->
if _listening.indexOf(station) isnt -1
console.log 'already listening on that station (somehow).'
else
_listening.push station
createStation: (station) ->
_stations.push(station) if _stations.indexOf(station) is -1
loadStations: (stations) -> _stations = stations
getStations: -> _stations
setStation: (station) -> _station = station
unsetStation: (station) ->
_station = null if _station is station
getStation: -> _station
joinStation: (station) ->
if _config.court?.sources.indexOf(station) is -1
_config.court.sources.push station
getTyping: () -> _typing
setTyping: (station,state) ->
for k,v of _typing
_typing[k] = (k is station)
_typing[station] = state
}
StationStore.setMaxListeners 100
StationStore.dispatchToken = StationDispatcher.register (payload) ->
action = payload.action
switch action.type
when 'station-audience-toggle'
StationStore.toggleAudience action.station
StationStore.emitChange()
break
when 'station-set-audience'
StationStore.setAudience action.audience
StationStore.emitChange()
break
when 'station-switch'
StationStore.setAudience []
StationStore.setStation action.station
StationStore.emitChange()
break
when 'station-listen'
StationStore.setListening action.station
StationStore.emitChange()
break
when "config-load"
StationStore.loadConfig action.station,action.config
StationStore.emitChange()
break
when "stations-load"
StationStore.loadStations action.stations
StationStore.emitChange()
break
when "stations-leave"
StationStore.loadStations action.stations
StationStore.unsetStation action.station
StationStore.emitChange()
break
when "station-create"
StationStore.createStation action.station
StationStore.emitChange()
break
when "members-load"
StationStore.loadMembers action.station,action.members
StationStore.emitChange()
break
when "typing-set"
StationStore.setTyping action.station,action.state
StationStore.emitChange()
break
module.exports = StationStore

19439
main/pub/talk/src/js/test.js Normal file

File diff suppressed because it is too large Load Diff