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 {
|
||||
.modal-container .modal-body {
|
||||
max-height: none;
|
||||
|
|
@ -715,12 +733,4 @@ hr {
|
|||
// - Hide features that have not been implemented yet.
|
||||
#main-buttons > section.navbar-section:nth-child(1) {
|
||||
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
|
||||
categories*: seq[Category]
|
||||
|
||||
proc cmpNames*(cat1: Category, cat2: Category): int =
|
||||
cat1.name.cmp(cat2.name)
|
||||
|
||||
when defined(js):
|
||||
include karax/prelude
|
||||
import karax / [vstyles, kajax, kdom]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
when defined(js):
|
||||
import sugar, httpcore, options, json, strutils
|
||||
import sugar, httpcore, options, json, strutils, algorithm
|
||||
import dom except Event
|
||||
|
||||
include karax/prelude
|
||||
|
|
@ -13,13 +13,17 @@ when defined(js):
|
|||
list: Option[CategoryList]
|
||||
selectedCategoryID*: int
|
||||
loading: bool
|
||||
modalShown: bool
|
||||
addEnabled: bool
|
||||
status: HttpCode
|
||||
error: Option[PostError]
|
||||
onCategoryChange: proc (oldCategory: Category, newCategory: Category)
|
||||
onAddCategory: proc (category: Category)
|
||||
|
||||
proc slug(name: string): string =
|
||||
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) =
|
||||
state.loading = false
|
||||
state.status = httpStatus.HttpCode
|
||||
|
|
@ -27,6 +31,7 @@ when defined(js):
|
|||
|
||||
let parsed = parseJson($response)
|
||||
let list = parsed.to(CategoryList)
|
||||
list.categories.sort(cmpNames)
|
||||
|
||||
if state.list.isSome:
|
||||
state.list.get().categories.add(list.categories)
|
||||
|
|
@ -39,23 +44,36 @@ when defined(js):
|
|||
proc loadCategories(state: CategoryPicker) =
|
||||
if not state.loading:
|
||||
state.loading = true
|
||||
ajaxGet(makeUri("categories.json"), @[], onCategoryList(state))
|
||||
ajaxGet(makeUri("categories.json"), @[], onCategoryLoad(state))
|
||||
|
||||
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*(
|
||||
onCategoryChange: proc (oldCategory: Category, newCategory: Category) =
|
||||
proc (oldCategory: Category, newCategory: Category) = discard
|
||||
onCategoryChange: proc(oldCategory: Category, newCategory: Category) = nullCategoryChange,
|
||||
onAddCategory: proc(category: Category) = nullAddCategory
|
||||
): CategoryPicker =
|
||||
result = CategoryPicker(
|
||||
list: none[CategoryList](),
|
||||
selectedCategoryID: 0,
|
||||
loading: false,
|
||||
modalShown: false,
|
||||
addEnabled: false,
|
||||
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) =
|
||||
state.selectedCategoryID = id
|
||||
state.markDirty()
|
||||
|
|
@ -68,6 +86,75 @@ when defined(js):
|
|||
state.select(cat.id)
|
||||
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 =
|
||||
if state.status != Http200:
|
||||
return renderError("Couldn't retrieve categories.", state.status)
|
||||
|
|
@ -77,15 +164,13 @@ when defined(js):
|
|||
return buildHtml(tdiv(class="loading loading-lg"))
|
||||
|
||||
let list = state.list.get().categories
|
||||
let selectedCategory = list[state.selectedCategoryID]
|
||||
let selectedCategory = state[state.selectedCategoryID]
|
||||
|
||||
result = buildHtml():
|
||||
tdiv(id="category-selection", class="input-group"):
|
||||
label(class="d-inline-block form-label"):
|
||||
text "Category"
|
||||
tdiv(class="dropdown"):
|
||||
a(class="btn btn-link dropdown-toggle", tabindex="0"):
|
||||
tdiv(class="d-inline-block"):
|
||||
tdiv(class="selected-category d-inline-block"):
|
||||
render(selectedCategory)
|
||||
text " "
|
||||
italic(class="fas fa-caret-down")
|
||||
|
|
@ -94,4 +179,6 @@ when defined(js):
|
|||
li(class="menu-item"):
|
||||
a(class="category-" & $category.id & " " & category.name.slug,
|
||||
onClick=onCategoryClick(state, category)):
|
||||
render(category)
|
||||
render(category)
|
||||
if state.addEnabled:
|
||||
genAddCategory(state)
|
||||
|
|
@ -60,7 +60,10 @@ when defined(js):
|
|||
if state.error.isSome():
|
||||
p(class="text-error"):
|
||||
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]())
|
||||
tdiv(class="footer"):
|
||||
|
||||
|
|
|
|||
|
|
@ -211,6 +211,9 @@ when defined(js):
|
|||
let currentAdmin =
|
||||
currentUser.isSome() and currentUser.get().rank == Admin
|
||||
|
||||
if currentAdmin:
|
||||
state.categoryPicker.setAddEnabled(true)
|
||||
|
||||
result = buildHtml():
|
||||
tdiv():
|
||||
if authoredByUser or currentAdmin:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue