Commit a2f26d06 authored by Randall Leeds's avatar Randall Leeds

Merge branch '356-firehose-3' into 356-firehose-4

Conflicts:
	h/assets.py
	h/css/common.scss
	h/templates/displayer.pt
	h/views.py
parents daa14376 2fa58213
......@@ -860,6 +860,149 @@ blockquote {
}
}
//STREAMER////////////////////////////////
//This is specific to the streamer page
.main-content {
margin:0;
}
.center_button {
text-align: center;
}
.button_controls {
margin-left: 1em;
margin-right: 1em;
margin-bottom: 0.5em;
margin-top: 0.5em;
}
.stream-list {
margin-bottom: 1.5em;
}
.filter-body {
overflow: visible;
padding-bottom: 1.5em;
}
.side-bar-right {
width: 15em;
height: 100%;
position: absolute;
top: 0px;
right: 0px;
}
.side-bar-left {
width: 30em;
height: 100%;
position: absolute;
top: 0px;
left: 0px;
}
@mixin sidebar_animation {
-webkit-transition: 1s linear all;
-moz-transition: 1s linear all;
-o-transition: 1s linear all;
transition: 1s linear all;
overflow-y:hidden;
}
.sidebar_anim_show_left-setup {
@include sidebar_animation;
opacity: 0;
width: 0em;
}
.sidebar_anim_show_left-setup.sidebar_anim_show_left-start {
opacity: 1;
width: 30em;
}
.sidebar_anim_show_right-setup {
@include sidebar_animation;
opacity: 0;
width: 0em;
}
.sidebar_anim_show_right-setup.sidebar_anim_show_right-start {
opacity: 1;
width: 15em;
}
.sidebar_anim_hide_right-setup {
@include sidebar_animation;
opacity: 1;
width: 15em;
}
.sidebar_anim_hide_right-setup.sidebar_anim_hide_right-start {
opacity: 0;
width: 0em;
}
.sidebar_anim_hide_left-setup {
@include sidebar_animation;
opacity: 1;
width: 30em;
}
.sidebar_anim_hide_left-setup.sidebar_anim_hide_left-start {
opacity: 0;
width: 0em;
}
.searchbar-border {
height: auto;
padding-bottom: 2em;
}
.searchbar-error {
color: red;
}
.menu-width {
width: 20em;
white-space: pre-line;
}
pre {outline: 1px solid #ccc; padding: 5px; margin: 5px; }
.string { color: green; }
.number { color: darkorange; }
.boolean { color: blue; }
.null { color: magenta; }
.key { color: red; }
.sidebar-content {
padding-top: 4em;
overflow-x:hidden;
overflow-y:hidden;
}
.searchbar {
margin-top: 8em;
}
.searchinput {
width: 79%;
}
.searchbutton {
width: 19%
}
.searchfield {
width: 100%
}
.small-padding {
padding-top: 1em;
padding-bottom: 1em;
}
//A grid of cards
.card-grid {
......
angular.module('h.displayer',[])
.controller('DisplayerCtrl',
($scope, $element) ->
$scope.toggleCollapse = (event, replynumber, thread) ->
#Plus/minus sign and italics
elem = (angular.element event.srcElement).parent()
if elem.hasClass 'hyp-collapsed'
elem.removeClass 'hyp-collapsed'
expand = true
else elem.addClass 'hyp-collapsed'
#Now for the replies
if replynumber
toggle_elem = $element.find('.thread_' + (thread+1)).parent().parent()
if expand? then toggle_elem.removeAttr 'style'
else toggle_elem.css 'display', 'none'
)
\ No newline at end of file
get_quote = (annotation) ->
if not 'target' in annotation then return ''
quote = '(This is a reply annotation)'
for target in annotation['target']
for selector in target['selector']
if selector['type'] is 'TextQuoteSelector'
quote = selector['exact'] + ' '
quote
class Displayer
this.$inject = ['$scope','$element','$timeout']
idTable : {}
constructor: ($scope, $element, $timeout) ->
$scope.annotation = {}
$scope.annotations = [$scope.annotation]
$scope.annotation.replies = []
$scope.annotation.reply_count = 0
$scope.annotation.id = document.body.attributes.internalid.value
@idTable[$scope.annotation.id] = $scope.annotation
$scope.filter =
match_policy : 'include_any'
clauses : [
field: "/references"
operator: "first_of"
value: $scope.annotation.id
,
field: "/id"
operator: "equals"
value: $scope.annotation.id
]
actions :
create: true
edit: true
delete: true
past_data:
load_past: 'replies'
id_for_reply: $scope.annotation.id
$scope.change_annotation_content = (id, new_annotation) =>
to_change = @idTable[id]
replies = to_change.replies
reply_count = to_change.reply_count
for k, v of to_change
delete to_change.k
angular.extend to_change, new_annotation
to_change.replies = replies
to_change.reply_count = reply_count
$scope.open = =>
transports = ['xhr-streaming', 'iframe-eventsource', 'iframe-htmlfile', 'xhr-polling', 'iframe-xhr-polling', 'jsonp-polling']
$scope.sock = new SockJS(window.location.protocol + '//' + window.location.hostname + ':' + window.location.port + '/__streamer__', transports)
$scope.sock.onopen = ->
$scope.sock.send JSON.stringify $scope.filter
$scope.sock.onclose = ->
$timeout $scope.open, 5000
$scope.sock.onmessage = (msg) =>
console.log 'Got something'
console.log msg
$scope.$apply =>
data = msg.data[0]
unless data instanceof Array then data = [data]
#sort annotations by creation date
data.sort (a, b) ->
if a.created > b.created then return 1
if a.created > b.created then return -1
0
action = msg.data[1]
for annotation in data
annotation.quote = get_quote annotation
switch action
when 'create', 'past'
#Ignore duplicates caused by server restarting
if annotation.id in @idTable
break
for i in [$scope.annotation.ref_length..annotation.references.length-1]
reference = annotation.references[i]
@idTable[reference].reply_count += 1
replies = @idTable[annotation.references[annotation.references.length-1]].replies
#Find the place to insert annotation
pos = 0
for reply in replies
if reply.updated < annotation.updated
break
pos += 1
annotation.replies = []
annotation.reply_count = 0
@idTable[annotation.id] = annotation
replies.splice pos, 0, annotation
when 'edit'
$scope.change_annotation_content annotation.id, annotation
when 'delete'
if 'deleted' in annotation
#Redaction
$scope.change_annotation_content annotation.id, annotation
else
#Real delete
unless @idTable[annotation.id]?
break
#Update the reply counter for all referenced annotation
for i in [$scope.annotation.ref_length..annotation.references.length-1]
reference = annotation.references[i]
@idTable[reference].reply_count -= 1
replies = @idTable[annotation.references[annotation.references.length-1]].replies
#Find the place to insert annotation
pos = replies.indexOf @idTable[annotation.id]
replies.splice pos, 1
delete @idTable[annotation.id]
$scope.open()
angular.module('h.displayer',['h.filters','bootstrap'])
.controller('DisplayerCtrl', Displayer)
get_quote = (annotation) ->
if not 'target' in annotation then return ''
quote = '(This is a reply annotation)'
for target in annotation['target']
for selector in target['selector']
if selector['type'] is 'TextQuoteSelector'
quote = selector['exact'] + ' '
quote
syntaxHighlight = (json) ->
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, (match) ->
cls = 'number'
if /^"/.test(match)
if /:$/.test(match) then cls = 'key'
else cls = 'string'
else
if /true|false/.test(match) then cls = 'boolean'
else
if /null/.test(match) then cls = 'null'
return '<span class="' + cls + '">' + match + '</span>'
)
class ClauseParser
filter_fields : ['references', 'text', 'user','uri', 'id']
operators: ['=', '>', '<', '=>', '>=', '<=', '=<', '[', '#']
operator_mapping:
'=': 'equals'
'>': 'gt'
'<': 'lt'
'=>' : 'ge'
'<=' : 'ge'
'=<': 'le'
'<=' : 'le'
'[' : 'one_of'
'#' : 'matches'
parse_clauses: (clauses) ->
bads = []
structure = []
unless clauses
return
clauses = clauses.split ' '
for clause in clauses
#Here comes the long and boring validation checking
clause = clause.trim()
if clause.length < 1 then continue
parts = clause.split /:(.+)/
unless parts.length > 1
bads.push [clause, 'Filter clause is not well separated']
continue
unless parts[0] in @filter_fields
bads.push [clause, 'Unknown filter field']
continue
field = parts[0]
operator_found = false
for operator in @operators
if (parts[1].indexOf operator) is 0
oper = @operator_mapping[operator]
if operator is '['
value = parts[1][operator.length..].split ','
else
value = parts[1][operator.length..]
operator_found = true
break
unless operator_found
bads.push [clause, 'Unknown operator']
continue
structure.push
'field' : '/' + field
'operator': oper
'value' : value
[structure, bads]
class Streamer
this.$inject = ['$location', '$scope']
strategies: ['include_any', 'include_all', 'exclude_any', 'exclude_all']
past_modes: ['none','hits','time']
constructor: ($location, $scope) ->
$scope.streaming = false
$scope.annotations = []
$scope.bads = []
@parser = new ClauseParser()
#Json structure we will watch and update
$scope.filter =
match_policy : 'include_any'
clauses : []
actions :
create: true
edit: true
delete: true
past_data:
load_past: 'hits'
go_back: 5
hits: 100
#parse for route params
params = $location.search()
if params.match_policy in @strategies
$scope.filter.match_policy = params.match_policy
if params.action_create
if (typeof params.action_create) is 'boolean'
$scope.filter.actions.create = params.action_create
else
$scope.filter.actions.create = params.action_create is 'true'
if params.action_edit
if (typeof params.action_edit) is 'boolean'
$scope.filter.actions.edit = params.action_edit
else
$scope.filter.actions.edit = params.action_edit is 'true'
if params.action_delete
if (typeof params.action_delete) is 'boolean'
$scope.filter.actions.delete = params.action_delete
else
$scope.filter.actions.delete = params.action_delete is 'true'
if params.load_past in @past_modes
$scope.filter.past_data.load_past = params.load_past
if params.hits? and parseInt(params.hits) is not NaN
$scope.filter.past_data.hits = parseInt(params.hits)
if params.go_back? and parseInt(params.go_back) is not NaN
$scope.filter.past_data.go_back = parseInt(params.go_back)
if params.clauses
test_clauses = params.clauses.replace ",", " "
res = @parser.parse_clauses test_clauses
if res[1]?.length is 0
$scope.filter.clauses = res[0]
$scope.clauses = test_clauses
console.log $scope.filter
$scope.toggle_past = ->
switch $scope.filter.past_data.load_past
when 'none' then $scope.filter.past_data.load_past = 'time'
when 'time' then $scope.filter.past_data.load_past = 'hits'
when 'hits' then $scope.filter.past_data.load_past = 'none'
$scope.$watch 'filter', (newValue, oldValue) =>
json = JSON.stringify $scope.filter, undefined, 2
$scope.json_content = syntaxHighlight json
,true
$scope.clause_change = =>
if $scope.clauses.slice(-1) is ' ' or $scope.clauses.length is 0
res = @parser.parse_clauses($scope.clauses)
if res?
$scope.filter.clauses = res[0]
$scope.bads = res[1]
else
$scope.filter.clauses = []
$scope.bads = []
$scope.start_streaming = =>
if $scope.streaming
$scope.sock.close()
$scope.streaming = false
res = @parser.parse_clauses($scope.clauses)
if res
$scope.filter.clauses = res[0]
$scope.bads = res[1]
unless $scope.bads.length is 0
return
transports = ['xhr-streaming', 'iframe-eventsource', 'iframe-htmlfile', 'xhr-polling', 'iframe-xhr-polling', 'jsonp-polling']
$scope.sock = new SockJS(window.location.protocol + '//' + window.location.hostname + ':' + window.location.port + '/__streamer__', transports)
$scope.sock.onopen = ->
$scope.sock.send $scope.filter
$scope.$apply =>
$scope.streaming = true
$scope.sock.onclose = ->
$scope.$apply =>
$scope.streaming = false
$scope.sock.onmessage = (msg) =>
$scope.$apply =>
data = msg.data[0]
unless data instanceof Array then data = [data]
action = msg.data[1]
for annotation in data
annotation['action'] = action
annotation['quote'] = get_quote annotation
$scope.annotations.splice 0,0,annotation
#Update the parameters
$location.search
'match_policy': $scope.filter.match_policy
'action_create': $scope.filter.actions.create
'action_edit': $scope.filter.actions.edit
'action_delete': $scope.filter.actions.delete
'load_past': $scope.filter.past_data.load_past
'go_back': $scope.filter.past_data.go_back
'hits': $scope.filter.past_data.hits
'clauses' : $scope.clauses.replace " ", ","
$scope.stop_streaming = ->
$scope.sock.close()
$scope.streaming = false
angular.module('h.streamer',['h.filters','bootstrap'])
.controller('StreamerCtrl', Streamer)
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment