fix: team and users

This commit is contained in:
Torsten Dittmann
2022-09-30 11:09:15 +02:00
parent b15669f934
commit 4debbd9023
10 changed files with 304 additions and 144 deletions
+2 -1
View File
@@ -1,9 +1,10 @@
<script lang="ts">
export let disabled = false;
export let icon: string = null;
</script>
<li class="drop-list-item">
<button class="drop-button" on:click|preventDefault>
<button class="drop-button" on:click|preventDefault {disabled}>
<span class="text"><slot /></span>
{#if icon}
<span class={`icon-${icon}`} aria-hidden="true" />
+1 -1
View File
@@ -34,8 +34,8 @@
};
const closeModal = () => {
if (closable) {
show = false;
dispatch('close');
show = false;
}
};
+39 -29
View File
@@ -1,10 +1,13 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
export let sum: number;
export let limit: number;
export let offset: number;
export let hidePages = false;
const dispatch = createEventDispatcher();
$: totalPages = Math.ceil(sum / limit);
$: currentPage = Math.floor(offset / limit + 1);
$: pages = pagination(currentPage, totalPages);
@@ -50,6 +53,7 @@
{#if totalPages > 1}
<nav class="pagination">
<button
type="button"
on:click={() => handleButtonPage('prev')}
class:is-disabled={currentPage <= 1}
class="button is-text"
@@ -57,30 +61,34 @@
<span class="icon-cheveron-left" aria-hidden="true" />
<span class="text">Prev</span>
</button>
<ol class="pagination-list is-only-desktop">
{#each pages as page}
{#if typeof page === 'number'}
<li class="pagination-item">
<button
class="button"
on:click={() => handleOptionClick(page)}
class:is-disabled={currentPage === page}
class:is-text={currentPage !== page}
aria-label="page">
<span class="text">{page}</span>
</button>
</li>
{:else}
<li class="li is-text">
<span class="icon">...</span>
</li>
{/if}
{/each}
</ol>
{#if !hidePages}
<ol class="pagination-list is-only-desktop">
{#each pages as page}
{#if typeof page === 'number'}
<li class="pagination-item">
<button
type="button"
class="button"
on:click={() => handleOptionClick(page)}
class:is-disabled={currentPage === page}
class:is-text={currentPage !== page}
aria-label="page">
<span class="text">{page}</span>
</button>
</li>
{:else}
<li class="li is-text">
<span class="icon">...</span>
</li>
{/if}
{/each}
</ol>
{/if}
<button
on:click={() => handleButtonPage('next')}
class:is-disabled={currentPage === totalPages}
class="button is-text"
type="button"
aria-label="next page">
<span class="text">Next</span>
<span class="icon-cheveron-right" aria-hidden="true" />
@@ -88,18 +96,20 @@
</nav>
{:else}
<nav class="pagination">
<button class="button is-text is-disabled" aria-label="prev page">
<button type="button" class="button is-text is-disabled" aria-label="prev page">
<span class="icon-cheveron-left" aria-hidden="true" />
<span class="text">Prev</span>
</button>
<ol class="pagination-list is-only-desktop">
<li class="pagination-item">
<button class="button is-disabled" aria-label="page">
<span class="text">1</span>
</button>
</li>
</ol>
<button class="button is-text is-disabled" aria-label="next page">
{#if !hidePages}
<ol class="pagination-list is-only-desktop">
<li class="pagination-item">
<button type="button" class="button is-disabled" aria-label="page">
<span class="text">1</span>
</button>
</li>
</ol>
{/if}
<button type="button" class="button is-text is-disabled" aria-label="next page">
<span class="text">Next</span>
<span class="icon-cheveron-right" aria-hidden="true" />
</button>
+41 -15
View File
@@ -1,20 +1,46 @@
<script lang="ts">
import { Button, Form, FormList, Helper } from '$lib/elements/forms';
import { createEventDispatcher } from 'svelte';
import { Modal } from '..';
import type { Writable } from 'svelte/store';
import type { Permission } from './permissions.svelte';
import InputText from '$lib/elements/forms/inputText.svelte';
export let role: string;
export let permission: Permission;
export let show: boolean;
export let groups: Writable<Map<string, Permission>>;
const dispatch = createEventDispatcher();
let value = '';
function reset() {
value = '';
}
function create() {
dispatch('create', [value]);
reset();
}
</script>
<div class="u-flex u-cross-center u-gap-8">
<div>
{#if role === 'users'}
<div>Users</div>
{:else if role === 'guests'}
<div>Guests</div>
{:else if role === 'any'}
<div>Any</div>
{:else}
<div>{role}</div>
{/if}
</div>
</div>
<Form on:submit={create}>
<Modal bind:show on:close={reset}>
<svelte:fragment slot="header">Custom permission</svelte:fragment>
<FormList>
<InputText
showLabel={false}
id="custom-permission"
label="Custom permission"
placeholder="user:[USER_ID] or team:[TEAM_ID]/[ROLE]"
{value} />
<Helper type="neutral">
A permission should be formatted as: user:[USER_ID] or team:[TEAM_ID]/[ROLE]¸
</Helper>
</FormList>
<svelte:fragment slot="footer">
<Button submit disabled={!value}>Create</Button>
</svelte:fragment>
</Modal>
</Form>
@@ -16,6 +16,7 @@
import { writable } from 'svelte/store';
import { DropList, DropListItem } from '..';
import Custom from './custom.svelte';
import Row from './row.svelte';
import Team from './team.svelte';
import User from './user.svelte';
@@ -24,6 +25,7 @@
let showUser = false;
let showTeam = false;
let showCustom = false;
let showDropdown = false;
const groups = writable<Map<string, Permission>>(new Map());
@@ -39,24 +41,28 @@
});
function create(event: CustomEvent<string[]>) {
groups.update((n) => {
for (const role of event.detail) {
if (n.has(role)) {
return n;
}
for (const role of event.detail) {
addRole(role);
}
n.set(role, {
create: false,
read: false,
update: false,
delete: false
});
}
showTeam = showUser = false;
}
function addRole(role: string) {
if ($groups.has(role)) {
return;
}
groups.update((n) => {
n.set(role, {
create: false,
read: false,
update: false,
delete: false
});
return n;
});
showTeam = showUser = false;
}
function fromPermissionString(permission: string): void {
@@ -136,7 +142,7 @@
{#each [...$groups] as [role, permission]}
<tr class="table-row">
<td class="table-col" data-title="Role">
<Custom {role} {permission} />
<Row {role} {permission} />
</td>
{#if withCreate}
<td class="table-col" data-title="Create">
@@ -200,10 +206,21 @@
<span class="text">Add role</span>
</Button>
<svelte:fragment slot="list">
<DropListItem disabled={$groups.has('any')} on:click={() => addRole('any')}>
Any
</DropListItem>
<DropListItem disabled={$groups.has('guests')} on:click={() => addRole('guests')}>
All guests
</DropListItem>
<DropListItem disabled={$groups.has('users')} on:click={() => addRole('users')}>
All users
</DropListItem>
<DropListItem on:click={() => (showUser = true)}>Select users</DropListItem>
<DropListItem on:click={() => (showTeam = true)}>Select teams</DropListItem>
<DropListItem on:click={() => (showCustom = true)}>Custom permission</DropListItem>
</svelte:fragment>
</DropList>
<User bind:show={showUser} on:create={create} {groups} />
<Team bind:show={showTeam} on:create={create} {groups} />
<Custom bind:show={showCustom} on:create={create} {groups} />
+20
View File
@@ -0,0 +1,20 @@
<script lang="ts">
import type { Permission } from './permissions.svelte';
export let role: string;
export let permission: Permission;
</script>
<div class="u-flex u-cross-center u-gap-8">
<div>
{#if role === 'users'}
<div>Users</div>
{:else if role === 'guests'}
<div>Guests</div>
{:else if role === 'any'}
<div>Any</div>
{:else}
<div>{role}</div>
{/if}
</div>
</div>
+79 -40
View File
@@ -1,11 +1,11 @@
<script lang="ts">
import { Button, Form } from '$lib/elements/forms';
import { createEventDispatcher, onMount } from 'svelte';
import { Modal } from '..';
import { Button, Form, InputSearch } from '$lib/elements/forms';
import { createEventDispatcher } from 'svelte';
import { sdkForProject } from '$lib/stores/sdk';
import { Query, type Models } from '@aw-labs/appwrite-console';
import { Avatar, Modal, Pagination } from '..';
import type { Writable } from 'svelte/store';
import type { Permission } from './permissions.svelte';
import InputSearch from '$lib/elements/forms/inputSearch.svelte';
import { sdkForProject } from '$lib/stores/sdk';
export let show: boolean;
export let groups: Writable<Map<string, Permission>>;
@@ -13,60 +13,99 @@
const dispatch = createEventDispatcher();
let search = '';
let results = [];
let offset = 0;
let results: Models.TeamList;
let selected: Set<string> = new Set();
let hasSelection = false;
onMount(request);
function reset() {
offset = 0;
search = '';
selected.clear();
}
function create() {
dispatch('create', Array.from(selected));
reset();
}
async function request() {
results = (await sdkForProject.teams.list([], search)).teams;
if (!show) return;
results = await sdkForProject.teams.list([Query.limit(5), Query.offset(offset)], search);
}
function onSelection(event: Event, role: string) {
const { checked } = event.currentTarget as HTMLInputElement;
if (checked) {
selected.add(role);
} else {
selected.delete(role);
}
hasSelection = selected.size > 0;
}
$: if (show) {
request();
}
$: if (offset !== null) {
request();
}
$: if (search !== null) {
offset = 0;
request();
}
</script>
<Form on:submit={create}>
<Modal bind:show>
<svelte:fragment slot="header">Select team</svelte:fragment>
<Modal bind:show on:close={reset}>
<svelte:fragment slot="header">Select teams</svelte:fragment>
<InputSearch bind:value={search} />
<div class="table-wrapper">
<table class="table is-table-layout-auto is-remove-outer-styles">
<tbody class="table-tbody">
{#each results as team (team.$id)}
{@const role = `team:${team.$id}`}
<tr class="table-row">
<td class="table-col" data-title="Enabled" style="--p-col-width:40">
<input
type="checkbox"
class="icon-check"
aria-label="Create"
checked={selected.has(role)}
on:change={() => selected.add(role)} />
</td>
<td class="table-col" data-title="Team">
<div class="u-flex u-cross-center u-gap-8">
<div class="avatar is-size-small is-color-orange">
<span class="text" aria-hidden="true">ch</span>
</div>
<div class="u-line-height-1-5">
<div class="body-text-2">{team.name}</div>
<div class="u-x-small">{team.$id}</div>
</div>
</div>
</td>
</tr>
{/each}
</tbody>
</table>
{#if results?.teams}
<div class="table-wrapper">
<table class="table is-table-layout-auto is-remove-outer-styles">
<tbody class="table-tbody">
{#each results.teams as team (team.$id)}
{@const role = `team:${team.$id}`}
{@const exists = $groups.has(role)}
<tr class="table-row">
<td class="table-col" data-title="Enabled" style="--p-col-width:40">
<input
id={team.$id}
type="checkbox"
class="icon-check"
aria-label="Create"
checked={exists || selected.has(role)}
disabled={exists}
on:change={(event) => onSelection(event, role)} />
</td>
<td class="table-col" data-title="Team">
<label class="u-flex u-cross-center u-gap-8" for={team.$id}>
<Avatar
src={sdkForProject.avatars
.getInitials(team.name, 64, 64)
.toString()}
size={32}
name={team.name} />
<div class="u-line-height-1-5">
<div class="body-text-2">{team.name}</div>
<div class="u-x-small">{team.$id}</div>
</div>
</label>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
<div class="u-flex u-margin-block-start-32 u-main-space-between">
<p class="text">Total results: {results?.total}</p>
<Pagination limit={5} bind:offset sum={results?.total} hidePages />
</div>
<svelte:fragment slot="footer">
<Button submit>Create</Button>
<Button submit disabled={!hasSelection}>Create</Button>
</svelte:fragment>
</Modal>
</Form>
+79 -40
View File
@@ -1,11 +1,11 @@
<script lang="ts">
import { Button, Form } from '$lib/elements/forms';
import { createEventDispatcher, onMount } from 'svelte';
import { Modal } from '..';
import { Button, Form, InputSearch } from '$lib/elements/forms';
import { createEventDispatcher } from 'svelte';
import { Avatar, Modal, Pagination } from '..';
import { sdkForProject } from '$lib/stores/sdk';
import { Query, type Models } from '@aw-labs/appwrite-console';
import type { Writable } from 'svelte/store';
import type { Permission } from './permissions.svelte';
import InputSearch from '$lib/elements/forms/inputSearch.svelte';
import { sdkForProject } from '$lib/stores/sdk';
export let show: boolean;
export let groups: Writable<Map<string, Permission>>;
@@ -13,60 +13,99 @@
const dispatch = createEventDispatcher();
let search = '';
let results = [];
let offset = 0;
let results: Models.UserList<Record<string, unknown>>;
let selected: Set<string> = new Set();
let hasSelection = false;
onMount(request);
function reset() {
offset = 0;
search = '';
selected.clear();
}
function create() {
dispatch('create', Array.from(selected));
reset();
}
async function request() {
results = (await sdkForProject.users.list([], search)).users;
if (!show) return;
results = await sdkForProject.users.list([Query.limit(5), Query.offset(offset)], search);
}
function onSelection(event: Event, role: string) {
const { checked } = event.currentTarget as HTMLInputElement;
if (checked) {
selected.add(role);
} else {
selected.delete(role);
}
hasSelection = selected.size > 0;
}
$: if (show) {
request();
}
$: if (offset !== null) {
request();
}
$: if (search !== null) {
offset = 0;
request();
}
</script>
<Form on:submit={create}>
<Modal bind:show>
<svelte:fragment slot="header">Select team</svelte:fragment>
<Modal bind:show on:close={reset}>
<svelte:fragment slot="header">Select users</svelte:fragment>
<InputSearch bind:value={search} />
<div class="table-wrapper">
<table class="table is-table-layout-auto is-remove-outer-styles">
<tbody class="table-tbody">
{#each results as user (user.$id)}
{@const role = `user:${user.$id}`}
<tr class="table-row">
<td class="table-col" data-title="Enabled" style="--p-col-width:40">
<input
type="checkbox"
class="icon-check"
aria-label="Create"
checked={selected.has(role)}
on:change={() => selected.add(role)} />
</td>
<td class="table-col" data-title="User">
<div class="u-flex u-cross-center u-gap-8">
<div class="avatar is-size-small is-color-orange">
<span class="text" aria-hidden="true">ch</span>
</div>
<div class="u-line-height-1-5">
<div class="body-text-2">{user.name}</div>
<div class="u-x-small">{user.$id}</div>
</div>
</div>
</td>
</tr>
{/each}
</tbody>
</table>
{#if results?.users}
<div class="table-wrapper">
<table class="table is-table-layout-auto is-remove-outer-styles">
<tbody class="table-tbody">
{#each results.users as user (user.$id)}
{@const role = `user:${user.$id}`}
{@const exists = $groups.has(role)}
<tr class="table-row">
<td class="table-col" data-title="Enabled" style="--p-col-width:40">
<input
id={user.$id}
type="checkbox"
class="icon-check"
aria-label="Create"
checked={exists || selected.has(role)}
disabled={exists}
on:change={(event) => onSelection(event, role)} />
</td>
<td class="table-col" data-title="User">
<label class="u-flex u-cross-center u-gap-8" for={user.$id}>
<Avatar
src={sdkForProject.avatars
.getInitials(user.name, 64, 64)
.toString()}
size={32}
name={user.name} />
<div class="u-line-height-1-5">
<div class="body-text-2">{user.name}</div>
<div class="u-x-small">{user.$id}</div>
</div>
</label>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
<div class="u-flex u-margin-block-start-32 u-main-space-between">
<p class="text">Total results: {results?.total}</p>
<Pagination limit={5} bind:offset sum={results?.total} hidePages />
</div>
<svelte:fragment slot="footer">
<Button submit>Create</Button>
<Button submit disabled={!hasSelection}>Create</Button>
</svelte:fragment>
</Modal>
</Form>
@@ -1,5 +1,6 @@
<script lang="ts">
import { onMount } from 'svelte';
import { onDestroy } from 'svelte';
export let value = '';
export let placeholder = '';
@@ -18,6 +19,13 @@
}
});
onDestroy(() => {
value = '';
if (timer) {
clearTimeout(timer);
}
});
const valueChange = (event: Event) => {
clearTimeout(timer);
timer = setTimeout(() => {
+3 -3
View File
@@ -37,9 +37,9 @@
href="https://appwrite.io/support"
target="_blank"
rel="noopener noreferrer"
class="button is-small is-text u-margin-inline-end-16"
><span class="text">Support</span></a>
<button class="button is-small is-secondary"><span class="text">Upgrade</span></button>
class="button is-small is-text">
<span class="text">Support</span>
</a>
</nav>
<nav class="user-profile">
{#if $user}