todod/client/index.ls

486 lines
11 KiB
Plaintext

maquette = require "maquette"
nmd = require "nano-markdown"
{create-projector, h} = maquette
projector = create-projector!
bulma = require "./bulma.ls"
get-previous = (collection, element) ->
var previous
for item in collection
if item == element
return previous
previous = item
get-next = (collection, element) ->
var found-element
for item in collection
if found-element
return item
if item == element
found-element := true
Task = (self, project) ->
self.render = ->
author = model.users[self.author]
if typeof(author) != "object" and author != "request sent"
model.users[self.author] = "request sent"
# FIXME: This should go directly to authd.
socket.send JSON.stringify {
type: "get-user"
uid: self.author
}
assigned_to = model.users[self.assigned_to]
if assigned_to and typeof(assigned_to) != "object" and assigned_to != "request sent"
model.users[self.assigned_to] = "request sent"
# FIXME: This should go directly to authd.
socket.send JSON.stringify {
type: "get-user"
uid: self.assigned_to
}
is-selected = model.selected == self.id
h \div.card {
key: self.id
classes: {
"is-selected": is-selected
}
onclick: ->
model.selected := self.id
} [
h \div.card-content [
h \div.media [
h \div.media-left [
h \img.image.is-48x48.avatar {
alt: "user image"
src: if typeof(assigned_to) == "object"
assigned_to.avatar
else
"https://bulma.io/images/placeholders/96x96.png"
}
]
h \div.media-content [
if model.editing == self.id + ".title"
h \input.input {
value: self.title
onchange: (e) ->
model.editing := undefined
socket.send JSON.stringify {
type: "edit-task"
project: project.id
task: self.id
title: e.target.value
}
} [ self.title ]
else
bulma.title 4 self.title
if typeof(model.users[self.assigned_to]) == "object"
user = model.users[self.assigned_to]
h \div.subtitle.is-6 [
"@" + (user.full_name || user.login)
]
]
if is-selected
h \div.media-right {key: "edit"} [
h \span.small {
onclick: ->
if model.editing == self.id + ".title"
model.editing := undefined
else
model.editing := self.id + ".title"
} [
"Edit"
]
]
if is-selected
h \div.media-right {key: "delete"} [
h \span.small {
onclick: ->
model.editing := self.id + ".delete"
} [
"Delete"
]
]
]
if is-selected
h \div.content {
after-create: (dom) ->
dom.innerHTML = nmd self.description
} [
if model.editing == self.id + ".description"
h \form.form [
h \textarea.textarea {
value: self.description
oninput: (e) ->
model.editing-data := e.target.value
}
h \div.button.is-fullwidth {
onclick: ->
socket.send JSON.stringify {
type: "edit-task"
project: project.id
task: self.id
description: model.editing-data
}
model.editing-data := undefined
model.editing := undefined
} [ "Update" ]
]
]
if is-selected
h \span.button.is-small {
onclick: ->
model.editing := self.id + ".description"
} [
"edit"
]
]
if is-selected
h \div.card-footer {key: "assign"} [
if model.editing == self.id + ".assigned_to"
h \div.card-footer-item {
key: "assign.clicked"
} [
h \input.input {
onchange: (e) ->
model.editing := undefined
socket.send JSON.stringify {
type: "edit-task"
project: project.id
task: self.id
assigned_to: Number e.target.value
}
}
]
else
h \div.card-footer-item {
key: "assign"
onclick: ->
model.editing := self.id + ".assigned_to"
} [ "Assign" ]
]
if is-selected
h \div.card-footer {key: "move"} [
h \div.card-footer-item {
key: "⇐"
onclick: ->
socket.send JSON.stringify {
type: "edit-task"
project: project.id
task: self.id
column: get-previous project.columns.map((.id)), self.column
}
} [ "⇐" ]
if model.editing == self.id + ".delete"
h \div.card-footer-item {
key: "delete"
} [
h \div.button.is-danger {
onclick: ->
socket.send JSON.stringify {
type: "delete-task"
project: project.id
task: self.id
}
} [ "Delete! For real!" ]
]
h \div.card-footer-item {
key: "⇒"
onclick: ->
socket.send JSON.stringify {
type: "edit-task"
project: project.id
task: self.id
column: get-next project.columns.map((.id)), self.column
}
} [ "⇒" ]
]
]
self
Project = (self) ->
self.tasks = self.tasks.map (e) -> Task e, self
self.render-column = (column) ->
h \div.column.is-2 {
key: column.id
} [
h \div.card.is-column-header {
key: column.id
} [
h \div.card-header [
if model.editing == column.id + ".title"
h \input.input {
type: "text",
value: column.name
onchange: (e) ->
console.log "onchange??"
model.editing := undefined
socket.send JSON.stringify {
type: "edit-column"
project: self.id
column: column.id
name: e.target.value
}
}
else
h \div.card-header-title [
bulma.title 3 column.name
]
h \div.card-header-icon {
key: "edit"
onclick: ->
if model.editing == column.id + ".title"
model.editing := undefined
else
model.editing := column.id + ".title"
} [
"Edit"
]
if self.tasks.filter((.column == column.id)).length == 0
h \div.card-header-icon {
key: "delete"
onclick: ->
model.editing := column.id + ".delete"
} [
"Delete"
]
]
if model.editing == column.id + ".delete"
h \div.card-content [
h \div.button.is-fullwidth.is-danger {
onclick: ->
socket.send JSON.stringify {
type: "delete-column"
project: self.id
column: column.id
}
} [ "Delete me!"]
]
]
for task in self.tasks
continue if task.column != column.id
task.render!
h \div.button.is-fullwidth {
onclick: ->
socket.send JSON.stringify {
type: "new-task"
project: self.id
column: column.id
title: "General Kenobi…"
description: ""
}
} [ "New task" ]
]
self.render = ->
h \div.project {
key: self.id
} [
h \div.hero.is-dark { key: "title" } [
h \div.hero-body [
# FIXME: Consider using a .level for this.
h \div.is-pulled-right {
onclick: ->
model.editing := self.id + ".name"
} [
"Edit"
]
if model.editing == self.id + ".name"
h \input.input {
onchange: (e) ->
model.editing := undefined
socket.send JSON.stringify {
type: "edit-project"
project: self.id
name: e.target.value
}
value: self.name
}
else
h \div.title [ self.name ]
]
]
h \div.columns [
for dom in self.columns.map((column) -> self.render-column(column))
dom
h \div.column.is-2 {
key: "new-column"
} [
h \div.button.is-fullwidth {
onclick: ->
socket.send JSON.stringify {
type: "new-column"
project: self.id
name: "Hello, there!"
}
} [ "New Column" ]
]
]
]
self
model = {
current-view: "login"
editing: undefined
selected: undefined
users: {}
projects: {}
projects-list: []
}
socket = new WebSocket "ws://localhost:8888/socket"
socket.onopen = (event) ->
# Nothing to do here ATM.
socket.onerror = (event) ->
model.state = "websocket-error"
projector.schedule-render!
socket.onclase = (event) ->
model.state = "websocket-error"
model.websocket-error = event.reason
projector.schedule-render!
socket.onmessage = (event) ->
console.log event.data
message = JSON.parse event.data
switch message.type
when "login"
model.current-view := "projects-list"
when "list-projects"
for project in message.projects
model.projects[project.id] = Project project
model.projects-list := message.projects
when "project"
model.projects[message.project.id] = Project message.project
when "user"
model.users[message.user.uid] := message.user
else
console.log "RECEIVED UNKNOWN MESSAGE TYPE: #{message.type}"
projector.schedule-render!
console.log message
renderer = ->
h \div.section [
switch model.current-view
when "login"
h \div.container [
h \div.box [
h \form [
bulma.field [
bulma.label "Login"
bulma.input {
oninput: (e) ->
model.login = e.target.value
name: \login
id: \login-input
}
]
bulma.field [
bulma.label "Password"
bulma.input {
oninput: (e) ->
model.password = e.target.value
name: \password
type: \password
id: \password-input
}
]
h \button.button.is-fullwidth.is-primary {
onclick: (e) ->
e.prevent-default!
socket.send JSON.stringify {
type: "login",
login: model.login
password: model.password
}
} [ "Letsu go!" ]
]
]
]
when "project"
h \div [
h \div.navbar [
h \div.navbar-end [
h \a.navbar-item {
onclick: ->
model.viewed-project := undefined
model.current-view := "projects-list"
} [ "Go back" ]
]
]
if model.projects[model.viewed-project]
model.projects[model.viewed-project].render!
]
when "projects-list"
h \div#projects-list [
for project in (model.projects-list || [])
h \div.box {
key: project.id
onclick: ->
model.current-view := "project"
model.viewed-project := project.id
socket.send JSON.stringify {
type: "project",
project: project.id
}
} [
bulma.title 3 project.name
]
h \div.button.is-primary.is-large.is-fullwidth {
onclick: ->
socket.send JSON.stringify {
type: "new-project"
name: "Hello, there!"
}
} [ "New project! (random atm)" ]
]
else
h \div.notification.is-error [
"Wait, what? Internal error!"
]
]
document.add-event-listener 'DOMContentLoaded' ->
projector.append document.body, renderer