mirror of
https://github.com/verdigado/organization_folders.git
synced 2024-12-06 11:22:41 +01:00
initial commit of GUI
This commit is contained in:
parent
b64ae41cd0
commit
f07b9953e3
20 changed files with 1416 additions and 0 deletions
92
src/components/ConfirmDeleteDialog.vue
Normal file
92
src/components/ConfirmDeleteDialog.vue
Normal file
|
@ -0,0 +1,92 @@
|
|||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import NcTextField from "@nextcloud/vue/dist/Components/NcTextField.js";
|
||||
import NcModal from "@nextcloud/vue/dist/Components/NcModal.js";
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: "Löschen",
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
matchText: {
|
||||
type: String,
|
||||
default: "löschen",
|
||||
},
|
||||
});
|
||||
|
||||
const open = ref(false);
|
||||
const confirmText = ref("");
|
||||
|
||||
const openDialog = () => {
|
||||
open.value = true;
|
||||
}
|
||||
|
||||
const closeDialog = () => {
|
||||
open.value = false;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<slot name="activator" :open="openDialog">
|
||||
<button type="button" @click="openDialog">
|
||||
Löschen
|
||||
</button>
|
||||
</slot>
|
||||
<NcModal v-if="open"
|
||||
class="modal"
|
||||
:out-transition="true"
|
||||
:has-next="false"
|
||||
:has-previous="false"
|
||||
@close="closeDialog">
|
||||
<div class="modal__content">
|
||||
<div class="modal__title">
|
||||
<h1>
|
||||
{{ props.title }}
|
||||
</h1>
|
||||
</div>
|
||||
<div>
|
||||
<slot name="content" />
|
||||
<p>
|
||||
Gib hier als Bestätigung "<span style="user-select: all;">{{ props.matchText }}</span>" ein.
|
||||
</p>
|
||||
<NcTextField class="confirmText"
|
||||
:value.sync="confirmText"
|
||||
style=" --color-border-maxcontrast: #949494;" />
|
||||
<slot name="delete-button" :close="closeDialog" :disabled="confirmText !== props.matchText">
|
||||
<button type="button">
|
||||
Löschen
|
||||
</button>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</NcModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.confirmText {
|
||||
margin: 1rem 0 1rem 0;
|
||||
}
|
||||
|
||||
.modal__title {
|
||||
margin-bottom: 16px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.modal__title h1 {
|
||||
text-align: center;
|
||||
font-size: 1.6rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal__content {
|
||||
margin: 50px;
|
||||
min-height: 500px;
|
||||
}
|
||||
</style>
|
162
src/components/MemberList/MemberList.vue
Normal file
162
src/components/MemberList/MemberList.vue
Normal file
|
@ -0,0 +1,162 @@
|
|||
<script setup>
|
||||
import NcEmptyContent from "@nextcloud/vue/dist/Components/NcEmptyContent.js"
|
||||
import NcLoadingIcon from "@nextcloud/vue/dist/Components/NcLoadingIcon.js"
|
||||
import NcActions from "@nextcloud/vue/dist/Components/NcActions.js"
|
||||
import NcActionButton from "@nextcloud/vue/dist/Components/NcActionButton.js"
|
||||
import NcButton from "@nextcloud/vue/dist/Components/NcButton.js"
|
||||
import { showError } from "@nextcloud/dialogs"
|
||||
//import MemberListNewItem from "./MemberListNewItem.vue"
|
||||
import MemberListItem from "./MemberListItem.vue"
|
||||
import Plus from "vue-material-design-icons/Plus.vue"
|
||||
import Close from "vue-material-design-icons/Close.vue"
|
||||
import HelpCircle from "vue-material-design-icons/HelpCircle.vue"
|
||||
import api from "../../api.js"
|
||||
import { ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
members: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
const error = ref(undefined);
|
||||
const newItemComponent = ref(null);
|
||||
const addMenuOpen = ref(false);
|
||||
|
||||
const setNewItemComponent = (name) => {
|
||||
this.newItemComponent.value = name
|
||||
this.addMenuOpen.value = false
|
||||
};
|
||||
|
||||
const deleteMember = async (memberId) => {
|
||||
this.loading.value = true
|
||||
try {
|
||||
api.deleteGroupMember(this.groupId, memberId)
|
||||
//this.members.value = this.members.filter((m) => m.id !== memberId)
|
||||
} catch (err) {
|
||||
showError(err.message)
|
||||
} finally {
|
||||
this.loading.value = false
|
||||
}
|
||||
};
|
||||
|
||||
const updateMember = async (memberId, changes) => {
|
||||
this.loading.value = true
|
||||
try {
|
||||
const member = await api.updateGroupMember(this.groupId, memberId, changes)
|
||||
this.members = this.members.map((m) => m.id === member.id ? member : m)
|
||||
} catch (err) {
|
||||
showError(err.message)
|
||||
} finally {
|
||||
this.loading.value = false
|
||||
}
|
||||
};
|
||||
|
||||
const addMember = async ({ mappingId, mappingType }) => {
|
||||
this.loading.value = true
|
||||
try {
|
||||
const _member = await api.addGroupMember(this.groupId, {
|
||||
mappingType,
|
||||
mappingId,
|
||||
type: "member",
|
||||
})
|
||||
this.members.push(_member)
|
||||
this.setNewItemComponent(null)
|
||||
} catch (err) {
|
||||
showError(err.message)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="title">
|
||||
<h3>Mitglieder</h3>
|
||||
<!--<NcActions :disabled="!!newItemComponent" type="secondary">
|
||||
<template #icon>
|
||||
<Plus :size="20" />
|
||||
</template>
|
||||
<NcActionButton icon="icon-group" close-after-click @click="setNewItemComponent('new_item')">
|
||||
Benutzer/Gruppe hinzufügen
|
||||
</NcActionButton>
|
||||
<NcActionButton icon="icon-group" close-after-click @click="setNewItemComponent('new_role_item')">
|
||||
Organisation Rolle hinzufügen
|
||||
</NcActionButton>
|
||||
</NcActions>-->
|
||||
</div>
|
||||
<!--<div v-if="newItemComponent" class="new-item">
|
||||
<NcButton type="tertiary" @click="setNewItemComponent(null)">
|
||||
<template #icon>
|
||||
<Close />
|
||||
</template>
|
||||
</NcButton>
|
||||
<MemberListNewItem v-if="newItemComponent === 'new_item'" :group-id="groupId" @selected="addMember" />
|
||||
</div>-->
|
||||
<table>
|
||||
<thead style="display: contents;">
|
||||
<tr>
|
||||
<th />
|
||||
<th>Name</th>
|
||||
<th>
|
||||
<div style="display: flex; align-items: center;">
|
||||
<span>Typ</span>
|
||||
<HelpCircle v-tooltip="'Für Admins gelten die oben ausgewählten Ordneradministrator*innen Berechtigungen, für Mitglieder die Ordnermitglieder Berechtigungen. Admins haben auf diese Einstellungen Zugriff.'" style="margin-left: 5px;" :size="15" />
|
||||
</div>
|
||||
</th>
|
||||
<th>Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody style="display: contents">
|
||||
<tr v-if="loading">
|
||||
<td colspan="4" style="grid-column-start: 1; grid-column-end: 5">
|
||||
<NcLoadingIcon :size="50" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="!loading && !members.length">
|
||||
<td colspan="4" style="grid-column-start: 1; grid-column-end: 5">
|
||||
<NcEmptyContent title="Keine Gruppenmitglieder" />
|
||||
</td>
|
||||
</tr>
|
||||
<MemberListItem v-for="member in members"
|
||||
:key="member.id"
|
||||
:member="member"
|
||||
@update="updateMember"
|
||||
@delete="deleteMember" />
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
table {
|
||||
width: 100%;
|
||||
margin-bottom: 14px;
|
||||
display: grid;
|
||||
grid-template-columns: max-content minmax(30px, auto) max-content max-content;
|
||||
}
|
||||
table tr {
|
||||
display: contents;
|
||||
}
|
||||
table td, table th {
|
||||
padding: 8px;
|
||||
}
|
||||
.title {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-top: 24px;
|
||||
}
|
||||
h3 {
|
||||
font-weight: bold;
|
||||
margin-right: 24px;
|
||||
}
|
||||
.new-item {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
95
src/components/MemberList/MemberListItem.vue
Normal file
95
src/components/MemberList/MemberListItem.vue
Normal file
|
@ -0,0 +1,95 @@
|
|||
<script setup>
|
||||
import Delete from "vue-material-design-icons/Delete.vue"
|
||||
import NcButton from "@nextcloud/vue/dist/Components/NcButton.js"
|
||||
import NcAvatar from "@nextcloud/vue/dist/Components/NcAvatar.js"
|
||||
import ChevronRight from "vue-material-design-icons/ChevronRight.vue"
|
||||
|
||||
import { computed } from "vue"
|
||||
|
||||
const props = defineProps({
|
||||
member: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const friendlyNameParts = computed(() => props.member.principal.split(" / "));
|
||||
|
||||
const emit = defineEmits(["update", "delete"]);
|
||||
|
||||
const typeOptions = [
|
||||
{ label: "Mitglied", value: 1 },
|
||||
{ label: "Manager", value: 2 },
|
||||
];
|
||||
|
||||
const onTypeSelected = (e) => {
|
||||
emit("update", props.member.id, {
|
||||
type: e.target.value,
|
||||
})
|
||||
};
|
||||
|
||||
const onDeleteClicked = (e) => {
|
||||
emit("delete", props.member.id)
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr>
|
||||
<td>
|
||||
<NcAvatar :user="props.member.type === 1 ? props.member.principal : undefined"
|
||||
:disabled-menu="true"
|
||||
:disabled-tooltip="true"
|
||||
:icon-class="props.member.type === 2 ? 'icon-group' : undefined" />
|
||||
</td>
|
||||
<td>
|
||||
<div class="friendlyNameParts">
|
||||
<div v-for="(friendlyNamePart, index) of friendlyNameParts" :key="'breadcrumb-' + friendlyNamePart" class="friendlyNamePartDiv">
|
||||
<p v-tooltip="friendlyNamePart" class="friendlyNamePartP">
|
||||
{{ friendlyNamePart }}
|
||||
</p>
|
||||
<ChevronRight v-if="index !== friendlyNameParts.length - 1" :size="20" />
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<select :value="props.member.permissionLevel" @input="onTypeSelected">
|
||||
<option v-for="{ label, value} in typeOptions" :key="value" :value="value">
|
||||
{{ label }}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<NcButton type="tertiary-no-background" @click="onDeleteClicked">
|
||||
<template #icon>
|
||||
<Delete :size="20" />
|
||||
</template>
|
||||
</NcButton>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
td {
|
||||
padding: 8px;
|
||||
}
|
||||
.friendlyNameParts {
|
||||
display: inline-flex;
|
||||
max-width: 100%;
|
||||
overflow-x: clip;
|
||||
}
|
||||
.friendlyNamePartP {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
}
|
||||
.friendlyNamePartP:not(:last-child) {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.friendlyNamePartDiv {
|
||||
display: inline-flex;
|
||||
min-width: 20px;
|
||||
}
|
||||
.friendlyNamePartDiv:last-child {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
1
src/components/MemberList/index.js
Normal file
1
src/components/MemberList/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from "./MemberList.vue"
|
93
src/components/Permissions/Permissions.vue
Normal file
93
src/components/Permissions/Permissions.vue
Normal file
|
@ -0,0 +1,93 @@
|
|||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import PermissionsInputRow from "./PermissionsInputRow.vue";
|
||||
|
||||
const props = defineProps({
|
||||
resource: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(["permissionUpdated"]);
|
||||
|
||||
const permissionGroups = computed(() => {
|
||||
return [
|
||||
{
|
||||
field: "managersAclPermission",
|
||||
label: "Resourcenadministrator*innen",
|
||||
value: props.resource.managersAclPermission,
|
||||
mask: 31,
|
||||
},
|
||||
{
|
||||
field: "membersAclPermission",
|
||||
label: "Resourcenmitglieder",
|
||||
value: props.resource.membersAclPermission,
|
||||
mask: 31,
|
||||
},
|
||||
{
|
||||
field: "inheritedAclPermission",
|
||||
label: "Vererbte Berechtigungen",
|
||||
value: props.resource.inheritedAclPermission,
|
||||
mask: 31,
|
||||
},
|
||||
]
|
||||
});
|
||||
|
||||
const permissionUpdated = async (field, value) => {
|
||||
emit("permissionUpdated", { field, value });
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th v-tooltip="t('groupfolders', 'Read')" class="state-column">
|
||||
{{ t('groupfolders', 'Read') }}
|
||||
</th>
|
||||
<th v-tooltip="t('groupfolders', 'Write')" class="state-column">
|
||||
{{ t('groupfolders', 'Write') }}
|
||||
</th>
|
||||
<th v-tooltip="t('groupfolders', 'Create')" class="state-column">
|
||||
{{ t('groupfolders', 'Create') }}
|
||||
</th>
|
||||
<th v-tooltip="t('groupfolders', 'Delete')" class="state-column">
|
||||
{{ t('groupfolders', 'Delete') }}
|
||||
</th>
|
||||
<th v-tooltip="t('groupfolders', 'Share')" class="state-column">
|
||||
{{ t('groupfolders', 'Share') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<PermissionsInputRow v-for="{ field, label, mask, value} in permissionGroups"
|
||||
:key="field"
|
||||
:label="label"
|
||||
:mask="mask"
|
||||
:value="value"
|
||||
@change="(val) => permissionUpdated(field, val)" />
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
table {
|
||||
width: 100%;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
table td, table th {
|
||||
padding: 0
|
||||
}
|
||||
.state-column {
|
||||
text-align: center;
|
||||
width: 44px !important;
|
||||
padding: 3px;
|
||||
}
|
||||
thead .state-column {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
82
src/components/Permissions/PermissionsInputRow.vue
Normal file
82
src/components/Permissions/PermissionsInputRow.vue
Normal file
|
@ -0,0 +1,82 @@
|
|||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import { calcBits, toggleBit } from "../../helpers/permission-helpers.js";
|
||||
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
mask: {
|
||||
type: Number,
|
||||
default: 31,
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(["change"]);
|
||||
|
||||
const calcBitButtonProps = (bitName, bitState) => {
|
||||
const states = {
|
||||
INHERIT_DENY: {
|
||||
tooltipText: t("groupfolders", "Denied (Inherited permission)"),
|
||||
className: "icon-deny inherited",
|
||||
},
|
||||
INHERIT_ALLOW: {
|
||||
tooltipText: t("groupfolders", "Allowed (Inherited permission)"),
|
||||
className: "icon-checkmark inherited",
|
||||
},
|
||||
SELF_DENY: {
|
||||
tooltipText: t("groupfolders", "Denied"),
|
||||
className: "icon-deny",
|
||||
},
|
||||
SELF_ALLOW: {
|
||||
tooltipText: t("groupfolders", "Allowed"),
|
||||
className: "icon-checkmark",
|
||||
},
|
||||
}
|
||||
return {
|
||||
...states[bitState],
|
||||
bitName,
|
||||
}
|
||||
};
|
||||
|
||||
const bitButtonProps = computed(() => Object.entries(calcBits(props.value, props.mask)).map(([bitName, { state }]) => calcBitButtonProps(bitName, state)));
|
||||
|
||||
const onClick = (bitName) => emit("change", toggleBit(props.value, bitName));
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr>
|
||||
<td v-tooltip="props.label">
|
||||
{{ props.label }}
|
||||
</td>
|
||||
|
||||
<td v-for="({ bitName, className, tooltipText }) in bitButtonProps" :key="bitName">
|
||||
<button v-tooltip="tooltipText"
|
||||
:class="className"
|
||||
@click="() => onClick(bitName)" />
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
button {
|
||||
height: 24px;
|
||||
border-color: transparent;
|
||||
}
|
||||
button:hover {
|
||||
height: 24px;
|
||||
border-color: var(--color-primary, #0082c9);
|
||||
}
|
||||
.icon-deny {
|
||||
background-image: url('../../../img/deny.svg');
|
||||
}
|
||||
.inherited {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
1
src/components/Permissions/index.js
Normal file
1
src/components/Permissions/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from "./Permissions.vue"
|
Loading…
Add table
Add a link
Reference in a new issue