From 95b21874dba6840f14b0061b0ffa324f02c0d140 Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Thu, 20 Feb 2020 17:54:19 -0700 Subject: [PATCH] Separate out add category modal --- src/frontend/addcategorymodal.nim | 87 ++++++++++++++++++++++++ src/frontend/category.nim | 3 + src/frontend/categorypicker.nim | 109 ++++++++---------------------- src/frontend/user.nim | 5 +- 4 files changed, 121 insertions(+), 83 deletions(-) create mode 100644 src/frontend/addcategorymodal.nim diff --git a/src/frontend/addcategorymodal.nim b/src/frontend/addcategorymodal.nim new file mode 100644 index 0000000..1232afb --- /dev/null +++ b/src/frontend/addcategorymodal.nim @@ -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" diff --git a/src/frontend/category.nim b/src/frontend/category.nim index 75ee947..7b46194 100644 --- a/src/frontend/category.nim +++ b/src/frontend/category.nim @@ -10,6 +10,9 @@ type CategoryList* = ref object categories*: seq[Category] + CategoryEvent* = proc (category: Category) {.closure.} + CategoryChangeEvent* = proc (oldCategory: Category, newCategory: Category) {.closure.} + const categoryDescriptionCharLimit = 250 proc cmpNames*(cat1: Category, cat2: Category): int = diff --git a/src/frontend/categorypicker.nim b/src/frontend/categorypicker.nim index cc14d62..38f9300 100644 --- a/src/frontend/categorypicker.nim +++ b/src/frontend/categorypicker.nim @@ -1,25 +1,24 @@ when defined(js): import sugar, httpcore, options, json, strutils, algorithm import dom except Event - import jsffi except `&` include karax/prelude import karax / [kajax, kdom, vdom] import error, category, user - import category, karaxutils + import category, karaxutils, addcategorymodal type CategoryPicker* = ref object of VComponent 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) + addCategoryModal: AddCategoryModal + onCategoryChange: CategoryChangeEvent + onAddCategory: CategoryEvent proc onCategoryLoad(state: CategoryPicker): proc (httpStatus: int, response: kstring) = return @@ -51,18 +50,26 @@ when defined(js): return cat raise newException(IndexError, "Category at " & $id & " not found!") - proc nullAddCategory(category: Category) = discard - proc nullCategoryChange(oldCategory: Category, newCategory: Category) = discard + let nullAddCategory: CategoryEvent = proc (category: Category) = discard + let nullCategoryChange: CategoryChangeEvent = proc (oldCategory: Category, newCategory: Category) = discard - proc newCategoryPicker*( - onCategoryChange: proc(oldCategory: Category, newCategory: Category) = nullCategoryChange, - onAddCategory: proc(category: Category) = nullAddCategory - ): CategoryPicker = + proc select*(state: CategoryPicker, id: int) = + state.selectedCategoryID = id + state.markDirty() + + 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( list: none[CategoryList](), selectedCategoryID: 0, loading: false, - modalShown: false, addEnabled: false, status: Http200, error: none[PostError](), @@ -70,13 +77,14 @@ when defined(js): onAddCategory: onAddCategory ) + let state = result + result.addCategoryModal = newAddCategoryModal( + onAddCategory=onCategory(state) + ) + proc setAddEnabled*(state: CategoryPicker, enabled: bool) = 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) = # this is necessary to capture the right value let cat = category @@ -86,81 +94,18 @@ 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, @[], 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 = result = buildHtml(): tdiv(id="add-category"): button(class="plus-btn btn btn-link", onClick=(ev: Event, n: VNode) => ( - state.modalShown = true; - state.markDirty() + state.addCategoryModal.setModalShown(true) )): 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" + render(state.addCategoryModal) proc render*(state: CategoryPicker, currentUser: Option[User], compact=true): VNode = - let loggedIn = currentUser.isSome() - let currentAdmin = - loggedIn and currentUser.get().rank == Admin - - if currentAdmin: + if currentUser.isAdmin(): state.setAddEnabled(true) if state.status != Http200: diff --git a/src/frontend/user.nim b/src/frontend/user.nim index ea0624b..fe4efa7 100644 --- a/src/frontend/user.nim +++ b/src/frontend/user.nim @@ -1,4 +1,4 @@ -import times +import times, options type # If you add more "Banned" states, be sure to modify forum's threadsQuery too. @@ -27,6 +27,9 @@ type proc isOnline*(user: User): bool = 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 = u1.name == u2.name