Add frontend for adding a category with the category picker
This commit is contained in:
parent
3b681e32f6
commit
f1c5db2ced
5 changed files with 128 additions and 22 deletions
|
|
@ -107,6 +107,24 @@ $logo-height: $navbar-height - 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#category-selection {
|
||||||
|
.dropdown {
|
||||||
|
.btn {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.plus-btn {
|
||||||
|
margin-right: 0px;
|
||||||
|
i {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
#new-thread {
|
#new-thread {
|
||||||
.modal-container .modal-body {
|
.modal-container .modal-body {
|
||||||
max-height: none;
|
max-height: none;
|
||||||
|
|
@ -715,12 +733,4 @@ hr {
|
||||||
// - Hide features that have not been implemented yet.
|
// - Hide features that have not been implemented yet.
|
||||||
#main-buttons > section.navbar-section:nth-child(1) {
|
#main-buttons > section.navbar-section:nth-child(1) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#threads-list.table {
|
|
||||||
tr > th:nth-child(2), tr > td:nth-child(2) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.category, div.flag-button {
|
|
||||||
}
|
|
||||||
|
|
@ -9,6 +9,9 @@ type
|
||||||
CategoryList* = ref object
|
CategoryList* = ref object
|
||||||
categories*: seq[Category]
|
categories*: seq[Category]
|
||||||
|
|
||||||
|
proc cmpNames*(cat1: Category, cat2: Category): int =
|
||||||
|
cat1.name.cmp(cat2.name)
|
||||||
|
|
||||||
when defined(js):
|
when defined(js):
|
||||||
include karax/prelude
|
include karax/prelude
|
||||||
import karax / [vstyles, kajax, kdom]
|
import karax / [vstyles, kajax, kdom]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
when defined(js):
|
when defined(js):
|
||||||
import sugar, httpcore, options, json, strutils
|
import sugar, httpcore, options, json, strutils, algorithm
|
||||||
import dom except Event
|
import dom except Event
|
||||||
|
|
||||||
include karax/prelude
|
include karax/prelude
|
||||||
|
|
@ -13,13 +13,17 @@ when defined(js):
|
||||||
list: Option[CategoryList]
|
list: Option[CategoryList]
|
||||||
selectedCategoryID*: int
|
selectedCategoryID*: int
|
||||||
loading: bool
|
loading: bool
|
||||||
|
modalShown: bool
|
||||||
|
addEnabled: bool
|
||||||
status: HttpCode
|
status: HttpCode
|
||||||
|
error: Option[PostError]
|
||||||
onCategoryChange: proc (oldCategory: Category, newCategory: Category)
|
onCategoryChange: proc (oldCategory: Category, newCategory: Category)
|
||||||
|
onAddCategory: proc (category: Category)
|
||||||
|
|
||||||
proc slug(name: string): string =
|
proc slug(name: string): string =
|
||||||
name.strip().replace(" ", "-").toLowerAscii
|
name.strip().replace(" ", "-").toLowerAscii
|
||||||
|
|
||||||
proc onCategoryList(state: CategoryPicker): proc (httpStatus: int, response: kstring) =
|
proc onCategoryLoad(state: CategoryPicker): proc (httpStatus: int, response: kstring) =
|
||||||
return proc (httpStatus: int, response: kstring) =
|
return proc (httpStatus: int, response: kstring) =
|
||||||
state.loading = false
|
state.loading = false
|
||||||
state.status = httpStatus.HttpCode
|
state.status = httpStatus.HttpCode
|
||||||
|
|
@ -27,6 +31,7 @@ when defined(js):
|
||||||
|
|
||||||
let parsed = parseJson($response)
|
let parsed = parseJson($response)
|
||||||
let list = parsed.to(CategoryList)
|
let list = parsed.to(CategoryList)
|
||||||
|
list.categories.sort(cmpNames)
|
||||||
|
|
||||||
if state.list.isSome:
|
if state.list.isSome:
|
||||||
state.list.get().categories.add(list.categories)
|
state.list.get().categories.add(list.categories)
|
||||||
|
|
@ -39,23 +44,36 @@ when defined(js):
|
||||||
proc loadCategories(state: CategoryPicker) =
|
proc loadCategories(state: CategoryPicker) =
|
||||||
if not state.loading:
|
if not state.loading:
|
||||||
state.loading = true
|
state.loading = true
|
||||||
ajaxGet(makeUri("categories.json"), @[], onCategoryList(state))
|
ajaxGet(makeUri("categories.json"), @[], onCategoryLoad(state))
|
||||||
|
|
||||||
proc `[]`*(state: CategoryPicker, id: int): Category =
|
proc `[]`*(state: CategoryPicker, id: int): Category =
|
||||||
state.list.get().categories[id]
|
for cat in state.list.get().categories:
|
||||||
|
if cat.id == id:
|
||||||
|
return cat
|
||||||
|
raise newException(IndexError, "Category at " & $id & " not found!")
|
||||||
|
|
||||||
|
proc nullAddCategory(category: Category) = discard
|
||||||
|
proc nullCategoryChange(oldCategory: Category, newCategory: Category) = discard
|
||||||
|
|
||||||
proc newCategoryPicker*(
|
proc newCategoryPicker*(
|
||||||
onCategoryChange: proc (oldCategory: Category, newCategory: Category) =
|
onCategoryChange: proc(oldCategory: Category, newCategory: Category) = nullCategoryChange,
|
||||||
proc (oldCategory: Category, newCategory: Category) = discard
|
onAddCategory: proc(category: Category) = nullAddCategory
|
||||||
): CategoryPicker =
|
): CategoryPicker =
|
||||||
result = CategoryPicker(
|
result = CategoryPicker(
|
||||||
list: none[CategoryList](),
|
list: none[CategoryList](),
|
||||||
selectedCategoryID: 0,
|
selectedCategoryID: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
modalShown: false,
|
||||||
|
addEnabled: false,
|
||||||
status: Http200,
|
status: Http200,
|
||||||
onCategoryChange: onCategoryChange
|
error: none[PostError](),
|
||||||
|
onCategoryChange: onCategoryChange,
|
||||||
|
onAddCategory: onAddCategory
|
||||||
)
|
)
|
||||||
|
|
||||||
|
proc setAddEnabled*(state: CategoryPicker, enabled: bool) =
|
||||||
|
state.addEnabled = enabled
|
||||||
|
|
||||||
proc select*(state: CategoryPicker, id: int) =
|
proc select*(state: CategoryPicker, id: int) =
|
||||||
state.selectedCategoryID = id
|
state.selectedCategoryID = id
|
||||||
state.markDirty()
|
state.markDirty()
|
||||||
|
|
@ -68,6 +86,75 @@ when defined(js):
|
||||||
state.select(cat.id)
|
state.select(cat.id)
|
||||||
state.onCategoryChange(oldCategory, cat)
|
state.onCategoryChange(oldCategory, cat)
|
||||||
|
|
||||||
|
proc onAddCategoryPost(httpStatus: int, response: kstring, state: CategoryPicker) =
|
||||||
|
postFinished:
|
||||||
|
state.modalShown = false
|
||||||
|
let j = parseJson($response)
|
||||||
|
let category = j.to(Category)
|
||||||
|
|
||||||
|
state.list.get().categories.add(category)
|
||||||
|
state.list.get().categories.sort(cmpNames)
|
||||||
|
state.select(category.id)
|
||||||
|
|
||||||
|
state.onAddCategory(category)
|
||||||
|
|
||||||
|
proc onAddCategoryClick(state: CategoryPicker) =
|
||||||
|
state.loading = true
|
||||||
|
state.error = none[PostError]()
|
||||||
|
|
||||||
|
let uri = makeUri("createCategory")
|
||||||
|
let form = dom.document.getElementById("add-category-form")
|
||||||
|
let formData = newFormData(form)
|
||||||
|
|
||||||
|
ajaxPost(uri, @[], cast[cstring](formData),
|
||||||
|
(s: int, r: kstring) => onAddCategoryPost(s, r, state))
|
||||||
|
|
||||||
|
proc onClose(ev: Event, n: VNode, state: CategoryPicker) =
|
||||||
|
state.modalShown = false
|
||||||
|
state.markDirty()
|
||||||
|
ev.preventDefault()
|
||||||
|
|
||||||
|
proc genAddCategory(state: CategoryPicker): VNode =
|
||||||
|
result = buildHtml():
|
||||||
|
tdiv(id="add-category"):
|
||||||
|
button(class="plus-btn btn btn-link",
|
||||||
|
onClick=(ev: Event, n: VNode) => (
|
||||||
|
state.modalShown = true;
|
||||||
|
state.markDirty()
|
||||||
|
)):
|
||||||
|
italic(class="fas fa-plus")
|
||||||
|
tdiv(class=class({"active": state.modalShown}, "modal modal-sm")):
|
||||||
|
a(href="", class="modal-overlay", "aria-label"="close",
|
||||||
|
onClick=(ev: Event, n: VNode) => onClose(ev, n, state))
|
||||||
|
tdiv(class="modal-container"):
|
||||||
|
tdiv(class="modal-header"):
|
||||||
|
tdiv(class="card-title h5"):
|
||||||
|
text "Add New Category"
|
||||||
|
tdiv(class="modal-body"):
|
||||||
|
form(id="add-category-form"):
|
||||||
|
genFormField(
|
||||||
|
state.error, "name", "Name", "text", false,
|
||||||
|
placeholder="Category Name")
|
||||||
|
genFormField(
|
||||||
|
state.error, "color", "Color", "color", false,
|
||||||
|
placeholder="#XXYYZZ"
|
||||||
|
)
|
||||||
|
genFormField(
|
||||||
|
state.error,
|
||||||
|
"description",
|
||||||
|
"Description",
|
||||||
|
"text",
|
||||||
|
true,
|
||||||
|
placeholder="Description"
|
||||||
|
)
|
||||||
|
tdiv(class="modal-footer"):
|
||||||
|
button(
|
||||||
|
id="add-category-btn",
|
||||||
|
class="btn btn-primary",
|
||||||
|
onClick=(ev: Event, n: VNode) =>
|
||||||
|
state.onAddCategoryClick()):
|
||||||
|
text "Add"
|
||||||
|
|
||||||
proc render*(state: CategoryPicker): VNode =
|
proc render*(state: CategoryPicker): VNode =
|
||||||
if state.status != Http200:
|
if state.status != Http200:
|
||||||
return renderError("Couldn't retrieve categories.", state.status)
|
return renderError("Couldn't retrieve categories.", state.status)
|
||||||
|
|
@ -77,15 +164,13 @@ when defined(js):
|
||||||
return buildHtml(tdiv(class="loading loading-lg"))
|
return buildHtml(tdiv(class="loading loading-lg"))
|
||||||
|
|
||||||
let list = state.list.get().categories
|
let list = state.list.get().categories
|
||||||
let selectedCategory = list[state.selectedCategoryID]
|
let selectedCategory = state[state.selectedCategoryID]
|
||||||
|
|
||||||
result = buildHtml():
|
result = buildHtml():
|
||||||
tdiv(id="category-selection", class="input-group"):
|
tdiv(id="category-selection", class="input-group"):
|
||||||
label(class="d-inline-block form-label"):
|
|
||||||
text "Category"
|
|
||||||
tdiv(class="dropdown"):
|
tdiv(class="dropdown"):
|
||||||
a(class="btn btn-link dropdown-toggle", tabindex="0"):
|
a(class="btn btn-link dropdown-toggle", tabindex="0"):
|
||||||
tdiv(class="d-inline-block"):
|
tdiv(class="selected-category d-inline-block"):
|
||||||
render(selectedCategory)
|
render(selectedCategory)
|
||||||
text " "
|
text " "
|
||||||
italic(class="fas fa-caret-down")
|
italic(class="fas fa-caret-down")
|
||||||
|
|
@ -94,4 +179,6 @@ when defined(js):
|
||||||
li(class="menu-item"):
|
li(class="menu-item"):
|
||||||
a(class="category-" & $category.id & " " & category.name.slug,
|
a(class="category-" & $category.id & " " & category.name.slug,
|
||||||
onClick=onCategoryClick(state, category)):
|
onClick=onCategoryClick(state, category)):
|
||||||
render(category)
|
render(category)
|
||||||
|
if state.addEnabled:
|
||||||
|
genAddCategory(state)
|
||||||
|
|
@ -60,7 +60,10 @@ when defined(js):
|
||||||
if state.error.isSome():
|
if state.error.isSome():
|
||||||
p(class="text-error"):
|
p(class="text-error"):
|
||||||
text state.error.get().message
|
text state.error.get().message
|
||||||
render(state.categoryPicker)
|
tdiv():
|
||||||
|
label(class="d-inline-block form-label"):
|
||||||
|
text "Category"
|
||||||
|
render(state.categoryPicker)
|
||||||
renderContent(state.replyBox, none[Thread](), none[Post]())
|
renderContent(state.replyBox, none[Thread](), none[Post]())
|
||||||
tdiv(class="footer"):
|
tdiv(class="footer"):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,9 @@ when defined(js):
|
||||||
let currentAdmin =
|
let currentAdmin =
|
||||||
currentUser.isSome() and currentUser.get().rank == Admin
|
currentUser.isSome() and currentUser.get().rank == Admin
|
||||||
|
|
||||||
|
if currentAdmin:
|
||||||
|
state.categoryPicker.setAddEnabled(true)
|
||||||
|
|
||||||
result = buildHtml():
|
result = buildHtml():
|
||||||
tdiv():
|
tdiv():
|
||||||
if authoredByUser or currentAdmin:
|
if authoredByUser or currentAdmin:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue