Gisle Aune
6 years ago
28 changed files with 551 additions and 3 deletions
-
3marko/components/compact-list-item/index.marko
-
5marko/components/compact-list-item/style.less
-
5marko/components/compact-list-options/index.marko
-
28marko/components/compact-list-options/style.less
-
4marko/components/compact-list-property/index.marko
-
24marko/components/compact-list-property/style.less
-
3marko/components/compact-list/index.marko
-
13marko/components/compact-list/style.less
-
1marko/components/layout-navbar/index.marko
-
3marko/components/menu-link/style.less
-
34marko/global/colors.less
-
6marko/page/data/characters.marko
-
69marko/page/data/components/add-character-modal/component.js
-
22marko/page/data/components/add-character-modal/index.marko
-
5marko/page/data/components/character-list/index.marko
-
20marko/page/data/components/character/component.js
-
12marko/page/data/components/character/index.marko
-
36marko/page/data/components/characters-page/component.js
-
5marko/page/data/components/characters-page/index.marko
-
9marko/page/data/components/data-menu/index.marko
-
57marko/page/data/components/edit-character-modal/component.js
-
16marko/page/data/components/edit-character-modal/index.marko
-
42marko/page/data/components/remove-character-modal/component.js
-
9marko/page/data/components/remove-character-modal/index.marko
-
15routes/data/characters.js
-
8routes/data/index.js
-
96rpdata/api/Character.js
-
4server.js
@ -0,0 +1,3 @@ |
|||
<tr class="compact-list-item color-highlight-dark"> |
|||
<include(input.renderBody)/> |
|||
</tr> |
@ -0,0 +1,5 @@ |
|||
tr.compact-list-item { |
|||
user-select: none; |
|||
outline: 0.05px dotted; |
|||
vertical-align: middle; |
|||
} |
@ -0,0 +1,5 @@ |
|||
<td class=["compact-list-options", input.short ? "short" : null, input.long ? "long" : null]> |
|||
<div class="content color-menu"> |
|||
<include(input.renderBody)/> |
|||
</div> |
|||
</td> |
@ -0,0 +1,28 @@ |
|||
td.compact-list-options { |
|||
width: 20%; |
|||
padding: 0 0.5ch; |
|||
|
|||
|
|||
div.content { |
|||
font-size: 1em; |
|||
text-align: right; |
|||
|
|||
a { |
|||
display: inline-block; |
|||
margin-right: 1ch; |
|||
opacity: 0.5; |
|||
} |
|||
a:hover { |
|||
opacity: 1; |
|||
cursor: pointer; |
|||
} |
|||
} |
|||
} |
|||
|
|||
td.compact-list-options.short { |
|||
width: 10%; |
|||
} |
|||
|
|||
td.compact-list-options.long { |
|||
width: 30%; |
|||
} |
@ -0,0 +1,4 @@ |
|||
<td class=["compact-list-property", input.short ? "short" : null, input.long ? "long" : null]> |
|||
<div class="label color-menu">${input.label}</div> |
|||
<div class=["value", input.primary ? "color-primary" : "color-text"]>${input.value}</div> |
|||
</td> |
@ -0,0 +1,24 @@ |
|||
td.compact-list-property { |
|||
width: 20%; |
|||
padding: 0 0.5ch; |
|||
|
|||
div.label { |
|||
font-size: 0.5em; |
|||
line-height: 1em; |
|||
padding-top: 0.5em; |
|||
} |
|||
|
|||
div.value { |
|||
font-size: 0.75em; |
|||
padding-bottom: 0.25em; |
|||
user-select: text; |
|||
} |
|||
} |
|||
|
|||
td.compact-list-property.short { |
|||
width: 10%; |
|||
} |
|||
|
|||
td.compact-list-property.long { |
|||
width: 30%; |
|||
} |
@ -0,0 +1,3 @@ |
|||
<table class="compact-list color-primary"> |
|||
<include(input.renderBody)/> |
|||
</table> |
@ -0,0 +1,13 @@ |
|||
table.compact-list { |
|||
padding-top: 0.5em; |
|||
padding-bottom: 3em; |
|||
|
|||
width: 90%; |
|||
max-width: 100ch; |
|||
padding-right: 1em; |
|||
|
|||
margin: auto; |
|||
|
|||
border-collapse: separate; |
|||
border-spacing: 0 0.5em; |
|||
} |
@ -0,0 +1,6 @@ |
|||
<include("../layout", {title: "Data", site: "data"})> |
|||
<@body> |
|||
<background src="/assets/images/bg.png" opacity=0.25 /> |
|||
<characters-page user=input.user characters=input.characters selected=input.selected /> |
|||
</@body> |
|||
</include> |
@ -0,0 +1,69 @@ |
|||
const moment = require("moment") |
|||
|
|||
const {charactersApi} = require("../../../../../rpdata/api/Character") |
|||
|
|||
module.exports = class { |
|||
onCreate(input) { |
|||
this.state = { |
|||
error: null, |
|||
loading: false, |
|||
values: { |
|||
name: "", |
|||
nick: "", |
|||
shortName: "", |
|||
author: "", |
|||
description: "", |
|||
} |
|||
} |
|||
} |
|||
|
|||
onInput(input) { |
|||
this.state.values = Object.assign({}, this.state.values, {author: input.user.name}) |
|||
} |
|||
|
|||
change(key, ev) { |
|||
this.state.values[key] = ev.target.value |
|||
} |
|||
|
|||
open() { |
|||
|
|||
} |
|||
|
|||
close() { |
|||
this.emit("close") |
|||
} |
|||
|
|||
save() { |
|||
if (this.state.loading) { |
|||
return |
|||
} |
|||
|
|||
const values = Object.assign({}, this.state.values) |
|||
if (values.name.length < 2) { |
|||
this.state.error = "Name is too short (min: 2)" |
|||
return |
|||
} |
|||
if (values.author == "") { |
|||
delete values.author |
|||
} |
|||
|
|||
this.state.loading = true |
|||
charactersApi.add(this.state.values).then((res) => { |
|||
this.emit("added", res) |
|||
this.emit("close") |
|||
this.state.values = { |
|||
name: "", |
|||
nick: "", |
|||
shortName: "", |
|||
author: this.input.user.name, |
|||
description: "", |
|||
} |
|||
}).catch(errs => { |
|||
console.warn("Failed to edit:", errs) |
|||
|
|||
this.state.error = "Failed to edit: " + errs[0].message |
|||
}).then(() => { |
|||
this.state.loading = false |
|||
}) |
|||
} |
|||
} |
@ -0,0 +1,22 @@ |
|||
<modal class="modal color-text nolabel" key="modal" enabled=(input.enabled) closable on-close("close") on-open("open") > |
|||
<h1>Add Character</h1> |
|||
|
|||
<p class="color-error">${state.error}</p> |
|||
|
|||
<label>Full Name</label> |
|||
<input key="name" placeholder="" class="big" on-change("change", "name") value=state.values.name /> |
|||
|
|||
<label>Short Name</label> |
|||
<input key="shortName" placeholder="(Optional)" on-change("change", "shortName") value=state.values.shortName /> |
|||
|
|||
<label>IRC Nick</label> |
|||
<input key="nick" placeholder="" class="big" on-change("change", "nick") value=state.values.nick /> |
|||
|
|||
<label>Author</label> |
|||
<input key="author" placeholder=(input.user.name) on-change("change", "author") value=state.values.author /> |
|||
|
|||
<label>Description</label> |
|||
<textarea key="description" placeholder="Description" on-change("change", "description") value=state.values.description /> |
|||
|
|||
<button disabled=state.loading on-click("save")>Save character!</button> |
|||
</modal> |
@ -0,0 +1,5 @@ |
|||
<compact-list> |
|||
<compact-list-item for(character in input.characters) key=character.id id=character.id> |
|||
<character data=character on-removed("emit", "removed", character) on-edited("emit", "edited", character) /> |
|||
</compact-list-item> |
|||
</compact-list> |
@ -0,0 +1,20 @@ |
|||
module.exports = class { |
|||
onCreate() { |
|||
this.state = { |
|||
modal: null, |
|||
deleted: false, |
|||
} |
|||
} |
|||
|
|||
open(modal) { |
|||
this.state.modal = modal |
|||
} |
|||
|
|||
close() { |
|||
this.state.modal = null |
|||
} |
|||
|
|||
removed() { |
|||
this.state.deleted = true |
|||
} |
|||
} |
@ -0,0 +1,12 @@ |
|||
<if (!state.deleted)> |
|||
<compact-list-property primary short label="ID" value=input.data.id /> |
|||
<compact-list-property label="Nick" value=(input.data.nick) weak=("+" + (input.data.nicks.length - 1)) showWeak=(input.data.nicks.length > 0) /> |
|||
<compact-list-property long label="Name" value=input.data.name /> |
|||
<compact-list-property short label="Author" value=input.data.author /> |
|||
<compact-list-options long> |
|||
<a key="edit" on-click("open", "edit", input.data)>Edit</a> |
|||
<a key="remove" on-click("open", "remove", input.data)>Remove</a> |
|||
</compact-list-options> |
|||
<remove-character-modal enabled=(state.modal === "remove") character=input.data on-removed("emit", "removed") on-close("close") /> |
|||
<edit-character-modal enabled=(state.modal === "edit") character=input.data on-edited("emit", "edited") on-close("close") /> |
|||
</if> |
@ -0,0 +1,36 @@ |
|||
module.exports = class { |
|||
onCreate(input) { |
|||
this.state = { |
|||
characters: input.characters, |
|||
modal: null, |
|||
} |
|||
} |
|||
|
|||
open(modal) { |
|||
this.state.modal = modal |
|||
} |
|||
|
|||
close() { |
|||
this.state.modal = null |
|||
} |
|||
|
|||
characterRemoved({id}) { |
|||
this.state.characters = this.state.characters.filter(c => c.id !== id) |
|||
} |
|||
|
|||
characterEdited(character, patch) { |
|||
const characters = this.state.characters.slice() |
|||
const index = characters.findIndex(c => c.id === character.id) |
|||
if (index === -1) { |
|||
return |
|||
} |
|||
|
|||
characters[index] = Object.assign(characters[index], patch) |
|||
|
|||
this.state.characters = characters |
|||
} |
|||
|
|||
characterAdded(character) { |
|||
this.state.characters = this.state.characters.concat(character) |
|||
} |
|||
} |
@ -0,0 +1,5 @@ |
|||
<data-menu categories=input.categories selected=(input.selected || {}) user=input.user on-open("open") /> |
|||
<main> |
|||
<character-list characters=state.characters on-removed("characterRemoved") on-edited("characterEdited") /> |
|||
</main> |
|||
<add-character-modal enabled=(state.modal === "character.add") user=input.user on-added("characterAdded") on-close("close") /> |
@ -0,0 +1,9 @@ |
|||
<menu user=input.user> |
|||
<menu-header>List</menu-header> |
|||
<menu-link key="characters" selected=input.selected.characters icon="C" href="/data/characters/">Characters</menu-link> |
|||
<menu-link key="channels" selected=input.selected.channels icon="#" href="/data/characters/">Channels</menu-link> |
|||
<menu-gap /> |
|||
<menu-header>Options</menu-header> |
|||
<menu-link dark key="characters_add" icon="+" on-click("emit", "open", "character.add")>Add Character</menu-link> |
|||
<menu-link dark key="channels_add" icon="+" on-click("emit", "open", "channel.add")>Add Channel</menu-link> |
|||
</menu> |
@ -0,0 +1,57 @@ |
|||
const moment = require("moment") |
|||
|
|||
const {charactersApi} = require("../../../../../rpdata/api/Character") |
|||
|
|||
module.exports = class { |
|||
onCreate(input) { |
|||
this.state = { |
|||
error: null, |
|||
loading: false, |
|||
values: { |
|||
name: "", |
|||
shortName: "", |
|||
description: "", |
|||
} |
|||
} |
|||
} |
|||
|
|||
change(key, ev) { |
|||
this.state.values[key] = ev.target.value |
|||
} |
|||
|
|||
onInput(input) { |
|||
this.state.values = { |
|||
name: input.character.name, |
|||
shortName: input.character.shortName, |
|||
description: input.character.description, |
|||
} |
|||
} |
|||
|
|||
open() { |
|||
|
|||
} |
|||
|
|||
close() { |
|||
this.emit("close") |
|||
} |
|||
|
|||
save() { |
|||
if (this.state.loading) { |
|||
return |
|||
} |
|||
|
|||
const input = Object.assign({id: this.input.character.id}, this.state.values) |
|||
|
|||
this.state.loading = true |
|||
charactersApi.edit(input).then((res) => { |
|||
this.emit("edited", res) |
|||
this.emit("close") |
|||
}).catch(errs => { |
|||
console.warn("Failed to edit:", errs) |
|||
|
|||
this.state.error = "Failed to edit: " + errs[0].message |
|||
}).then(() => { |
|||
this.state.loading = false |
|||
}) |
|||
} |
|||
} |
@ -0,0 +1,16 @@ |
|||
<modal class="modal color-text nolabel" key="modal" enabled=(input.enabled) closable on-close("close") on-open("open") > |
|||
<h1>Edit Character</h1> |
|||
|
|||
<p class="color-error">${state.error}</p> |
|||
|
|||
<label>Full Name</label> |
|||
<input key="name" placeholder="" class="big" on-change("change", "name") value=state.values.name /> |
|||
|
|||
<label>Short Name</label> |
|||
<input key="shortName" placeholder="E.g. first/given name" on-change("change", "shortName") value=state.values.shortName /> |
|||
|
|||
<label>Description</label> |
|||
<textarea key="description" placeholder="Description" on-change("change", "description") value=state.values.description /> |
|||
|
|||
<button disabled=state.loading on-click("save")>Save character!</button> |
|||
</modal> |
@ -0,0 +1,42 @@ |
|||
const moment = require("moment") |
|||
|
|||
const {charactersApi} = require("../../../../../rpdata/api/Character") |
|||
|
|||
module.exports = class { |
|||
onCreate(input) { |
|||
this.state = { |
|||
error: null, |
|||
loading: false, |
|||
} |
|||
} |
|||
|
|||
change(key, ev) { |
|||
this.state.values[key] = ev.target.value |
|||
} |
|||
|
|||
open() { |
|||
|
|||
} |
|||
|
|||
close() { |
|||
this.emit("close") |
|||
} |
|||
|
|||
doIt() { |
|||
if (this.state.loading) { |
|||
return |
|||
} |
|||
|
|||
this.state.loading = true |
|||
charactersApi.remove({id: this.input.character.id}).then(() => { |
|||
this.emit("removed") |
|||
this.emit("close") |
|||
}).catch(errs => { |
|||
console.warn("Failed to delete:", errs) |
|||
|
|||
this.state.error = "Failed to delete: " + errs[0].message |
|||
}).then(() => { |
|||
this.state.loading = false |
|||
}) |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
<modal class="modal color-text nolabel" key="modal" enabled=(input.enabled) notification closable on-close("close") on-open("open") > |
|||
<h1>Remove ${input.character.name}</h1> |
|||
|
|||
<p class="color-error">${state.error}</p> |
|||
|
|||
<p class="color-danger">This is irreversible!</p> |
|||
|
|||
<button disabled=state.loading on-click("doIt")>Yes, do it!</button> |
|||
</modal> |
@ -0,0 +1,15 @@ |
|||
const express = require("express") |
|||
const router = express.Router() |
|||
|
|||
const {charactersApi} = require("../../rpdata/api/Character") |
|||
|
|||
const charactersTemplate = require("../../marko/page/data/characters.marko") |
|||
|
|||
router.get("/", async(req, res) => { |
|||
res.markoAsync(charactersTemplate, { |
|||
characters: charactersApi.list(), |
|||
selected: {characters: true}, |
|||
}) |
|||
}) |
|||
|
|||
module.exports = router |
@ -0,0 +1,8 @@ |
|||
const express = require("express") |
|||
const router = express.Router() |
|||
|
|||
router.get("/", async(req, res) => { |
|||
return res.redirect("/data/characters/") |
|||
}) |
|||
|
|||
module.exports = router |
Write
Preview
Loading…
Cancel
Save
Reference in new issue