diff --git a/client/authd.ls b/client/authd.ls index 368353b..b4afbc3 100644 --- a/client/authd.ls +++ b/client/authd.ls @@ -6,12 +6,23 @@ 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 = { @@ -20,6 +31,9 @@ module.exports = { "user": 2 "user-added": 3 "user-edited": 4 + "extra": 5 + "extra-updated": 6 + "users-list": 7 } # TODO: naming convention @@ -92,6 +106,23 @@ module.exports = { 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 { diff --git a/client/index.ls b/client/index.ls index 664a2c7..bbfcaea 100644 --- a/client/index.ls +++ b/client/index.ls @@ -1,41 +1,8 @@ -# first: -# connection to authd -# display the authd widget -# -# second: -# connection to todod -# rewrite the whole body component -# page 1: (TODO) list of lists (or todos) -# display: -# navbar -# -# "element 1" button -# "element 2" button -# ... -# "element x" button -# -# "new list" button -# widgets: -# navbar: [return to list button] [logout] -# element button: -# [project list name] -# or -# [project list name] [RIGTHS] [DELETE] -# -# page 2: -# display: -# (TODO) list of tasks (this client has a kanban display of tasks, with columns) -# (TODO) task widget -# widgets: -# task widget: -# TODO -# -# page 3: (TODO) pages about - maquette = require "maquette" nmd = require "nano-markdown" authd = require "./authd.ls" +LoginForm = require "../lib/authd/client/login-form.ls" todows = require "./todowebsocket.ls" bulma = require "./bulma.ls" Task = require "./task.ls" @@ -49,16 +16,10 @@ projector = create-projector! model = { # view: login, project-list, project, network-error (TODO: other views, such as rights) - # XXX FIXME TODO: TESTING THINGS current-view: "login" viewed-project: void - # current-view: "testing-modals" - - # { uid => user data } - users: {} - # list of Project objects project-list: [] @@ -71,6 +32,9 @@ model = { previous-error: undefined error: undefined + + # { uid => user data } + users: {} } model.authd-url = @@ -122,22 +86,30 @@ model.authd-ws.user-on-socket-close ++= [ authd-on-websocket-close ] # authd message handlers -model.authd-ws.add-event-listener \token, (message) -> - model.current-view := "project-list" - model.todod-ws.token := message.token - model.todod-ws.list-lists! - projector.schedule-render! +# HANDLED with the "on-login" callback in the LoginForm component +# model.authd-ws.add-event-listener \token, (message) -> +# model.current-view := "project-list" +# model.authd-ws.token := message.token +# model.todod-ws.token := message.token +# model.todod-ws.list-lists! +# model.authd-ws.list-users! +# projector.schedule-render! model.authd-ws.add-event-listener \error, (message) -> - # console.log "authd error", message + console.log "authd error", message projector.schedule-render! model.authd-ws.add-event-listener \user, (message) -> model.users[message.user.uid] := message.user projector.schedule-render! -# TODO: user-added, user-edited +model.authd-ws.add-event-listener \users-list, (message) -> + console.log "Received users: ", message + message.users.map (user) -> + model.users[user.uid] := user + projector.schedule-render! +# TODO: user-added, user-edited # @@ -167,7 +139,7 @@ model.todod-ws.add-event-listener \lists-list, (message) -> model.project-list := message.lists.map (x) -> old-project = model.project-list.find((.id == x.id)) - new-project = Project x, model.todod-ws + new-project = Project x, model.todod-ws, model.users if old-project && new-project.id == old-project.id new-project.tasks = old-project.tasks @@ -179,13 +151,13 @@ model.todod-ws.add-event-listener \lists-list, (message) -> model.todod-ws.add-event-listener \new-list, (message) -> console.log "New project", message - model.project-list := model.project-list ++ [ (Project message.list, model.todod-ws) ] + model.project-list := model.project-list ++ [ (Project message.list, model.todod-ws, model.users) ] projector.schedule-render! model.todod-ws.add-event-listener \list-updated, (message) -> console.log "Project updated", message - new-project = Project message.list, model.todod-ws + new-project = Project message.list, model.todod-ws, model.users model.project-list := model.project-list.map (project) -> if project.id == message.list.id @@ -268,22 +240,24 @@ render-navbar = -> h \div.navbar-start [ h \a.navbar-item.is-size-2 { onclick: -> - model.todod-ws.unsubscribe model.viewed-project.id + if model.viewed-project + model.todod-ws.unsubscribe model.viewed-project.id model.todod-ws.list-lists! model.viewed-project := void model.current-view := "project-list" } [ "⌂" ] ] - if model.viewed-project + if model.current-view == "project" && model.viewed-project model.viewed-project.inner-nav-render! h \div.navbar-end [ - if model.viewed-project + if model.current-view == "project" && model.viewed-project model.viewed-project.right-nav-render! h \a.navbar-item { + key: "logout" onclick: -> model.current-view := "login" # TODO: remove anything related to the old session on the client @@ -336,11 +310,23 @@ render-project = (project) -> bulma.title 3 "Error, we did not get the project id " + project.id ] +model.login-form = LoginForm { + authws-url: model.authd-url + on-login: (user, token) -> + model.current-view := "project-list" + model.authd-ws.token := token + model.todod-ws.token := token + model.todod-ws.list-lists! + model.authd-ws.list-users! + projector.schedule-render! +} + render-body = -> h \div.section [ switch model.current-view when "login" - authd.login-page model + # authd.login-page model + model.login-form.render! when "project-list" h \div#project-list [ diff --git a/client/project-creation-modal.ls b/client/project-creation-modal.ls index da13103..e21b28d 100644 --- a/client/project-creation-modal.ls +++ b/client/project-creation-modal.ls @@ -24,18 +24,37 @@ col-to-lines = (column, self) -> } [ "DELETE" ] ] -#
-#

-# -#

-#

-# -# @gmail.com -# -#

-#
+user-form-selection = (self, user) -> + h \option { + value: user.uid + } [ user.login ] -ProjectCreationModal = (project, todod-ws) -> +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 @@ -48,11 +67,15 @@ ProjectCreationModal = (project, todod-ws) -> self = { title: project.title || "" - permissions: project.permissions || [[]] - new-user: "New user" - new-column-input: { - title: "New column !" - } + permissions: project.permissions || {admin: [], edit: [], post: [], read: []} + + tmp: + new-user-permission: + id: void + permission: permission-groups[0] + new-column-input: + title: "New column !" + users: users || [] extra_properties: columns: columns-copy @@ -73,29 +96,30 @@ ProjectCreationModal = (project, todod-ws) -> } ] - h \hr [] + # h \hr [] - 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 - } - ] + # 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 + # } + # ] h \hr [] h \aside.menu [ h \p.menu-label [ "Permissions" ] - h \ul.menu-list self.permissions.map (permission) -> - h \li [ - h \p permission.map (e, index) -> + h \ul.menu-list Object.keys(self.permissions).map (permission) -> + h \li { + key: "permission" + permission + } [ + h \p self.permissions[permission].map (e, index) -> if index == 0 - "Permissions '" + e + "': " + "Permissions '" + permission + "': " + e + ", " else "" + e + ", " ] @@ -103,6 +127,43 @@ ProjectCreationModal = (project, todod-ws) -> h \hr [] + h \p [ "Adding new user" ] + + h \div.field.has-addons { + key: "adding-user" + } [ + h \div.select.control [ + h \select { + onchange: (e) -> + self.tmp.new-user-permission.permission := e.target.value + } permission-groups.map (permission) -> permission-to-form-selection self, permission + ] + + h \div.select.control.is-expanded [ + h \select { + 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 + ] + ] + + h \div.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 [] + h \p [ "Choose the columns" ] for dom in (self.extra_properties.columns.map (column) -> col-to-lines column, self) @@ -115,9 +176,9 @@ ProjectCreationModal = (project, todod-ws) -> } [ h \p.control.is-expanded [ h \input.input { - value: self.new-column-input.title + value: self.tmp.new-column-input.title oninput: (e) -> - self.new-column-input.title := e.target.value + self.tmp.new-column-input.title := e.target.value } [ ] ] @@ -125,20 +186,21 @@ ProjectCreationModal = (project, todod-ws) -> onclick: -> new-col = { id: UUID! - title: self.new-column-input.title + title: self.tmp.new-column-input.title } self.extra_properties.columns ++= [ new-col ] - self.new-column-input.title := "New column !" + self.tmp.new-column-input.title := "New column !" } [ "+" ] ] - ] 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 = -> diff --git a/client/project.ls b/client/project.ls index 8d87b00..0ad731a 100644 --- a/client/project.ls +++ b/client/project.ls @@ -4,7 +4,7 @@ bulma = require "./bulma.ls" Task = require "./task.ls" Modal = require './modal.ls' TaskCreationModal = require './task-creation-modal.ls' -ProjectCreationModal = require "./project-creation-modal.ls" +ProjectCreationModal = require './project-creation-modal.ls' is-right-column = (task, column-id) -> task.extra_properties && task.extra_properties.column && task.extra_properties.column == column-id @@ -20,9 +20,10 @@ orphan-tasks = (tasks, columns) -> tasks.filter (task) -> (! has-column) || inexistant-column task, columns -Project = (self, todod-ws) -> +Project = (self, todod-ws, users) -> self.todod-ws = todod-ws self.tasks = [] + self.users = users || [] modal = void @@ -33,6 +34,10 @@ Project = (self, todod-ws) -> if first tasks-to-display ++= orphan-tasks self.tasks, self.extra_properties.columns + # + # COLUMNS + # + h \div.column { key: column.id } [ @@ -52,21 +57,27 @@ Project = (self, todod-ws) -> self.right-nav-render = -> [ - h \div.navbar-item [ + h \div.navbar-item { + key: "navbar-new-task" + } [ h \div.button.is-success.is-outlined { onclick: -> - modal := TaskCreationModal self, self.todod-ws + modal := TaskCreationModal self, self.todod-ws, void, self.users } [ "New task" ] ] - h \div.navbar-item [ + h \div.navbar-item { + key: "navbar-edit-project" + } [ h \div.button.is-outlined { onclick: -> - modal := ProjectCreationModal self, self.todod-ws + modal := ProjectCreationModal self, self.todod-ws, self.users } [ "Edit this project" ] ] - h \div.navbar-item [ + h \div.navbar-item { + key: "navbar-delete-project" + } [ h \div.button.is-danger.is-outlined { onclick: -> modal := Modal { diff --git a/client/style.sass b/client/style.sass index 7183fa3..a61683f 100644 --- a/client/style.sass +++ b/client/style.sass @@ -1,14 +1,14 @@ @charset "utf-8" -@import "../node_modules/bulmaswatch/superhero/_variables.scss" +// @import "../node_modules/bulmaswatch/superhero/_variables.scss" // Import Bulma core @import "../node_modules/bulma/bulma" -@import "../node_modules/bulmaswatch/superhero/_overrides.scss" +// @import "../node_modules/bulmaswatch/superhero/_overrides.scss" .project>.columns - overflow-x: scroll + overflow-x: auto .avatar border-radius: 4px diff --git a/client/task-creation-modal.ls b/client/task-creation-modal.ls index 30f93cf..6fcd757 100644 --- a/client/task-creation-modal.ls +++ b/client/task-creation-modal.ls @@ -1,7 +1,30 @@ +# TODO: on modification, the description isn't updated on the client + h = require 'maquette' .h Modal = require './modal.ls' +colors = [ + "white" + "black" + "light" + "dark" + "primary" + "info" + "link" + "success" + "warning" + "danger" + "black-bis" + "black-ter" + "grey-darker" + "grey-dark" + "grey" + "grey-light" + "grey-lighter" + "white-ter" + "white-bis" +] column-form-selection = (self, column) -> h \option { @@ -9,32 +32,57 @@ column-form-selection = (self, column) -> selected: self.extra_properties && self.extra_properties.column == column.id } [ column.title ] +color-to-form-selection = (self, color, current-color) -> + h \option { + value: color + selected: current-color && current-color == color + } [ color ] -TaskCreationModal = (project, todod-ws, task) -> +user-form-selection = (self, user) -> + h \option { + value: user.uid + } [ user.login ] + +TaskCreationModal = (project, todod-ws, task, users) -> task ||= {} # copy not to override anything on cancel self = { title: task.title || "" description: task.description || "" - extra_properties: { + extra_properties: column: "" - } + background-color: "" + assignee-id: task.assignee-id || void + tmp: + users: users || [] } - if task.extra_properties && task.extra_properties.column - self.extra_properties.column = task.extra_properties.column + # copy extra properties + # currently: column + background-color + assignee + expected duration time + for k,v of task.extra_properties + self.extra_properties[k] = v modal = Modal { +visible content-render: (self) -> h \div.form [ + + # + # TITLE + # + h \input.input { value: self.title oninput: (e) -> self.title := e.target.value } + + # + # DESCRIPTION + # + h \textarea { value: self.description oninput: (e) -> @@ -51,13 +99,52 @@ TaskCreationModal = (project, todod-ws, task) -> } project.extra_properties.columns.map (column) -> column-form-selection self, column ] + + # + # USER MANAGEMENT + # + + h \hr [] + + h \p [ "Assign someone to the task" ] + + h \div.field.has-addons { + key: "assign-someone-to-task" + } [ + h \div.select.control [ + h \select { + onchange: (e) -> + self.extra_properties.assignee-id := 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 + ] + ] + ] + + + # + # BACKGROUND COLOR + # + + h \p [ "Choose the background color" ] + + h \div.select [ + h \select { + onchange: (e) -> + self.extra_properties.background-color := e.target.value + } colors.map (color) -> color-to-form-selection self, color, self.extra_properties.background-color + ] ] on-validation: -> + tmp = delete self.tmp if task.id todod-ws.edit-task task.id, self else todod-ws.add-task project.id, self.title, self + self.tmp = tmp }, self self.render = -> diff --git a/client/task.ls b/client/task.ls index 73535b3..53df5b7 100644 --- a/client/task.ls +++ b/client/task.ls @@ -1,62 +1,53 @@ -# -# Tasks, previous version of todos -# - h = require 'maquette' .h bulma = require "./bulma.ls" nmd = require "nano-markdown" TaskCreationModal = require './task-creation-modal.ls' TaskRemovalModal = require './task-removal-modal.ls' -# -# generic functions -# - -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 +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 ] + else + h \p [ '-' ] Task = (self, project, todod-ws) -> modal = void self.render = -> - h \div.card { key: self.id } [ + background-color = "grey" + if self.extra_properties && self.extra_properties.background-color + background-color = self.extra_properties.background-color + + h "div.card.has-background-#{background-color}" { + key: self.id + } [ h \div.card-content [ h \div.media [ h \div.media-left [ + # FIXME: assignee card image "LEFT" + display-login self, project.users ] - h \div.media-content [ - self.title - ] + + h \div.media-content [ self.title ] + h \div.button { onclick: -> - modal := TaskCreationModal project, todod-ws, self + modal := TaskCreationModal project, todod-ws, self, project.users } [ "Edit" ] - h \div.button { + + h \div.button.is-danger { onclick: -> modal := TaskRemovalModal project.id, todod-ws, self } [ "X" ] ] - h \div.content [ - self.description - ] + + h \div.content { + key: "task-description-#{self.id}" + after-create: (dom) -> + dom.innerHTML = nmd self.description + } [ ] ] if modal modal.render! diff --git a/client/todowebsocket.ls b/client/todowebsocket.ls index 64e46f3..c4354a4 100644 --- a/client/todowebsocket.ls +++ b/client/todowebsocket.ls @@ -68,10 +68,11 @@ module.exports = { self.socket.onmessage = (event) -> message = JSON.parse(event.data) - console.log "todod message received: ", message + parsed-message = JSON.parse(message.payload) + console.log "todod message #{message.mtype} received: ", parsed-message for f in self.callbacks[message.mtype] - f JSON.parse(message.payload) + f parsed-message self.reopen = -> self.socket.close! @@ -80,37 +81,43 @@ module.exports = { self.open-socket! self.send = (type, opts) -> - console.log JSON.stringify { mtype: type, payload: opts } + console.log "sending message #{type} to todod: ", opts + # console.log JSON.stringify { mtype: type, payload: opts } self.socket.send JSON.stringify { mtype: type, payload: opts } self.list-lists = -> self.send request-types[\list-lists], JSON.stringify { token: self.token + id: "list-lists" } self.get-list = (list-id) -> self.send request-types[\get-list], JSON.stringify { token: self.token list: list-id + id: "get-list" } self.get-tasks = (list-id) -> self.send request-types[\get-tasks], JSON.stringify { token: self.token list: list-id + id: "get-tasks" } self.get-task = (task-id) -> self.send request-types[\get-task], JSON.stringify { token: self.token task: task-id + id: "get-task" } self.remove-list = (list-id) -> self.send request-types[\remove-list], JSON.stringify { token: self.token list: list-id + id: "remove-list" } # TODO: extra properties @@ -118,6 +125,7 @@ module.exports = { payload = { token: self.token title: title + id: "add-list" } for key, value of opts @@ -129,6 +137,7 @@ module.exports = { payload = { token: self.token list: list-id + id: "edit-list" } for key, value of opts @@ -140,6 +149,7 @@ module.exports = { self.send request-types[\remove-task], JSON.stringify { token: self.token task: task-id + id: "remove-task" } # TODO: extra properties @@ -148,6 +158,7 @@ module.exports = { token: self.token list: list-id title: title + id: "add-task" } for key, value of opts @@ -159,6 +170,7 @@ module.exports = { payload = { token: self.token task: task-id + id: "edit-task" } for key, value of opts @@ -170,12 +182,14 @@ module.exports = { self.send request-types[\subscribe], JSON.stringify { token: self.token list: list-id + id: "subscribe" } self.unsubscribe = (list-id) -> self.send request-types[\unsubscribe], JSON.stringify { token: self.token list: list-id + id: "unsubscribe" } self