init
This commit is contained in:
243
settings/desktop/DesktopRolesSection.js
Normal file
243
settings/desktop/DesktopRolesSection.js
Normal file
@@ -0,0 +1,243 @@
|
||||
class DesktopRolesSection extends Shadow {
|
||||
constructor(roles, allApps, selectedRoleId, roleApps, confirmDeleteId, onSelectRole, onDeleteRole, onSetConfirmDelete, onCreateRole, onToggleApp) {
|
||||
super()
|
||||
this.roles = roles
|
||||
this.allApps = allApps
|
||||
this.selectedRoleId = selectedRoleId
|
||||
this.roleApps = roleApps
|
||||
this.confirmDeleteId = confirmDeleteId
|
||||
this.onSelectRole = onSelectRole
|
||||
this.onDeleteRole = onDeleteRole
|
||||
this.onSetConfirmDelete = onSetConfirmDelete
|
||||
this.onCreateRole = onCreateRole
|
||||
this.onToggleApp = onToggleApp
|
||||
this.newRoleName = ""
|
||||
}
|
||||
|
||||
get selectedRole() {
|
||||
return this.roles.find(r => r.id === this.selectedRoleId) ?? null
|
||||
}
|
||||
|
||||
get selectedRoleAppIds() {
|
||||
return this.roleApps[this.selectedRoleId] ?? new Set()
|
||||
}
|
||||
|
||||
render() {
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
VStack(() => {
|
||||
p("Roles & Apps")
|
||||
.margin(0).fontSize(1.35, em).fontWeight("700").color("var(--headertext)")
|
||||
p("Control which apps each role can access")
|
||||
.margin(0).marginTop(0.2, em).fontSize(0.75, em)
|
||||
.color("var(--headertext)").opacity(0.42)
|
||||
})
|
||||
.gap(0).flex(1)
|
||||
})
|
||||
.paddingHorizontal(1.75, em).paddingTop(1.5, em).paddingBottom(1.1, em)
|
||||
.alignItems("flex-start").flexShrink(0)
|
||||
|
||||
HStack(() => {
|
||||
// ── Role list ─────────────────────────────────────────
|
||||
VStack(() => {
|
||||
p("ROLES")
|
||||
.margin(0).marginBottom(0.5, em)
|
||||
.fontSize(0.62, em).fontWeight("700").letterSpacing("0.07em")
|
||||
.color("var(--headertext)").opacity(0.35)
|
||||
|
||||
this.roles.forEach(role => {
|
||||
const isSelected = this.selectedRoleId === role.id
|
||||
const isConfirming = this.confirmDeleteId === role.id
|
||||
const appCount = (this.roleApps[role.id] ?? new Set()).size
|
||||
|
||||
HStack(() => {
|
||||
VStack(() => {
|
||||
p(role.name)
|
||||
.margin(0).fontSize(0.88, em)
|
||||
.fontWeight(isSelected ? "700" : "500")
|
||||
.color("var(--headertext)")
|
||||
.overflow("hidden").whiteSpace("nowrap").textOverflow("ellipsis")
|
||||
p(`${appCount} app${appCount !== 1 ? "s" : ""}`)
|
||||
.margin(0).marginTop(0.12, em).fontSize(0.68, em)
|
||||
.color("var(--headertext)").opacity(0.38)
|
||||
})
|
||||
.flex(1).minWidth(0).gap(0)
|
||||
|
||||
if (isConfirming) {
|
||||
HStack(() => {
|
||||
button("Delete")
|
||||
.padding("0.2em 0.5em").border("none").borderRadius(0.3, em)
|
||||
.background("var(--quillred)").color("white")
|
||||
.fontSize(0.68, em).fontWeight("600").cursor("pointer")
|
||||
.onClick((done) => { if (done) this.onDeleteRole(role.id) })
|
||||
button("✕")
|
||||
.padding("0.2em 0.45em").border("none").borderRadius(0.3, em)
|
||||
.background("transparent").color("var(--headertext)")
|
||||
.opacity(0.4).fontSize(0.72, em).cursor("pointer")
|
||||
.onClick((done) => { if (done) this.onSetConfirmDelete(null) })
|
||||
})
|
||||
.gap(0.3, em).alignItems("center")
|
||||
} else {
|
||||
button("⋯")
|
||||
.border("none").background("transparent")
|
||||
.color("var(--headertext)").opacity(0.3)
|
||||
.fontSize(1, em).cursor("pointer").padding("0.1em 0.35em")
|
||||
.borderRadius(0.3, em)
|
||||
.onClick((done) => { if (done) this.onSetConfirmDelete(role.id) })
|
||||
}
|
||||
})
|
||||
.paddingHorizontal(0.75, em).paddingVertical(0.6, em)
|
||||
.borderRadius(0.5, em)
|
||||
.background(isSelected ? "var(--app)" : "transparent")
|
||||
.border(`1px solid ${isSelected ? "var(--quillred)" : "transparent"}`)
|
||||
.cursor("pointer").alignItems("center")
|
||||
.onClick((done) => { if (done) this.onSelectRole(role.id) })
|
||||
})
|
||||
|
||||
// New role input
|
||||
HStack(() => {
|
||||
p("+").margin(0).fontSize(1, em).color("var(--headertext)").opacity(0.4).flexShrink(0)
|
||||
input("New role name…", "100%")
|
||||
.border("none").background("transparent")
|
||||
.color("var(--headertext)").fontSize(0.85, em).outline("none").flex(1)
|
||||
.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.5, em).alignItems("center")
|
||||
.paddingHorizontal(0.75, em).paddingVertical(0.55, em)
|
||||
.borderRadius(0.5, em).border("1px dashed var(--divider)")
|
||||
.marginTop(0.35, em).cursor("text")
|
||||
})
|
||||
.width(220, px).flexShrink(0).paddingHorizontal(1.75, em).gap(0.25, em)
|
||||
|
||||
VStack(() => {}).width(1, px).background("var(--divider)").alignSelf("stretch").flexShrink(0)
|
||||
|
||||
// ── Role detail ───────────────────────────────────────
|
||||
VStack(() => {
|
||||
if (!this.selectedRole) {
|
||||
VStack(() => {
|
||||
p("Select a role to configure it")
|
||||
.margin(0).fontSize(0.88, em)
|
||||
.color("var(--headertext)").opacity(0.35).textAlign("center")
|
||||
})
|
||||
.flex(1).justifyContent("center").alignItems("center")
|
||||
} else {
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
p(this.selectedRole.name)
|
||||
.margin(0).fontSize(1.1, em).fontWeight("700").color("var(--headertext)")
|
||||
if (this.selectedRole.is_default) {
|
||||
p("default")
|
||||
.margin(0).fontSize(0.65, em).fontWeight("600")
|
||||
.color("var(--quillred)")
|
||||
.background("rgba(159,28,41,0.1)")
|
||||
.paddingHorizontal(0.55, em).paddingVertical(0.15, em)
|
||||
.borderRadius(100, px)
|
||||
}
|
||||
})
|
||||
.gap(0.65, em).alignItems("center").marginBottom(1.25, em)
|
||||
|
||||
this.settingSection("📱", "App Access", "Choose which apps this role can access", () => {
|
||||
VStack(() => {
|
||||
this.allApps.forEach(app => {
|
||||
const hasApp = this.selectedRoleAppIds.has(app.id)
|
||||
const comingSoonApps = ["tasks", "jobs", "politics", "files"]
|
||||
const isComingSoon = comingSoonApps.includes(app.name)
|
||||
|
||||
HStack(() => {
|
||||
VStack(() => {
|
||||
if (hasApp) {
|
||||
p("✓").margin(0).fontSize(0.6, em).fontWeight("800").color("white").lineHeight("1")
|
||||
}
|
||||
})
|
||||
.width(1.05, em).height(1.05, em).borderRadius(0.25, em)
|
||||
.background(hasApp ? "var(--quillred)" : "transparent")
|
||||
.border(`1.5px solid ${hasApp ? "var(--quillred)" : "var(--divider)"}`)
|
||||
.justifyContent("center").alignItems("center")
|
||||
.boxSizing("border-box").flexShrink(0)
|
||||
|
||||
p(app.name.charAt(0).toUpperCase() + app.name.slice(1))
|
||||
.margin(0).fontSize(0.88, em).color("var(--headertext)")
|
||||
|
||||
if (isComingSoon) {
|
||||
p("coming soon")
|
||||
.margin(0).fontSize(0.62, em).fontWeight("600")
|
||||
.color("var(--headertext)").opacity(0.25)
|
||||
.background("var(--divider)")
|
||||
.paddingHorizontal(0.45, em).paddingVertical(0.12, em).borderRadius(100, px)
|
||||
}
|
||||
})
|
||||
.gap(0.75, em).alignItems("center")
|
||||
.paddingVertical(0.5, em).paddingHorizontal(0.75, em)
|
||||
.borderRadius(0.45, em)
|
||||
.background("var(--darkaccent)").border("1px solid var(--divider)")
|
||||
.cursor(isComingSoon ? "default" : "pointer")
|
||||
.opacity(isComingSoon ? 0.5 : 1)
|
||||
.onClick((done) => { if (!isComingSoon && done) this.onToggleApp(this.selectedRoleId, app.id, !hasApp) })
|
||||
})
|
||||
})
|
||||
.gap(0.45, em)
|
||||
})
|
||||
|
||||
this.comingSoonSection("🔐", "Permissions", "Fine-grained action-level permissions per app")
|
||||
this.comingSoonSection("🔔", "Notifications", "Configure default notification settings")
|
||||
this.comingSoonSection("🎨", "Color & Badge", "Assign a display color and badge label")
|
||||
})
|
||||
.paddingHorizontal(1.75, em).paddingBottom(1.75, em).paddingTop(0.5, em).gap(0)
|
||||
}
|
||||
})
|
||||
.flex(1).minWidth(0).overflowY("auto").height(100, pct)
|
||||
})
|
||||
.flex(1).minHeight(0).alignItems("flex-start").overflow("hidden")
|
||||
})
|
||||
.height(100, pct).overflow("hidden")
|
||||
}
|
||||
|
||||
settingSection(icon, title, subtitle, renderContent) {
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
p(icon).margin(0).fontSize(1, em).lineHeight("1").flexShrink(0)
|
||||
VStack(() => {
|
||||
p(title).margin(0).fontSize(0.92, em).fontWeight("700").color("var(--headertext)")
|
||||
p(subtitle).margin(0).marginTop(0.1, em).fontSize(0.72, em).color("var(--headertext)").opacity(0.42)
|
||||
})
|
||||
.gap(0)
|
||||
})
|
||||
.gap(0.65, em).alignItems("flex-start").marginBottom(0.85, em)
|
||||
renderContent()
|
||||
})
|
||||
.padding(1.1, em).background("var(--darkaccent)").border("1px solid var(--divider)")
|
||||
.borderRadius(0.65, em).marginBottom(0.85, em)
|
||||
}
|
||||
|
||||
comingSoonSection(icon, title, subtitle) {
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
p(icon).margin(0).fontSize(1, em).lineHeight("1").flexShrink(0).opacity(0.35)
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
p(title).margin(0).fontSize(0.92, em).fontWeight("700").color("var(--headertext)").opacity(0.35)
|
||||
p("coming soon")
|
||||
.margin(0).fontSize(0.62, em).fontWeight("600")
|
||||
.color("var(--headertext)").opacity(0.25)
|
||||
.background("var(--divider)")
|
||||
.paddingHorizontal(0.45, em).paddingVertical(0.12, em).borderRadius(100, px)
|
||||
})
|
||||
.gap(0.55, em).alignItems("center")
|
||||
p(subtitle).margin(0).marginTop(0.1, em).fontSize(0.72, em).color("var(--headertext)").opacity(0.28)
|
||||
})
|
||||
.gap(0)
|
||||
})
|
||||
.gap(0.65, em).alignItems("flex-start")
|
||||
})
|
||||
.padding(1.1, em).border("1px solid var(--divider)").borderRadius(0.65, em)
|
||||
.marginBottom(0.85, em).opacity(0.6)
|
||||
}
|
||||
}
|
||||
|
||||
register(DesktopRolesSection)
|
||||
Reference in New Issue
Block a user