Separate out add category modal
This commit is contained in:
parent
7ec3ff9cac
commit
95b21874db
4 changed files with 121 additions and 83 deletions
87
src/frontend/addcategorymodal.nim
Normal file
87
src/frontend/addcategorymodal.nim
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
when defined(js):
|
||||||
|
import sugar, httpcore, options, json, strutils
|
||||||
|
import dom except Event
|
||||||
|
import jsffi except `&`
|
||||||
|
|
||||||
|
include karax/prelude
|
||||||
|
import karax / [kajax, kdom, vdom]
|
||||||
|
|
||||||
|
import error, category
|
||||||
|
import category, karaxutils
|
||||||
|
|
||||||
|
type
|
||||||
|
AddCategoryModal* = ref object of VComponent
|
||||||
|
modalShown: bool
|
||||||
|
loading: bool
|
||||||
|
error: Option[PostError]
|
||||||
|
onAddCategory: CategoryEvent
|
||||||
|
|
||||||
|
let nullCategory: CategoryEvent = proc (category: Category) = discard
|
||||||
|
|
||||||
|
proc newAddCategoryModal*(onAddCategory=nullCategory): AddCategoryModal =
|
||||||
|
result = AddCategoryModal(
|
||||||
|
modalShown: false,
|
||||||
|
loading: false,
|
||||||
|
onAddCategory: onAddCategory
|
||||||
|
)
|
||||||
|
|
||||||
|
proc onAddCategoryPost(httpStatus: int, response: kstring, state: AddCategoryModal) =
|
||||||
|
postFinished:
|
||||||
|
state.modalShown = false
|
||||||
|
let j = parseJson($response)
|
||||||
|
let category = j.to(Category)
|
||||||
|
|
||||||
|
state.onAddCategory(category)
|
||||||
|
|
||||||
|
proc onAddCategoryClick(state: AddCategoryModal) =
|
||||||
|
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, @[], formData.to(cstring),
|
||||||
|
(s: int, r: kstring) => onAddCategoryPost(s, r, state))
|
||||||
|
|
||||||
|
proc setModalShown*(state: AddCategoryModal, visible: bool) =
|
||||||
|
state.modalShown = visible
|
||||||
|
state.markDirty()
|
||||||
|
|
||||||
|
proc onModalClose(state: AddCategoryModal, ev: Event, n: VNode) =
|
||||||
|
state.setModalShown(false)
|
||||||
|
ev.preventDefault()
|
||||||
|
|
||||||
|
proc render*(state: AddCategoryModal): VNode =
|
||||||
|
result = buildHtml():
|
||||||
|
tdiv(class=class({"active": state.modalShown}, "modal modal-sm")):
|
||||||
|
a(href="", class="modal-overlay", "aria-label"="close",
|
||||||
|
onClick=(ev: Event, n: VNode) => onModalClose(state, ev, n))
|
||||||
|
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"
|
||||||
|
|
@ -10,6 +10,9 @@ type
|
||||||
CategoryList* = ref object
|
CategoryList* = ref object
|
||||||
categories*: seq[Category]
|
categories*: seq[Category]
|
||||||
|
|
||||||
|
CategoryEvent* = proc (category: Category) {.closure.}
|
||||||
|
CategoryChangeEvent* = proc (oldCategory: Category, newCategory: Category) {.closure.}
|
||||||
|
|
||||||
const categoryDescriptionCharLimit = 250
|
const categoryDescriptionCharLimit = 250
|
||||||
|
|
||||||
proc cmpNames*(cat1: Category, cat2: Category): int =
|
proc cmpNames*(cat1: Category, cat2: Category): int =
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,24 @@
|
||||||
when defined(js):
|
when defined(js):
|
||||||
import sugar, httpcore, options, json, strutils, algorithm
|
import sugar, httpcore, options, json, strutils, algorithm
|
||||||
import dom except Event
|
import dom except Event
|
||||||
import jsffi except `&`
|
|
||||||
|
|
||||||
include karax/prelude
|
include karax/prelude
|
||||||
import karax / [kajax, kdom, vdom]
|
import karax / [kajax, kdom, vdom]
|
||||||
|
|
||||||
import error, category, user
|
import error, category, user
|
||||||
import category, karaxutils
|
import category, karaxutils, addcategorymodal
|
||||||
|
|
||||||
type
|
type
|
||||||
CategoryPicker* = ref object of VComponent
|
CategoryPicker* = ref object of VComponent
|
||||||
list: Option[CategoryList]
|
list: Option[CategoryList]
|
||||||
selectedCategoryID*: int
|
selectedCategoryID*: int
|
||||||
loading: bool
|
loading: bool
|
||||||
modalShown: bool
|
|
||||||
addEnabled: bool
|
addEnabled: bool
|
||||||
status: HttpCode
|
status: HttpCode
|
||||||
error: Option[PostError]
|
error: Option[PostError]
|
||||||
onCategoryChange: proc (oldCategory: Category, newCategory: Category)
|
addCategoryModal: AddCategoryModal
|
||||||
onAddCategory: proc (category: Category)
|
onCategoryChange: CategoryChangeEvent
|
||||||
|
onAddCategory: CategoryEvent
|
||||||
|
|
||||||
proc onCategoryLoad(state: CategoryPicker): proc (httpStatus: int, response: kstring) =
|
proc onCategoryLoad(state: CategoryPicker): proc (httpStatus: int, response: kstring) =
|
||||||
return
|
return
|
||||||
|
|
@ -51,18 +50,26 @@ when defined(js):
|
||||||
return cat
|
return cat
|
||||||
raise newException(IndexError, "Category at " & $id & " not found!")
|
raise newException(IndexError, "Category at " & $id & " not found!")
|
||||||
|
|
||||||
proc nullAddCategory(category: Category) = discard
|
let nullAddCategory: CategoryEvent = proc (category: Category) = discard
|
||||||
proc nullCategoryChange(oldCategory: Category, newCategory: Category) = discard
|
let nullCategoryChange: CategoryChangeEvent = proc (oldCategory: Category, newCategory: Category) = discard
|
||||||
|
|
||||||
proc newCategoryPicker*(
|
proc select*(state: CategoryPicker, id: int) =
|
||||||
onCategoryChange: proc(oldCategory: Category, newCategory: Category) = nullCategoryChange,
|
state.selectedCategoryID = id
|
||||||
onAddCategory: proc(category: Category) = nullAddCategory
|
state.markDirty()
|
||||||
): CategoryPicker =
|
|
||||||
|
proc onCategory(state: CategoryPicker): CategoryEvent =
|
||||||
|
result =
|
||||||
|
proc (category: Category) =
|
||||||
|
state.list.get().categories.add(category)
|
||||||
|
state.list.get().categories.sort(cmpNames)
|
||||||
|
state.select(category.id)
|
||||||
|
state.onAddCategory(category)
|
||||||
|
|
||||||
|
proc newCategoryPicker*(onCategoryChange=nullCategoryChange, onAddCategory=nullAddCategory): CategoryPicker =
|
||||||
result = CategoryPicker(
|
result = CategoryPicker(
|
||||||
list: none[CategoryList](),
|
list: none[CategoryList](),
|
||||||
selectedCategoryID: 0,
|
selectedCategoryID: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
modalShown: false,
|
|
||||||
addEnabled: false,
|
addEnabled: false,
|
||||||
status: Http200,
|
status: Http200,
|
||||||
error: none[PostError](),
|
error: none[PostError](),
|
||||||
|
|
@ -70,13 +77,14 @@ when defined(js):
|
||||||
onAddCategory: onAddCategory
|
onAddCategory: onAddCategory
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let state = result
|
||||||
|
result.addCategoryModal = newAddCategoryModal(
|
||||||
|
onAddCategory=onCategory(state)
|
||||||
|
)
|
||||||
|
|
||||||
proc setAddEnabled*(state: CategoryPicker, enabled: bool) =
|
proc setAddEnabled*(state: CategoryPicker, enabled: bool) =
|
||||||
state.addEnabled = enabled
|
state.addEnabled = enabled
|
||||||
|
|
||||||
proc select*(state: CategoryPicker, id: int) =
|
|
||||||
state.selectedCategoryID = id
|
|
||||||
state.markDirty()
|
|
||||||
|
|
||||||
proc onCategoryClick(state: CategoryPicker, category: Category): proc (ev: Event, n: VNode) =
|
proc onCategoryClick(state: CategoryPicker, category: Category): proc (ev: Event, n: VNode) =
|
||||||
# this is necessary to capture the right value
|
# this is necessary to capture the right value
|
||||||
let cat = category
|
let cat = category
|
||||||
|
|
@ -86,81 +94,18 @@ 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, @[], formData.to(cstring),
|
|
||||||
(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 =
|
proc genAddCategory(state: CategoryPicker): VNode =
|
||||||
result = buildHtml():
|
result = buildHtml():
|
||||||
tdiv(id="add-category"):
|
tdiv(id="add-category"):
|
||||||
button(class="plus-btn btn btn-link",
|
button(class="plus-btn btn btn-link",
|
||||||
onClick=(ev: Event, n: VNode) => (
|
onClick=(ev: Event, n: VNode) => (
|
||||||
state.modalShown = true;
|
state.addCategoryModal.setModalShown(true)
|
||||||
state.markDirty()
|
|
||||||
)):
|
)):
|
||||||
italic(class="fas fa-plus")
|
italic(class="fas fa-plus")
|
||||||
tdiv(class=class({"active": state.modalShown}, "modal modal-sm")):
|
render(state.addCategoryModal)
|
||||||
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, currentUser: Option[User], compact=true): VNode =
|
proc render*(state: CategoryPicker, currentUser: Option[User], compact=true): VNode =
|
||||||
let loggedIn = currentUser.isSome()
|
if currentUser.isAdmin():
|
||||||
let currentAdmin =
|
|
||||||
loggedIn and currentUser.get().rank == Admin
|
|
||||||
|
|
||||||
if currentAdmin:
|
|
||||||
state.setAddEnabled(true)
|
state.setAddEnabled(true)
|
||||||
|
|
||||||
if state.status != Http200:
|
if state.status != Http200:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import times
|
import times, options
|
||||||
|
|
||||||
type
|
type
|
||||||
# If you add more "Banned" states, be sure to modify forum's threadsQuery too.
|
# If you add more "Banned" states, be sure to modify forum's threadsQuery too.
|
||||||
|
|
@ -27,6 +27,9 @@ type
|
||||||
proc isOnline*(user: User): bool =
|
proc isOnline*(user: User): bool =
|
||||||
return getTime().toUnix() - user.lastOnline < (60*5)
|
return getTime().toUnix() - user.lastOnline < (60*5)
|
||||||
|
|
||||||
|
proc isAdmin*(user: Option[User]): bool =
|
||||||
|
return user.isSome and user.get().rank == Admin
|
||||||
|
|
||||||
proc `==`*(u1, u2: User): bool =
|
proc `==`*(u1, u2: User): bool =
|
||||||
u1.name == u2.name
|
u1.name == u2.name
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue