refactor
This commit is contained in:
@@ -1 +1 @@
|
||||
<path fill-rule="nonzero" fill="rgb(0%, 0%, 0%)" fill-opacity="1" d="M 18.429688 10.285156 C 18.785156 10.285156 19.089844 10.410156 19.339844 10.660156 C 19.589844 10.910156 19.714844 11.214844 19.714844 11.570312 L 19.714844 19.285156 C 19.714844 19.644531 19.589844 19.945312 19.339844 20.195312 C 19.089844 20.445312 18.785156 20.570312 18.429688 20.570312 L 5.570312 20.570312 C 5.214844 20.570312 4.910156 20.445312 4.660156 20.195312 C 4.410156 19.945312 4.285156 19.644531 4.285156 19.285156 L 4.285156 11.570312 C 4.285156 11.214844 4.410156 10.910156 4.660156 10.660156 C 4.910156 10.410156 5.214844 10.285156 5.570312 10.285156 L 6 10.285156 L 6 6 C 6 4.347656 6.585938 2.933594 7.761719 1.761719 C 8.933594 0.585938 10.347656 0 12 0 C 13.652344 0 15.066406 0.585938 16.238281 1.761719 C 17.414062 2.933594 18 4.347656 18 6 C 18 6.230469 17.914062 6.433594 17.746094 6.601562 C 17.574219 6.773438 17.375 6.855469 17.144531 6.855469 L 16.285156 6.855469 C 16.054688 6.855469 15.851562 6.773438 15.683594 6.601562 C 15.511719 6.433594 15.429688 6.230469 15.429688 6 C 15.429688 5.054688 15.09375 4.246094 14.425781 3.574219 C 13.753906 2.90625 12.945312 2.570312 12 2.570312 C 11.054688 2.570312 10.246094 2.90625 9.574219 3.574219 C 8.90625 4.246094 8.570312 5.054688 8.570312 6 L 8.570312 10.285156 Z M 18.429688 10.285156 "/>
|
||||
<path fill-rule="nonzero" fill-opacity="1" d="M 18.429688 10.285156 C 18.785156 10.285156 19.089844 10.410156 19.339844 10.660156 C 19.589844 10.910156 19.714844 11.214844 19.714844 11.570312 L 19.714844 19.285156 C 19.714844 19.644531 19.589844 19.945312 19.339844 20.195312 C 19.089844 20.445312 18.785156 20.570312 18.429688 20.570312 L 5.570312 20.570312 C 5.214844 20.570312 4.910156 20.445312 4.660156 20.195312 C 4.410156 19.945312 4.285156 19.644531 4.285156 19.285156 L 4.285156 11.570312 C 4.285156 11.214844 4.410156 10.910156 4.660156 10.660156 C 4.910156 10.410156 5.214844 10.285156 5.570312 10.285156 L 6 10.285156 L 6 6 C 6 4.347656 6.585938 2.933594 7.761719 1.761719 C 8.933594 0.585938 10.347656 0 12 0 C 13.652344 0 15.066406 0.585938 16.238281 1.761719 C 17.414062 2.933594 18 4.347656 18 6 C 18 6.230469 17.914062 6.433594 17.746094 6.601562 C 17.574219 6.773438 17.375 6.855469 17.144531 6.855469 L 16.285156 6.855469 C 16.054688 6.855469 15.851562 6.773438 15.683594 6.601562 C 15.511719 6.433594 15.429688 6.230469 15.429688 6 C 15.429688 5.054688 15.09375 4.246094 14.425781 3.574219 C 13.753906 2.90625 12.945312 2.570312 12 2.570312 C 11.054688 2.570312 10.246094 2.90625 9.574219 3.574219 C 8.90625 4.246094 8.570312 5.054688 8.570312 6 L 8.570312 10.285156 Z M 18.429688 10.285156 "/>
|
||||
|
||||
@@ -15,21 +15,6 @@ func IdentifyPopover(docID string, m *models.DocumentMetadata) g.Node {
|
||||
return nil
|
||||
}
|
||||
|
||||
if m.Error != nil {
|
||||
return ui.Popover(h.Div(
|
||||
h.Class("flex flex-col gap-2"),
|
||||
h.H3(
|
||||
h.Class("text-lg font-bold text-center"),
|
||||
g.Text("Error"),
|
||||
),
|
||||
h.Div(
|
||||
h.Class("bg-gray-100 dark:bg-gray-900 p-2"),
|
||||
h.P(g.Text(*m.Error)),
|
||||
),
|
||||
ui.LinkButton(g.Text("Back to Document"), fmt.Sprintf("/documents/%s", docID)),
|
||||
))
|
||||
}
|
||||
|
||||
return ui.Popover(h.Div(
|
||||
h.Class("flex flex-col gap-2"),
|
||||
h.H3(
|
||||
25
web/components/ui/notification.go
Normal file
25
web/components/ui/notification.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
g "maragu.dev/gomponents"
|
||||
h "maragu.dev/gomponents/html"
|
||||
"reichard.io/antholume/pkg/sliceutils"
|
||||
"reichard.io/antholume/web/models"
|
||||
)
|
||||
|
||||
func Notifications(notifications []*models.Notification) g.Node {
|
||||
if len(notifications) == 0 {
|
||||
return nil
|
||||
}
|
||||
return h.Div(
|
||||
h.Class("fixed flex flex-col gap-2 bottom-0 right-0 p-2 sm:p-4 text-white dark:text-black"),
|
||||
g.Group(sliceutils.Map(notifications, notificationNode)),
|
||||
)
|
||||
}
|
||||
|
||||
func notificationNode(n *models.Notification) g.Node {
|
||||
return h.Div(
|
||||
h.Class("bg-gray-600 dark:bg-gray-400 px-4 py-2 rounded-lg shadow-lg w-64 animate-notification"),
|
||||
h.P(g.Text(n.Content)),
|
||||
)
|
||||
}
|
||||
7
web/models/device.go
Normal file
7
web/models/device.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package models
|
||||
|
||||
type Device struct {
|
||||
DeviceName string
|
||||
LastSynced string
|
||||
CreatedAt string
|
||||
}
|
||||
@@ -29,5 +29,4 @@ type DocumentMetadata struct {
|
||||
Author string
|
||||
Description string
|
||||
Source metadata.Source
|
||||
Error *string
|
||||
}
|
||||
|
||||
12
web/models/info.go
Normal file
12
web/models/info.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package models
|
||||
|
||||
type UserInfo struct {
|
||||
Username string
|
||||
IsAdmin bool
|
||||
}
|
||||
|
||||
type ServerInfo struct {
|
||||
RegistrationEnabled bool
|
||||
SearchEnabled bool
|
||||
Version string
|
||||
}
|
||||
13
web/models/notification.go
Normal file
13
web/models/notification.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package models
|
||||
|
||||
type NotificationType int
|
||||
|
||||
const (
|
||||
NotificationTypeSuccess NotificationType = iota
|
||||
NotificationTypeError
|
||||
)
|
||||
|
||||
type Notification struct {
|
||||
Content string
|
||||
Type NotificationType
|
||||
}
|
||||
52
web/models/page.go
Normal file
52
web/models/page.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package models
|
||||
|
||||
type PageContext struct {
|
||||
Route PageRoute
|
||||
UserInfo *UserInfo
|
||||
ServerInfo *ServerInfo
|
||||
Notifications []*Notification
|
||||
}
|
||||
|
||||
func (ctx PageContext) WithRoute(route PageRoute) PageContext {
|
||||
ctx.Route = route
|
||||
return ctx
|
||||
}
|
||||
|
||||
type PageRoute string
|
||||
|
||||
const (
|
||||
HomePage PageRoute = "home"
|
||||
DocumentPage PageRoute = "document"
|
||||
DocumentsPage PageRoute = "documents"
|
||||
ProgressPage PageRoute = "progress"
|
||||
ActivityPage PageRoute = "activity"
|
||||
SearchPage PageRoute = "search"
|
||||
SettingsPage PageRoute = "settings"
|
||||
AdminGeneralPage PageRoute = "admin-general"
|
||||
AdminImportPage PageRoute = "admin-import"
|
||||
AdminUsersPage PageRoute = "admin-users"
|
||||
AdminLogsPage PageRoute = "admin-logs"
|
||||
)
|
||||
|
||||
var pageTitleMap = map[PageRoute]string{
|
||||
HomePage: "Home",
|
||||
DocumentPage: "Document",
|
||||
DocumentsPage: "Documents",
|
||||
ProgressPage: "Progress",
|
||||
ActivityPage: "Activity",
|
||||
SearchPage: "Search",
|
||||
SettingsPage: "Settings",
|
||||
AdminGeneralPage: "Admin - General",
|
||||
AdminImportPage: "Admin - Import",
|
||||
AdminUsersPage: "Admin - Users",
|
||||
AdminLogsPage: "Admin - Logs",
|
||||
}
|
||||
|
||||
func (p PageRoute) Title() string {
|
||||
return pageTitleMap[p]
|
||||
}
|
||||
|
||||
func (p PageRoute) Valid() bool {
|
||||
_, ok := pageTitleMap[p]
|
||||
return ok
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"reichard.io/antholume/pkg/sliceutils"
|
||||
"reichard.io/antholume/web/components/ui"
|
||||
"reichard.io/antholume/web/models"
|
||||
"reichard.io/antholume/web/pages/layout"
|
||||
)
|
||||
|
||||
var _ Page = (*Activity)(nil)
|
||||
@@ -17,14 +18,15 @@ type Activity struct {
|
||||
Data []models.Activity
|
||||
}
|
||||
|
||||
func (Activity) Route() PageRoute { return ActivityPage }
|
||||
|
||||
func (p Activity) Render() g.Node {
|
||||
return h.Div(
|
||||
h.Class("overflow-x-auto"),
|
||||
func (p *Activity) Generate(ctx models.PageContext) (g.Node, error) {
|
||||
return layout.Layout(
|
||||
ctx.WithRoute(models.ActivityPage),
|
||||
h.Div(
|
||||
h.Class("inline-block min-w-full overflow-hidden rounded shadow"),
|
||||
ui.Table(p.buildTableConfig()),
|
||||
h.Class("overflow-x-auto"),
|
||||
h.Div(
|
||||
h.Class("inline-block min-w-full overflow-hidden rounded shadow"),
|
||||
ui.Table(p.buildTableConfig()),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"reichard.io/antholume/web/components/document"
|
||||
"reichard.io/antholume/web/components/ui"
|
||||
"reichard.io/antholume/web/models"
|
||||
"reichard.io/antholume/web/pages/layout"
|
||||
)
|
||||
|
||||
var _ Page = (*Document)(nil)
|
||||
@@ -22,9 +23,14 @@ type Document struct {
|
||||
Search *models.DocumentMetadata
|
||||
}
|
||||
|
||||
func (Document) Route() PageRoute { return DocumentPage }
|
||||
func (p *Document) Generate(ctx models.PageContext) (g.Node, error) {
|
||||
return layout.Layout(
|
||||
ctx.WithRoute(models.DocumentPage),
|
||||
p.content(),
|
||||
)
|
||||
}
|
||||
|
||||
func (p Document) Render() g.Node {
|
||||
func (p *Document) content() g.Node {
|
||||
return h.Div(
|
||||
h.Class("h-full w-full overflow-scroll bg-white shadow-lg dark:bg-gray-700 rounded dark:text-white p-4"),
|
||||
document.Actions(p.Data),
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"reichard.io/antholume/web/components/document"
|
||||
"reichard.io/antholume/web/components/ui"
|
||||
"reichard.io/antholume/web/models"
|
||||
"reichard.io/antholume/web/pages/layout"
|
||||
)
|
||||
|
||||
var _ Page = (*Documents)(nil)
|
||||
@@ -20,15 +21,13 @@ type Documents struct {
|
||||
Limit int
|
||||
}
|
||||
|
||||
func (Documents) Route() PageRoute { return DocumentsPage }
|
||||
|
||||
func (p Documents) Render() g.Node {
|
||||
return g.Group([]g.Node{
|
||||
func (p Documents) Generate(ctx models.PageContext) (g.Node, error) {
|
||||
return layout.Layout(ctx.WithRoute(models.DocumentsPage),
|
||||
searchBar(),
|
||||
documentGrid(p.Data),
|
||||
pagination(p.Previous, p.Next, p.Limit),
|
||||
uploadFAB(),
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
func searchBar() g.Node {
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
h "maragu.dev/gomponents/html"
|
||||
"reichard.io/antholume/database"
|
||||
"reichard.io/antholume/web/components/stats"
|
||||
"reichard.io/antholume/web/models"
|
||||
"reichard.io/antholume/web/pages/layout"
|
||||
)
|
||||
|
||||
var _ Page = (*Home)(nil)
|
||||
@@ -16,9 +18,11 @@ type Home struct {
|
||||
RecordInfo *database.GetDatabaseInfoRow
|
||||
}
|
||||
|
||||
func (Home) Route() PageRoute { return HomePage }
|
||||
func (p *Home) Generate(ctx models.PageContext) (g.Node, error) {
|
||||
return layout.Layout(ctx.WithRoute(models.HomePage), p.content())
|
||||
}
|
||||
|
||||
func (p Home) Render() g.Node {
|
||||
func (p *Home) content() g.Node {
|
||||
return h.Div(
|
||||
g.Attr("class", "flex flex-col gap-4"),
|
||||
h.Div(
|
||||
|
||||
@@ -1,35 +1,41 @@
|
||||
package layout
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
g "maragu.dev/gomponents"
|
||||
h "maragu.dev/gomponents/html"
|
||||
"reichard.io/antholume/web/pages"
|
||||
"reichard.io/antholume/web/components/ui"
|
||||
"reichard.io/antholume/web/models"
|
||||
)
|
||||
|
||||
type LayoutOptions struct {
|
||||
SearchEnabled bool
|
||||
IsAdmin bool
|
||||
Username string
|
||||
Version string
|
||||
}
|
||||
func Layout(ctx models.PageContext, children ...g.Node) (g.Node, error) {
|
||||
if ctx.UserInfo == nil {
|
||||
return nil, errors.New("no user info")
|
||||
} else if ctx.ServerInfo == nil {
|
||||
return nil, errors.New("no server info")
|
||||
} else if !ctx.Route.Valid() {
|
||||
return nil, fmt.Errorf("invalid route: %s", ctx.Route)
|
||||
}
|
||||
|
||||
func Layout(p pages.Page, opts LayoutOptions) g.Node {
|
||||
return h.Doctype(
|
||||
h.HTML(
|
||||
g.Attr("lang", "en"),
|
||||
Head(p.Route().Title()),
|
||||
Head(ctx.Route.Title()),
|
||||
h.Body(
|
||||
g.Attr("class", "bg-gray-100 dark:bg-gray-800 text-black dark:text-white"),
|
||||
Navigation(p.Route(), &opts),
|
||||
Base(p.Render()),
|
||||
Navigation(ctx),
|
||||
Base(children),
|
||||
ui.Notifications(ctx.Notifications),
|
||||
),
|
||||
),
|
||||
)
|
||||
), nil
|
||||
}
|
||||
|
||||
func Head(routeTitle string) g.Node {
|
||||
return h.Head(
|
||||
h.Title("AnthoLume - "+routeTitle),
|
||||
g.El("title", g.Text("AnthoLume - "+routeTitle)),
|
||||
h.Meta(g.Attr("charset", "utf-8")),
|
||||
h.Meta(g.Attr("name", "viewport"), g.Attr("content", "width=device-width, initial-scale=0.9, user-scalable=no, viewport-fit=cover")),
|
||||
h.Meta(g.Attr("name", "apple-mobile-web-app-capable"), g.Attr("content", "yes")),
|
||||
@@ -45,13 +51,13 @@ func Head(routeTitle string) g.Node {
|
||||
)
|
||||
}
|
||||
|
||||
func Base(body g.Node) g.Node {
|
||||
func Base(body []g.Node) g.Node {
|
||||
return h.Main(
|
||||
g.Attr("class", "relative overflow-hidden"),
|
||||
h.Div(
|
||||
g.Attr("id", "container"),
|
||||
g.Attr("class", "h-[100dvh] px-4 overflow-auto md:px-6 lg:ml-48"),
|
||||
body,
|
||||
g.Group(body),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
g "maragu.dev/gomponents"
|
||||
h "maragu.dev/gomponents/html"
|
||||
"reichard.io/antholume/web/assets"
|
||||
"reichard.io/antholume/web/pages"
|
||||
"reichard.io/antholume/web/models"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -14,29 +14,28 @@ const (
|
||||
inactive = "border-transparent text-gray-400 hover:text-gray-800 dark:hover:text-gray-100"
|
||||
)
|
||||
|
||||
func Navigation(currentRoute pages.PageRoute, opts *LayoutOptions) g.Node {
|
||||
func Navigation(ctx models.PageContext) g.Node {
|
||||
return h.Div(
|
||||
g.Attr("class", "flex items-center justify-between w-full h-16"),
|
||||
Sidebar(currentRoute, opts),
|
||||
h.H1(g.Attr("class", "text-xl font-bold px-6 lg:ml-44"), g.Text(currentRoute.Title())),
|
||||
Dropdown(opts.Username),
|
||||
Sidebar(ctx),
|
||||
h.H1(g.Attr("class", "text-xl font-bold px-6 lg:ml-44"), g.Text(ctx.Route.Title())),
|
||||
Dropdown(ctx.UserInfo.Username),
|
||||
)
|
||||
}
|
||||
|
||||
func Sidebar(currentRoute pages.PageRoute, opts *LayoutOptions) g.Node {
|
||||
func Sidebar(ctx models.PageContext) g.Node {
|
||||
links := []g.Node{
|
||||
navLink(currentRoute, pages.HomePage, "/", "home"),
|
||||
navLink(currentRoute, pages.DocumentsPage, "/documents", "documents"),
|
||||
navLink(currentRoute, pages.ProgressPage, "/progress", "activity"),
|
||||
navLink(currentRoute, pages.ActivityPage, "/activity", "activity"),
|
||||
navLink(ctx.Route, models.HomePage, "/", "home"),
|
||||
navLink(ctx.Route, models.DocumentsPage, "/documents", "documents"),
|
||||
navLink(ctx.Route, models.ProgressPage, "/progress", "activity"),
|
||||
navLink(ctx.Route, models.ActivityPage, "/activity", "activity"),
|
||||
}
|
||||
if opts.SearchEnabled {
|
||||
links = append(links, navLink(currentRoute, pages.SearchPage, "/search", "search"))
|
||||
if ctx.ServerInfo.SearchEnabled {
|
||||
links = append(links, navLink(ctx.Route, models.SearchPage, "/search", "search"))
|
||||
}
|
||||
if opts.IsAdmin {
|
||||
links = append(links, adminLinks(currentRoute))
|
||||
if ctx.UserInfo.IsAdmin {
|
||||
links = append(links, adminLinks(ctx.Route))
|
||||
}
|
||||
|
||||
return h.Div(
|
||||
g.Attr("id", "mobile-nav-button"),
|
||||
g.Attr("class", "flex flex-col z-40 relative ml-6"),
|
||||
@@ -54,13 +53,13 @@ func Sidebar(currentRoute pages.PageRoute, opts *LayoutOptions) g.Node {
|
||||
g.Attr("target", "_blank"),
|
||||
g.Attr("class", "flex flex-col gap-2 justify-center items-center p-6 w-full absolute bottom-0 text-black dark:text-white"),
|
||||
assets.Icon("gitea", 20),
|
||||
h.Span(g.Attr("class", "text-xs"), g.Text(opts.Version)),
|
||||
h.Span(g.Attr("class", "text-xs"), g.Text(ctx.ServerInfo.Version)),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func navLink(currentRoute, linkRoute pages.PageRoute, path, icon string) g.Node {
|
||||
func navLink(currentRoute, linkRoute models.PageRoute, path, icon string) g.Node {
|
||||
class := inactive
|
||||
if currentRoute == linkRoute {
|
||||
class = active
|
||||
@@ -73,7 +72,7 @@ func navLink(currentRoute, linkRoute pages.PageRoute, path, icon string) g.Node
|
||||
)
|
||||
}
|
||||
|
||||
func adminLinks(currentRoute pages.PageRoute) g.Node {
|
||||
func adminLinks(currentRoute models.PageRoute) g.Node {
|
||||
routeID := string(currentRoute)
|
||||
|
||||
class := inactive
|
||||
@@ -83,10 +82,10 @@ func adminLinks(currentRoute pages.PageRoute) g.Node {
|
||||
|
||||
children := g.If(strings.HasPrefix(routeID, "admin"),
|
||||
g.Group([]g.Node{
|
||||
subNavLink(currentRoute, pages.AdminGeneralPage, "/admin"),
|
||||
subNavLink(currentRoute, pages.AdminImportPage, "/admin/import"),
|
||||
subNavLink(currentRoute, pages.AdminUsersPage, "/admin/users"),
|
||||
subNavLink(currentRoute, pages.AdminLogsPage, "/admin/logs"),
|
||||
subNavLink(currentRoute, models.AdminGeneralPage, "/admin"),
|
||||
subNavLink(currentRoute, models.AdminImportPage, "/admin/import"),
|
||||
subNavLink(currentRoute, models.AdminUsersPage, "/admin/users"),
|
||||
subNavLink(currentRoute, models.AdminLogsPage, "/admin/logs"),
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -102,7 +101,7 @@ func adminLinks(currentRoute pages.PageRoute) g.Node {
|
||||
)
|
||||
}
|
||||
|
||||
func subNavLink(currentRoute, linkRoute pages.PageRoute, path string) g.Node {
|
||||
func subNavLink(currentRoute, linkRoute models.PageRoute, path string) g.Node {
|
||||
class := inactive
|
||||
if currentRoute == linkRoute {
|
||||
class = active
|
||||
35
web/pages/layout/route.go
Normal file
35
web/pages/layout/route.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package layout
|
||||
|
||||
type Route string
|
||||
|
||||
const (
|
||||
HomePage Route = "home"
|
||||
DocumentPage Route = "document"
|
||||
DocumentsPage Route = "documents"
|
||||
ProgressPage Route = "progress"
|
||||
ActivityPage Route = "activity"
|
||||
SearchPage Route = "search"
|
||||
SettingsPage Route = "settings"
|
||||
AdminGeneralPage Route = "admin-general"
|
||||
AdminImportPage Route = "admin-import"
|
||||
AdminUsersPage Route = "admin-users"
|
||||
AdminLogsPage Route = "admin-logs"
|
||||
)
|
||||
|
||||
var pageTitleMap = map[Route]string{
|
||||
HomePage: "Home",
|
||||
DocumentPage: "Document",
|
||||
DocumentsPage: "Documents",
|
||||
ProgressPage: "Progress",
|
||||
ActivityPage: "Activity",
|
||||
SearchPage: "Search",
|
||||
SettingsPage: "Settings",
|
||||
AdminGeneralPage: "Admin - General",
|
||||
AdminImportPage: "Admin - Import",
|
||||
AdminUsersPage: "Admin - Users",
|
||||
AdminLogsPage: "Admin - Logs",
|
||||
}
|
||||
|
||||
func (p Route) Title() string {
|
||||
return pageTitleMap[p]
|
||||
}
|
||||
@@ -2,41 +2,9 @@ package pages
|
||||
|
||||
import (
|
||||
g "maragu.dev/gomponents"
|
||||
"reichard.io/antholume/web/models"
|
||||
)
|
||||
|
||||
type PageRoute string
|
||||
|
||||
const (
|
||||
HomePage PageRoute = "home"
|
||||
DocumentPage PageRoute = "document"
|
||||
DocumentsPage PageRoute = "documents"
|
||||
ProgressPage PageRoute = "progress"
|
||||
ActivityPage PageRoute = "activity"
|
||||
SearchPage PageRoute = "search"
|
||||
AdminGeneralPage PageRoute = "admin-general"
|
||||
AdminImportPage PageRoute = "admin-import"
|
||||
AdminUsersPage PageRoute = "admin-users"
|
||||
AdminLogsPage PageRoute = "admin-logs"
|
||||
)
|
||||
|
||||
var pageTitleMap = map[PageRoute]string{
|
||||
HomePage: "Home",
|
||||
DocumentPage: "Document",
|
||||
DocumentsPage: "Documents",
|
||||
ProgressPage: "Progress",
|
||||
ActivityPage: "Activity",
|
||||
SearchPage: "Search",
|
||||
AdminGeneralPage: "Admin - General",
|
||||
AdminImportPage: "Admin - Import",
|
||||
AdminUsersPage: "Admin - Users",
|
||||
AdminLogsPage: "Admin - Logs",
|
||||
}
|
||||
|
||||
func (p PageRoute) Title() string {
|
||||
return pageTitleMap[p]
|
||||
}
|
||||
|
||||
type Page interface {
|
||||
Route() PageRoute
|
||||
Render() g.Node
|
||||
Generate(ctx models.PageContext) (g.Node, error)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"reichard.io/antholume/pkg/sliceutils"
|
||||
"reichard.io/antholume/web/components/ui"
|
||||
"reichard.io/antholume/web/models"
|
||||
"reichard.io/antholume/web/pages/layout"
|
||||
)
|
||||
|
||||
var _ Page = (*Progress)(nil)
|
||||
@@ -16,14 +17,15 @@ type Progress struct {
|
||||
Data []models.Progress
|
||||
}
|
||||
|
||||
func (Progress) Route() PageRoute { return ProgressPage }
|
||||
|
||||
func (p Progress) Render() g.Node {
|
||||
return h.Div(
|
||||
h.Class("overflow-x-auto"),
|
||||
func (p *Progress) Generate(ctx models.PageContext) (g.Node, error) {
|
||||
return layout.Layout(
|
||||
ctx.WithRoute(models.ProgressPage),
|
||||
h.Div(
|
||||
h.Class("inline-block min-w-full overflow-hidden rounded shadow"),
|
||||
ui.Table(p.buildTableConfig()),
|
||||
h.Class("overflow-x-auto"),
|
||||
h.Div(
|
||||
h.Class("inline-block min-w-full overflow-hidden rounded shadow"),
|
||||
ui.Table(p.buildTableConfig()),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"reichard.io/antholume/web/assets"
|
||||
"reichard.io/antholume/web/components/ui"
|
||||
"reichard.io/antholume/web/models"
|
||||
"reichard.io/antholume/web/pages/layout"
|
||||
)
|
||||
|
||||
var _ Page = (*Search)(nil)
|
||||
@@ -23,9 +24,14 @@ type Search struct {
|
||||
Error string
|
||||
}
|
||||
|
||||
func (Search) Route() PageRoute { return SearchPage }
|
||||
func (p Search) Generate(ctx models.PageContext) (g.Node, error) {
|
||||
return layout.Layout(
|
||||
ctx.WithRoute(models.SearchPage),
|
||||
p.content(),
|
||||
)
|
||||
}
|
||||
|
||||
func (p Search) Render() g.Node {
|
||||
func (p *Search) content() g.Node {
|
||||
return h.Div(
|
||||
h.Class("flex flex-col gap-4"),
|
||||
h.Div(
|
||||
@@ -96,7 +102,7 @@ func (p Search) Render() g.Node {
|
||||
)
|
||||
}
|
||||
|
||||
func (p Search) tableRows() []ui.TableRow {
|
||||
func (p *Search) tableRows() []ui.TableRow {
|
||||
return sliceutils.Map(p.Results, func(r models.SearchResult) ui.TableRow {
|
||||
return ui.TableRow{
|
||||
"": ui.TableCell{
|
||||
|
||||
184
web/pages/settings.go
Normal file
184
web/pages/settings.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
g "maragu.dev/gomponents"
|
||||
h "maragu.dev/gomponents/html"
|
||||
"reichard.io/antholume/pkg/sliceutils"
|
||||
"reichard.io/antholume/web/assets"
|
||||
"reichard.io/antholume/web/components/ui"
|
||||
"reichard.io/antholume/web/models"
|
||||
"reichard.io/antholume/web/pages/layout"
|
||||
)
|
||||
|
||||
var _ Page = (*Settings)(nil)
|
||||
|
||||
type Settings struct {
|
||||
Timezone string
|
||||
Devices []models.Device
|
||||
}
|
||||
|
||||
func (p *Settings) Generate(ctx models.PageContext) (g.Node, error) {
|
||||
return layout.Layout(
|
||||
ctx.WithRoute(models.SettingsPage),
|
||||
h.Div(
|
||||
h.Class("flex flex-col md:flex-row gap-4"),
|
||||
h.Div(
|
||||
h.Div(
|
||||
h.Class("flex flex-col p-4 items-center rounded shadow-lg md:w-60 lg:w-80 bg-white dark:bg-gray-700 text-gray-500 dark:text-white"),
|
||||
assets.Icon("user", 60),
|
||||
h.P(h.Class("text-lg"), g.Text(ctx.UserInfo.Username)),
|
||||
),
|
||||
),
|
||||
h.Div(
|
||||
h.Class("flex flex-col gap-4 grow"),
|
||||
p.passwordForm(),
|
||||
p.timezoneForm(),
|
||||
p.devicesTable(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (p Settings) passwordForm() g.Node {
|
||||
return h.Div(
|
||||
h.Class("flex flex-col gap-2 p-4 rounded shadow-lg bg-white dark:bg-gray-700 text-gray-500 dark:text-white"),
|
||||
h.P(h.Class("text-lg font-semibold"), g.Text("Change Password")),
|
||||
h.Form(
|
||||
h.Class("flex gap-4 flex-col lg:flex-row"),
|
||||
h.Action("./settings"),
|
||||
h.Method("POST"),
|
||||
// Current Password
|
||||
h.Div(
|
||||
h.Class("flex grow"),
|
||||
h.Span(
|
||||
h.Class("inline-flex items-center px-3 border-t bg-white border-l border-b border-gray-300 text-gray-500 shadow-sm text-sm"),
|
||||
assets.Icon("password", 15),
|
||||
),
|
||||
h.Input(
|
||||
h.Type("password"),
|
||||
h.ID("password"),
|
||||
h.Name("password"),
|
||||
h.Class("flex-1 appearance-none rounded-none border border-gray-300 w-full py-2 px-4 bg-white text-gray-700 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:ring-purple-600 focus:border-transparent"),
|
||||
h.Placeholder("Password"),
|
||||
),
|
||||
),
|
||||
// New Password
|
||||
h.Div(
|
||||
h.Class("flex grow"),
|
||||
h.Span(
|
||||
h.Class("inline-flex items-center px-3 border-t bg-white border-l border-b border-gray-300 text-gray-500 shadow-sm text-sm"),
|
||||
assets.Icon("password", 15),
|
||||
),
|
||||
h.Input(
|
||||
h.Type("password"),
|
||||
h.ID("new_password"),
|
||||
h.Name("new_password"),
|
||||
h.Class("flex-1 appearance-none rounded-none border border-gray-300 w-full py-2 px-4 bg-white text-gray-700 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:ring-purple-600 focus:border-transparent"),
|
||||
h.Placeholder("New Password"),
|
||||
),
|
||||
),
|
||||
// Submit Button
|
||||
h.Div(
|
||||
h.Class("lg:w-60"),
|
||||
ui.FormButton(
|
||||
g.Text("Submit"),
|
||||
"",
|
||||
ui.ButtonConfig{Variant: ui.ButtonVariantSecondary},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (p Settings) timezoneForm() g.Node {
|
||||
tzs := []string{
|
||||
"Africa/Cairo",
|
||||
"Africa/Johannesburg",
|
||||
"Africa/Lagos",
|
||||
"Africa/Nairobi",
|
||||
"America/Adak",
|
||||
"America/Anchorage",
|
||||
"America/Buenos_Aires",
|
||||
"America/Chicago",
|
||||
"America/Denver",
|
||||
"America/Los_Angeles",
|
||||
"America/Mexico_City",
|
||||
"America/New_York",
|
||||
"America/Nuuk",
|
||||
"America/Phoenix",
|
||||
"America/Puerto_Rico",
|
||||
"America/Sao_Paulo",
|
||||
"America/St_Johns",
|
||||
"America/Toronto",
|
||||
"Asia/Dubai",
|
||||
"Asia/Hong_Kong",
|
||||
"Asia/Kolkata",
|
||||
"Asia/Seoul",
|
||||
"Asia/Shanghai",
|
||||
"Asia/Singapore",
|
||||
"Asia/Tokyo",
|
||||
"Atlantic/Azores",
|
||||
"Australia/Melbourne",
|
||||
"Australia/Sydney",
|
||||
"Europe/Berlin",
|
||||
"Europe/London",
|
||||
"Europe/Moscow",
|
||||
"Europe/Paris",
|
||||
"Pacific/Auckland",
|
||||
"Pacific/Honolulu",
|
||||
}
|
||||
|
||||
return h.Div(
|
||||
h.Class("flex flex-col grow gap-2 p-4 rounded shadow-lg bg-white dark:bg-gray-700 text-gray-500 dark:text-white"),
|
||||
h.P(h.Class("text-lg font-semibold"), g.Text("Change Timezone")),
|
||||
h.Form(
|
||||
h.Class("flex gap-4 flex-col lg:flex-row"),
|
||||
h.Action("./settings"),
|
||||
h.Method("POST"),
|
||||
h.Div(
|
||||
h.Class("flex grow"),
|
||||
h.Span(
|
||||
h.Class("inline-flex items-center px-3 border-t bg-white border-l border-b border-gray-300 text-gray-500 shadow-sm text-sm"),
|
||||
assets.Icon("clock", 15),
|
||||
),
|
||||
h.Select(
|
||||
h.Class("flex-1 appearance-none rounded-none border border-gray-300 w-full py-2 px-4 bg-white text-gray-700 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:ring-purple-600 focus:border-transparent"),
|
||||
h.ID("timezone"),
|
||||
h.Name("timezone"),
|
||||
g.Group(g.Map(tzs, func(tz string) g.Node {
|
||||
return h.Option(
|
||||
h.Value(tz),
|
||||
g.If(tz == p.Timezone, h.Selected()),
|
||||
g.Text(tz),
|
||||
)
|
||||
})),
|
||||
),
|
||||
),
|
||||
h.Div(
|
||||
h.Class("lg:w-60"),
|
||||
ui.FormButton(
|
||||
g.Text("Submit"),
|
||||
"",
|
||||
ui.ButtonConfig{Variant: ui.ButtonVariantSecondary},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (p Settings) devicesTable() g.Node {
|
||||
return h.Div(
|
||||
h.Class("flex flex-col grow p-4 rounded shadow-lg bg-white dark:bg-gray-700 text-gray-500 dark:text-white"),
|
||||
h.P(h.Class("text-lg font-semibold"), g.Text("Devices")),
|
||||
ui.Table(ui.TableConfig{
|
||||
Columns: []string{"Name", "Last Sync", "Created"},
|
||||
Rows: sliceutils.Map(p.Devices, func(d models.Device) ui.TableRow {
|
||||
return ui.TableRow{
|
||||
"Name": ui.TableCell{String: d.DeviceName},
|
||||
"Last Sync": ui.TableCell{String: d.LastSynced},
|
||||
"Created": ui.TableCell{String: d.CreatedAt},
|
||||
}
|
||||
}),
|
||||
}),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user