Browse Source

logs: Added filter menu.

1.1 1.1.0
Gisle Aune 6 years ago
parent
commit
326e4478c0
  1. 5
      marko/components/right-menu/index.marko
  2. 26
      marko/components/right-menu/style.less
  3. 5
      marko/global/base.less
  4. 129
      marko/page/logs/components/filter-menu/component.js
  5. 36
      marko/page/logs/components/filter-menu/index.marko
  6. 24
      marko/page/logs/components/filter-menu/style.less
  7. 49
      marko/page/logs/components/logs-menu/component.js
  8. 4
      marko/page/logs/components/logs-menu/index.marko
  9. 12
      marko/page/logs/components/logs-table-row/style.less
  10. 2
      marko/page/logs/components/logs-table/style.less
  11. 32
      marko/page/logs/components/page/component.js
  12. 6
      marko/page/logs/components/page/index.marko
  13. 9
      marko/page/logs/list.marko
  14. 3
      routes/logs/index.js

5
marko/components/right-menu/index.marko

@ -0,0 +1,5 @@
<div class=["right-menu", input.class]>
<div class="menu-body">
<include(input.renderBody) />
</div>
</div>

26
marko/components/right-menu/style.less

@ -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;
}
}

5
marko/global/base.less

@ -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;

129
marko/page/logs/components/filter-menu/component.js

@ -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
}
}

36
marko/page/logs/components/filter-menu/index.marko

@ -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>

24
marko/page/logs/components/filter-menu/style.less

@ -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;
}
}

49
marko/page/logs/components/logs-menu/component.js

@ -13,54 +13,7 @@ module.exports = class {
return return
} }
const filters = []
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)
this.state.filtered = input.filter.search || input.filter.channels || input.filter.characters || input.filter.events
} }
select(value) { select(value) {

4
marko/page/logs/components/logs-menu/index.marko

@ -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>

12
marko/page/logs/components/logs-table-row/style.less

@ -10,11 +10,17 @@ tr.logs-table-row {
} }
td.expand { td.expand {
width: 2%;
width: 0%;
} }
td.channel, td.date, td.event {
width: 12%;
td.date {
width: 16%;
}
td.channel {
width: 14%;
}
td.event {
width: 18%;
} }
td.character { td.character {

2
marko/page/logs/components/logs-table/style.less

@ -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;
} }

32
marko/page/logs/components/page/component.js

@ -19,11 +19,18 @@ module.exports = class {
addFilter(type, filter) { addFilter(type, filter) {
if (type === "search") { if (type === "search") {
if (filter.trim().length > 0) {
this.state.filter = Object.assign({}, this.state.filter, {search: filter}) 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() {

6
marko/page/logs/components/page/index.marko

@ -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 />
<main>
<logs-menu key="menu" on-select("open") selected=(input.selected || {}) user=input.user filter=state.filter characters=input.characters />
<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") />

9
marko/page/logs/list.marko

@ -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 filter=input.filter characters=input.characters user=input.user selected=input.selected />
<page logs=input.logs
filter=input.filter
characters=input.characters
channels=input.channels
eventNames=input.eventNames
user=input.user
selected=input.selected />
</@body> </@body>
</include> </include>

3
routes/logs/index.js

@ -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) {

Loading…
Cancel
Save