summaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/actions/index.js4
-rw-r--r--client/components/Browser/Files/FileListView.jsx4
-rw-r--r--client/components/Browser/Folders/FolderListView.jsx4
-rw-r--r--client/components/Tasks/TaskListView.jsx41
-rw-r--r--client/components/UI/AudioPlayerView.jsx19
-rw-r--r--client/components/UI/Link.jsx8
-rw-r--r--client/containers/fileLink.js16
-rw-r--r--client/containers/taskContentLink.js1
-rw-r--r--client/containers/taskList.js9
-rw-r--r--client/containers/taskStyleLink.js1
-rw-r--r--client/index.jsx2
-rw-r--r--client/reducers/folders.js2
-rw-r--r--client/reducers/tasks.js14
-rw-r--r--client/socket.js13
-rw-r--r--client/vendor/format.js210
-rw-r--r--client/vendor/paths.js18
16 files changed, 319 insertions, 47 deletions
diff --git a/client/actions/index.js b/client/actions/index.js
index 6a552ac..4263c3b 100644
--- a/client/actions/index.js
+++ b/client/actions/index.js
@@ -41,6 +41,10 @@ export const loadTasks = (tasks) => ({
type: 'LOAD_TASKS',
tasks,
})
+export const cancelTask = (task) => ({
+ type: 'CANCEL_TASK',
+ task,
+})
/* folders */
diff --git a/client/components/Browser/Files/FileListView.jsx b/client/components/Browser/Files/FileListView.jsx
index 4615cf7..df59a1d 100644
--- a/client/components/Browser/Files/FileListView.jsx
+++ b/client/components/Browser/Files/FileListView.jsx
@@ -21,7 +21,7 @@ export default function FileListView (props) {
return (
<div key={i} class={props.selected === file ? 'selected' : ''}>
<span class='name'><FileLink file={file}>{file.name}</FileLink></span>
- <span class='mime'>{file.processed ? file.mime : 'working...'}</span>
+ <span class='mime'>{file.processed ? file.mime : '(waiting)'}</span>
<span class='duration'>{file.duration ? (file.duration.toFixed(1) + 's') : ''}</span>
<span class='actions'>
<TaskContentLink file={file}>content</TaskContentLink>
@@ -39,7 +39,7 @@ export default function FileListView (props) {
<button onClick={props.onClose}>&times;</button>
</div>
</div>
- <div class='list'>
+ <div class='files list'>
{files}
</div>
</div>
diff --git a/client/components/Browser/Folders/FolderListView.jsx b/client/components/Browser/Folders/FolderListView.jsx
index 0a0d0e2..e9d2d44 100644
--- a/client/components/Browser/Folders/FolderListView.jsx
+++ b/client/components/Browser/Folders/FolderListView.jsx
@@ -3,7 +3,7 @@ import { h, Component } from 'preact'
export default function FolderListView (props) {
const folders = props.folders.map( (folder, i) => (
<div key={i} onClick={() => props.openFolder(folder)}>
- <span class='name'>{folder.name}</span>
+ <a class='name'>{folder.name}</a>
</div>
))
@@ -15,7 +15,7 @@ export default function FolderListView (props) {
<button onClick={props.initNewFolder}>+ folder</button>
</div>
</div>
- <div class='list'>
+ <div class='folders list'>
{folders}
</div>
</div>
diff --git a/client/components/Tasks/TaskListView.jsx b/client/components/Tasks/TaskListView.jsx
index 47be794..b93ecc3 100644
--- a/client/components/Tasks/TaskListView.jsx
+++ b/client/components/Tasks/TaskListView.jsx
@@ -1,24 +1,51 @@
import { h, Component } from 'preact'
+import format from '../../vendor/format.js'
import FileLink from '../../containers/fileLink.js'
export default function TaskListView (props) {
const tasks = (props.tasks || []).map( (task, i) => {
- return (
- <div key={i}>
- <span>{task.id}</span>
- <span>{task.created_at}</span>
+ const created_at = format.verboseDate(task.created_at)
+ let files = []
+ let cancel
+ if (task.content_file) {
+ files.push(
<span class='name'><FileLink file={task.content_file} /></span>
+ )
+ }
+ if (task.style_file) {
+ files.push(
<span class='name'><FileLink file={task.style_file} /></span>
- <span>α={task.alpha}</span>
- <span class='name'><FileLink file={task.output_file} /></span>
+ )
+ }
+ if (! task.output_file && ! task.processing) {
+ cancel = (
+ <span class='cancel' onClick={() => props.cancelTask(task)}>x</span>
+ )
+ }
+ const completed = task.completed ? 'completed' : ''
+ let filename = task.output_file ? task.output_file.name :
+ task.processing ? '(processing)' : '(waiting)'
+ return (
+ <div key={i}>
+ <div class='row'>
+ <span class='date'>{created_at.date}</span>
+ <span class='time'>{created_at.time}</span>
+ <span class={'name output ' + completed}><FileLink file={task.output_file}>{filename}</FileLink></span>
+ {cancel}
+ </div>
+ <div class='row'>
+ <span class='alpha'>α={task.alpha}</span>
+ {files}
+ </div>
</div>
)
// <span class='name'>{task.result_file.name}</span>
})
return (
- <div class='list'>
+ <div class='tasks list'>
{tasks}
</div>
)
}
+
diff --git a/client/components/UI/AudioPlayerView.jsx b/client/components/UI/AudioPlayerView.jsx
index d2c9982..e715c27 100644
--- a/client/components/UI/AudioPlayerView.jsx
+++ b/client/components/UI/AudioPlayerView.jsx
@@ -1,19 +1,21 @@
import { h, Component } from 'preact'
-
+import { pngpath, mp3path } from '../../vendor/paths'
const audio = document.createElement('audio')
export default function AudioPlayerView (props) {
if (props.file) {
document.body.style.backgroundImage = 'url(' + pngpath(props.file) + ')'
audio.src = mp3path(props.file)
+ audio.currentTime = 0
audio.play()
return (
- <div class='audioPlayer'>
+ <div class='audioPlayer' onClick={() => audio.paused ? audio.play() : audio.pause()}>
Playing {props.file.name}
</div>
)
}
else {
+ audio.pause()
return (
<div class='audioPlayer'>
Not Playing
@@ -21,16 +23,3 @@ export default function AudioPlayerView (props) {
)
}
}
-
-function filepath (file) {
- return '/data/' + file.folder_id + '/' + encodeURIComponent(file.name)
-}
-function mp3path (file) {
- if (file.mime !== 'audio/mp3') {
- return filepath(file) + '.mp3'
- }
- return filepath(file)
-}
-function pngpath (file) {
- return filepath(file) + '.png'
-}
diff --git a/client/components/UI/Link.jsx b/client/components/UI/Link.jsx
index d71582b..b4d2d28 100644
--- a/client/components/UI/Link.jsx
+++ b/client/components/UI/Link.jsx
@@ -2,15 +2,15 @@ import { h, Component } from 'preact'
import React from 'react'
// import PropTypes from 'prop-types'
-const Link = ({ active, children, onClick, disabled }) => {
+const Link = ({ href, active, children, onClick, selected, disabled }) => {
if (active) {
return <span>{children}</span>
}
- const className = disabled ? 'disabled' : ''
-
+ const className = disabled ? 'disabled' :
+ selected ? 'selected' : ''
return (
// eslint-disable-next-line
- <a href="#"
+ <a href={href || '#'}
class={className}
onClick={e => {
e.preventDefault()
diff --git a/client/containers/fileLink.js b/client/containers/fileLink.js
index 92933cf..22834d8 100644
--- a/client/containers/fileLink.js
+++ b/client/containers/fileLink.js
@@ -1,17 +1,27 @@
import { connect } from 'react-redux'
import { audioPlayFile } from '../actions'
+import { filepath } from '../vendor/paths'
import Link from '../components/UI/Link.jsx'
const mapStateToProps = (state, ownProps) => ({
- children: ownProps.children || ownProps.file ? ownProps.file.name : "(~)",
- disabled: ownProps.disabled || ! ownProps.file
+ href: ownProps.file ? filepath(ownProps.file) : '#',
+ children: ownProps.children || (ownProps.file ? ownProps.file.name : "(~)"),
+ disabled: ownProps.disabled || ! ownProps.file,
+ selected: ownProps.file && state.audioPlayer.file && state.audioPlayer.file.id == ownProps.file.id
})
const mapDispatchToProps = (dispatch, ownProps) => ({
onClick: () => {
switch (ownProps.file.type) {
case 'audio':
- dispatch(audioPlayFile(ownProps.file))
+ let file = ownProps.file
+ dispatch(audioPlayFile(null))
+ setTimeout(() => {
+ dispatch(audioPlayFile(ownProps.file))
+ }, 10)
+ break
+ case 'image':
+ // document.body.style.backgroundImage =
break
}
}
diff --git a/client/containers/taskContentLink.js b/client/containers/taskContentLink.js
index bb8ae37..524e6aa 100644
--- a/client/containers/taskContentLink.js
+++ b/client/containers/taskContentLink.js
@@ -3,6 +3,7 @@ import { setContent } from '../actions'
import Link from '../components/UI/Link.jsx'
const mapStateToProps = (state, ownProps) => ({
+ selected: state.currentTask.content == ownProps.file
})
const mapDispatchToProps = (dispatch, ownProps) => ({
diff --git a/client/containers/taskList.js b/client/containers/taskList.js
index f4a5a1d..b341e91 100644
--- a/client/containers/taskList.js
+++ b/client/containers/taskList.js
@@ -1,11 +1,14 @@
import { connect } from 'react-redux'
-// import {} from '../actions'
+import { cancelTask } from '../actions'
import TaskListView from '../components/Tasks/TaskListView.jsx'
const mapStateToProps = (state) => ({ tasks: state.tasks })
-const mapDispatchToProps = {
-}
+const mapDispatchToProps = (dispatch) => ({
+ cancelTask: (task) => {
+ dispatch(cancelTask(task))
+ }
+})
const TaskList = connect(
mapStateToProps,
diff --git a/client/containers/taskStyleLink.js b/client/containers/taskStyleLink.js
index 7ec5ce5..6157e00 100644
--- a/client/containers/taskStyleLink.js
+++ b/client/containers/taskStyleLink.js
@@ -3,6 +3,7 @@ import { setStyle } from '../actions'
import Link from '../components/UI/Link.jsx'
const mapStateToProps = (state, ownProps) => ({
+ selected: state.currentTask.style == ownProps.file
})
const mapDispatchToProps = (dispatch, ownProps) => ({
diff --git a/client/index.jsx b/client/index.jsx
index 1a8577e..4b95736 100644
--- a/client/index.jsx
+++ b/client/index.jsx
@@ -14,7 +14,7 @@ client.folder.index().then( folders => {
store.dispatch( loadOpenFolders(openFolders) )
openFolders.forEach( folder_id => {
client.file.index({ folder_id }).then( files => {
- store.dispatch(loadFiles(files))
+ store.dispatch( loadFiles(files) )
})
})
}
diff --git a/client/reducers/folders.js b/client/reducers/folders.js
index e77c954..6731758 100644
--- a/client/reducers/folders.js
+++ b/client/reducers/folders.js
@@ -44,7 +44,7 @@ const folders = (state = {}, action) => {
folder = action.folder
folder_id = folder.id
if (openFolders.indexOf(folder.id) === -1) {
- openFolders = openFolders.concat(folder.id)
+ openFolders = [folder.id].concat(openFolders || [])
localStorage['openFolders'] = JSON.stringify(openFolders)
}
filesAreLoaded = state.folders.some( (folder) => {
diff --git a/client/reducers/tasks.js b/client/reducers/tasks.js
index 9c2b0b4..904c368 100644
--- a/client/reducers/tasks.js
+++ b/client/reducers/tasks.js
@@ -1,6 +1,8 @@
import client from '../client'
const tasks = (state = [], action) => {
+ let updated_tasks;
+
switch (action.type) {
case 'LOAD_TASKS':
return action.tasks
@@ -9,13 +11,17 @@ const tasks = (state = [], action) => {
return [action.task].concat(state)
case 'UPDATE_TASK':
- const updated_tasks = state.map(task => {
- if (task.id == id) {
- return task
+ updated_tasks = state.map(task => {
+ if (task.id === action.task.id) {
+ return action.task
}
- return id
+ return task
})
return updated_tasks
+
+ case 'CANCEL_TASK':
+ client.task.destroy(action.task)
+ return state.filter(task => task !== action.task)
default:
return state
diff --git a/client/socket.js b/client/socket.js
index d94a968..9c35af5 100644
--- a/client/socket.js
+++ b/client/socket.js
@@ -1,4 +1,4 @@
-import { updateFile } from './actions'
+import { updateFile, updateTask, addFile } from './actions'
import store from './store'
const socket = io(window.location.origin)
@@ -9,13 +9,16 @@ socket.on('connect', (data) => {
socket.on('worker', (data) => {
console.log('worker connected', data)
})
-socket.on('processed', (data) => {
- console.log('processed', data)
+socket.on('updateFile', (data) => {
+ console.log('updateFile', data)
store.dispatch(updateFile(data.file))
})
-socket.on('completed', (data) => {
- console.log('completed', data)
+socket.on('updateTask', (data) => {
+ console.log('updateTask', data)
store.dispatch(updateTask(data.task))
+ if (data.task.output_file) {
+ store.dispatch(addFile(data.task.output_file))
+ }
})
export default socket
diff --git a/client/vendor/format.js b/client/vendor/format.js
new file mode 100644
index 0000000..1e5836e
--- /dev/null
+++ b/client/vendor/format.js
@@ -0,0 +1,210 @@
+export default {
+ commatize,
+ privacyDot,
+ shortMonths,
+ verboseDate,
+ carbonDate,
+ hushViews,
+ hushSize,
+ hushNull,
+ courtesyS,
+ getRevision,
+ getAge,
+ tidyURLs,
+ getDomain,
+}
+
+function commatize (n) {
+ var nums = [], i, counter = 0, r = Math.floor
+ if (n > 1024) {
+ n /= 1024
+ nums.unshift(r((n * 10) % 10))
+ nums.unshift(".")
+ }
+ do {
+ i = n % 10
+ n = r(n / 10)
+ if (n && ! (++counter % 3))
+ { i = ' ' + r(i) }
+ nums.unshift(r(i))
+ }
+ while (n)
+ return nums.join("")
+}
+
+function privacyDot (p) {
+ if (! p) return "&middot;"
+ else return "&middot;:"
+}
+
+const shortMonths = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" ")
+function verboseDate (dateStr, pad_hours) {
+ const dateObj = new Date(dateStr)
+ let d = dateObj.getDate()
+ let m = dateObj.getMinutes()
+ let h = dateObj.getHours()
+ let meridian
+
+ if (h == 0) {
+ h = 12
+ meridian = " am"
+ }
+ else if (h == 12) {
+ meridian = " pm"
+ }
+ else if (h > 12) {
+ h -= 12
+ meridian = " pm"
+ }
+ else {
+ meridian = " am"
+ }
+
+ if (d < 10) d = "0" + d
+ if (m < 10) m = "0" + m
+ if (pad_hours && h < 10) h = "0" + h
+
+ const date = d + '-' + shortMonths[dateObj.getMonth()] + '-' + dateObj.getFullYear()
+ const time = h + ':' + m + meridian
+
+ return { date, time }
+}
+function carbonDate (date, no_bold) {
+ const span = (+new Date() - new Date(date)) / 1000
+ if (! no_bold && span < 86400) // modified today
+ { color = "new" }
+ else if (span < 604800) // modifed this week
+ { color = "recent" }
+ else if (span < 1209600) // modifed 2 weeks ago
+ { color = "med" }
+ else if (span < 3024000) // modifed 5 weeks ago
+ { color = "old" }
+ else if (span < 12315200) // modifed 6 months ago
+ { color = "older" }
+ else
+ { color = "quiet" }
+ return color
+}
+
+function hushViews (n, bias, no_bold) {
+ const txt = commatize(n)
+ bias = bias || 1
+ n = n || 0
+ if (n < 30) { return["quiet", n + "&nbsp;v."] }
+ if (n < 200) { return ["quiet", txt + "&nbsp;v."] }
+ else if (n < 500) { return ["quiet", txt + "&nbsp;v."] }
+ else if (n < 1000) { return ["old", txt + "&nbsp;v."] }
+ else if (n < 5000) { return ["med", txt + "&nbsp;kv."] }
+ else if (nobold || n < 10000) { return ["recent", txt + "&nbsp;kv."] }
+ else { return ["new", txt + "&nbsp;kv."] }
+}
+
+function hushSize (n, bias, nobold) {
+ const txt = commatize(Math.floor(n / 1024))
+ bias = 1 || bias
+ n = n || 0
+ if (n < 1024) {
+ return ["quiet", n + "&nbsp;b."]
+ }
+ if (n < 1024*1024) {
+ return ["quiet", txt + "&nbsp;kb."]
+ }
+ else if (n < (20000000/bias)) {
+ return ["quiet", txt + "&nbsp;mb."]
+ }
+ else if (n < (50000000/bias)) {
+ return ["old", txt + "&nbsp;mb."]
+ }
+ else if (n < (80000000/bias)) {
+ return ["med", txt + "&nbsp;mb."]
+ }
+ else if (nobold || n < (170000000/bias)) {
+ return ["recent", txt + "&nbsp;mb."]
+ }
+ else {
+ return ["new", txt + "&nbsp;mb."]
+ }
+}
+
+function hushNull (n, unit, no_bold) {
+ const s = unit ? n + "&nbsp;" + unit + "." : n
+ if (n < 3) {
+ return ["quiet", s]
+ }
+ else if (n < 6) {
+ return ["older", s]
+ }
+ else if (n < 10) {
+ return ["old", s]
+ }
+ else if (n < 16) {
+ return ["med", s]
+ }
+ else if (no_bold || n < 21) {
+ return ["recent", s]
+ }
+ else {
+ return ["new", s]
+ }
+}
+
+function courtesyS (n, s) { return v == 1 ? "" : (s || "s") }
+
+const revisionLetters = "z a b c d f g h j k l m n p q r s t v w x y".split(" ")
+function getRevision (thread) {
+ if (! thread.revision) return ""
+ let rev = thread.revision
+ let n = 0
+ let digits = ""
+ do {
+ n = rev % 21
+ rev = Math.floor(rev / 21)
+ digits = revisionLetters[n] + digits
+ }
+ while (rev !== 0)
+ return digits
+}
+
+function getAge (t) {
+ let age = Math.abs( Date.now()/1000 - t)
+ const r = Math.floor
+ let m
+ if (age < 5) { return "now" }
+ if (age < 60) { return r(age) + "s" }
+ age /= 60
+ if (age < 60) { return r(age) + "m" }
+ m = r(age % 60)
+ age /= 60
+ if (m > 0 && age < 2) { return m + "m" + r(age) + "h" }
+ if (age < 24) { return r(age) + "h" }
+ age /= 24
+ if (age < 7) { return r(age) + "d" }
+ age /= 7
+ if (age < 12) { return r(age) + "w" }
+ age /= 4
+ if (age < 12) { return r(age) + "m" }
+ age /= 12
+ return r(age) + "y"
+}
+
+function tidyURLs (s, short_urls) {
+ return s.split("\n").map(function(line){
+ if (line.indexOf("<") !== -1) {
+ return line
+ }
+ return line.replace(/https?:\/\/[^ ]+/g, function(url){
+ if (is_image(url)) {
+ return '<a href="' + url + '" target="_blank"><img src="' + url + '"></a>'
+ }
+ else if (short_urls) {
+ return '<a href="' + url + '" target="_blank">[' + get_domain(url) + ']</a>'
+ }
+ else {
+ return '<a href="' + url + '" target="_blank">' + url + '</a>'
+ }
+ })
+ }).join("<br>\n")
+}
+function getDomain(url){
+ return url.replace(/https?:\/\//,"").replace(/\/.*/,"").replace(/www\./, "")
+}
diff --git a/client/vendor/paths.js b/client/vendor/paths.js
new file mode 100644
index 0000000..14c35b1
--- /dev/null
+++ b/client/vendor/paths.js
@@ -0,0 +1,18 @@
+function filepath (file) {
+ return '/data/' + file.folder_id + '/' + encodeURIComponent(file.name)
+}
+function mp3path (file) {
+ if (file.mime !== 'audio/mp3') {
+ return filepath(file) + '.mp3'
+ }
+ return filepath(file)
+}
+function pngpath (file) {
+ return filepath(file) + '.png'
+}
+
+export {
+ filepath,
+ mp3path,
+ pngpath,
+} \ No newline at end of file