UX redesign, some grooming.
This commit is contained in:
parent
a928449c90
commit
f096fcec4f
11
Makefile
11
Makefile
@ -18,7 +18,7 @@ main.js: main.bundle.js
|
||||
$(Q)npx babel --minified main.bundle.js -o main.js
|
||||
|
||||
|
||||
main.bundle.js: client/index.ls client/authd.ls client/authws.ls client/bulma.ls client/card.ls client/modal.ls client/navbar.ls client/project-creation-modal.ls client/project.ls client/task-creation-modal.ls client/task.ls client/task-removal-modal.ls client/todowebsocket.ls client/validation-modal.ls
|
||||
main.bundle.js: client/index.ls client/authws.ls client/bulma.ls client/card.ls client/column-edit-modal.ls client/font-awesome.ls client/modal.ls client/navbar.ls client/project-creation-modal.ls client/project.ls client/task-creation-modal.ls client/task.ls client/task-removal-modal.ls client/todowebsocket.ls client/validation-modal.ls
|
||||
@echo '[01;32m BUN > [01;37mmain.bundle.js[00m'
|
||||
$(Q)npx browserify -t browserify-livescript client/index.ls -o main.bundle.js
|
||||
|
||||
@ -96,10 +96,11 @@ $(PACKAGE)-$(VERSION).tar.gz: distdir
|
||||
$(Q)tar czf $(PACKAGE)-$(VERSION).tar.gz \
|
||||
$(PACKAGE)-$(VERSION)/client/index.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/style.sass \
|
||||
$(PACKAGE)-$(VERSION)/client/authd.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/authws.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/bulma.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/card.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/column-edit-modal.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/font-awesome.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/modal.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/navbar.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/project-creation-modal.ls \
|
||||
@ -116,10 +117,11 @@ $(PACKAGE)-$(VERSION).tar.xz: distdir
|
||||
$(Q)tar cJf $(PACKAGE)-$(VERSION).tar.xz \
|
||||
$(PACKAGE)-$(VERSION)/client/index.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/style.sass \
|
||||
$(PACKAGE)-$(VERSION)/client/authd.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/authws.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/bulma.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/card.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/column-edit-modal.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/font-awesome.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/modal.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/navbar.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/project-creation-modal.ls \
|
||||
@ -136,10 +138,11 @@ $(PACKAGE)-$(VERSION).tar.bz2: distdir
|
||||
$(Q)tar cjf $(PACKAGE)-$(VERSION).tar.bz2 \
|
||||
$(PACKAGE)-$(VERSION)/client/index.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/style.sass \
|
||||
$(PACKAGE)-$(VERSION)/client/authd.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/authws.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/bulma.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/card.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/column-edit-modal.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/font-awesome.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/modal.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/navbar.ls \
|
||||
$(PACKAGE)-$(VERSION)/client/project-creation-modal.ls \
|
||||
|
177
client/authd.ls
177
client/authd.ls
@ -1,177 +0,0 @@
|
||||
bulma = require "./bulma.ls"
|
||||
h = require 'maquette' .h
|
||||
|
||||
|
||||
module.exports = {
|
||||
create-socket: (socket-url) ->
|
||||
self = {}
|
||||
|
||||
self.token = ""
|
||||
|
||||
request-types = {
|
||||
"get-token": 0
|
||||
"add-user": 1
|
||||
"get-user": 2
|
||||
"get-user-by-credentials": 3
|
||||
"mod-user": 4
|
||||
|
||||
# TODO: code these messages
|
||||
"register": 5
|
||||
"get-extra": 6
|
||||
"set-extra": 7
|
||||
"update-password": 8
|
||||
|
||||
"list-users": 9
|
||||
"set-permissions": 10
|
||||
}
|
||||
|
||||
response-types = {
|
||||
"error": 0
|
||||
"token": 1
|
||||
"user": 2
|
||||
"user-added": 3
|
||||
"user-edited": 4
|
||||
"extra": 5
|
||||
"extra-updated": 6
|
||||
"users-list": 7
|
||||
}
|
||||
|
||||
# TODO: naming convention
|
||||
# users can record functions to run on events
|
||||
self.user-on-socket-error = []
|
||||
self.user-on-socket-close = []
|
||||
|
||||
self.callbacks = {}
|
||||
for key, value of response-types
|
||||
self.callbacks[value] = []
|
||||
|
||||
# self.user-on-message = []
|
||||
|
||||
self.add-event-listener = (type, callback) ->
|
||||
type = response-types[type]
|
||||
|
||||
self.callbacks[type] ++= [callback]
|
||||
|
||||
self.open-socket = ->
|
||||
console.log "Opening socket to #{socket-url}"
|
||||
self.socket := new WebSocket socket-url
|
||||
|
||||
self.socket.onerror = (event) ->
|
||||
for f in self.user-on-socket-error
|
||||
f event
|
||||
self.socket.close!
|
||||
|
||||
self.socket.onclose = (event) ->
|
||||
for f in self.user-on-socket-close
|
||||
f event
|
||||
|
||||
self.socket.onmessage = (event) ->
|
||||
message = JSON.parse(event.data)
|
||||
|
||||
for f in self.callbacks[message.mtype]
|
||||
f JSON.parse(message.payload)
|
||||
|
||||
self.reopen = ->
|
||||
self.socket.close!
|
||||
self.open-socket!
|
||||
|
||||
self.open-socket!
|
||||
|
||||
self.send = (type, opts) ->
|
||||
self.socket.send JSON.stringify { mtype: type, payload: opts }
|
||||
|
||||
self.get-token = (login, password) ->
|
||||
self.send request-types[\get-token], JSON.stringify {
|
||||
login: login
|
||||
password: password
|
||||
}
|
||||
|
||||
self.get-user-by-credentials = (login, password) ->
|
||||
self.send request-types[\get-user-by-credentials], JSON.stringify {
|
||||
login: login
|
||||
password: password
|
||||
}
|
||||
|
||||
self.login = (login, password) ->
|
||||
self.get-token login, password
|
||||
self.get-user-by-credentials login, password
|
||||
|
||||
|
||||
self.get-user = (uid) ->
|
||||
self.send request-types[\get-user], JSON.stringify {
|
||||
uid: uid
|
||||
}
|
||||
|
||||
self.list-users = ->
|
||||
self.send request-types[\list-users], JSON.stringify {
|
||||
token: self.token
|
||||
# FIXME: this will be removed once the authd program will accept
|
||||
# any list-users requests from any user
|
||||
# which is the only logical choice since any user can create projects and assign people to it
|
||||
key: "nico-nico-nii"
|
||||
}
|
||||
|
||||
self.set-permissions = (list-id, user-id, permission) ->
|
||||
self.send request-types[\set-permissions], JSON.stringify {
|
||||
token: self.token
|
||||
list: list-id
|
||||
uid: user-id
|
||||
permission: permission
|
||||
}
|
||||
|
||||
# TODO: authd overhaul
|
||||
#self.add-user = (login, password) ->
|
||||
# self.send request-types[\add-user], JSON.stringify {
|
||||
# login: login
|
||||
# password: password
|
||||
# }
|
||||
|
||||
# TODO: authd overhaul
|
||||
#self.mod-user = (uid) ->
|
||||
# self.send request-types[\mod-user], JSON.stringify {
|
||||
# uid: uid
|
||||
# }
|
||||
|
||||
self
|
||||
|
||||
login-page: (model) ->
|
||||
# XXX: container = marge
|
||||
# XXX: box = dessiner un contour
|
||||
h \div.container [
|
||||
h \div.box [
|
||||
if model.login-error
|
||||
h \div.notification.is-danger [
|
||||
model.login-error
|
||||
]
|
||||
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) ->
|
||||
# not to refresh the page since it's a form button
|
||||
e.prevent-default!
|
||||
|
||||
model.authd-ws.get-token model.login, model.password
|
||||
} [ "Connexion" ]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
136
client/authws.ls
Normal file
136
client/authws.ls
Normal file
@ -0,0 +1,136 @@
|
||||
bulma = require "./bulma.ls"
|
||||
h = require 'maquette' .h
|
||||
|
||||
AuthWS = (socket-url) ->
|
||||
self = {}
|
||||
|
||||
request-types = {
|
||||
"get-token": 0
|
||||
"add-user": 1
|
||||
"get-user": 2
|
||||
"get-user-by-credentials": 3
|
||||
"mod-user": 4
|
||||
"register": 5
|
||||
"update-password": 6
|
||||
"list-users": 7
|
||||
"check-permission": 8
|
||||
"set-permission": 9
|
||||
}
|
||||
|
||||
response-types = {
|
||||
"error": 0
|
||||
"token": 1
|
||||
"user": 2
|
||||
"user-added": 3
|
||||
"user-edited": 4
|
||||
"users-list": 5
|
||||
"permission-check": 6
|
||||
"permission-set": 7
|
||||
}
|
||||
|
||||
# TODO: naming convention
|
||||
# users can record functions to run on events
|
||||
self.user-on-socket-error = []
|
||||
self.user-on-socket-close = []
|
||||
|
||||
self.callbacks = {}
|
||||
for key, value of response-types
|
||||
self.callbacks[value] = []
|
||||
|
||||
self.add-event-listener = (type, callback) ->
|
||||
type = response-types[type]
|
||||
|
||||
self.callbacks[type] ++= [callback]
|
||||
|
||||
self.open-socket = ->
|
||||
self.socket := new WebSocket socket-url
|
||||
|
||||
self.socket.onerror = (event) ->
|
||||
for f in self.user-on-socket-error
|
||||
f event
|
||||
self.socket.close!
|
||||
|
||||
self.socket.onclose = (event) ->
|
||||
for f in self.user-on-socket-close
|
||||
f event
|
||||
|
||||
self.socket.onmessage = (event) ->
|
||||
message = JSON.parse(event.data)
|
||||
|
||||
for f in self.callbacks[message.mtype]
|
||||
f JSON.parse(message.payload)
|
||||
|
||||
self.reopen = ->
|
||||
self.socket.close!
|
||||
self.open-socket!
|
||||
|
||||
self.open-socket!
|
||||
|
||||
self.send = (type, opts) ->
|
||||
self.socket.send JSON.stringify { mtype: type, payload: opts }
|
||||
|
||||
self.get-token = (login, password) ->
|
||||
self.send request-types[\get-token], JSON.stringify {
|
||||
login: login
|
||||
password: password
|
||||
}
|
||||
|
||||
self.get-user-by-credentials = (login, password) ->
|
||||
self.send request-types[\get-user-by-credentials], JSON.stringify {
|
||||
login: login
|
||||
password: password
|
||||
}
|
||||
|
||||
self.login = (login, password) ->
|
||||
self.get-token login, password
|
||||
self.get-user-by-credentials login, password
|
||||
|
||||
|
||||
self.get-user = (uid) ->
|
||||
self.send request-types[\get-user], JSON.stringify {
|
||||
uid: uid
|
||||
}
|
||||
|
||||
self.register = (login, password) ->
|
||||
self.send request-types[\register], JSON.stringify {
|
||||
login: login
|
||||
password: password
|
||||
}
|
||||
|
||||
self.get-extra = (token, name) ->
|
||||
console.warn "get-extra: deprecated"
|
||||
|
||||
self.set-extra = (token, name, extra) ->
|
||||
console.warn "set-extra: deprecated"
|
||||
|
||||
self.update-password = (login, old-password, new-password) ->
|
||||
self.send request-types[\update-password], JSON.stringify {
|
||||
login: login
|
||||
old_password: old-password
|
||||
new_password: new-password
|
||||
}
|
||||
|
||||
self.list-users = (token) ->
|
||||
console.warn "list-users: unstable API"
|
||||
|
||||
self.send request-types[\list-users], JSON.stringify {
|
||||
token: token
|
||||
}
|
||||
|
||||
# TODO: authd overhaul required
|
||||
#self.add-user = (login, password) ->
|
||||
# self.send request-types[\add-user], JSON.stringify {
|
||||
# login: login
|
||||
# password: password
|
||||
# }
|
||||
|
||||
# TODO: authd overhaul required
|
||||
#self.mod-user = (uid) ->
|
||||
# self.send request-types[\mod-user], JSON.stringify {
|
||||
# uid: uid
|
||||
# }
|
||||
|
||||
self
|
||||
|
||||
module.exports = AuthWS
|
||||
|
44
client/column-edit-modal.ls
Normal file
44
client/column-edit-modal.ls
Normal file
@ -0,0 +1,44 @@
|
||||
{h} = require "maquette"
|
||||
|
||||
{field, control, input, label} = require "./bulma.ls"
|
||||
|
||||
Modal = require "./modal.ls"
|
||||
|
||||
deep-copy = (object) ->
|
||||
JSON.parse JSON.stringify object
|
||||
|
||||
ColumnEditModal = (project, column, args) ->
|
||||
self = {
|
||||
column: deep-copy column
|
||||
|
||||
on-validation: args.on-validation || (column) ->
|
||||
}
|
||||
|
||||
modal = Modal {
|
||||
+visible
|
||||
|
||||
content: [
|
||||
field [
|
||||
label "Column title"
|
||||
|
||||
control [
|
||||
input {
|
||||
value: self.column.title
|
||||
oninput: (e) ->
|
||||
self.column.title = e.target.value
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
on-validation: ->
|
||||
self.on-validation self.column
|
||||
}
|
||||
|
||||
self.render = ->
|
||||
modal.render!
|
||||
|
||||
self
|
||||
|
||||
module.exports = ColumnEditModal
|
||||
|
13
client/font-awesome.ls
Normal file
13
client/font-awesome.ls
Normal file
@ -0,0 +1,13 @@
|
||||
{h} = require "maquette"
|
||||
|
||||
module.exports = {
|
||||
icon: (selector, icon) ->
|
||||
unless icon
|
||||
icon = selector
|
||||
selector = ""
|
||||
|
||||
h (\span.icon + selector), [
|
||||
h "span.fas.fa-#{icon}"
|
||||
]
|
||||
}
|
||||
|
@ -1,16 +1,15 @@
|
||||
|
||||
maquette = require "maquette"
|
||||
nmd = require "nano-markdown"
|
||||
authd = require "./authd.ls"
|
||||
LoginForm = require "../lib/authd/client/login-form.ls"
|
||||
todows = require "./todowebsocket.ls"
|
||||
AuthWS = require "./authws.ls"
|
||||
bulma = require "./bulma.ls"
|
||||
Task = require "./task.ls"
|
||||
Project = require "./project.ls"
|
||||
Modal = require "./modal.ls"
|
||||
Navbar = require "./navbar.ls"
|
||||
# ValidationModal = require "./validation-modal.ls"
|
||||
UUID = require "uuid/v4"
|
||||
|
||||
{create-projector, h} = maquette
|
||||
projector = create-projector!
|
||||
@ -58,7 +57,7 @@ console.log "todod url: " + model.todod-url
|
||||
# network configuration
|
||||
#
|
||||
|
||||
model.authd-ws = authd.create-socket model.authd-url
|
||||
model.authd-ws = AuthWS model.authd-url
|
||||
model.todod-ws = todows.create-socket model.todod-url
|
||||
|
||||
|
||||
@ -71,9 +70,13 @@ model.todod-ws = todows.create-socket model.todod-url
|
||||
authd-on-websocket-error = (event) ->
|
||||
console.log "WebSocket error.", event
|
||||
model.current-view := "network-error"
|
||||
model.last-network-error := event
|
||||
projector.schedule-render!
|
||||
|
||||
authd-on-websocket-close = (event) ->
|
||||
if model.current-view == "network-error"
|
||||
return
|
||||
|
||||
model.current-view := "login"
|
||||
console.log "WebSocket has been closed.", event
|
||||
# model.todod-ws.reopen!
|
||||
@ -122,9 +125,13 @@ model.authd-ws.add-event-listener \users-list, (message) ->
|
||||
on-websocket-error = (event) ->
|
||||
console.log "WebSocket error.", event
|
||||
model.current-view := "network-error"
|
||||
model.last-network-error := event
|
||||
projector.schedule-render!
|
||||
|
||||
on-websocket-close = (event) ->
|
||||
if model.current-view == "network-error"
|
||||
return
|
||||
|
||||
model.current-view := "login"
|
||||
console.log "WebSocket has been closed.", event
|
||||
# model.todod-ws.reopen!
|
||||
@ -136,7 +143,7 @@ model.todod-ws.user-on-socket-error ++= [ on-websocket-error ]
|
||||
model.todod-ws.user-on-socket-close ++= [ on-websocket-close ]
|
||||
|
||||
model.todod-ws.add-event-listener \lists-list, (message) ->
|
||||
console.log "Project list received", message
|
||||
console.log "Projects list received", message
|
||||
|
||||
model.project-list := message.lists.map (x) ->
|
||||
old-project = model.project-list.find((.id == x.id))
|
||||
@ -192,7 +199,7 @@ model.todod-ws.add-event-listener \tasks, (message) ->
|
||||
project = model.project-list.find((.id == message.list))
|
||||
project.tasks := message.tasks.map (e) -> Task e, project, model.todod-ws
|
||||
|
||||
if model.viewed-project.id == project.id
|
||||
if model.viewed-project && model.viewed-project.id == project.id
|
||||
model.viewed-project := project
|
||||
|
||||
projector.schedule-render!
|
||||
@ -202,7 +209,7 @@ model.todod-ws.add-event-listener \task-created, (message) ->
|
||||
|
||||
task = message.task
|
||||
list = model.project-list.find((.id == task.list))
|
||||
if list.id == model.viewed-project.id
|
||||
if model.viewed-project && list.id == model.viewed-project.id
|
||||
model.viewed-project := list
|
||||
|
||||
if list
|
||||
@ -249,12 +256,6 @@ render-project-list = ->
|
||||
]
|
||||
]
|
||||
|
||||
Column = (title) ->
|
||||
{
|
||||
title: title
|
||||
id: UUID! # TODO FIXME XXX
|
||||
}
|
||||
|
||||
render-project = (project) ->
|
||||
if project
|
||||
project.render!
|
||||
@ -266,6 +267,10 @@ render-project = (project) ->
|
||||
model.login-form = LoginForm {
|
||||
authws-url: model.authd-url
|
||||
on-login: (user, token) ->
|
||||
# FIXME: May double-login if the user clicks the “login” button too much.
|
||||
if model.current-view != "login"
|
||||
return
|
||||
|
||||
model.current-view := "project-list"
|
||||
model.authd-ws.token := token
|
||||
model.todod-ws.token := token
|
||||
@ -295,6 +300,19 @@ render-body = ->
|
||||
render-project-list!
|
||||
when "project"
|
||||
render-project model.viewed-project
|
||||
when "network-error"
|
||||
h \div.container [
|
||||
h \div.notification.is-danger [
|
||||
h \div.title.is-2 [ "Socket error!" ]
|
||||
|
||||
h \div.content [
|
||||
h \p [ "We’re very sorry, but something got very wrong between here and the backend." ]
|
||||
h \p [ "Please check your network connection and refresh the page." ]
|
||||
|
||||
h \p.small [ "The error happened on " + model.last-network-error.explicit-original-target.url ]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
else
|
||||
h \div.notification.is-error [
|
||||
|
@ -53,7 +53,7 @@ Modal = (args, caller) ->
|
||||
]
|
||||
]
|
||||
|
||||
h \button.button.modal-close {
|
||||
h \a.modal-close {
|
||||
aria-label: "close"
|
||||
onclick: ->
|
||||
self.visible := false
|
||||
|
@ -1,23 +1,18 @@
|
||||
{h} = require "maquette"
|
||||
|
||||
{button, field, control} = require "./bulma.ls"
|
||||
{icon} = require "./font-awesome.ls"
|
||||
|
||||
render-new-project-button = (model) ->
|
||||
h \div.button.is-success.is-medium.is-outlined {
|
||||
onclick: ->
|
||||
model.todod-ws.add-list "New project", {
|
||||
extra_properties: {
|
||||
columns: [
|
||||
Column "Unassigned"
|
||||
Column "Work in progress"
|
||||
Column "To be checked"
|
||||
Column "Being checked"
|
||||
Column "Done"
|
||||
]
|
||||
}
|
||||
}
|
||||
} [ "New project!" ]
|
||||
Modal = require "./modal.ls"
|
||||
UUID = require "uuid/v4"
|
||||
|
||||
ProjectCreationModal = require "./project-creation-modal.ls"
|
||||
|
||||
Column = (title) ->
|
||||
{
|
||||
title: title
|
||||
id: UUID! # TODO FIXME XXX
|
||||
}
|
||||
|
||||
Navbar = ->
|
||||
self = {}
|
||||
@ -45,19 +40,31 @@ Navbar = ->
|
||||
if model.current-view != "login"
|
||||
h \div.navbar-end [
|
||||
if model.current-view == "project-list"
|
||||
h \div.navbar-item {key: "new project"} [
|
||||
render-new-project-button model
|
||||
h \a.navbar-item.has-text-success {
|
||||
key: \new-project
|
||||
onclick: ->
|
||||
model.modal := ProjectCreationModal {
|
||||
on-validation: (project) ->
|
||||
model.todod-ws.add-list project.title, project
|
||||
}
|
||||
} [
|
||||
h \span [ "New project" ]
|
||||
|
||||
icon \plus
|
||||
]
|
||||
else if model.current-view == "project" && model.viewed-project
|
||||
model.viewed-project.right-nav-render!
|
||||
|
||||
h \div.navbar-item {key: "logout"} [
|
||||
h \a.button.is-medium.is-outlined {
|
||||
onclick: ->
|
||||
model.current-view := "login"
|
||||
# TODO: remove anything related to the old session on the client
|
||||
model.todod-ws.reopen!
|
||||
} [ "Logout" ]
|
||||
h \a.navbar-item {
|
||||
key: "logout"
|
||||
onclick: ->
|
||||
model.current-view := "login"
|
||||
# TODO: remove anything related to the old session on the client
|
||||
model.todod-ws.reopen!
|
||||
} [
|
||||
h \span [ "Logout" ]
|
||||
|
||||
icon \sign-out-alt
|
||||
]
|
||||
]
|
||||
]
|
||||
|
@ -6,216 +6,155 @@ bulma = require "./bulma.ls"
|
||||
|
||||
{field, control, label, button, tag, input, select} = bulma
|
||||
|
||||
col-to-lines = (column, self) ->
|
||||
field \.has-addons {
|
||||
key: "column.#{column.id}"
|
||||
} [
|
||||
control \.is-expanded [
|
||||
input {
|
||||
key: "input" + column.id
|
||||
value: column.title
|
||||
oninput: (e) ->
|
||||
self.extra_properties.columns.find((.id == column.id)).title := e.target.value
|
||||
}
|
||||
]
|
||||
deep-copy = (object) ->
|
||||
JSON.parse JSON.stringify object
|
||||
|
||||
control [
|
||||
button \.is-danger.is-outlined {
|
||||
key: "button" + column.id
|
||||
onclick: ->
|
||||
self.extra_properties.columns := self.extra_properties.columns.filter((.id != column.id))
|
||||
} [ "DELETE" ]
|
||||
]
|
||||
]
|
||||
const PERMISSION_LEVELS = ["admin", "edit", "read"]
|
||||
|
||||
user-form-selection = (self, user) ->
|
||||
h \option {
|
||||
value: user.uid
|
||||
} [ user.login ]
|
||||
remove-permission = (project, uid) ->
|
||||
for key, value of project.permissions
|
||||
project.permissions[key] := value.filter (!= uid)
|
||||
|
||||
permission-groups =
|
||||
"read"
|
||||
"post"
|
||||
"edit"
|
||||
"admin"
|
||||
|
||||
permissions-add = (self, permission, user-id) ->
|
||||
perm-list = self.permissions[permission]
|
||||
if perm-list
|
||||
is-already-there = perm-list.find (e) -> (""+ e) == ("" + user-id)
|
||||
if is-already-there
|
||||
console.log "user #{user-id} already in #{perm-list}"
|
||||
else
|
||||
perm-list ++= [ parseInt(user-id) ]
|
||||
self.permissions[permission] := perm-list
|
||||
# console.log "adding user #{user-id} to #{perm-list[0]}: #{perm-list}"
|
||||
else
|
||||
console.log "Cannot find #{permission} permissions, creating it"
|
||||
self.permissions[permission] := user-id
|
||||
|
||||
permission-to-form-selection = (self, permission) ->
|
||||
h \option {
|
||||
value: permission
|
||||
} [ permission ]
|
||||
|
||||
ProjectCreationModal = (project, todod-ws, users) ->
|
||||
|
||||
# work on a copy of the columns
|
||||
# in case of cancelled modifications, only the copies are changed
|
||||
columns-copy = []
|
||||
for col in project.extra_properties.columns
|
||||
new-col = {}
|
||||
for k,v of col
|
||||
new-col[k] = v
|
||||
columns-copy ++= [ new-col ]
|
||||
add-permission = (project, uid, perm) ->
|
||||
project.permissions[perm].push uid
|
||||
|
||||
ProjectCreationModal = (args) ->
|
||||
self = {
|
||||
title: project.title || ""
|
||||
permissions: project.permissions || {admin: [], edit: [], post: [], read: []}
|
||||
project: if args.project
|
||||
deep-copy args.project
|
||||
else
|
||||
{}
|
||||
visible: args.visible || true
|
||||
|
||||
tmp:
|
||||
new-user-permission:
|
||||
id: void
|
||||
permission: permission-groups[0]
|
||||
new-column-input:
|
||||
title: "New column !"
|
||||
users: users || []
|
||||
on-validation: args.on-validation || (project) ->
|
||||
|
||||
extra_properties:
|
||||
columns: columns-copy
|
||||
input : {
|
||||
# Used when giving permissions to users not currently in the
|
||||
# list of permissions.
|
||||
new-user: ""
|
||||
new-user-permission: \read
|
||||
}
|
||||
}
|
||||
|
||||
modal = Modal {
|
||||
+visible
|
||||
content-render: (self) ->
|
||||
h \div.form [
|
||||
self.project.tasks := void
|
||||
|
||||
field [
|
||||
label "Project title"
|
||||
unless self.project.extra_properties
|
||||
Column = (title, args) ->
|
||||
self = {
|
||||
title: title
|
||||
id: UUID!
|
||||
}
|
||||
|
||||
input {
|
||||
value: self.title
|
||||
oninput: (e) ->
|
||||
self.title := e.target.value
|
||||
}
|
||||
]
|
||||
for key, value of (args || {})
|
||||
self[key] = value
|
||||
|
||||
# h \hr []
|
||||
self
|
||||
|
||||
# bulma.field [
|
||||
# bulma.label "Adding a user"
|
||||
# bulma.input {
|
||||
# value: self.new-user
|
||||
# oninput: (e) ->
|
||||
# self.new-user := e.target.value
|
||||
# name: \new-user
|
||||
# id: \user-add
|
||||
# }
|
||||
# ]
|
||||
self.project.extra_properties = {
|
||||
columns: [
|
||||
Column "Unassigned" {color: "red"}
|
||||
Column "Work in Progress"
|
||||
Column "To be Checked"
|
||||
Column "Being Checked"
|
||||
Column "Done" {color: "green"}
|
||||
]
|
||||
}
|
||||
|
||||
h \hr []
|
||||
self.modal = Modal {
|
||||
on-validation: ->
|
||||
if true # FIXME: Validate project
|
||||
self.visible := false
|
||||
|
||||
field [
|
||||
args.on-validation self.project
|
||||
|
||||
on-cancellation: ->
|
||||
self.visible := false
|
||||
|
||||
content-render: -> [
|
||||
field {key: \title} [
|
||||
label "Title"
|
||||
|
||||
input {
|
||||
value: self.project.title
|
||||
oninput: (e) ->
|
||||
console.log "oninput => ", e.target.value
|
||||
self.project.title := e.target.value
|
||||
}
|
||||
]
|
||||
|
||||
if self.project.permissions
|
||||
field {key: \permissions} [
|
||||
label "Permissions"
|
||||
|
||||
h \table.table.is-fullwidth.is-striped [
|
||||
h \table.table.is-fullwidth [
|
||||
h \thead [
|
||||
h \th [ "User" ]
|
||||
h \th [ "Permission" ]
|
||||
]
|
||||
h \tbody [
|
||||
for permission, uids of self.permissions
|
||||
for uid in uids
|
||||
h \tr {key: uid.to-string!} [
|
||||
h \td [
|
||||
uid.to-string!
|
||||
for permission in PERMISSION_LEVELS
|
||||
[
|
||||
for uid in self.project.permissions[permission]
|
||||
h \tr {key: uid} [
|
||||
# FIXME: show full name or login
|
||||
h \td [ uid.to-string! ]
|
||||
h \td [ permission ]
|
||||
]
|
||||
|
||||
h \td.is-narrow [
|
||||
permission
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
h \hr []
|
||||
|
||||
# FIXME: This is supposed to go in a .field, right?
|
||||
label "Adding new user"
|
||||
|
||||
field \.has-addons { key: \new-user } [
|
||||
control [
|
||||
select {
|
||||
onchange: (e) ->
|
||||
self.tmp.new-user-permission.permission := e.target.value
|
||||
} permission-groups.map (permission) -> permission-to-form-selection self, permission
|
||||
]
|
||||
|
||||
control \.is-expanded [
|
||||
select \.is-fullwidth {
|
||||
onchange: (e) ->
|
||||
self.tmp.new-user-permission.uid := e.target.value
|
||||
} [
|
||||
user-form-selection self, { login: "Choose a user", uid: "-" }
|
||||
for user-id, user of self.tmp.users
|
||||
user-form-selection self, user
|
||||
]
|
||||
]
|
||||
|
||||
control [
|
||||
button \.is-success.is-outlined {
|
||||
onclick: ->
|
||||
if self.tmp.new-user-permission.uid == void || self.tmp.new-user-permission.uid == "-"
|
||||
console.log "adding an user permission on the kanban: failed, no user selected"
|
||||
else
|
||||
# TODO:
|
||||
# adding the permissions in self.permissions
|
||||
# then editing the project in the db via todod-ws
|
||||
permissions-add self, self.tmp.new-user-permission.permission, self.tmp.new-user-permission.uid
|
||||
} [ "+" ]
|
||||
]
|
||||
]
|
||||
|
||||
h \hr []
|
||||
|
||||
label [ "Columns" ]
|
||||
|
||||
for dom in (self.extra_properties.columns.map (column) -> col-to-lines column, self)
|
||||
dom
|
||||
|
||||
h \hr []
|
||||
|
||||
field \.has-addons { key: \new-column } [
|
||||
if self.project.permissions
|
||||
field \.has-addons [
|
||||
control \.is-expanded [
|
||||
# FIXME: Replace by a “user search input” as soon
|
||||
# as one is available.
|
||||
input {
|
||||
value: self.tmp.new-column-input.title
|
||||
value: self.input.new-user
|
||||
oninput: (e) ->
|
||||
self.tmp.new-column-input.title := e.target.value
|
||||
self.input.new-user := e.target.value
|
||||
}
|
||||
]
|
||||
|
||||
control [
|
||||
button \.is-success.is-outlined {
|
||||
select {
|
||||
onchange: (e) ->
|
||||
self.input.new-user-permission := e.target.value
|
||||
} PERMISSION_LEVELS.map (perm) ->
|
||||
h \option {
|
||||
selected: perm == \read
|
||||
} [ perm ]
|
||||
]
|
||||
|
||||
control [
|
||||
h \div.button.is-success.is-outlined {
|
||||
onclick: ->
|
||||
new-col = {
|
||||
id: UUID!
|
||||
title: self.tmp.new-column-input.title
|
||||
}
|
||||
self.extra_properties.columns ++= [ new-col ]
|
||||
self.tmp.new-column-input.title := "New column !"
|
||||
} [ "+" ]
|
||||
uid = parse-int self.input.new-user
|
||||
perm = self.input.new-user-permission
|
||||
|
||||
self.input.new-user := ""
|
||||
# FIXME: Really? :thonk:
|
||||
#self.input.new-user-permission := \read
|
||||
|
||||
console.log "Adding new perm? :/", uid, perm
|
||||
|
||||
remove-permission self.project, uid
|
||||
add-permission self.project, uid, perm
|
||||
|
||||
console.log self.project.permissions
|
||||
} [
|
||||
"+"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
on-validation: ->
|
||||
tmp = delete self.tmp
|
||||
if project.id
|
||||
todod-ws.edit-list project.id, self
|
||||
else
|
||||
todod-ws.add-list self.title, self
|
||||
self.tmp := tmp
|
||||
}, self
|
||||
]
|
||||
}
|
||||
|
||||
self.render = ->
|
||||
modal.render!
|
||||
self.modal.visible = self.visible
|
||||
|
||||
self.modal.render!
|
||||
|
||||
self
|
||||
|
||||
|
@ -5,6 +5,12 @@ Task = require "./task.ls"
|
||||
Modal = require './modal.ls'
|
||||
TaskCreationModal = require './task-creation-modal.ls'
|
||||
ProjectCreationModal = require './project-creation-modal.ls'
|
||||
ColumnEditModal = require './column-edit-modal.ls'
|
||||
|
||||
{icon} = require './font-awesome.ls'
|
||||
|
||||
deep-copy = (object) ->
|
||||
JSON.parse JSON.stringify object
|
||||
|
||||
is-right-column = (task, column-id) ->
|
||||
task.extra_properties && task.extra_properties.column && task.extra_properties.column == column-id
|
||||
@ -43,7 +49,31 @@ Project = (self, todod-ws, users) ->
|
||||
h \div.column.cards-list {
|
||||
key: column.id
|
||||
} [
|
||||
h \p.title.is-4 [ column.title ]
|
||||
h \p.title.is-4 [
|
||||
column.title
|
||||
|
||||
h \a.is-pulled-right.icon.has-text-grey {
|
||||
onclick: ->
|
||||
modal := ColumnEditModal self, column, {
|
||||
on-validation: (column) ->
|
||||
console.log "column update:", column
|
||||
modal.visible := false
|
||||
|
||||
extra_properties = deep-copy(self.extra_properties)
|
||||
extra_properties.columns = extra_properties.columns.map (old-column) ->
|
||||
if old-column.id == column.id
|
||||
column
|
||||
else
|
||||
old-column
|
||||
|
||||
todod-ws.edit-list self.id, {
|
||||
extra_properties: extra_properties
|
||||
}
|
||||
}
|
||||
} [
|
||||
icon \cog
|
||||
]
|
||||
]
|
||||
|
||||
tasks-to-display.map (task) ->
|
||||
task.render {
|
||||
@ -61,37 +91,44 @@ Project = (self, todod-ws, users) ->
|
||||
|
||||
self.right-nav-render = ->
|
||||
[
|
||||
h \div.navbar-item {
|
||||
h \a.navbar-item.has-text-success {
|
||||
key: "navbar-new-task"
|
||||
onclick: ->
|
||||
modal := TaskCreationModal self, self.todod-ws, void, self.users
|
||||
} [
|
||||
h \div.button.is-success.is-outlined.is-medium {
|
||||
onclick: ->
|
||||
modal := TaskCreationModal self, self.todod-ws, void, self.users
|
||||
} [ "New task" ]
|
||||
h \span [ "New task" ]
|
||||
|
||||
icon \plus
|
||||
]
|
||||
|
||||
h \div.navbar-item {
|
||||
h \a.navbar-item {
|
||||
key: "navbar-edit-project"
|
||||
onclick: ->
|
||||
modal := ProjectCreationModal {
|
||||
project: self
|
||||
|
||||
on-validation: (project) ->
|
||||
console.log "Requesting edit for", project
|
||||
self.todod-ws.edit-list project.id, project
|
||||
}
|
||||
} [
|
||||
h \div.button.is-dark.is-outlined.is-medium {
|
||||
onclick: ->
|
||||
modal := ProjectCreationModal self, self.todod-ws, self.users
|
||||
} [ "Edit this project" ]
|
||||
icon \cog
|
||||
# "Edit this project"
|
||||
]
|
||||
|
||||
h \div.navbar-item {
|
||||
h \a.navbar-item.has-text-danger {
|
||||
key: "navbar-delete-project"
|
||||
onclick: ->
|
||||
modal := Modal {
|
||||
+visible
|
||||
content:
|
||||
h \p [ "Are you sure you want to remove board #{self.title}?" ]
|
||||
on-validation: ->
|
||||
self.todod-ws.remove-list self.id
|
||||
}
|
||||
} [
|
||||
h \div.button.is-danger.is-outlined.is-medium {
|
||||
onclick: ->
|
||||
modal := Modal {
|
||||
+visible
|
||||
content:
|
||||
h \p [ "Are you sure you want to remove board #{self.title}?" ]
|
||||
on-validation: ->
|
||||
self.todod-ws.remove-list self.id
|
||||
}
|
||||
} [ "Delete this project" ]
|
||||
icon \skull-crossbones
|
||||
# "Delete this project"
|
||||
]
|
||||
]
|
||||
|
||||
|
@ -4,6 +4,8 @@ nmd = require "nano-markdown"
|
||||
TaskCreationModal = require './task-creation-modal.ls'
|
||||
TaskRemovalModal = require './task-removal-modal.ls'
|
||||
|
||||
{icon} = require "./font-awesome.ls"
|
||||
|
||||
display-login = (task, users) ->
|
||||
if task.extra_properties && task.extra_properties.assignee-id && users && users[task.extra_properties.assignee-id] && users[task.extra_properties.assignee-id].login
|
||||
h \p [ '@' + users[task.extra_properties.assignee-id].login ]
|
||||
@ -31,7 +33,15 @@ Task = (self, project, todod-ws) ->
|
||||
args.onclick(e)
|
||||
} [
|
||||
h \div.card-content [
|
||||
h \div.title.is-5 [ self.title ]
|
||||
h \div.title.is-5 [
|
||||
h \a.has-text-grey.is-pulled-right {
|
||||
onclick: ->
|
||||
modal := TaskCreationModal project, todod-ws, self, project.users
|
||||
} [
|
||||
icon \cog
|
||||
]
|
||||
self.title
|
||||
]
|
||||
h \div.media [
|
||||
h \div.media-left [
|
||||
# FIXME: assignee card image
|
||||
@ -50,11 +60,6 @@ Task = (self, project, todod-ws) ->
|
||||
|
||||
if args.is-selected
|
||||
h \div.card-footer [
|
||||
h \a.card-footer-item {
|
||||
onclick: ->
|
||||
modal := TaskCreationModal project, todod-ws, self, project.users
|
||||
} [ "Edit" ]
|
||||
|
||||
h \a.card-footer-item.has-text-danger {
|
||||
onclick: ->
|
||||
modal := TaskRemovalModal project.id, todod-ws, self
|
||||
|
@ -21,6 +21,7 @@ module.exports = {
|
||||
"edit-task": 9
|
||||
"subscribe": 10
|
||||
"unsubscribe": 11
|
||||
"set-permission": 12
|
||||
}
|
||||
|
||||
response-types = {
|
||||
@ -70,6 +71,8 @@ module.exports = {
|
||||
message = JSON.parse(event.data)
|
||||
parsed-message = JSON.parse(message.payload)
|
||||
|
||||
console.log "received todod message:", parsed-message
|
||||
|
||||
for f in self.callbacks[message.mtype]
|
||||
f parsed-message
|
||||
|
||||
|
@ -4,8 +4,8 @@
|
||||
<title>Kanban</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="stylesheet" href="/font-awesome/fontawesome.min.css"/>
|
||||
<link rel="stylesheet" href="/font-awesome/solid.min.css"/>
|
||||
<link rel="stylesheet" href="/font-awesome/css/fontawesome.min.css"/>
|
||||
<link rel="stylesheet" href="/font-awesome/css/solid.min.css"/>
|
||||
<link rel="stylesheet" href="/style.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
Loading…
Reference in New Issue
Block a user