Refactor javascript following Locality of Behaviour (LoB) principle

This commit is contained in:
Luis Eduardo Jeréz Girón
2024-10-14 02:17:41 -06:00
parent 6842e094b6
commit 82f10d45db
18 changed files with 281 additions and 274 deletions

View File

@@ -1,77 +0,0 @@
export const githubRepoInfo = {
name: 'githubRepoInfo',
fn: () => ({
stars: '',
latestRelease: '',
async init () {
const stars = await this.getStars()
if (stars !== null) {
this.stars = stars
}
const latestRelease = await this.getLatestRelease()
if (latestRelease !== null) {
this.latestRelease = latestRelease
}
},
async getStars () {
const cacheKey = 'pbw_gh_stars'
const cachedData = this.getCachedData(cacheKey)
if (cachedData !== null) {
return cachedData
}
const url = 'https://api.github.com/repos/eduardolat/pgbackweb'
try {
const response = await fetch(url)
if (!response.ok) {
return null
}
const data = await response.json()
this.cacheData(cacheKey, data.stargazers_count)
return data.stargazers_count
} catch {
return null
}
},
async getLatestRelease () {
const cacheKey = 'pbw_gh_last_release'
const cachedData = this.getCachedData(cacheKey)
if (cachedData !== null) {
return cachedData
}
const url = 'https://api.github.com/repos/eduardolat/pgbackweb/releases/latest'
try {
const response = await fetch(url)
if (!response.ok) {
return null
}
const data = await response.json()
this.cacheData(cacheKey, data.name)
return data.name
} catch {
return null
}
},
getCachedData (key) {
const cachedJSON = localStorage.getItem(key)
if (!cachedJSON) {
return null
}
const cached = JSON.parse(cachedJSON)
if (Date.now() - cached.timestamp < 2 * 60 * 1000) {
return cached.value
}
return null
},
cacheData (key, value) {
const data = JSON.stringify({
value,
timestamp: Date.now()
})
localStorage.setItem(key, data)
}
})
}

View File

@@ -2,14 +2,10 @@ import { initThemeHelper } from './init-theme-helper.js'
import { initSweetAlert2 } from './init-sweetalert2.js'
import { initNotyf } from './init-notyf.js'
import { initHTMX } from './init-htmx.js'
import { initAlpineComponents } from './init-alpine-components.js'
import { initHelpers } from './init-helpers.js'
import { initDashboardAsideScroll } from './dashboard-aside-scroll.js'
initThemeHelper()
initSweetAlert2()
initNotyf()
initHTMX()
initAlpineComponents()
initHelpers()
initDashboardAsideScroll()

View File

@@ -1,22 +0,0 @@
export function initDashboardAsideScroll () {
document.addEventListener('DOMContentLoaded', function () {
const el = document.getElementById('dashboard-aside')
const key = 'dashboard-aside-scroll-position'
if (!el) return
const saveScrollPosition = window.debounce(
() => {
const scrollPosition = el.scrollTop
localStorage.setItem(key, scrollPosition)
},
200
)
el.addEventListener('scroll', saveScrollPosition)
const scrollPosition = localStorage.getItem(key)
if (scrollPosition) {
el.scrollTop = parseInt(scrollPosition, 10)
}
})
}

View File

@@ -1,15 +0,0 @@
import { changeThemeButton } from './alpine-components/change-theme-button.js'
import { githubRepoInfo } from './alpine-components/github-repo-info.js'
import { dashboardAsideItem } from './alpine-components/dashboard-aside-item.js'
import { genericSlider } from './alpine-components/generic-slider.js'
import { optionsDropdown } from './alpine-components/options-dropdown.js'
export function initAlpineComponents () {
document.addEventListener('alpine:init', () => {
Alpine.data(changeThemeButton.name, changeThemeButton.fn)
Alpine.data(githubRepoInfo.name, githubRepoInfo.fn)
Alpine.data(dashboardAsideItem.name, dashboardAsideItem.fn)
Alpine.data(genericSlider.name, genericSlider.fn)
Alpine.data(optionsDropdown.name, optionsDropdown.fn)
})
}

View File

