@@ -0,0 +1,5 @@
|
|||||||
|
<div class=["right-menu", input.class]>
|
||||||
|
<div class="menu-body">
|
||||||
|
<include(input.renderBody) />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
.right-menu {
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 28ch;
|
||||||
|
|
||||||
|
padding: 0.333em;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
user-select: none;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.showhide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
.right-menu {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,11 @@ body {
|
|||||||
width: calc(100% - 32ch);
|
width: calc(100% - 32ch);
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
@media screen and (min-width: 700px) {
|
||||||
|
main.with-right {
|
||||||
|
width: calc(100% - 60ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
module.exports = class {
|
||||||
|
onCreate(input) {
|
||||||
|
this.state = {
|
||||||
|
characters: [],
|
||||||
|
channels: [],
|
||||||
|
eventNames: [],
|
||||||
|
filters: [],
|
||||||
|
search: "",
|
||||||
|
activeMap: {
|
||||||
|
events: {},
|
||||||
|
characters: {},
|
||||||
|
channels: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onInput(input) {
|
||||||
|
this.update(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(input) {
|
||||||
|
this.getEl("search").addEventListener("keydown", ev => {
|
||||||
|
if (this.waiting) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.timeout != null) {
|
||||||
|
clearTimeout(this.timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ev.key === "Enter") {
|
||||||
|
this.emit("add", "search", ev.target.value)
|
||||||
|
this.getEl("search").value = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
this.timeout = setTimeout(() => {
|
||||||
|
this.timeout = null
|
||||||
|
this.state.search = ev.target.value
|
||||||
|
|
||||||
|
this.update(this.input)
|
||||||
|
}, 200)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
update(input) {
|
||||||
|
if (input.filter) {
|
||||||
|
this.updateActive(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
const search = this.state.search.toLocaleLowerCase()
|
||||||
|
|
||||||
|
this.state.characters = input.characters.filter(c => !this.state.activeMap.characters[c.id]).filter(c => this.matches(c.name, search)).sort((a,b) => a.name.localeCompare(b.name))
|
||||||
|
this.state.channels = input.channels.filter(c => !this.state.activeMap.channels[c.name]).filter(c => this.matches(c.name, search)).sort((a,b) => a.name.localeCompare(b.name))
|
||||||
|
this.state.eventNames = input.eventNames.filter(e => !this.state.activeMap.events[e]).filter(e => this.matches(e, search)).sort((a,b) => a.localeCompare(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
updateActive({filter, characters, channels, eventNames}) {
|
||||||
|
const filters = []
|
||||||
|
const activeMap = {
|
||||||
|
events: {},
|
||||||
|
characters: {},
|
||||||
|
channels: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.search != null) {
|
||||||
|
filters.push({
|
||||||
|
type: "search",
|
||||||
|
key: "search",
|
||||||
|
icon: "T",
|
||||||
|
color: "color-text",
|
||||||
|
text: filter.search,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const character of (filter.characters || [])) {
|
||||||
|
activeMap.characters[character] = true
|
||||||
|
|
||||||
|
filters.push({
|
||||||
|
type: "characters",
|
||||||
|
key: character,
|
||||||
|
icon: "C",
|
||||||
|
color: "color-tag-character",
|
||||||
|
id: character,
|
||||||
|
text: (characters.find(c => c.id === character) || {name: "Unknown ("+character+")"}).name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const channel of (filter.channels || [])) {
|
||||||
|
activeMap.channels[channel] = true
|
||||||
|
|
||||||
|
filters.push({
|
||||||
|
type: "channels",
|
||||||
|
key: channel.replace(/'/g, "__"),
|
||||||
|
icon: "#",
|
||||||
|
color: "color-tag-location",
|
||||||
|
id: channel,
|
||||||
|
text: channel,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const event of (filter.events || [])) {
|
||||||
|
activeMap.events[event] = true
|
||||||
|
|
||||||
|
filters.push({
|
||||||
|
type: "events",
|
||||||
|
key: event.replace(/['\s\.]/g, "__"),
|
||||||
|
icon: "E",
|
||||||
|
color: "color-tag-event",
|
||||||
|
id: event,
|
||||||
|
text: event,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.filters = filters
|
||||||
|
this.state.activeMap = activeMap
|
||||||
|
}
|
||||||
|
|
||||||
|
matches(str, search) {
|
||||||
|
if (search == "") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search.length > str.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return str.toLowerCase().indexOf(search) !== -1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<right-menu class="filter-menu">
|
||||||
|
<input key="search" />
|
||||||
|
<menu-gap />
|
||||||
|
<menu-header if(state.filters.length > 0)>Active Filters</menu-header>
|
||||||
|
<div class="filter-section">
|
||||||
|
<menu-link for(filter in state.filters)
|
||||||
|
on-click("emit", "remove", filter.type, filter.id)
|
||||||
|
textClass=filter.color
|
||||||
|
unselectable
|
||||||
|
icon=filter.icon >${filter.text}</menu-link>
|
||||||
|
</div>
|
||||||
|
<menu-gap if(state.filters.length > 0) />
|
||||||
|
<menu-header>Characters</menu-header>
|
||||||
|
<div class="filter-section">
|
||||||
|
<menu-link for(character in state.characters)
|
||||||
|
on-click("emit", "add", "characters", character.id)
|
||||||
|
icon=character.name.charAt(0).toUpperCase()
|
||||||
|
textClass="color-tag-character">${character.name}</menu-link>
|
||||||
|
</div>
|
||||||
|
<menu-gap />
|
||||||
|
<menu-header>Channels</menu-header>
|
||||||
|
<div class="filter-section">
|
||||||
|
<menu-link for(channel in state.channels)
|
||||||
|
on-click("emit", "add", "channels", channel.name)
|
||||||
|
icon=channel.name.charAt(1).toUpperCase()
|
||||||
|
textClass="color-tag-location">${channel.name}</menu-link>
|
||||||
|
</div>
|
||||||
|
<menu-gap />
|
||||||
|
<menu-header>Events</menu-header>
|
||||||
|
<div class="filter-section">
|
||||||
|
<menu-link for(eventName in state.eventNames)
|
||||||
|
on-click("emit", "add", "events", eventName)
|
||||||
|
icon=eventName.charAt(0).toUpperCase()
|
||||||
|
textClass="color-tag-event">${eventName}</menu-link>
|
||||||
|
</div>
|
||||||
|
</right-menu>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
div.right-menu.filter-menu {
|
||||||
|
input {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
width: 95%;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-left: 2.5%;
|
||||||
|
border: 1px solid rgba(119, 119, 119, 0.25);
|
||||||
|
font-size: 1.1em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
input:focus {
|
||||||
|
color: #FC1;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.filter-section {
|
||||||
|
max-height: calc(25vh - 3em);
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,54 +13,7 @@ module.exports = class {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = []
|
this.state.filtered = input.filter.search || input.filter.channels || input.filter.characters || input.filter.events
|
||||||
const {characters, channels, events, search} = input.filter
|
|
||||||
|
|
||||||
if (search != null) {
|
|
||||||
filters.push({
|
|
||||||
type: "search",
|
|
||||||
key: "search",
|
|
||||||
icon: "T",
|
|
||||||
color: "color-text",
|
|
||||||
text: search,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const character of (characters || [])) {
|
|
||||||
filters.push({
|
|
||||||
type: "characters",
|
|
||||||
key: character,
|
|
||||||
icon: "C",
|
|
||||||
color: "color-tag-character",
|
|
||||||
id: character,
|
|
||||||
text: (input.characters.find(c => c.id === character) || {name: "Unknown ("+character+")"}).name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const channel of (channels || [])) {
|
|
||||||
filters.push({
|
|
||||||
type: "channels",
|
|
||||||
key: channel.replace(/'/g, "__"),
|
|
||||||
icon: "#",
|
|
||||||
color: "color-tag-location",
|
|
||||||
id: channel,
|
|
||||||
text: channel,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const event of (events || [])) {
|
|
||||||
filters.push({
|
|
||||||
type: "events",
|
|
||||||
key: event.replace(/['\s\.]/g, "__"),
|
|
||||||
icon: "E",
|
|
||||||
color: "color-tag-event",
|
|
||||||
id: event,
|
|
||||||
text: event,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state.filters = filters
|
|
||||||
this.state.filtered = (filters.length > 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
select(value) {
|
select(value) {
|
||||||
|
|||||||
@@ -4,8 +4,4 @@
|
|||||||
<if-permitted key="if-permitted" user=input.user permission="log.add">
|
<if-permitted key="if-permitted" user=input.user permission="log.add">
|
||||||
<menu-link key="add_log" dark on-click("select", "log.add") icon="+">Add Log</menu-link>
|
<menu-link key="add_log" dark on-click("select", "log.add") icon="+">Add Log</menu-link>
|
||||||
</if-permitted>
|
</if-permitted>
|
||||||
<menu-gap />
|
|
||||||
<menu-header key="filters">Filters</menu-header>
|
|
||||||
<menu-link for(filter in state.filters) textClass=filter.color unselectable icon=filter.icon on-click("emit", "removefilter", filter.type, filter.id)>${filter.text}</menu-link>
|
|
||||||
<menu-link key="add_filter" dark on-click("select", "filter.add") icon="+">Add Filter</menu-link>
|
|
||||||
</menu>
|
</menu>
|
||||||
@@ -10,11 +10,17 @@ tr.logs-table-row {
|
|||||||
}
|
}
|
||||||
|
|
||||||
td.expand {
|
td.expand {
|
||||||
width: 2%;
|
width: 0%;
|
||||||
}
|
}
|
||||||
|
|
||||||
td.channel, td.date, td.event {
|
td.date {
|
||||||
width: 12%;
|
width: 16%;
|
||||||
|
}
|
||||||
|
td.channel {
|
||||||
|
width: 14%;
|
||||||
|
}
|
||||||
|
td.event {
|
||||||
|
width: 18%;
|
||||||
}
|
}
|
||||||
|
|
||||||
td.character {
|
td.character {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ table.logs-table {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 900px) {
|
@media screen and (max-width: 1200px) {
|
||||||
td.event, th.event {
|
td.event, th.event {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,18 @@ module.exports = class {
|
|||||||
|
|
||||||
addFilter(type, filter) {
|
addFilter(type, filter) {
|
||||||
if (type === "search") {
|
if (type === "search") {
|
||||||
this.state.filter = Object.assign({}, this.state.filter, {search: filter})
|
if (filter.trim().length > 0) {
|
||||||
|
this.state.filter = Object.assign({}, this.state.filter, {search: filter})
|
||||||
|
} else {
|
||||||
|
this.state.filter = Object.assign({}, this.state.filter, {search: null})
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.state.filter = Object.assign({}, this.state.filter, {[type]: (this.state.filter[type] || []).filter(f => f !== filter).concat(filter)})
|
this.state.filter = Object.assign({}, this.state.filter, {[type]: (this.state.filter[type] || []).filter(f => f !== filter).concat(filter)})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.updateQuery(this.state.filter)
|
||||||
|
|
||||||
this.refresh()
|
this.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +48,31 @@ module.exports = class {
|
|||||||
this.refresh()
|
this.refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.updateQuery(this.state.filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateQuery(filter) {
|
||||||
|
const queries = []
|
||||||
|
|
||||||
|
if (filter.characters) {
|
||||||
|
queries.push("characters=" + filter.characters.join(","))
|
||||||
|
}
|
||||||
|
if (filter.channels) {
|
||||||
|
queries.push("channels=" + filter.channels.map(c => encodeURIComponent(c)).join(","))
|
||||||
|
}
|
||||||
|
if (filter.events) {
|
||||||
|
queries.push("events=" + filter.events.map(e => encodeURIComponent(e)).join(","))
|
||||||
|
}
|
||||||
|
if (filter.search) {
|
||||||
|
queries.push("search=" + encodeURIComponent(filter.search))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queries.length > 0) {
|
||||||
|
history.replaceState("", "", `/logs/?${queries.join("&")}`)
|
||||||
|
} else {
|
||||||
|
history.replaceState("", "", "/logs/")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<background src="/assets/images/bg.png" opacity=0.25 />
|
<background src="/assets/images/bg.png" opacity=0.25 />
|
||||||
<logs-menu key="menu" on-select("open") on-removefilter("removeFilter") selected=(input.selected || {}) user=input.user filter=state.filter characters=input.characters />
|
<logs-menu key="menu" on-select("open") selected=(input.selected || {}) user=input.user filter=state.filter characters=input.characters />
|
||||||
<main>
|
<filter-menu on-add("addFilter") on-remove("removeFilter") filter=state.filter characters=input.characters channels=input.channels eventNames=input.eventNames />
|
||||||
|
<main class="with-right">
|
||||||
<logs-table logs=state.logs on-addfilter("addFilter") />
|
<logs-table logs=state.logs on-addfilter("addFilter") />
|
||||||
</main>
|
</main>
|
||||||
<add-log-modal enabled=(state.modal === "log.add") on-close("close") />
|
<add-log-modal enabled=(state.modal === "log.add") on-close("close") />
|
||||||
<add-filter-modal enabled=(state.modal === "filter.add") characters=input.characters filter=state.filter on-add("addFilter") on-close("close") />
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
<include("../layout", {title: "Logs", site: "logs"})>
|
<include("../layout", {title: "Logs", site: "logs"})>
|
||||||
<@body>
|
<@body>
|
||||||
<!-- Page needed to get component.js functionality -->
|
<page logs=input.logs
|
||||||
<page logs=input.logs filter=input.filter characters=input.characters user=input.user selected=input.selected />
|
filter=input.filter
|
||||||
|
characters=input.characters
|
||||||
|
channels=input.channels
|
||||||
|
eventNames=input.eventNames
|
||||||
|
user=input.user
|
||||||
|
selected=input.selected />
|
||||||
</@body>
|
</@body>
|
||||||
</include>
|
</include>
|
||||||
@@ -3,6 +3,7 @@ const router = express.Router()
|
|||||||
|
|
||||||
const {logHeaderApi, eventNames} = require("../../rpdata/api/LogHeader")
|
const {logHeaderApi, eventNames} = require("../../rpdata/api/LogHeader")
|
||||||
const {charactersApi} = require("../../rpdata/api/Character")
|
const {charactersApi} = require("../../rpdata/api/Character")
|
||||||
|
const {channelApi} = require("../../rpdata/api/Channel")
|
||||||
|
|
||||||
const listTemplate = require("../../marko/page/logs/list.marko")
|
const listTemplate = require("../../marko/page/logs/list.marko")
|
||||||
|
|
||||||
@@ -27,6 +28,8 @@ router.get("/", async(req, res) => {
|
|||||||
filter: filter,
|
filter: filter,
|
||||||
logs: logHeaderApi.list(filter),
|
logs: logHeaderApi.list(filter),
|
||||||
characters: charactersApi.listHeaders(),
|
characters: charactersApi.listHeaders(),
|
||||||
|
channels: channelApi.list(),
|
||||||
|
eventNames: logHeaderApi.eventNames(),
|
||||||
selected: {index: true},
|
selected: {index: true},
|
||||||
})
|
})
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
|
|||||||
Reference in New Issue
Block a user