init
This commit is contained in:
250
settings/SettingsRolesSection.js
Normal file
250
settings/SettingsRolesSection.js
Normal file
@@ -0,0 +1,250 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user