maquette = require "maquette" nmd = require "nano-markdown" 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" UsersCache = require "./users-cache.ls" {create-projector, h} = maquette projector = create-projector! model = { # view: login, project-list, project, network-error (TODO: other views, such as rights) # FIXME: Replace model.current-view by an object that has a .render # method. current-view: "login" viewed-project: void # list of Project objects project-list: [] port: 9999 authd-url: undefined todod-url: undefined authd-ws: undefined todod-ws: undefined previous-error: undefined error: undefined # { uid => user data } users: {} # uninitialized UserCache users-cache: void } model.authd-url = (if location.protocol == 'https' then 'wss' else 'ws') + '://' + location.hostname + \: + model.port + "/auth.JSON" model.todod-url = (if location.protocol == 'https' then 'wss' else 'ws') + '://' + location.hostname + \: + model.port + "/todo.JSON" console.log "authd url: " + model.authd-url console.log "todod url: " + model.todod-url # # network configuration # model.authd-ws = AuthWS model.authd-url model.todod-ws = todows.create-socket model.todod-url model.users-cache = UsersCache model.authd-ws, -> projector.schedule-render! # # authd messages management # # authd socket errors 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! projector.schedule-render! # record changes that need to happen on a network event model.authd-ws.user-on-socket-error ++= [ authd-on-websocket-error ] model.authd-ws.user-on-socket-close ++= [ authd-on-websocket-close ] # authd message handlers # 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 projector.schedule-render! model.authd-ws.add-event-listener \user, (message) -> model.users[message.user.uid] := message.user projector.schedule-render! 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 # # todod messages management # # todod socket errors 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! projector.schedule-render! # record changes that need to happen on a network event 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 "Projects list received", message model.project-list := message.lists.map (x) -> old-project = model.project-list.find((.id == x.id)) new-project = Project x, model.todod-ws, model.users-cache if old-project && new-project.id == old-project.id new-project.tasks = old-project.tasks new-project projector.schedule-render! 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.users-cache) ] 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, model.users-cache model.project-list := model.project-list.map (project) -> if project.id == message.list.id new-project else project if model.viewed-project && model.viewed-project.id == message.list.id # create tasks in order to have the updated project properties new-project.tasks = model.viewed-project.tasks.map (task) -> Task task, new-project, model.todod-ws, model.users-cache model.viewed-project = new-project projector.schedule-render! model.todod-ws.add-event-listener \list-removed, (message) -> console.log "A list has been removed", message if model.current-view == "project" && model.viewed-project.id == message.list model.current-view := "project-list" model.viewed-project := void projector.schedule-render! model.project-list := model.project-list.filter((.id != message.list)) # tasks model.todod-ws.add-event-listener \tasks, (message) -> console.log "Tasks received", message project = model.project-list.find((.id == message.list)) project.tasks := message.tasks.map (e) -> Task e, project, model.todod-ws, model.users-cache if model.viewed-project && model.viewed-project.id == project.id model.viewed-project := project projector.schedule-render! model.todod-ws.add-event-listener \task-created, (message) -> console.log "A task has been created", message task = message.task list = model.project-list.find((.id == task.list)) if model.viewed-project && list.id == model.viewed-project.id model.viewed-project := list if list list.tasks ++= [ Task task, list, model.todod-ws, model.users-cache ] projector.schedule-render! model.todod-ws.add-event-listener \task-updated, (message) -> console.log "A task has been updated", message task = message.task list = model.project-list.find((.id == task.list)) if list list.tasks = list.tasks.map (e) -> if e.id == task.id Task task, list, model.todod-ws, model.users-cache else e projector.schedule-render! model.todod-ws.add-event-listener \task-removed, (message) -> console.log "A task has been removed", message task = message.task for project in model.project-list if project.tasks.find((.id == task)) project.tasks := project.tasks.filter((.id != task)) projector.schedule-render! render-project-list = -> h \div.cards-list model.project-list.map (project) -> h \div.card.is-grey { key: project.id onclick: -> model.current-view := "project" model.viewed-project := project model.todod-ws.subscribe project.id model.todod-ws.get-list project.id model.todod-ws.get-tasks project.id } [ h \div.card-content [ bulma.title 3 project.title ] ] render-project = (project) -> if project project.render! else h \div.notification.is-error [ bulma.title 3 "Error, we did not get the project id " + project.id ] model.login-form = LoginForm { authws-url: model.authd-url schedule-render: -> projector.schedule-render! 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 model.todod-ws.list-lists! model.authd-ws.list-users! projector.schedule-render! } navbar = Navbar! render-body = -> h \div#body [ navbar.render model h \div#main-section [ switch model.current-view when "login" h \div.columns [ h \div.column h \div.column [ model.login-form.render! ] h \div.column ] when "project-list" 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 [ "Wait, what? Internal error!" ] ] if model.modal model.modal.render! ] document.add-event-listener 'DOMContentLoaded' -> projector.append document.body, render-body