From f096fcec4f049e6dafdd70eb3a595e3120e63650 Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Fri, 20 Dec 2019 01:34:41 +0100 Subject: [PATCH] UX redesign, some grooming. --- Makefile | 11 +- client/authd.ls | 177 ------------------- client/authws.ls | 136 +++++++++++++++ client/column-edit-modal.ls | 44 +++++ client/font-awesome.ls | 13 ++ client/index.ls | 42 +++-- client/modal.ls | 2 +- client/navbar.ls | 55 +++--- client/project-creation-modal.ls | 289 ++++++++++++------------------- client/project.ls | 81 ++++++--- client/task.ls | 17 +- client/todowebsocket.ls | 3 + index.html | 4 +- 13 files changed, 451 insertions(+), 423 deletions(-) delete mode 100644 client/authd.ls create mode 100644 client/authws.ls create mode 100644 client/column-edit-modal.ls create mode 100644 client/font-awesome.ls diff --git a/Makefile b/Makefile index 6a475e7..65e9f64 100644 --- a/Makefile +++ b/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 ' BUN > main.bundle.js' $(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 \ diff --git a/client/authd.ls b/client/authd.ls deleted file mode 100644 index 4c64211..0000000 --- a/client/authd.ls +++ /dev/null @@ -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" ] - ] - ] - ] -} diff --git a/client/authws.ls b/client/authws.ls new file mode 100644 index 0000000..21beb3b --- /dev/null +++ b/client/authws.ls @@ -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 + diff --git a/client/column-edit-modal.ls b/client/column-edit-modal.ls new file mode 100644 index 0000000..dfc511c --- /dev/null +++ b/client/column-edit-modal.ls @@ -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 + diff --git a/client/font-awesome.ls b/client/font-awesome.ls new file mode 100644 index 0000000..47fe39d --- /dev/null +++ b/client/font-awesome.ls @@ -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}" + ] +} + diff --git a/client/index.ls b/client/index.ls index 5c5bcc0..a6375d4 100644 --- a/client/index.ls +++ b/client/index.ls @@ -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 [ diff --git a/client/modal.ls b/client/modal.ls index ae79ebd..fe187ea 100644 --- a/client/modal.ls +++ b/client/modal.ls @@ -53,7 +53,7 @@ Modal = (args, caller) -> ] ] - h \button.button.modal-close { + h \a.modal-close { aria-label: "close" onclick: -> self.visible := false diff --git a/client/navbar.ls b/client/navbar.ls index 8fa412b..8cfdd43 100644 --- a/client/navbar.ls +++ b/client/navbar.ls @@ -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 ] ] ] diff --git a/client/project-creation-modal.ls b/client/project-creation-modal.ls index 0669e36..13f0b4e 100644 --- a/client/project-creation-modal.ls +++ b/client/project-creation-modal.ls @@ -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 diff --git a/client/project.ls b/client/project.ls index 91ea075..3182b59 100644 --- a/client/project.ls +++ b/client/project.ls @@ -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" ] ] diff --git a/client/task.ls b/client/task.ls index 1c698a9..82527f9 100644 --- a/client/task.ls +++ b/client/task.ls @@ -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 diff --git a/client/todowebsocket.ls b/client/todowebsocket.ls index a3ba7e1..c36284d 100644 --- a/client/todowebsocket.ls +++ b/client/todowebsocket.ls @@ -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 diff --git a/index.html b/index.html index 80b720b..0651590 100644 --- a/index.html +++ b/index.html @@ -4,8 +4,8 @@ Kanban - - + +