feat: user editing support using yaml

Signed-off-by: Ryan Wang <i@ryanc.cc>
pull/3445/head
Ryan Wang 2022-07-22 17:04:30 +08:00
parent e218818a59
commit 51c87c1519
8 changed files with 145 additions and 53 deletions

View File

@ -49,7 +49,8 @@
"vue": "^3.2.37",
"vue-filepond": "^7.0.3",
"vue-grid-layout": "3.0.0-beta1",
"vue-router": "^4.1.2"
"vue-router": "^4.1.2",
"yaml": "^2.1.1"
},
"devDependencies": {
"@changesets/cli": "^2.23.2",

View File

@ -49,7 +49,7 @@
"@rollup/plugin-typescript": "^8.3.3",
"histoire": "^0.7.9",
"unplugin-icons": "^0.14.7",
"vite-plugin-dts": "^1.3.1"
"vite-plugin-dts": "^1.4.0"
},
"peerDependencies": {
"vue": "^3.2.37",
@ -64,6 +64,8 @@
},
"dependencies": {
"@codemirror/commands": "^6.0.1",
"@codemirror/language": "^6.2.1",
"@codemirror/legacy-modes": "^6.1.0",
"@codemirror/state": "^6.1.0",
"@codemirror/view": "^6.1.0",
"codemirror": "^6.0.1"

View File

@ -15,3 +15,4 @@ export * from "./components/textarea";
export * from "./components/switch";
export * from "./components/dialog";
export * from "./components/pagination";
export * from "./components/codemirror";

View File

@ -10,7 +10,7 @@ function initState() {
<template>
<Story :initState="initState" title="Codemirror">
<template #default="{ state }">
<VCodemirror v-model="state.value" height="500px" language="javascript" />
<VCodemirror v-model="state.value" height="500px" language="yaml" />
</template>
</Story>
</template>

View File

@ -5,6 +5,12 @@ import type { EditorStateConfig } from "@codemirror/state";
import { EditorState } from "@codemirror/state";
import { EditorView } from "@codemirror/view";
import { basicSetup } from "codemirror";
import { StreamLanguage } from "@codemirror/language";
import { yaml } from "@codemirror/legacy-modes/mode/yaml";
const languages = {
yaml: StreamLanguage.define(yaml),
};
const props = defineProps({
modelValue: {
@ -15,6 +21,10 @@ const props = defineProps({
type: String,
default: "auto",
},
language: {
type: String as PropType<"yaml">,
default: "yaml",
},
extensions: {
type: Array as PropType<EditorStateConfig["extensions"]>,
default: () => [],
@ -41,6 +51,7 @@ const createCmEditor = () => {
basicSetup,
EditorView.lineWrapping,
customTheme,
languages[props.language],
EditorView.updateListener.of((viewUpdate) => {
if (viewUpdate.docChanged) {
const doc = viewUpdate.state.doc.toString();

View File

@ -39,6 +39,7 @@ import IconShieldUser from "~icons/ri/shield-user-line";
import IconGitBranch from "~icons/ri/git-branch-line";
import IconStopCircle from "~icons/ri/stop-circle-line";
import IconForbidLine from "~icons/ri/forbid-line";
import IconCodeBoxLine from "~icons/ri/code-box-line";
export {
IconDashboard,
@ -82,4 +83,5 @@ export {
IconGitBranch,
IconStopCircle,
IconForbidLine,
IconCodeBoxLine,
};

View File

@ -71,6 +71,7 @@ importers:
vue-grid-layout: 3.0.0-beta1
vue-router: ^4.1.2
vue-tsc: ^0.38.8
yaml: ^2.1.1
dependencies:
'@formkit/addons': 1.0.0-beta.9_vue@3.2.37
'@formkit/core': 1.0.0-beta.9
@ -97,6 +98,7 @@ importers:
vue-filepond: 7.0.3_filepond@4.30.4+vue@3.2.37
vue-grid-layout: 3.0.0-beta1
vue-router: 4.1.2_vue@3.2.37
yaml: 2.1.1
devDependencies:
'@changesets/cli': 2.23.2
'@rushstack/eslint-patch': 1.1.4
@ -144,6 +146,8 @@ importers:
packages/components:
specifiers:
'@codemirror/commands': ^6.0.1
'@codemirror/language': ^6.2.1
'@codemirror/legacy-modes': ^6.1.0
'@codemirror/state': ^6.1.0
'@codemirror/view': ^6.1.0
'@iconify-json/ri': ^1.1.3
@ -151,9 +155,11 @@ importers:
codemirror: ^6.0.1
histoire: ^0.7.9
unplugin-icons: ^0.14.7
vite-plugin-dts: ^1.3.1
vite-plugin-dts: ^1.4.0
dependencies:
'@codemirror/commands': 6.0.1
'@codemirror/language': 6.2.1
'@codemirror/legacy-modes': 6.1.0
'@codemirror/state': 6.1.0
'@codemirror/view': 6.1.0
codemirror: 6.0.1
@ -162,7 +168,7 @@ importers:
'@rollup/plugin-typescript': 8.3.3
histoire: 0.7.9
unplugin-icons: 0.14.7
vite-plugin-dts: 1.3.1
vite-plugin-dts: 1.4.0
packages/shared:
specifiers:
@ -1644,6 +1650,12 @@ packages:
style-mod: 4.0.0
dev: false
/@codemirror/legacy-modes/6.1.0:
resolution: {integrity: sha512-V/PgGpndkZeTn3Hdlg/gd8MLFdyvTCIX+iwJzjUw5iNziWiNsAY8X0jvf7m3gSfxnKkNzmid6l0g4rYSpiDaCw==}
dependencies:
'@codemirror/language': 6.2.1
dev: false
/@codemirror/lint/6.0.0:
resolution: {integrity: sha512-nUUXcJW1Xp54kNs+a1ToPLK8MadO0rMTnJB8Zk4Z8gBdrN0kqV7uvUraU/T2yqg+grDNR38Vmy/MrhQN/RgwiA==}
dependencies:
@ -1652,8 +1664,8 @@ packages:
crelt: 1.0.5
dev: false
/@codemirror/search/6.0.0:
resolution: {integrity: sha512-rL0rd3AhI0TAsaJPUaEwC63KHLO7KL0Z/dYozXj6E7L3wNHRyx7RfE0/j5HsIf912EE5n2PCb4Vg0rGYmDv4UQ==}
/@codemirror/search/6.0.1:
resolution: {integrity: sha512-uOinkOrM+daMduCgMPomDfKLr7drGHB4jHl3Vq6xY2WRlL7MkNsBE0b+XHYa/Mee2npsJOgwvkW4n1lMFeBW2Q==}
dependencies:
'@codemirror/state': 6.1.0
'@codemirror/view': 6.1.0
@ -3659,7 +3671,7 @@ packages:
'@codemirror/commands': 6.0.1
'@codemirror/language': 6.2.1
'@codemirror/lint': 6.0.0
'@codemirror/search': 6.0.0
'@codemirror/search': 6.0.1
'@codemirror/state': 6.1.0
'@codemirror/view': 6.1.0
dev: false
@ -7923,6 +7935,23 @@ packages:
- supports-color
dev: true
/vite-plugin-dts/1.4.0:
resolution: {integrity: sha512-RyDCjQzVxUeDqF+Rl1hQT+t/rKmvfvo04gaGV/l3597FpeIWGKtNF1S4x509Kx1AfHOjLa1JdmjVgnSEIv+lpw==}
engines: {node: '>=12.0.0'}
peerDependencies:
vite: '>=2.4.4'
dependencies:
'@microsoft/api-extractor': 7.23.2
'@rushstack/node-core-library': 3.45.5
chalk: 4.1.2
debug: 4.3.4
fast-glob: 3.2.11
fs-extra: 10.1.0
ts-morph: 14.0.0
transitivePeerDependencies:
- supports-color
dev: true
/vite-plugin-externals/0.5.1_vite@2.9.14:
resolution: {integrity: sha512-HvRFG5y9wXoJUG9FSbSp9ikOiJRh7EzN6tJC5oIOcEj+19GUw9Z1NNCPFtAmX75Ajcr10FdELKNmuXS3lExkcg==}
peerDependencies:
@ -8512,6 +8541,11 @@ packages:
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
engines: {node: '>= 6'}
/yaml/2.1.1:
resolution: {integrity: sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==}
engines: {node: '>= 14'}
dev: false
/yargs-parser/18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}

View File

@ -3,10 +3,18 @@ import type { PropType } from "vue";
import { computed, onMounted, ref, watch } from "vue";
import { apiClient } from "@halo-dev/admin-shared";
import type { Role, User } from "@halo-dev/api-client";
import { IconSave, VButton, VModal } from "@halo-dev/components";
import {
IconCodeBoxLine,
IconEye,
IconSave,
VButton,
VCodemirror,
VModal,
} from "@halo-dev/components";
import { v4 as uuid } from "uuid";
import { roleLabels } from "@/constants/labels";
import { rbacAnnotations } from "@/constants/annotations";
import YAML from "yaml";
const props = defineProps({
visible: {
@ -24,6 +32,8 @@ const emit = defineEmits(["update:visible", "close"]);
interface EditingFormState {
user: User;
saving: boolean;
rawMode: boolean;
raw: string;
}
const roles = ref<Role[]>([]);
@ -46,6 +56,8 @@ const editingFormState = ref<EditingFormState>({
},
},
saving: false,
rawMode: false,
raw: "",
});
const selectedRole = ref("");
@ -57,6 +69,10 @@ const creationModalTitle = computed(() => {
return isUpdateMode.value ? "编辑用户" : "新增用户";
});
const modalWidth = computed(() => {
return editingFormState.value.rawMode ? 800 : 700;
});
const basicRoles = computed(() => {
return roles.value.filter(
(role) => role.metadata?.labels?.[roleLabels.TEMPLATE] !== "true"
@ -118,38 +134,62 @@ const handleCreateUser = async () => {
}
};
const handleRawModeChange = () => {
editingFormState.value.rawMode = !editingFormState.value.rawMode;
if (editingFormState.value.rawMode) {
editingFormState.value.raw = YAML.stringify(editingFormState.value.user);
} else {
editingFormState.value.user = YAML.parse(editingFormState.value.raw);
}
};
onMounted(handleFetchRoles);
</script>
<template>
<VModal
:title="creationModalTitle"
:visible="visible"
:width="700"
:width="modalWidth"
@update:visible="handleVisibleChange"
>
<FormKit id="user-form" type="form" @submit="handleCreateUser">
<FormKit
v-model="editingFormState.user.metadata.name"
:disabled="true"
label="用户名"
type="text"
validation="required"
></FormKit>
<FormKit
v-model="editingFormState.user.spec.displayName"
label="显示名称"
type="text"
validation="required"
></FormKit>
<FormKit
v-model="editingFormState.user.spec.email"
label="电子邮箱"
type="email"
validation="required"
></FormKit>
<FormKit
v-model="selectedRole"
:options="
<template #actions>
<div class="modal-header-action" @click="handleRawModeChange">
<IconCodeBoxLine v-if="!editingFormState.rawMode" />
<IconEye v-else />
</div>
</template>
<VCodemirror
v-show="editingFormState.rawMode"
v-model="editingFormState.raw"
height="50vh"
language="yaml"
/>
<div v-show="!editingFormState.rawMode">
<FormKit id="user-form" type="form" @submit="handleCreateUser">
<FormKit
v-model="editingFormState.user.metadata.name"
:disabled="true"
label="用户名"
type="text"
validation="required"
></FormKit>
<FormKit
v-model="editingFormState.user.spec.displayName"
label="显示名称"
type="text"
validation="required"
></FormKit>
<FormKit
v-model="editingFormState.user.spec.email"
label="电子邮箱"
type="email"
validation="required"
></FormKit>
<FormKit
v-model="selectedRole"
:options="
basicRoles.map((role:Role) => {
return {
label: role.metadata?.annotations?.[rbacAnnotations.DISPLAY_NAME] || role.metadata.name,
@ -157,26 +197,27 @@ onMounted(handleFetchRoles);
};
})
"
label="角色"
type="select"
validation="required"
></FormKit>
<FormKit
v-model="editingFormState.user.spec.phone"
label="手机号"
type="text"
></FormKit>
<FormKit
v-model="editingFormState.user.spec.avatar"
label="头像"
type="text"
></FormKit>
<FormKit
v-model="editingFormState.user.spec.bio"
label="描述"
type="textarea"
></FormKit>
</FormKit>
label="角色"
type="select"
validation="required"
></FormKit>
<FormKit
v-model="editingFormState.user.spec.phone"
label="手机号"
type="text"
></FormKit>
<FormKit
v-model="editingFormState.user.spec.avatar"
label="头像"
type="text"
></FormKit>
<FormKit
v-model="editingFormState.user.spec.bio"
label="描述"
type="textarea"
></FormKit>
</FormKit>
</div>
<template #footer>
<VButton
:loading="editingFormState.saving"