summaryrefslogtreecommitdiff
path: root/packages/frontend/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/frontend/src')
-rw-r--r--packages/frontend/src/pages/admin/RolesEditorFormula.vue129
-rw-r--r--packages/frontend/src/pages/admin/roles.editor.vue30
2 files changed, 157 insertions, 2 deletions
diff --git a/packages/frontend/src/pages/admin/RolesEditorFormula.vue b/packages/frontend/src/pages/admin/RolesEditorFormula.vue
new file mode 100644
index 0000000000..76ba639277
--- /dev/null
+++ b/packages/frontend/src/pages/admin/RolesEditorFormula.vue
@@ -0,0 +1,129 @@
+<template>
+<div :class="$style.root" class="_gaps">
+ <div :class="$style.header">
+ <MkSelect v-model="type" :class="$style.typeSelect">
+ <option value="isLocal">{{ i18n.ts._role._condition.isLocal }}</option>
+ <option value="isRemote">{{ i18n.ts._role._condition.isRemote }}</option>
+ <option value="createdLessThan">{{ i18n.ts._role._condition.createdLessThan }}</option>
+ <option value="createdMoreThan">{{ i18n.ts._role._condition.createdMoreThan }}</option>
+ <option value="and">{{ i18n.ts._role._condition.and }}</option>
+ <option value="or">{{ i18n.ts._role._condition.or }}</option>
+ <option value="not">{{ i18n.ts._role._condition.not }}</option>
+ </MkSelect>
+ <button v-if="draggable" class="drag-handle _button" :class="$style.dragHandle">
+ <i class="ti ti-menu-2"></i>
+ </button>
+ </div>
+
+ <div v-if="type === 'and' || type === 'or'" :class="$style.values" class="_gaps">
+ <Sortable v-model="v.values" tag="div" class="_gaps" item-key="id" handle=".drag-handle" :group="{ name: 'roleFormula' }" :animation="150" :swap-threshold="0.5">
+ <template #item="{element}">
+ <div :class="$style.item">
+ <!-- divが無いとエラーになる https://github.com/SortableJS/vue.draggable.next/issues/189 -->
+ <RolesEditorFormula :model-value="element" draggable @update:model-value="updated => valuesItemUpdated(updated)"/>
+ </div>
+ </template>
+ </Sortable>
+ <MkButton rounded style="margin: 0 auto;" @click="addValue"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
+ </div>
+
+ <div v-else-if="type === 'not'" :class="$style.item">
+ <RolesEditorFormula v-model="v.value"/>
+ </div>
+
+ <MkInput v-else-if="type === 'createdLessThan' || type === 'createdMoreThan'" v-model="v.sec" type="number">
+ <template #suffix>sec</template>
+ </MkInput>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed, defineAsyncComponent, ref, watch } from 'vue';
+import { v4 as uuid } from 'uuid';
+import MkInput from '@/components/MkInput.vue';
+import MkSelect from '@/components/MkSelect.vue';
+import MkTextarea from '@/components/MkTextarea.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import MkSwitch from '@/components/MkSwitch.vue';
+import MkButton from '@/components/MkButton.vue';
+import FormSlot from '@/components/form/slot.vue';
+import * as os from '@/os';
+import { i18n } from '@/i18n';
+import { deepClone } from '@/scripts/clone';
+
+const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
+
+const emit = defineEmits<{
+ (ev: 'update:modelValue', value: any): void;
+}>();
+
+const props = defineProps<{
+ modelValue: any;
+ draggable?: boolean;
+}>();
+
+const v = ref(deepClone(props.modelValue));
+
+watch(() => props.modelValue, () => {
+ if (JSON.stringify(props.modelValue) === JSON.stringify(v.value)) return;
+ v.value = deepClone(props.modelValue);
+}, { deep: true });
+
+watch(v, () => {
+ emit('update:modelValue', v.value);
+}, { deep: true });
+
+const type = computed({
+ get: () => v.value.type,
+ set: (t) => {
+ if (t === 'and') v.value.values = [];
+ if (t === 'or') v.value.values = [];
+ if (t === 'not') v.value.value = { id: uuid(), type: 'isRemote' };
+ if (t === 'createdLessThan') v.value.sec = 86400;
+ if (t === 'createdMoreThan') v.value.sec = 86400;
+ v.value.type = t;
+ },
+});
+
+function addValue() {
+ v.value.values.push({ id: uuid(), type: 'isRemote' });
+}
+
+function valuesItemUpdated(item) {
+ const i = v.value.values.findIndex(_item => _item.id === item.id);
+ v.value.values[i] = item;
+}
+</script>
+
+<style lang="scss" module>
+.root {
+
+}
+
+.header {
+ display: flex;
+}
+
+.typeSelect {
+ flex: 1;
+}
+
+.dragHandle {
+ cursor: move;
+ margin-left: 10px;
+}
+
+.item {
+ border: solid 2px var(--divider);
+ border-radius: var(--radius);
+ padding: 12px;
+
+ &:hover {
+ border-color: var(--accent);
+ }
+}
+
+.values {
+
+}
+</style>
diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue
index b8e45cda50..f584c5c8bf 100644
--- a/packages/frontend/src/pages/admin/roles.editor.vue
+++ b/packages/frontend/src/pages/admin/roles.editor.vue
@@ -15,12 +15,26 @@
<MkSelect v-model="rolePermission" :readonly="readonly">
<template #label>{{ i18n.ts._role.permission }}</template>
- <template #caption><div v-html="i18n.ts._role.descriptionOfType.replaceAll('\n', '<br>')"></div></template>
+ <template #caption><div v-html="i18n.ts._role.descriptionOfPermission.replaceAll('\n', '<br>')"></div></template>
<option value="normal">{{ i18n.ts.normalUser }}</option>
<option value="moderator">{{ i18n.ts.moderator }}</option>
<option value="administrator">{{ i18n.ts.administrator }}</option>
</MkSelect>
+ <MkSelect v-model="target" :readonly="readonly">
+ <template #label>{{ i18n.ts._role.assignTarget }}</template>
+ <template #caption><div v-html="i18n.ts._role.descriptionOfAssignTarget.replaceAll('\n', '<br>')"></div></template>
+ <option value="manual">{{ i18n.ts._role.manual }}</option>
+ <option value="conditional">{{ i18n.ts._role.conditional }}</option>
+ </MkSelect>
+
+ <MkFolder v-if="target === 'conditional'" default-open>
+ <template #label>{{ i18n.ts._role.condition }}</template>
+ <div class="_gaps">
+ <RolesEditorFormula v-model="condFormula"/>
+ </div>
+ </MkFolder>
+
<FormSlot>
<template #label>{{ i18n.ts._role.options }}</template>
<div class="_gaps_s">
@@ -107,7 +121,9 @@
</template>
<script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, watch } from 'vue';
+import { v4 as uuid } from 'uuid';
+import RolesEditorFormula from './RolesEditorFormula.vue';
import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkTextarea from '@/components/MkTextarea.vue';
@@ -134,6 +150,8 @@ let name = $ref(role?.name ?? 'New Role');
let description = $ref(role?.description ?? '');
let rolePermission = $ref(role?.isAdministrator ? 'administrator' : role?.isModerator ? 'moderator' : 'normal');
let color = $ref(role?.color ?? null);
+let target = $ref(role?.target ?? 'manual');
+let condFormula = $ref(role?.condFormula ?? { id: uuid(), type: 'isRemote' });
let isPublic = $ref(role?.isPublic ?? false);
let canEditMembersByModerator = $ref(role?.canEditMembersByModerator ?? false);
let options_gtlAvailable_useDefault = $ref(role?.options?.gtlAvailable?.useDefault ?? true);
@@ -147,6 +165,10 @@ let options_driveCapacityMb_value = $ref(role?.options?.driveCapacityMb?.value ?
let options_antennaLimit_useDefault = $ref(role?.options?.antennaLimit?.useDefault ?? true);
let options_antennaLimit_value = $ref(role?.options?.antennaLimit?.value ?? 0);
+watch($$(condFormula), () => {
+ console.log(condFormula);
+}, { deep: true });
+
function getOptions() {
return {
gtlAvailable: { useDefault: options_gtlAvailable_useDefault, value: options_gtlAvailable_value },
@@ -165,6 +187,8 @@ async function save() {
name,
description,
color: color === '' ? null : color,
+ target,
+ condFormula,
isAdministrator: rolePermission === 'administrator',
isModerator: rolePermission === 'moderator',
isPublic,
@@ -177,6 +201,8 @@ async function save() {
name,
description,
color: color === '' ? null : color,
+ target,
+ condFormula,
isAdministrator: rolePermission === 'administrator',
isModerator: rolePermission === 'moderator',
isPublic,