UI for file stuff.
the build was successful

This commit is contained in:
2019-09-15 16:34:40 +02:00
parent bb2ccf6f4b
commit 5fe56eaae8
35 changed files with 875 additions and 68 deletions
@@ -1,3 +1,3 @@
<tr class="compact-list-item color-highlight-dark">
<tr class=["compact-list-item", input.highlight ? "color-highlight-primary" : "color-highlight-dark"]>
<include(input.renderBody)/>
</tr>
@@ -2,4 +2,22 @@ tr.compact-list-item {
user-select: none;
outline: 0.05px dotted;
vertical-align: middle;
}
tr.compact-list-item.color-highlight-primary {
a {
color: #FFF !important;
}
label {
color: #CCC !important;
}
div.label {
color: #AA8 !important;
}
div.value.color-text {
color: #FFF !important;
}
}
-1
View File
@@ -1,6 +1,5 @@
table.compact-list {
padding-top: 0.5em;
padding-bottom: 3em;
width: 90%;
max-width: 100ch;
@@ -0,0 +1,61 @@
const moment = require("moment")
const {fileApi} = require("../../../../../rpdata/api/File")
module.exports = class {
onCreate(input) {
this.state = {
error: null,
loading: false,
values: {
file: null,
name: "",
public: false,
realFile: null,
}
}
}
change(key, ev) {
if (key === "file") {
this.state.values = Object.assign({}, this.state.values, {
[key]: ev.target.value,
realFile: ev.target.files[0],
name: ev.target.files[0].name,
})
} else {
this.state.values = Object.assign({}, this.state.values, {[key]: ev.target.value})
}
}
open() {
}
close() {
this.emit("close")
}
save() {
if (this.state.loading) {
return
}
const input = Object.assign({}, this.state.values)
input.file = input.realFile
delete input.realFile
this.state.loading = true
fileApi.uploadFile(input).then((res) => {
this.emit("added", res)
this.emit("close")
}).catch(errs => {
console.warn("Failed to add:", errs)
this.state.error = "Failed to add: " + JSON.stringify(errs)
}).then(() => {
this.state.loading = false
})
}
}
@@ -0,0 +1,19 @@
<modal class="modal color-text nolabel" key="modal" enabled=(input.enabled) closable on-close("close") on-open("open") >
<h1>Upload File</h1>
<p class="color-error">${state.error}</p>
<label>File</label>
<input key="eventName" type="file" placeholder="(None)" on-change("change", "file") value=state.values.file />
<label>Name</label>
<input key="name" autofocus placeholder="(Required)" class="big" on-change("change", "name") value=state.values.name />
<label>Public</label>
<toggle value=state.values.public on="On" off="Off"
onDesc="Other members may see this file on this page."
offDesc="Only you may see this file on this page."
on-change("change", "public") />
<button disabled=state.loading on-click("save")>Save</button>
</modal>
@@ -99,6 +99,19 @@ module.exports = class {
break
}
case "File": {
const key = data.keys.find(k => k.id !== "*")
const fileObj = data.objects.find(o => o.type === "File") || {name: key.id}
this.state.operation += " file"
this.state.links = [{
text: fileObj.name,
href: `/data/files#${key.id}`,
}]
break
}
case "Post": {
const logKey = data.keys.find(k => k.model === "Log")
const postKeys = data.keys.filter(k => k.model === "Post")
@@ -124,6 +137,7 @@ module.exports = class {
const opText = {
add: "added",
remove: "removed",
upload: "uploaded",
move: "moved",
edit: "edited",
import: "imported",
@@ -4,3 +4,4 @@
</main>
<add-channel-modal enabled=(state.modal === "channel.add") user=input.user on-added("channelAdded") on-close("close") />
<add-character-modal enabled=(state.modal === "character.add") user=input.user on-close("close") />
<add-file-modal enabled=(state.modal === "file.add") user=input.user on-close("close") />
@@ -31,6 +31,15 @@ module.exports = class {
}
characterAdded(character) {
const index = this.state.characters.findIndex(c => c.id === character.id)
if (index !== -1) {
const characters = this.state.characters.slice()
characters[index] = character
this.state.characters = characters
return
}
this.state.characters = this.state.characters.concat(character)
}
@@ -4,3 +4,4 @@
</main>
<add-character-modal enabled=(state.modal === "character.add") user=input.user on-added("characterAdded") on-close("close") />
<add-channel-modal enabled=(state.modal === "channel.add") user=input.user on-close("close") />
<add-file-modal enabled=(state.modal === "file.add") user=input.user on-close("close") />
@@ -3,14 +3,18 @@
<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/channels/">Channels</menu-link>
<menu-link key="history" selected=input.selected.changes icon="H" href="/data/changes/">History</menu-link>
<if-permitted user=input.user permission=["member", "character.add", "channel.add"]>
<menu-link key="files" selected=input.selected.files icon="F" href="/data/files/">Files</menu-link>
<if-permitted user=input.user permission=["member", "character.add", "channel.add", "file.add"]>
<menu-gap />
<menu-header>Add</menu-header>
<if-permitted user=input.user permission=["character.add", "member"]>
<menu-link dark key="characters_add" icon="+" on-click("emit", "open", "character.add")>Character</menu-link>
<if-permitted key="characters_add" user=input.user permission=["character.add", "member"]>
<menu-link dark icon="+" on-click("emit", "open", "character.add")>Character</menu-link>
</if-permitted>
<if-permitted user=input.user permission="channel.add">
<menu-link dark key="channels_add" icon="+" on-click("emit", "open", "channel.add")>Channel</menu-link>
<if-permitted key="channels_add" user=input.user permission="channel.add">
<menu-link dark icon="+" on-click("emit", "open", "channel.add")>Channel</menu-link>
</if-permitted>
<if-permitted key="files_add" user=input.user permission=["file.add", "member"]>
<menu-link dark icon="+" on-click("emit", "open", "file.add")>File</menu-link>
</if-permitted>
</if-permitted>
</menu>
@@ -0,0 +1,59 @@
const moment = require("moment")
const {fileApi} = require("../../../../../rpdata/api/File")
module.exports = class {
onCreate(input) {
this.state = {
error: null,
loading: false,
values: {
id: "",
name: "",
public: false,
}
}
}
onInput(input) {
this.state.values = {
id: input.file.id,
name: input.file.name,
public: input.file.public,
}
}
change(key, ev) {
this.state.values = Object.assign({}, this.state.values, {[key]: ev.target.value})
}
open() {
}
close() {
this.emit("close")
}
save() {
if (this.state.loading) {
return
}
const input = Object.assign({}, this.state.values)
input.file = input.realFile
delete input.realFile
this.state.loading = true
fileApi.editFile(input).then((res) => {
this.emit("edited", res)
this.emit("close")
}).catch(errs => {
console.warn("Failed to add:", errs)
this.state.error = "Failed to add: " + JSON.stringify(errs)
}).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>Upload File</h1>
<p class="color-error">${state.error}</p>
<label>Name</label>
<input key="name" autofocus placeholder="(Required)" class="big" on-change("change", "name") value=state.values.name />
<label>Public</label>
<toggle value=state.values.public on="On" off="Off"
onDesc="Other members may see this file on this page."
offDesc="Only you may see this file on this page."
on-change("change", "public") />
<button disabled=state.loading on-click("save")>Save</button>
</modal>
@@ -0,0 +1,7 @@
module.exports = class {
isHighlighted(id) {
if (typeof(window) !== "undefined") {
return window.location.hash.endsWith(id)
}
}
}
@@ -0,0 +1,9 @@
<compact-list>
<compact-list-item for(file in input.files) key=file.id id=file.id highlight=component.isHighlighted(file.id)>
<file data=file
user=input.user
on-removed("emit", "removed", file)
on-edited("emit", "edited", file)
/>
</compact-list-item>
</compact-list>
@@ -0,0 +1,38 @@
const moment = require("moment")
module.exports = class {
onCreate(input) {
this.state = {
modal: null,
deleted: false,
date: moment(input.data.time).format("MMM D, YYYY"),
}
}
onInput(input) {
if (input && input.data) {
this.state.date = moment(input.data.time).format("MMM D, YYYY")
}
}
open(modal) {
this.state.modal = modal
}
close() {
this.state.modal = null
}
removed() {
this.state.deleted = true
this.emit("removed")
}
sizeString(bytes) {
const order = Math.floor(Math.log2(bytes) / 10)
const unit = ["B", "KB", "MB", "GB", "TB", "PB"][order]
const number = bytes / Math.pow(2, (order)*10)
return `${number.toFixed(2)} ${unit}`
}
}
@@ -0,0 +1,22 @@
<if (!state.deleted)>
<a class="anchor" id=input.data.id />
<compact-list-property primary long label="Name" value=input.data.name />
<compact-list-property short label="Date" value=state.date />
<compact-list-property short label="Type" value=input.data.mimeType />
<compact-list-property short label="Author" value=input.data.author />
<compact-list-property short label="Size" value=component.sizeString(input.data.size) />
<compact-list-options long>
<a key="link" class="color-menu" on-click("open", "link")>Link</a>
<if(input.data.kind === "upload")>
<if-permitted user=input.user author=input.data.author permission="file.edit">
<a key="edit" on-click("open", "edit", input.data)>Edit</a>
</if-permitted>
<if-permitted user=input.user author=input.data.author permission="file.remove">
<a key="remove" on-click("open", "remove", input.data)>Remove</a>
</if-permitted>
</if>
</compact-list-options>
<link-file-modal enabled=(state.modal === "link") file=input.data on-close("close") />
<edit-file-modal enabled=(state.modal === "edit") file=input.data on-edited("emit", "edited") on-close("close") />
<remove-file-modal enabled=(state.modal === "remove") file=input.data on-removed("removed") on-close("close") />
</if>
@@ -0,0 +1,47 @@
module.exports = class {
onCreate(input) {
this.state = {
publics: input.publics.sort(byDate),
privates: input.privates.sort(byDate),
modal: null,
}
}
onMount() {
}
open(modal) {
this.state.modal = modal
}
close() {
this.state.modal = null
}
fileRemoved({id}) {
this.state.publics = this.state.publics.filter(f => f.id !== id)
this.state.privates = this.state.privates.filter(f => f.id !== id)
}
fileEdited(file, patch) {
this.fileRemoved({id: file.id})
this.fileAdded(Object.assign({}, file, patch))
console.log(Object.assign({}, file, patch))
}
fileAdded(file) {
if (file.public) {
this.state.publics = [].concat(...this.state.publics).concat(file).sort(byDate)
} else {
this.state.privates = [].concat(...this.state.privates).concat(file).sort(byDate)
}
}
}
function byDate(a, b) {
const ad = new Date(a.time)
const bd = new Date(b.time)
return ad.getTime() - bd.getTime()
}
@@ -0,0 +1,11 @@
<data-menu categories=input.categories selected=(input.selected || {}) user=input.user on-open("open") />
<main class="data-file-page">
<h1 class="file-list-headers color-primary">Your Private Files</h1>
<file-list user=input.user files=state.privates on-removed("fileRemoved") on-edited("fileEdited") on-nicks("fileNicksChanged") />
<h1 class="file-list-headers color-primary">Public Files</h1>
<file-list user=input.user files=state.publics on-removed("fileRemoved") on-edited("fileEdited") on-nicks("fileNicksChanged") />
</main>
<add-channel-modal enabled=(state.modal === "channel.add") user=input.user on-close("close") />
<add-character-modal enabled=(state.modal === "character.add") user=input.user on-close("close") />
<add-file-modal enabled=(state.modal === "file.add") user=input.user on-added("fileAdded") on-close("close") />
@@ -0,0 +1,10 @@
h1.file-list-headers {
margin: 0;
font-size: 1.5em;
margin-top: 0.5em;
text-align: center;
}
main.data-file-page {
margin-bottom: 4em;
}
@@ -0,0 +1,19 @@
module.exports = class {
onCreate(input) {
this.state = {
isImage: (input.file && input.file.mimeType.startsWith("image/"))
}
}
onInput(input) {
this.state.isImage = (input.file && input.file.mimeType.startsWith("image/"))
}
open() {
}
close() {
this.emit("close")
}
}
@@ -0,0 +1,36 @@
<modal class="modal color-text nolabel link-file-modal" key="modal" enabled=(input.enabled) closable on-close("close") on-open("open") >
<h1>Links for ${input.file.name}</h1>
<p>
Below are links and copy-pastable syntax for sharing the file. They can be used for linking to it
in your stories and wiki pages.
</p>
<p class="color-danger">
This is the link to the actual file on the CDN, not a share of it. You must delete the file if you want
to un-share it.
</p>
<if(state.isImage)>
<p class="color-danger">
Images uploaded here cannot be inlined on the wiki. For that you have to upload it there.
</p>
</if>
<label>URL</label>
<input key="url" disabled placeholder="(None)" on-change("change", "eventName") value=input.file.url />
<label>Markdown link</label>
<input key="mdLink" disabled placeholder="(None)" on-change("change", "locationName") value=`[Link text](${input.file.url})` />
<label>Wiki link</label>
<input key="wikiLink" disabled placeholder="(None)" on-change("change", "locationName") value=`[${input.file.url} Link text]` />
<if(state.isImage)>
<label>Markdown image</label>
<input key="mdImage" disabled placeholder="(None)" on-change("change", "locationName") value=`![Alt. text](${input.file.url})` />
<label>Image Preview</label>
<img src=input.file.url alt="File preview"/>
</if>
</modal>
@@ -0,0 +1,19 @@
div.link-file-modal {
user-select: text;
p {
user-select: text;
cursor: text;
}
img {
margin: 0;
padding: 0;
margin-top: 0.25em;
width: 100%;
}
input {
user-select: all !important;
}
}
@@ -0,0 +1,51 @@
const {fileApi} = require("../../../../../rpdata/api/File")
module.exports = class {
onCreate(input) {
this.state = {
error: null,
loading: false,
values: {
id: "",
}
}
}
onInput(input) {
this.state.values = {
id: input.file.id,
}
}
change(key, ev) {
this.state.values = Object.assign({}, this.state.values, {[key]: ev.target.value})
}
open() {
}
close() {
this.emit("close")
}
doIt() {
if (this.state.loading) {
return
}
const input = Object.assign({}, this.state.values)
this.state.loading = true
fileApi.removeFile(input).then((res) => {
this.emit("removed", res)
this.emit("close")
}).catch(errs => {
console.warn("Failed to add:", errs)
this.state.error = "Failed to add: " + JSON.stringify(errs)
}).then(() => {
this.state.loading = false
})
}
}
@@ -0,0 +1,11 @@
<modal class="modal color-text nolabel" key="modal" enabled=(input.enabled) closable on-close("close") on-open("open") >
<h1>Delete File ${input.file.name}</h1>
<p class="color-error">${state.error}</p>
<p class="color-danger">
It might take up to an hour for it to be removed from CDN caches if it's been recently loaded.
</p>
<button disabled=state.loading on-click("doIt")>Remove</button>
</modal>
+6
View File
@@ -0,0 +1,6 @@
<include("../layout", {title: "Data", site: "data"})>
<@body>
<background src="/assets/images/bg.png" opacity=0.25 />
<files-page user=input.user privates=input.privates publics=input.publics selected=input.selected />
</@body>
</include>