WIP LiveScript login component.
This commit is contained in:
		
							parent
							
								
									16fa2271f6
								
							
						
					
					
						commit
						b1b502ff66
					
				
					 5 changed files with 459 additions and 0 deletions
				
			
		
							
								
								
									
										109
									
								
								client/authws.ls
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								client/authws.ls
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,109 @@ | |||
| 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 | ||||
| 	} | ||||
| 
 | ||||
| 	response-types = { | ||||
| 		"error":                   0 | ||||
| 		"token":                   1 | ||||
| 		"user":                    2 | ||||
| 		"user-added":              3 | ||||
| 		"user-edited":             4 | ||||
| 	} | ||||
| 
 | ||||
| 	# 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 | ||||
| 		} | ||||
| 
 | ||||
| 	# 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 | ||||
| 
 | ||||
							
								
								
									
										39
									
								
								client/bulma.ls
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								client/bulma.ls
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | |||
| 
 | ||||
| h = require 'maquette' .h | ||||
| 
 | ||||
| module.exports = { | ||||
| 	box: (args, children) -> | ||||
| 		h \div.box args, children | ||||
| 	title: (level, args, label) -> | ||||
| 		if not label | ||||
| 			label = args | ||||
| 			args = {} | ||||
| 
 | ||||
| 		h "div.title.is-#{level}", args, [label] | ||||
| 	label: (args, label) -> | ||||
| 		if not label | ||||
| 			label = args | ||||
| 			args = {} | ||||
| 
 | ||||
| 		h \label.label args, [label] | ||||
| 	input: (args, children) -> | ||||
| 		h \input.input args, children | ||||
| 
 | ||||
| 	# FIXME: Use only args and add args.label and args.input? | ||||
| 	#        Or maybe args.name and args.type could be used directly? | ||||
| 	field: (args, children) -> | ||||
| 		h \div.field args, children | ||||
| 
 | ||||
| 	modal: (args, content) -> | ||||
| 		h \div.modal args, [ | ||||
| 			h \div.modal-background args.background | ||||
| 			h \div.modal-content [args.content] | ||||
| 		] | ||||
| 
 | ||||
| 	form: (method, url, content) -> | ||||
| 		h \form.form { | ||||
| 			action: url | ||||
| 			method: method | ||||
| 		}, content | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										62
									
								
								client/index.ls
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								client/index.ls
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,62 @@ | |||
| maquette = require "maquette" | ||||
| 
 | ||||
| {create-projector, h} = maquette | ||||
| 
 | ||||
| projector = create-projector! | ||||
| 
 | ||||
| bulma = require "./bulma.ls" | ||||
| 
 | ||||
| AuthWS = require "./authws.ls" | ||||
| 
 | ||||
| LoginForm = require "./login-form.ls" | ||||
| UserConfigurationPanel = require "./user-configuration-panel.ls" | ||||
| 
 | ||||
| model = { | ||||
| 	token: void | ||||
| } | ||||
| 
 | ||||
| document.add-event-listener \DOMContentLoaded -> | ||||
| 	user-config-panel = void | ||||
| 
 | ||||
| 	login-form = LoginForm { | ||||
| 		enable-registration: true | ||||
| 		authws-url: "ws://localhost:9999/auth.JSON" | ||||
| 
 | ||||
| 		on-login: (user, token) -> | ||||
| 			model.user := user | ||||
| 			model.token := token | ||||
| 
 | ||||
| 			user-config-panel := UserConfigurationPanel model.user, model.token | ||||
| 
 | ||||
| 			projector.schedule-render! | ||||
| 		on-error: (error) -> | ||||
| 			projector.schedule-render! | ||||
| 	} | ||||
| 
 | ||||
| 	projector.append document.body, -> | ||||
| 		h \div.body [ | ||||
| 			if model.token == void | ||||
| 				h \div.section.hero.is-fullheight [ | ||||
| 					h \div.hero-body [ | ||||
| 						h \div.container [ | ||||
| 							h \div.columns [ | ||||
| 								h \div.column [] | ||||
| 								h \div.column.is-3 [ | ||||
| 									login-form.render! | ||||
| 								] | ||||
| 								h \div.column [] | ||||
| 							] | ||||
| 						] | ||||
| 					] | ||||
| 				] | ||||
| 			else if user-config-panel | ||||
| 				h \div.section [ | ||||
| 					h \div.container [ | ||||
| 						h \div.box [ | ||||
| 							user-config-panel.render! | ||||
| 						] | ||||
| 					] | ||||
| 				] | ||||
| 		] | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										206
									
								
								client/login-form.ls
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								client/login-form.ls
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,206 @@ | |||
| maquette = require "maquette" | ||||
| 
 | ||||
| {h} = maquette | ||||
| 
 | ||||
| bulma = require "./bulma.ls" | ||||
| 
 | ||||
| AuthWS = require "./authws.ls" | ||||
| 
 | ||||
| LoginForm = (args) -> | ||||
| 	args or= {} | ||||
| 
 | ||||
| 	self = { | ||||
| 		on-login: args.on-login || -> | ||||
| 		on-error: args.on-error || -> | ||||
| 		current-view: "login" | ||||
| 
 | ||||
| 		enable-registration: args.enable-registration || false | ||||
| 		registrating: false | ||||
| 
 | ||||
| 		input: { | ||||
| 			login: "" | ||||
| 			password: "" | ||||
| 			repeat-password: "" | ||||
| 		} | ||||
| 		locked-input: false | ||||
| 
 | ||||
| 		error: void | ||||
| 
 | ||||
| 		authws-url: args.authws-url || | ||||
| 			((if location.protocol == 'https' then 'wss' else 'ws') + | ||||
| 			'://' + location.hostname + ":9999/auth.JSON") | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	auth-ws = AuthWS self.authws-url | ||||
| 
 | ||||
| 	auth-ws.add-event-listener \token, (message) -> | ||||
| 		self.error := void | ||||
| 
 | ||||
| 		self.token = message.token | ||||
| 		self.locked-input := false | ||||
| 
 | ||||
| 		if self.user | ||||
| 			self.on-login self.user, self.token | ||||
| 
 | ||||
| 	auth-ws.add-event-listener \user, (message) -> | ||||
| 		self.error := void | ||||
| 
 | ||||
| 		self.user = message.user | ||||
| 
 | ||||
| 		if self.token | ||||
| 			self.on-login self.user, self.token | ||||
| 
 | ||||
| 	auth-ws.add-event-listener \user-added, (message) -> | ||||
| 		{login, password} = {self.input.login, self.input.password} | ||||
| 
 | ||||
| 		console.log "user added, duh" | ||||
| 
 | ||||
| 		self.user := message.user | ||||
| 
 | ||||
| 		auth-ws.get-token login, password | ||||
| 
 | ||||
| 	auth-ws.add-event-listener \error, (message) -> | ||||
| 		# We’ll get another error that’s clearer. Dropping that one. | ||||
| 		if message.reason == "user not found" | ||||
| 			return | ||||
| 
 | ||||
| 		self.error := message.reason | ||||
| 		self.locked-input := false | ||||
| 
 | ||||
| 		self.on-error message.reason | ||||
| 
 | ||||
| 	self.render = -> | ||||
| 		h \form.form.login-form { | ||||
| 			key: self | ||||
| 			onsubmit: (e) -> | ||||
| 				{login, password} = {self.input.login, self.input.password} | ||||
| 
 | ||||
| 				self.locked-input := true | ||||
| 
 | ||||
| 				if self.registrating | ||||
| 					auth-ws.register login, password | ||||
| 				else | ||||
| 					auth-ws.get-token login, password | ||||
| 					auth-ws.get-user-by-credentials login, password | ||||
| 
 | ||||
| 				e.prevent-default! | ||||
| 		}, [ | ||||
| 			h \div.field {key: \login} [ | ||||
| 				bulma.label "Login" | ||||
| 				bulma.input { | ||||
| 					type: "text" | ||||
| 					id: "login" | ||||
| 					name: "login" | ||||
| 					classes: { | ||||
| 						"is-danger": self.error == "invalid credentials" | ||||
| 					} | ||||
| 					disabled: self.locked-input | ||||
| 					oninput: (e) -> | ||||
| 						self.input.login = e.target.value | ||||
| 				} | ||||
| 			] | ||||
| 
 | ||||
| 			h \div.field {key: \password} [ | ||||
| 				bulma.label "Password" | ||||
| 				bulma.input { | ||||
| 					type: "password" | ||||
| 					id: "password" | ||||
| 					name: "password" | ||||
| 					classes: { | ||||
| 						"is-danger": self.error == "invalid credentials" | ||||
| 					} | ||||
| 					oninput: (e) -> | ||||
| 						self.input.password = e.target.value | ||||
| 					disabled: self.locked-input | ||||
| 				} | ||||
| 			] | ||||
| 
 | ||||
| 			if self.registrating | ||||
| 				h \div.field {key: \password-repeat} [ | ||||
| 					bulma.label "Password (reapeat)" | ||||
| 					bulma.input { | ||||
| 						type: \password | ||||
| 						id: \password-repeat | ||||
| 						name: \password-repeat | ||||
| 						classes: { | ||||
| 							"is-danger": self.input.password != self.input.repeat-password | ||||
| 						} | ||||
| 						disabled: self.locked-input | ||||
| 						oninput: (e) -> | ||||
| 							self.input.repeat-password = e.target.value | ||||
| 					} | ||||
| 				] | ||||
| 
 | ||||
| 			if self.error | ||||
| 				h \div.field {key: \error-notification} [ | ||||
| 					h \div.notification.is-danger [ | ||||
| 						self.error | ||||
| 					] | ||||
| 				] | ||||
| 
 | ||||
| 			if self.registrating | ||||
| 				h \div.field.is-grouped {key: \login-button} [ | ||||
| 					if self.input.login == "" | ||||
| 						h \button.button.is-static.is-fullwidth { | ||||
| 							type: \submit | ||||
| 						} [ | ||||
| 							"(empty login)" | ||||
| 						] | ||||
| 					else if self.input.password != self.input.repeat-password | ||||
| 						h \button.button.is-static.is-fullwidth { | ||||
| 							type: \submit | ||||
| 						} [ | ||||
| 							"(passwords don’t match)" | ||||
| 						] | ||||
| 					else if self.input.password == "" | ||||
| 						h \button.button.is-static.is-fullwidth { | ||||
| 							type: \submit | ||||
| 						} [ | ||||
| 							"(empty password)" | ||||
| 						] | ||||
| 					else | ||||
| 						h \button.button.is-success.is-fullwidth { | ||||
| 							type: \submit | ||||
| 						} [ | ||||
| 							"Register!" | ||||
| 						] | ||||
| 				] | ||||
| 			else | ||||
| 				h \div.field.is-grouped {key: \login-button} [ | ||||
| 					h \button.button.is-fullwidth.is-success { | ||||
| 						type: \submit | ||||
| 					} [ | ||||
| 						"Log in!" | ||||
| 					] | ||||
| 				] | ||||
| 
 | ||||
| 			h \div.field.level {key: \extra-buttons} [ | ||||
| 				#h \div.level-left [ | ||||
| 				#	h \a.link [ "(lala, remember me?)" ] | ||||
| 				#] | ||||
| 
 | ||||
| 				if self.enable-registration | ||||
| 					h \div.level-right [ | ||||
| 						if self.registrating | ||||
| 							h \a.link { | ||||
| 								onclick: (e) -> | ||||
| 									self.registrating := false | ||||
| 							} [ | ||||
| 								"Log in" | ||||
| 							] | ||||
| 						else | ||||
| 							h \a.link { | ||||
| 								onclick: (e) -> | ||||
| 									self.registrating := true | ||||
| 							} [ | ||||
| 								"Create account!" | ||||
| 							] | ||||
| 					] | ||||
| 			] | ||||
| 		] | ||||
| 
 | ||||
| 	self | ||||
| 
 | ||||
| module.exports = LoginForm | ||||
| 
 | ||||
							
								
								
									
										43
									
								
								client/user-configuration-panel.ls
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								client/user-configuration-panel.ls
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | |||
| 
 | ||||
| {h} = require "maquette" | ||||
| 
 | ||||
| UserConfigurationPanel = (user, token) -> | ||||
| 	self = {} | ||||
| 
 | ||||
| 	console.log user | ||||
| 
 | ||||
| 	self.render = -> | ||||
| 		full-name = user.full_name | ||||
| 		if full-name == "" | ||||
| 			full-name = user.login | ||||
| 
 | ||||
| 		h \div.columns { | ||||
| 			key: self | ||||
| 		} [ | ||||
| 			h \div.column.is-one-quarter [ | ||||
| 				h \figure.image.is-128 [ | ||||
| 					h \img { | ||||
| 						# FIXME | ||||
| 						url: "https://bulma.io/images/placeholders/128x128.png" | ||||
| 						alt: "Avatar of #{full-name}" | ||||
| 					} | ||||
| 				] | ||||
| 			] | ||||
| 			h \div.column [ | ||||
| 				h \div.title.is-2 [ full-name ] | ||||
| 
 | ||||
| 				if full-name != user.login | ||||
| 					h \div.title.is-3.subtitle [ | ||||
| 						user.login | ||||
| 					] | ||||
| 
 | ||||
| 				h \div.title.is-4 [ "Permissions" ] | ||||
| 				h \div.tags user.groups.map (group) -> | ||||
| 					h \div.tag [ group ] | ||||
| 			] | ||||
| 		] | ||||
| 
 | ||||
| 	self | ||||
| 
 | ||||
| module.exports = UserConfigurationPanel | ||||
| 
 | ||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Luka Vandervelden
						Luka Vandervelden