251 lines
12 KiB
JavaScript
251 lines
12 KiB
JavaScript
class SettingsRolesSection extends Shadow {
|
||
newRoleName = ""
|
||
confirmDeleteId = null
|
||
|
||
constructor(roles, allApps, activeSection, activeRoleId, roleApps, basePath, callbacks, loading = false) {
|
||
super()
|
||
this.roles = roles
|
||
this.allApps = allApps
|
||
this.activeSection = activeSection // "roles" | "role-detail"
|
||
this.activeRoleId = activeRoleId
|
||
this.roleApps = roleApps
|
||
this.basePath = basePath
|
||
this.loading = loading
|
||
this.onDeleteRole = callbacks.onDeleteRole
|
||
this.onCreateRole = callbacks.onCreateRole
|
||
this.onToggleApp = callbacks.onToggleApp
|
||
}
|
||
|
||
get activeRole() {
|
||
return this.roles.find(r => String(r.id) === String(this.activeRoleId)) ?? null
|
||
}
|
||
|
||
get activeRoleAppIds() {
|
||
return this.roleApps[this.activeRoleId] ?? new Set()
|
||
}
|
||
|
||
render() {
|
||
if (this.activeSection === "role-detail") {
|
||
this.renderRoleDetail()
|
||
} else {
|
||
this.renderRolesList()
|
||
}
|
||
}
|
||
|
||
renderRolesList() {
|
||
VStack(() => {
|
||
this.backHeader("Roles & Apps", () => window.navigateTo(this.basePath))
|
||
|
||
VStack(() => {}).height(1, px).background("var(--divider)").flexShrink(0)
|
||
|
||
if (this.loading) { LoadingCircle(); return }
|
||
|
||
VStack(() => {
|
||
this.roles.forEach((role, i) => {
|
||
const appCount = (this.roleApps[role.id] ?? new Set()).size
|
||
const isConfirming = this.confirmDeleteId === role.id
|
||
|
||
HStack(() => {
|
||
VStack(() => {
|
||
HStack(() => {
|
||
p(role.name)
|
||
.margin(0).fontSize(0.92, em).fontWeight("600").color("var(--headertext)")
|
||
if (role.is_default) {
|
||
p("default")
|
||
.margin(0).fontSize(0.62, em).fontWeight("600")
|
||
.color("var(--quillred)")
|
||
.background("rgba(159,28,41,0.1)")
|
||
.paddingHorizontal(0.45, em).paddingVertical(0.1, em)
|
||
.borderRadius(100, px)
|
||
}
|
||
})
|
||
.gap(0.45, em).alignItems("center")
|
||
p(`${appCount} app${appCount !== 1 ? "s" : ""} assigned`)
|
||
.margin(0).marginTop(0.15, em).fontSize(0.72, em)
|
||
.color("var(--headertext)").opacity(0.4)
|
||
})
|
||
.flex(1).gap(0)
|
||
|
||
if (isConfirming) {
|
||
HStack(() => {
|
||
button("Delete")
|
||
.padding("0.25em 0.6em")
|
||
.border("none").borderRadius(0.35, em)
|
||
.background("var(--quillred)").color("white")
|
||
.fontSize(0.72, em).fontWeight("600").cursor("pointer")
|
||
.onTap(async (e) => {
|
||
e.stopPropagation()
|
||
await this.onDeleteRole(role.id)
|
||
})
|
||
button("✕")
|
||
.padding("0.25em 0.45em")
|
||
.border("none").borderRadius(0.35, em)
|
||
.background("transparent").color("var(--headertext)")
|
||
.opacity(0.4).fontSize(0.78, em).cursor("pointer")
|
||
.onTap((e) => {
|
||
e.stopPropagation()
|
||
this.confirmDeleteId = null
|
||
this.rerender()
|
||
})
|
||
})
|
||
.gap(0.35, em).alignItems("center")
|
||
} else {
|
||
HStack(() => {
|
||
button("⋯")
|
||
.border("none").background("transparent")
|
||
.color("var(--headertext)").opacity(0.35)
|
||
.fontSize(1.1, em).cursor("pointer").padding("0.1em 0.4em")
|
||
.onTap((e) => {
|
||
e.stopPropagation()
|
||
this.confirmDeleteId = role.id
|
||
this.rerender()
|
||
})
|
||
p("›").margin(0).fontSize(1.1, em).color("var(--headertext)").opacity(0.3)
|
||
})
|
||
.gap(0.1, em).alignItems("center")
|
||
}
|
||
})
|
||
.gap(0.75, em).alignItems("center")
|
||
.paddingVertical(0.85, em)
|
||
.cursor("pointer")
|
||
.onTap(() => window.navigateTo(`${this.basePath}/roles/${role.id}`))
|
||
|
||
if (i < this.roles.length - 1) {
|
||
VStack(() => {}).height(1, px).background("var(--divider)").marginLeft(0)
|
||
}
|
||
})
|
||
|
||
// New role input
|
||
HStack(() => {
|
||
p("+").margin(0).fontSize(1.1, em).color("var(--headertext)").opacity(0.35).flexShrink(0)
|
||
input("New role name…", "100%")
|
||
.border("none").background("transparent")
|
||
.color("var(--headertext)").fontSize(0.88, em).outline("none").flex(1)
|
||
.attr({ value: this.newRoleName })
|
||
.onInput(e => { this.newRoleName = e.target.value })
|
||
.onKeyDown(async (e) => {
|
||
if (e.key === "Enter" && this.newRoleName.trim()) {
|
||
await this.onCreateRole(this.newRoleName.trim())
|
||
this.newRoleName = ""
|
||
}
|
||
})
|
||
})
|
||
.gap(0.65, em).alignItems("center")
|
||
.paddingVertical(0.85, em)
|
||
.borderTop("1px dashed var(--divider)")
|
||
.marginTop(0.1, em)
|
||
})
|
||
.paddingHorizontal(1, em)
|
||
.flex(1).overflowY("auto")
|
||
})
|
||
.height(100, pct).overflow("hidden")
|
||
}
|
||
|
||
renderRoleDetail() {
|
||
VStack(() => {
|
||
this.backHeader(this.activeRole?.name ?? "Role", () => window.navigateTo(`${this.basePath}/roles`))
|
||
|
||
VStack(() => {}).height(1, px).background("var(--divider)").flexShrink(0)
|
||
|
||
if (this.loading) { LoadingCircle(); return }
|
||
|
||
VStack(() => {
|
||
VStack(() => {
|
||
p("APP ACCESS")
|
||
.margin(0).marginBottom(0.55, em)
|
||
.fontSize(0.62, em).fontWeight("700").letterSpacing("0.07em")
|
||
.color("var(--headertext)").opacity(0.35)
|
||
|
||
this.allApps.forEach((app, i) => {
|
||
const hasApp = this.activeRoleAppIds.has(app.id)
|
||
const comingSoonApps = ["jobs", "politics", "files"]
|
||
const isComingSoon = comingSoonApps.includes(app.name)
|
||
|
||
HStack(() => {
|
||
p(app.name.charAt(0).toUpperCase() + app.name.slice(1))
|
||
.margin(0).fontSize(0.9, em).color("var(--headertext)").flex(1)
|
||
|
||
HStack(() => {
|
||
if (isComingSoon) {
|
||
p("Coming soon")
|
||
.margin(0).fontSize(0.9, em).color("var(--headertext)").opacity(0.45)
|
||
.background("var(--darkaccent)").border("1px solid var(--divider)")
|
||
.paddingHorizontal(0.5, em).paddingVertical(0.15, em).borderRadius(100, px)
|
||
}
|
||
|
||
VStack(() => {})
|
||
.width(1.15, em).height(1.15, em)
|
||
.borderRadius(0.25, em)
|
||
.background(hasApp ? "var(--quillred)" : "transparent")
|
||
.border(`2px solid ${hasApp ? "var(--quillred)" : "var(--divider)"}`)
|
||
.boxSizing("border-box")
|
||
.justifyContent("center").alignItems("center")
|
||
})
|
||
.justifyContent("center").alignItems("center")
|
||
.gap(1, em)
|
||
})
|
||
.paddingVertical(0.8, em)
|
||
.gap(1, em).alignItems("center")
|
||
.cursor(isComingSoon ? "default" : "pointer")
|
||
.opacity(isComingSoon ? 0.5 : 1)
|
||
.onTap(() => { if (!isComingSoon) this.onToggleApp(this.activeRoleId, app.id, !hasApp) })
|
||
|
||
if (i < this.allApps.length - 1) {
|
||
VStack(() => {}).height(1, px).background("var(--divider)")
|
||
}
|
||
})
|
||
})
|
||
.paddingHorizontal(1, em)
|
||
.paddingVertical(0.75, em)
|
||
.marginBottom(1, em)
|
||
|
||
VStack(() => {}).height(1, px).background("var(--divider)").marginHorizontal(1, em)
|
||
|
||
VStack(() => {
|
||
this.comingSoonRow("🔐", "Permissions")
|
||
VStack(() => {}).height(1, px).background("var(--divider)")
|
||
this.comingSoonRow("🔔", "Notifications")
|
||
VStack(() => {}).height(1, px).background("var(--divider)")
|
||
this.comingSoonRow("🎨", "Color & Badge")
|
||
})
|
||
.paddingHorizontal(1, em)
|
||
.marginTop(1, em)
|
||
.opacity(0.4)
|
||
})
|
||
.flex(1).overflowY("auto").paddingBottom(2, em)
|
||
})
|
||
.height(100, pct).overflow("hidden")
|
||
}
|
||
|
||
backHeader(title, onBack) {
|
||
HStack(() => {
|
||
button("‹")
|
||
.border("none").background("transparent")
|
||
.color("var(--headertext)").fontSize(1.4, em)
|
||
.lineHeight("1").paddingVertical(0.2, em).paddingRight(0.3, em)
|
||
.cursor("pointer").flexShrink(0)
|
||
.onTap(onBack)
|
||
p(title)
|
||
.margin(0).fontSize(1.05, em).fontWeight("700").color("var(--headertext)")
|
||
.flex(1).overflow("hidden").whiteSpace("nowrap").textOverflow("ellipsis")
|
||
})
|
||
.gap(0.25, em).alignItems("center")
|
||
.paddingHorizontal(0.85, em).paddingTop(1.25, em).paddingBottom(0.85, em)
|
||
.flexShrink(0)
|
||
}
|
||
|
||
comingSoonRow(icon, label) {
|
||
HStack(() => {
|
||
p(icon).margin(0).fontSize(1, em).flexShrink(0)
|
||
p(label).margin(0).fontSize(0.9, em).color("var(--headertext)").flex(1)
|
||
p("Coming soon")
|
||
.margin(0).fontSize(0.9, em).color("var(--headertext)").opacity(0.45)
|
||
.background("var(--darkaccent)").border("1px solid var(--divider)")
|
||
.paddingHorizontal(0.5, em).paddingVertical(0.15, em).borderRadius(100, px)
|
||
})
|
||
.gap(0.75, em).alignItems("center").paddingVertical(0.85, em)
|
||
}
|
||
}
|
||
|
||
register(SettingsRolesSection)
|