Files
Toju/toju-app/src/app/features/settings/settings-modal/permissions-settings/permissions-settings.component.html
2026-04-02 03:18:37 +02:00

276 lines
13 KiB
HTML

@if (normalizedServer(); as room) {
<div class="max-w-5xl space-y-4">
<div class="rounded-lg border border-border/60 bg-background/60 p-4">
<p class="text-sm text-foreground">
Roles now define who can moderate, manage channels, upload files, and join voice. Channel overrides are optional and apply on top of the base
role permissions.
</p>
@if (!canManageRoles()) {
<p class="mt-2 text-xs text-muted-foreground">You can inspect this server's access model, but only members with Manage Roles can edit it.</p>
}
</div>
<div class="grid gap-4 xl:grid-cols-[16rem,minmax(0,1fr)]">
<div class="space-y-3 rounded-lg bg-secondary/50 p-3">
<div class="flex items-center justify-between gap-2">
<div>
<p class="text-sm font-medium text-foreground">Roles</p>
<p class="text-xs text-muted-foreground">Higher roles appear first.</p>
</div>
@if (canManageRoles()) {
<button
type="button"
(click)="createRole()"
class="inline-flex items-center gap-1 rounded-md border border-border bg-background px-2 py-1 text-xs text-foreground transition-colors hover:bg-background/80"
>
<ng-icon
name="lucidePlus"
class="h-3.5 w-3.5"
/>
<span>Role</span>
</button>
}
</div>
<div class="space-y-2">
@for (role of roles(); track role.id) {
<button
type="button"
(click)="selectRole(role.id)"
class="flex w-full items-center gap-2 rounded-lg border px-3 py-2 text-left transition-colors"
[class.border-primary/60]="selectedRoleKey === role.id"
[class.bg-background]="selectedRoleKey === role.id"
[class.border-border/60]="selectedRoleKey !== role.id"
[class.bg-background/60]="selectedRoleKey !== role.id"
>
<span
class="h-2.5 w-2.5 rounded-full"
[style.background]="role.color || '#94a3b8'"
></span>
<span class="min-w-0 flex-1 truncate text-sm text-foreground">{{ role.name }}</span>
@if (role.isSystem) {
<span class="rounded bg-primary/10 px-1.5 py-0.5 text-[10px] uppercase tracking-[0.16em] text-primary">System</span>
}
</button>
}
</div>
</div>
<div class="space-y-4">
<div class="rounded-lg bg-secondary/50 p-4">
<div class="flex items-center justify-between gap-4">
<div>
<p class="text-sm font-medium text-foreground">Slow Mode</p>
<p class="text-xs text-muted-foreground">Sets the minimum delay between messages for everyone in the server.</p>
</div>
<select
[ngModel]="slowModeValue(room.slowModeInterval)"
(ngModelChange)="updateSlowMode($event)"
[disabled]="!canManageServer()"
class="rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
>
<option value="0">Off</option>
<option value="5">5 seconds</option>
<option value="10">10 seconds</option>
<option value="30">30 seconds</option>
<option value="60">1 minute</option>
<option value="120">2 minutes</option>
</select>
</div>
</div>
@if (selectedRole(); as role) {
<div class="space-y-4 rounded-lg bg-secondary/50 p-4">
<div class="flex items-start justify-between gap-3">
<div>
<div class="flex items-center gap-2">
<span
class="h-3 w-3 rounded-full"
[style.background]="role.color || '#94a3b8'"
></span>
<p class="text-sm font-medium text-foreground">{{ role.name }}</p>
</div>
<p class="mt-1 text-xs text-muted-foreground">
Edit the role metadata here, then tune its global permissions and per-channel overrides below.
</p>
</div>
@if (role.isSystem) {
<span class="rounded bg-primary/10 px-2 py-1 text-[10px] uppercase tracking-[0.16em] text-primary">Protected role</span>
}
</div>
<div class="grid gap-3 md:grid-cols-[minmax(0,1fr),8rem]">
<label class="space-y-1">
<span class="text-xs font-medium uppercase tracking-[0.16em] text-muted-foreground">Role Name</span>
<input
type="text"
[ngModel]="roleName"
(ngModelChange)="roleName = $event"
[disabled]="!canEditSelectedRoleMetadata()"
class="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
/>
</label>
<label class="space-y-1">
<span class="text-xs font-medium uppercase tracking-[0.16em] text-muted-foreground">Color</span>
<input
type="color"
[ngModel]="roleColor"
(ngModelChange)="roleColor = $event"
[disabled]="!canEditSelectedRoleMetadata()"
class="h-10 w-full rounded-md border border-border bg-background px-1"
/>
</label>
</div>
<div class="flex flex-wrap items-center gap-2">
<button
type="button"
(click)="saveRoleDetails()"
[disabled]="!canEditSelectedRoleMetadata()"
class="inline-flex items-center gap-2 rounded-md bg-primary px-3 py-2 text-sm text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
>
<ng-icon
name="lucideCheck"
class="h-4 w-4"
/>
<span>Save Role</span>
</button>
<button
type="button"
(click)="moveSelectedRole('up')"
[disabled]="!canMoveSelectedRoleUp()"
class="inline-flex items-center gap-1 rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground transition-colors hover:bg-background/80 disabled:cursor-not-allowed disabled:opacity-50"
>
<ng-icon
name="lucideArrowUp"
class="h-4 w-4"
/>
<span>Move Up</span>
</button>
<button
type="button"
(click)="moveSelectedRole('down')"
[disabled]="!canMoveSelectedRoleDown()"
class="inline-flex items-center gap-1 rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground transition-colors hover:bg-background/80 disabled:cursor-not-allowed disabled:opacity-50"
>
<ng-icon
name="lucideArrowDown"
class="h-4 w-4"
/>
<span>Move Down</span>
</button>
@if (!role.isSystem) {
<button
type="button"
(click)="deleteSelectedRole()"
[disabled]="!canEditSelectedRoleMetadata()"
class="inline-flex items-center gap-1 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive transition-colors hover:bg-destructive/20 disabled:cursor-not-allowed disabled:opacity-50"
>
<ng-icon
name="lucideTrash2"
class="h-4 w-4"
/>
<span>Delete</span>
</button>
}
</div>
@if (role.isSystem) {
<p class="text-xs text-muted-foreground">
System roles can still have their permissions tuned, but their name, color, and membership in the base hierarchy stay fixed.
</p>
}
</div>
<div class="space-y-3 rounded-lg bg-secondary/50 p-4">
<div>
<p class="text-sm font-medium text-foreground">Base Permissions</p>
<p class="text-xs text-muted-foreground">These defaults apply everywhere unless a channel override changes them.</p>
</div>
<div class="space-y-2">
@for (permission of permissionDefinitions; track permission.key) {
<div class="flex items-center justify-between gap-4 rounded-lg border border-border/50 bg-background/60 p-3">
<div class="min-w-0 flex-1">
<p class="text-sm font-medium text-foreground">{{ permission.label }}</p>
<p class="text-xs text-muted-foreground">{{ permission.description }}</p>
</div>
<select
[ngModel]="permissionState(permission.key)"
(ngModelChange)="setSelectedRolePermission(permission.key, coercePermissionState($event))"
[disabled]="!canEditSelectedRole()"
class="rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
>
@for (state of permissionStates; track state) {
<option [value]="state">
{{ state === 'inherit' ? 'Inherit' : state === 'allow' ? 'Allow' : 'Deny' }}
</option>
}
</select>
</div>
}
</div>
</div>
<div class="space-y-3 rounded-lg bg-secondary/50 p-4">
<div>
<p class="text-sm font-medium text-foreground">Channel Overrides</p>
<p class="text-xs text-muted-foreground">
Override the selected role inside a specific channel without changing the server-wide default.
</p>
</div>
@if (channels().length === 0) {
<p class="text-sm text-muted-foreground">This server has no channels yet.</p>
} @else {
<div class="flex items-center gap-3">
<label class="min-w-0 flex-1 space-y-1">
<span class="text-xs font-medium uppercase tracking-[0.16em] text-muted-foreground">Channel</span>
<select
[ngModel]="selectedChannelKey"
(ngModelChange)="selectChannel($event)"
class="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
>
@for (channel of channels(); track channel.id) {
<option [value]="channel.id">{{ channel.name }} ({{ channel.type | titlecase }})</option>
}
</select>
</label>
</div>
<div class="space-y-2">
@for (permission of permissionDefinitions; track permission.key) {
<div class="flex items-center justify-between gap-4 rounded-lg border border-border/50 bg-background/60 p-3">
<div class="min-w-0 flex-1">
<p class="text-sm font-medium text-foreground">{{ permission.label }}</p>
<p class="text-xs text-muted-foreground">{{ permission.description }}</p>
</div>
<select
[ngModel]="channelOverrideState(permission.key)"
(ngModelChange)="setChannelOverride(permission.key, coercePermissionState($event))"
[disabled]="!canEditSelectedRole()"
class="rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
>
@for (state of permissionStates; track state) {
<option [value]="state">
{{ state === 'inherit' ? 'Inherit' : state === 'allow' ? 'Allow' : 'Deny' }}
</option>
}
</select>
</div>
}
</div>
}
</div>
}
</div>
</div>
</div>
} @else {
<div class="flex h-40 items-center justify-center text-sm text-muted-foreground">Select a server from the sidebar to manage</div>
}