@@ -16,7 +16,7 @@ type ChangeThemeButtonParams struct {
func ChangeThemeButton(params ChangeThemeButtonParams) gomponents.Node {
return html.Div(
alpine.XData("changeThemeButton"),
alpine.XData("alpineChangeThemeButton()"),
alpine.XCloak(),
components.Classes{

View File

@@ -1,6 +1,5 @@
export const changeThemeButton = {
name: 'changeThemeButton',
fn: () => ({
window.alpineChangeThemeButton = function () {
return {
theme: '',
loadTheme () {
@@ -16,5 +15,5 @@ export const changeThemeButton = {
init () {
this.loadTheme()
}
})
}
}

View File

@@ -11,7 +11,7 @@ import (
func OptionsDropdown(children ...gomponents.Node) gomponents.Node {
return html.Div(
html.Class("inline-block"),
alpine.XData("options_dropdown"),
alpine.XData("alpineOptionsDropdown()"),
alpine.XOn("mouseenter", "open()"),
alpine.XOn("mouseleave", "close()"),
html.Button(

View File

@@ -1,6 +1,5 @@
export const optionsDropdown = {
name: 'options_dropdown',
fn: () => ({
window.alpineOptionsDropdown = function () {
return {
isOpen: false,
buttonEl: null,
contentEl: null,
@@ -43,5 +42,5 @@ export const optionsDropdown = {
this.contentEl.style.top = `${buttonRect.top - contentHeight}px`
}
}
})
}
}

View File

@@ -10,7 +10,7 @@ import (
func StarOnGithub(size size) gomponents.Node {
return html.A(
alpine.XData("githubRepoInfo"),
alpine.XData("alpineStarOnGithub()"),
alpine.XCloak(),
components.Classes{
"btn btn-neutral": true,

View File

@@ -0,0 +1,42 @@
window.alpineStarOnGithub = function () {
return {
stars: null,
async init () {
const stars = await this.getStars()
if (stars !== null) {
this.stars = stars
}
},
async getStars () {
const cacheKey = 'pbw-gh-stars'
const cachedJSON = localStorage.getItem(cacheKey)
if (cachedJSON) {
const cached = JSON.parse(cachedJSON)
if (Date.now() - cached.timestamp < 2 * 60 * 1000) {
return cached.value
}
}
const url = 'https://api.github.com/repos/eduardolat/pgbackweb'
try {
const response = await fetch(url)
if (!response.ok) {
return null
}
const data = await response.json()
const value = data.stargazers_count
const dataToCache = JSON.stringify({
value,
timestamp: Date.now()
})
localStorage.setItem(cacheKey, dataToCache)
return value
} catch {
return null
}
}
}
}

View File

@@ -4,11 +4,9 @@ import (
"fmt"
"net/http"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/util/echoutil"
"github.com/eduardolat/pgbackweb/internal/view/reqctx"
"github.com/eduardolat/pgbackweb/internal/view/web/alpine"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/layout"
"github.com/google/uuid"
@@ -201,111 +199,8 @@ func indexPage(
BgColors: []string{blueColor, greenColor, redColor},
}),
),
html.Div(
alpine.XData("genericSlider(4)"),
alpine.XCloak(),
html.Class("mt-6 flex flex-col justify-center items-center mx-auto"),
component.H2Text("How to use PG Back Web"),
component.CardBox(component.CardBoxParams{
Class: "mt-4 space-y-4 max-w-[600px]",
Children: []gomponents.Node{
html.Div(
html.Class("flex justify-center"),
html.Ul(
html.Class("steps"),
html.Li(
html.Class("step"),
alpine.XBind("class", "currentSlide >= 1 ? 'step-primary' : ''"),
gomponents.Text("Create database"),
),
html.Li(
html.Class("step"),
alpine.XBind("class", "currentSlide >= 2 ? 'step-primary' : ''"),
gomponents.Text("Create destination"),
),
html.Li(
html.Class("step"),
alpine.XBind("class", "currentSlide >= 3 ? 'step-primary' : ''"),
gomponents.Text("Create backup"),
),
html.Li(
html.Class("step"),
alpine.XBind("class", "currentSlide >= 4 ? 'step-primary' : ''"),
gomponents.Text("Wait for executions"),
),
),
),
html.Div(
alpine.XShow("currentSlide === 1"),
component.H3Text("Create database"),
component.PText(`
To create a database, click on the "Databases" menu item on the
left sidebar. Then click on the "Create database" button. Fill
in the form and click on the "Save" button. You can create as
many databases as you want to backup.
`),
),
html.Div(
alpine.XShow("currentSlide === 2"),
component.H3Text("Create S3 destination (optional)"),
component.PText(`
To create a destination, click on the "Destinations" menu item on
the left sidebar. Then click on the "Create destination" button.
Fill in the form and click on the "Save" button. You can create
as many destinations as you want to store the backups. If you
don't want to use S3 destinations and store the backups locally,
you can skip this step.
`),
),
html.Div(
alpine.XShow("currentSlide === 3"),
component.H3Text("Create backup"),
component.PText(`
To create a backup you need to have at least one database and one
destination. Click on the "Backups" menu item on the left sidebar.
Then click on the "Create backup" button. Fill in the form and
click on the "Save" button. You can create as many backups as you
want including any combination of databases and destinations.
`),
),
html.Div(
alpine.XShow("currentSlide === 4"),
component.H3Text("Wait for executions"),
component.PText(`
When your backup is created and active, the system will start
creating executions based on the schedule you defined. You can
also create executions manually by clicking the "Run backup now"
button on the backups list. You can see the executions on the
"Executions" menu item on the left sidebar and then click on the
"Show details" button to see the details, logs, and download or
delete the backup file.
`),
),
html.Div(
html.Class("mt-4 flex justify-between"),
html.Button(
alpine.XBind("disabled", "!hasPrevSlide"),
alpine.XOn("click", "prevSlide"),
html.Class("btn btn-neutral btn-ghost"),
lucide.ChevronLeft(),
component.SpanText("Previous"),
),
html.Button(
alpine.XBind("disabled", "!hasNextSlide"),
alpine.XOn("click", "nextSlide"),
html.Class("btn btn-neutral btn-ghost"),
component.SpanText("Next"),
lucide.ChevronRight(),
),
),
},
}),
),
indexHowTo(),
}
return layout.Dashboard(reqCtx, layout.DashboardParams{

View File

@@ -0,0 +1,119 @@
package summary
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/view/web/alpine"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
)
func indexHowTo() gomponents.Node {
return html.Div(
alpine.XData("alpineSummaryHowToSlider()"),
alpine.XCloak(),
html.Class("mt-6 flex flex-col justify-center items-center mx-auto"),
component.H2Text("How to use PG Back Web"),
component.CardBox(component.CardBoxParams{
Class: "mt-4 space-y-4 max-w-[600px]",
Children: []gomponents.Node{
html.Div(
html.Class("flex justify-center"),
html.Ul(
html.Class("steps"),
html.Li(
html.Class("step"),
alpine.XBind("class", "currentSlide >= 1 ? 'step-primary' : ''"),
gomponents.Text("Create database"),
),
html.Li(
html.Class("step"),
alpine.XBind("class", "currentSlide >= 2 ? 'step-primary' : ''"),
gomponents.Text("Create destination"),
),
html.Li(
html.Class("step"),
alpine.XBind("class", "currentSlide >= 3 ? 'step-primary' : ''"),
gomponents.Text("Create backup"),
),
html.Li(
html.Class("step"),
alpine.XBind("class", "currentSlide >= 4 ? 'step-primary' : ''"),
gomponents.Text("Wait for executions"),
),
),
),
html.Div(
alpine.XShow("currentSlide === 1"),
component.H3Text("Create database"),
component.PText(`
To create a database, click on the "Databases" menu item on the
left sidebar. Then click on the "Create database" button. Fill
in the form and click on the "Save" button. You can create as
many databases as you want to backup.
`),
),
html.Div(
alpine.XShow("currentSlide === 2"),
component.H3Text("Create S3 destination (optional)"),
component.PText(`
To create a destination, click on the "Destinations" menu item on
the left sidebar. Then click on the "Create destination" button.
Fill in the form and click on the "Save" button. You can create
as many destinations as you want to store the backups. If you
don't want to use S3 destinations and store the backups locally,
you can skip this step.
`),
),
html.Div(
alpine.XShow("currentSlide === 3"),
component.H3Text("Create backup"),
component.PText(`
To create a backup you need to have at least one database and one
destination. Click on the "Backups" menu item on the left sidebar.
Then click on the "Create backup" button. Fill in the form and
click on the "Save" button. You can create as many backups as you
want including any combination of databases and destinations.
`),
),
html.Div(
alpine.XShow("currentSlide === 4"),
component.H3Text("Wait for executions"),
component.PText(`
When your backup is created and active, the system will start
creating executions based on the schedule you defined. You can
also create executions manually by clicking the "Run backup now"
button on the backups list. You can see the executions on the
"Executions" menu item on the left sidebar and then click on the
"Show details" button to see the details, logs, and download or
delete the backup file.
`),
),
html.Div(
html.Class("mt-4 flex justify-between"),
html.Button(
alpine.XBind("disabled", "!hasPrevSlide"),
alpine.XOn("click", "prevSlide"),
html.Class("btn btn-neutral btn-ghost"),
lucide.ChevronLeft(),
component.SpanText("Previous"),
),
html.Button(
alpine.XBind("disabled", "!hasNextSlide"),
alpine.XOn("click", "nextSlide"),
html.Class("btn btn-neutral btn-ghost"),
component.SpanText("Next"),
lucide.ChevronRight(),
),
),
},
}),
)
}

View File

@@ -1,18 +1,22 @@
export const genericSlider = {
name: 'genericSlider',
fn: (slidesQty = 0) => ({
currentSlide: slidesQty > 0 ? 1 : 0,
window.alpineSummaryHowToSlider = function () {
return {
slidesQty: 4,
currentSlide: 1,
get hasNextSlide () {
return this.currentSlide < slidesQty
return this.currentSlide < this.slidesQty
},
get hasPrevSlide () {
return this.currentSlide > 1
},
nextSlide () {
if (this.hasNextSlide) this.currentSlide++
},
prevSlide () {
if (this.hasPrevSlide) this.currentSlide--
}
})
}
}

View File

@@ -118,7 +118,7 @@ func dashboardAsideItem(
text, link string, strict bool,
) gomponents.Node {
return html.A(
alpine.XData(fmt.Sprintf("dashboardAsideItem('%s', %t)", link, strict)),
alpine.XData(fmt.Sprintf("alpineDashboardAsideItem('%s', %t)", link, strict)),
html.Class("block flex flex-col items-center justify-center group"),
html.Href(link),
html.Button(

View File

@@ -1,6 +1,26 @@
export const dashboardAsideItem = {
name: 'dashboardAsideItem',
fn: (link = '', strict = false) => ({
document.addEventListener('DOMContentLoaded', function () {
const el = document.getElementById('dashboard-aside')
const key = 'dashboard-aside-scroll-position'
if (!el) return
const saveScrollPosition = window.debounce(
() => {
const scrollPosition = el.scrollTop
localStorage.setItem(key, scrollPosition)
},
200
)
el.addEventListener('scroll', saveScrollPosition)
const scrollPosition = localStorage.getItem(key)
if (scrollPosition) {
el.scrollTop = parseInt(scrollPosition, 10)
}
})
window.alpineDashboardAsideItem = function (link = '', strict = false) {
return {
link,
strict,
is_active: false,
@@ -29,5 +49,5 @@ export const dashboardAsideItem = {
this.checkActive()
}
}
})
}
}

View File

@@ -1,11 +1,7 @@
package layout
import (
"fmt"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/config"
"github.com/eduardolat/pgbackweb/internal/view/web/alpine"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/maragudk/gomponents"
@@ -28,7 +24,7 @@ func dashboardHeader() gomponents.Node {
Size: component.SizeMd,
}),
component.StarOnGithub(component.SizeMd),
dashboardHeaderCheckForUpdates(),
dashboardHeaderUpdates(),
component.HxLoadingMd("header-indicator"),
),
html.Div(
@@ -55,26 +51,3 @@ func dashboardHeader() gomponents.Node {
),
)
}
func dashboardHeaderCheckForUpdates() gomponents.Node {
return html.A(
alpine.XData("githubRepoInfo"),
alpine.XCloak(),
alpine.XShow(fmt.Sprintf(
"latestRelease !== '' && latestRelease !== '%s'",
config.Version,
)),
components.Classes{
"btn btn-warning": true,
},
html.Href("https://github.com/eduardolat/pgbackweb/releases"),
html.Target("_blank"),
lucide.ExternalLink(),
component.SpanText("Update available"),
html.Span(
alpine.XShow("stars"),
alpine.XText("'( ' + latestRelease + ' )'"),
),
)
}

View File

@@ -0,0 +1,32 @@
package layout
import (
"fmt"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/config"
"github.com/eduardolat/pgbackweb/internal/view/web/alpine"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
)
func dashboardHeaderUpdates() gomponents.Node {
return html.A(
alpine.XData("alpineDashboardHeaderUpdates()"),
alpine.XCloak(),
alpine.XShow(fmt.Sprintf(
"latestRelease !== null && latestRelease !== '%s'",
config.Version,
)),
html.Class("btn btn-warning"),
html.Href("https://github.com/eduardolat/pgbackweb/releases"),
html.Target("_blank"),
lucide.ExternalLink(),
component.SpanText("Update available"),
html.Span(
alpine.XText("'( ' + latestRelease + ' )'"),
),
)
}

View File

@@ -0,0 +1,42 @@
window.alpineDashboardHeaderUpdates = function () {
return {
latestRelease: null,
async init () {
const latestRelease = await this.getLatestRelease()
if (latestRelease !== null) {
this.latestRelease = latestRelease
}
},
async getLatestRelease () {
const cacheKey = 'pbw-gh-last-release'
const cachedJSON = localStorage.getItem(cacheKey)
if (cachedJSON) {
const cached = JSON.parse(cachedJSON)
if (Date.now() - cached.timestamp < 2 * 60 * 1000) {
return cached.value
}
}
const url = 'https://api.github.com/repos/eduardolat/pgbackweb/releases/latest'
try {
const response = await fetch(url)
if (!response.ok) {
return null
}
const data = await response.json()
const value = data.name
const dataToCache = JSON.stringify({
value,
timestamp: Date.now()
})
localStorage.setItem(cacheKey, dataToCache)
return value
} catch {
return null
}
}
}
}