From 69d7cfc5ce2ec9892290403cdc353e7ea3bd6a96 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 4 Jan 2022 22:42:04 +0900
Subject: [PATCH] tweak ui

---
 .../client/src/components/debobigego/base.vue |  65 ----
 .../src/components/debobigego/button.vue      |  81 -----
 .../src/components/debobigego/debobigego.scss |  52 ----
 .../src/components/debobigego/group.vue       |  78 -----
 .../client/src/components/debobigego/info.vue |  47 ---
 .../src/components/debobigego/input.vue       | 292 ------------------
 .../components/debobigego/key-value-view.vue  |  38 ---
 .../client/src/components/debobigego/link.vue | 103 ------
 .../src/components/debobigego/object-view.vue | 102 ------
 .../src/components/debobigego/pagination.vue  |  42 ---
 .../src/components/debobigego/radios.vue      | 112 -------
 .../src/components/debobigego/range.vue       | 122 --------
 .../src/components/debobigego/select.vue      | 145 ---------
 .../src/components/debobigego/suspense.vue    | 101 ------
 .../src/components/debobigego/switch.vue      | 132 --------
 .../src/components/debobigego/textarea.vue    | 161 ----------
 .../src/components/debobigego/tuple.vue       |  36 ---
 .../client/src/components/form/folder.vue     |  10 +-
 .../client/src/components/object-view.vue     |  29 ++
 packages/client/src/pages/admin/database.vue  |   2 +-
 packages/client/src/pages/gallery/edit.vue    |  23 +-
 packages/client/src/pages/instance-info.vue   |   9 +-
 packages/client/src/pages/theme-editor.vue    | 220 ++++++-------
 packages/client/src/pages/user-info.vue       |   9 +-
 24 files changed, 170 insertions(+), 1841 deletions(-)
 delete mode 100644 packages/client/src/components/debobigego/base.vue
 delete mode 100644 packages/client/src/components/debobigego/button.vue
 delete mode 100644 packages/client/src/components/debobigego/debobigego.scss
 delete mode 100644 packages/client/src/components/debobigego/group.vue
 delete mode 100644 packages/client/src/components/debobigego/info.vue
 delete mode 100644 packages/client/src/components/debobigego/input.vue
 delete mode 100644 packages/client/src/components/debobigego/key-value-view.vue
 delete mode 100644 packages/client/src/components/debobigego/link.vue
 delete mode 100644 packages/client/src/components/debobigego/object-view.vue
 delete mode 100644 packages/client/src/components/debobigego/pagination.vue
 delete mode 100644 packages/client/src/components/debobigego/radios.vue
 delete mode 100644 packages/client/src/components/debobigego/range.vue
 delete mode 100644 packages/client/src/components/debobigego/select.vue
 delete mode 100644 packages/client/src/components/debobigego/suspense.vue
 delete mode 100644 packages/client/src/components/debobigego/switch.vue
 delete mode 100644 packages/client/src/components/debobigego/textarea.vue
 delete mode 100644 packages/client/src/components/debobigego/tuple.vue
 create mode 100644 packages/client/src/components/object-view.vue

diff --git a/packages/client/src/components/debobigego/base.vue b/packages/client/src/components/debobigego/base.vue
deleted file mode 100644
index 9ed59abc69..0000000000
--- a/packages/client/src/components/debobigego/base.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<template>
-<div v-size="{ max: [400] }" class="rbusrurv" :class="{ wide: forceWide }">
-	<slot></slot>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-
-export default defineComponent({
-	props: {
-		forceWide: {
-			type: Boolean,
-			required: false,
-			default: false,
-		}
-	}
-});
-</script>
-
-<style lang="scss" scoped>
-.rbusrurv {
-	// 他のCSSからも参照されるので消さないように
-	--debobigegoXPadding: 32px;
-	--debobigegoYPadding: 32px;
-
-	--debobigegoContentHMargin: 16px;
-
-	font-size: 95%;
-	line-height: 1.3em;
-	background: var(--bg);
-	padding: var(--debobigegoYPadding) var(--debobigegoXPadding);
-	max-width: 750px;
-	margin: 0 auto;
-
-	&:not(.wide).max-width_400px {
-		--debobigegoXPadding: 0px;
-
-		> ::v-deep(*) {
-			._debobigegoPanel {
-				border: solid 0.5px var(--divider);
-				border-radius: 0;
-				border-left: none;
-				border-right: none;
-			}
-
-			._debobigego_group {
-				> *:not(._debobigegoNoConcat) {
-					&:not(:last-child):not(._debobigegoNoConcatPrev) {
-						&._debobigegoPanel, ._debobigegoPanel {
-							border-bottom: solid 0.5px var(--divider);
-						}
-					}
-
-					&:not(:first-child):not(._debobigegoNoConcatNext) {
-						&._debobigegoPanel, ._debobigegoPanel {
-							border-top: none;
-						}
-					}
-				}
-			}
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/components/debobigego/button.vue b/packages/client/src/components/debobigego/button.vue
deleted file mode 100644
index b883e817a4..0000000000
--- a/packages/client/src/components/debobigego/button.vue
+++ /dev/null
@@ -1,81 +0,0 @@
-<template>
-<div class="yzpgjkxe _debobigegoItem">
-	<div class="_debobigegoLabel"><slot name="label"></slot></div>
-	<button class="main _button _debobigegoPanel _debobigegoClickable" :class="{ center, primary, danger }">
-		<slot></slot>
-		<div class="suffix">
-			<slot name="suffix"></slot>
-			<div class="icon">
-				<slot name="suffixIcon"></slot>
-			</div>
-		</div>
-	</button>
-	<div class="_debobigegoCaption"><slot name="desc"></slot></div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import './debobigego.scss';
-
-export default defineComponent({
-	props: {
-		primary: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		danger: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		disabled: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		center: {
-			type: Boolean,
-			required: false,
-			default: true,
-		}
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.yzpgjkxe {
-	> .main {
-		display: flex;
-		width: 100%;
-		box-sizing: border-box;
-		padding: 14px 16px;
-		text-align: left;
-		align-items: center;
-
-		&.center {
-			display: block;
-			text-align: center;
-		}
-
-		&.primary {
-			color: var(--accent);
-		}
-
-		&.danger {
-			color: #ff2a2a;
-		}
-
-		> .suffix {
-			display: inline-flex;
-			margin-left: auto;
-			opacity: 0.7;
-
-			> .icon {
-				margin-left: 1em;
-			}
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/components/debobigego/debobigego.scss b/packages/client/src/components/debobigego/debobigego.scss
deleted file mode 100644
index 833b656b66..0000000000
--- a/packages/client/src/components/debobigego/debobigego.scss
+++ /dev/null
@@ -1,52 +0,0 @@
-._debobigegoPanel {
-	background: var(--panel);
-	border-radius: var(--radius);
-	transition: background 0.2s ease;
-
-	&._debobigegoClickable {
-		&:hover {
-			//background: var(--panelHighlight);
-		}
-
-		&:active {
-			background: var(--panelHighlight);
-			transition: background 0s;
-		}
-	}
-}
-
-._debobigegoLabel,
-._debobigegoCaption {
-	font-size: 80%;
-	color: var(--fgTransparentWeak);
-
-	&:empty {
-		display: none;
-	}
-}
-
-._debobigegoLabel {
-	position: sticky;
-	top: var(--stickyTop, 0px);
-	z-index: 2;
-	margin: -8px calc(var(--debobigegoXPadding) * -1) 0 calc(var(--debobigegoXPadding) * -1);
-	padding: 8px calc(var(--debobigegoContentHMargin) + var(--debobigegoXPadding)) 8px calc(var(--debobigegoContentHMargin) + var(--debobigegoXPadding));
-	background: var(--X17);
-	-webkit-backdrop-filter: var(--blur, blur(10px));
-	backdrop-filter: var(--blur, blur(10px));
-}
-
-._themeChanging_ ._debobigegoLabel {
-	transition: none !important;
-	background: transparent;
-}
-
-._debobigegoCaption {
-	padding: 8px var(--debobigegoContentHMargin) 0 var(--debobigegoContentHMargin);
-}
-
-._debobigegoItem {
-	& + ._debobigegoItem {
-		margin-top: 24px;
-	}
-}
diff --git a/packages/client/src/components/debobigego/group.vue b/packages/client/src/components/debobigego/group.vue
deleted file mode 100644
index 871d3c8dba..0000000000
--- a/packages/client/src/components/debobigego/group.vue
+++ /dev/null
@@ -1,78 +0,0 @@
-<template>
-<div v-size="{ max: [500] }" v-sticky-container class="vrtktovg _debobigegoItem _debobigegoNoConcat">
-	<div class="_debobigegoLabel"><slot name="label"></slot></div>
-	<div ref="child" class="main _debobigego_group">
-		<slot></slot>
-	</div>
-	<div class="_debobigegoCaption"><slot name="caption"></slot></div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, onMounted, ref } from 'vue';
-
-export default defineComponent({
-	setup(props, context) {
-		const child = ref<HTMLElement | null>(null);
-
-		const scanChild = () => {
-			if (child.value == null) return;
-			const els = Array.from(child.value.children);
-			for (let i = 0; i < els.length; i++) {
-				const el = els[i];
-				if (el.classList.contains('_debobigegoNoConcat')) {
-					if (els[i - 1]) els[i - 1].classList.add('_debobigegoNoConcatPrev');
-					if (els[i + 1]) els[i + 1].classList.add('_debobigegoNoConcatNext');
-				}
-			}
-		};
-
-		onMounted(() => {
-			scanChild();
-
-			const observer = new MutationObserver(records => {
-				scanChild();
-			});
-
-			observer.observe(child.value, {
-				childList: true,
-				subtree: false,
-				attributes: false,
-				characterData: false,
-			});
-		});
-
-		return {
-			child
-		};
-	}
-});
-</script>
-
-<style lang="scss" scoped>
-.vrtktovg {
-	> .main {
-		> ::v-deep(*):not(._debobigegoNoConcat) {
-			&:not(._debobigegoNoConcatNext) {
-				margin: 0;
-			}
-
-			&:not(:last-child):not(._debobigegoNoConcatPrev) {
-				&._debobigegoPanel, ._debobigegoPanel {
-					border-bottom: solid 0.5px var(--divider);
-					border-bottom-left-radius: 0;
-					border-bottom-right-radius: 0;
-				}
-			}
-
-			&:not(:first-child):not(._debobigegoNoConcatNext) {
-				&._debobigegoPanel, ._debobigegoPanel {
-					border-top: none;
-					border-top-left-radius: 0;
-					border-top-right-radius: 0;
-				}
-			}
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/components/debobigego/info.vue b/packages/client/src/components/debobigego/info.vue
deleted file mode 100644
index 41afb03304..0000000000
--- a/packages/client/src/components/debobigego/info.vue
+++ /dev/null
@@ -1,47 +0,0 @@
-<template>
-<div class="fzenkabp _debobigegoItem">
-	<div class="_debobigegoPanel" :class="{ warn }">
-		<i v-if="warn" class="fas fa-exclamation-triangle"></i>
-		<i v-else class="fas fa-info-circle"></i>
-		<slot></slot>
-	</div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-
-export default defineComponent({
-	props: {
-		warn: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-	},
-	data() {
-		return {
-		};
-	}
-});
-</script>
-
-<style lang="scss" scoped>
-.fzenkabp {
-	> div {
-		padding: 14px 16px;
-		font-size: 90%;
-		background: var(--infoBg);
-		color: var(--infoFg);
-
-		&.warn {
-			background: var(--infoWarnBg);
-			color: var(--infoWarnFg);
-		}
-
-		> i {
-			margin-right: 4px;
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/components/debobigego/input.vue b/packages/client/src/components/debobigego/input.vue
deleted file mode 100644
index 6228a33fe4..0000000000
--- a/packages/client/src/components/debobigego/input.vue
+++ /dev/null
@@ -1,292 +0,0 @@
-<template>
-<FormGroup class="_debobigegoItem">
-	<template #label><slot></slot></template>
-	<div class="ztzhwixg _debobigegoItem" :class="{ inline, disabled }">
-		<div ref="icon" class="icon"><slot name="icon"></slot></div>
-		<div class="input _debobigegoPanel">
-			<div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div>
-			<input ref="inputEl"
-				v-model="v"
-				:type="type"
-				:disabled="disabled"
-				:required="required"
-				:readonly="readonly"
-				:placeholder="placeholder"
-				:pattern="pattern"
-				:autocomplete="autocomplete"
-				:spellcheck="spellcheck"
-				:step="step"
-				:list="id"
-				@focus="focused = true"
-				@blur="focused = false"
-				@keydown="onKeydown($event)"
-				@input="onInput"
-			>
-			<datalist v-if="datalist" :id="id">
-				<option v-for="data in datalist" :value="data"/>
-			</datalist>
-			<div ref="suffixEl" class="suffix"><slot name="suffix"></slot></div>
-		</div>
-	</div>
-	<template #caption><slot name="desc"></slot></template>
-
-	<FormButton v-if="manualSave && changed" primary @click="updated"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
-</FormGroup>
-</template>
-
-<script lang="ts">
-import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue';
-import './debobigego.scss';
-import FormButton from './button.vue';
-import FormGroup from './group.vue';
-
-export default defineComponent({
-	components: {
-		FormGroup,
-		FormButton,
-	},
-	props: {
-		modelValue: {
-			required: false
-		},
-		type: {
-			type: String,
-			required: false
-		},
-		required: {
-			type: Boolean,
-			required: false
-		},
-		readonly: {
-			type: Boolean,
-			required: false
-		},
-		disabled: {
-			type: Boolean,
-			required: false
-		},
-		pattern: {
-			type: String,
-			required: false
-		},
-		placeholder: {
-			type: String,
-			required: false
-		},
-		autofocus: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-		autocomplete: {
-			required: false
-		},
-		spellcheck: {
-			required: false
-		},
-		step: {
-			required: false
-		},
-		datalist: {
-			type: Array,
-			required: false,
-		},
-		inline: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-		manualSave: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-	},
-	emits: ['change', 'keydown', 'enter', 'update:modelValue'],
-	setup(props, context) {
-		const { modelValue, type, autofocus } = toRefs(props);
-		const v = ref(modelValue.value);
-		const id = Math.random().toString(); // TODO: uuid?
-		const focused = ref(false);
-		const changed = ref(false);
-		const invalid = ref(false);
-		const filled = computed(() => v.value !== '' && v.value != null);
-		const inputEl = ref(null);
-		const prefixEl = ref(null);
-		const suffixEl = ref(null);
-
-		const focus = () => inputEl.value.focus();
-		const onInput = (ev) => {
-			changed.value = true;
-			context.emit('change', ev);
-		};
-		const onKeydown = (ev: KeyboardEvent) => {
-			context.emit('keydown', ev);
-
-			if (ev.code === 'Enter') {
-				context.emit('enter');
-			}
-		};
-
-		const updated = () => {
-			changed.value = false;
-			if (type?.value === 'number') {
-				context.emit('update:modelValue', parseFloat(v.value));
-			} else {
-				context.emit('update:modelValue', v.value);
-			}
-		};
-
-		watch(modelValue.value, newValue => {
-			v.value = newValue;
-		});
-
-		watch(v, newValue => {
-			if (!props.manualSave) {
-				updated();
-			}
-
-			invalid.value = inputEl.value.validity.badInput;
-		});
-
-		onMounted(() => {
-			nextTick(() => {
-				if (autofocus.value) {
-					focus();
-				}
-
-				// このコンポーネントが作成された時、非表示状態である場合がある
-				// 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する
-				const clock = setInterval(() => {
-					if (prefixEl.value) {
-						if (prefixEl.value.offsetWidth) {
-							inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px';
-						}
-					}
-					if (suffixEl.value) {
-						if (suffixEl.value.offsetWidth) {
-							inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + 'px';
-						}
-					}
-				}, 100);
-
-				onUnmounted(() => {
-					clearInterval(clock);
-				});
-			});
-		});
-
-		return {
-			id,
-			v,
-			focused,
-			invalid,
-			changed,
-			filled,
-			inputEl,
-			prefixEl,
-			suffixEl,
-			focus,
-			onInput,
-			onKeydown,
-			updated,
-		};
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.ztzhwixg {
-	position: relative;
-
-	> .icon {
-		position: absolute;
-		top: 0;
-		left: 0;
-		width: 24px;
-		text-align: center;
-		line-height: 32px;
-
-		&:not(:empty) + .input {
-			margin-left: 28px;
-		}
-	}
-
-	> .input {
-		$height: 48px;
-		position: relative;
-
-		> input {
-			display: block;
-			height: $height;
-			width: 100%;
-			margin: 0;
-			padding: 0 16px;
-			font: inherit;
-			font-weight: normal;
-			font-size: 1em;
-			line-height: $height;
-			color: var(--inputText);
-			background: transparent;
-			border: none;
-			border-radius: 0;
-			outline: none;
-			box-shadow: none;
-			box-sizing: border-box;
-
-			&[type='file'] {
-				display: none;
-			}
-		}
-
-		> .prefix,
-		> .suffix {
-			display: block;
-			position: absolute;
-			z-index: 1;
-			top: 0;
-			padding: 0 16px;
-			font-size: 1em;
-			line-height: $height;
-			color: var(--inputLabel);
-			pointer-events: none;
-
-			&:empty {
-				display: none;
-			}
-
-			> * {
-				display: inline-block;
-				min-width: 16px;
-				max-width: 150px;
-				overflow: hidden;
-				white-space: nowrap;
-				text-overflow: ellipsis;
-			}
-		}
-
-		> .prefix {
-			left: 0;
-			padding-right: 8px;
-		}
-
-		> .suffix {
-			right: 0;
-			padding-left: 8px;
-		}
-	}
-
-	&.inline {
-		display: inline-block;
-		margin: 0;
-	}
-
-	&.disabled {
-		opacity: 0.7;
-
-		&, * {
-			cursor: not-allowed !important;
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/components/debobigego/key-value-view.vue b/packages/client/src/components/debobigego/key-value-view.vue
deleted file mode 100644
index 0e034a2d54..0000000000
--- a/packages/client/src/components/debobigego/key-value-view.vue
+++ /dev/null
@@ -1,38 +0,0 @@
-<template>
-<div class="_debobigegoItem">
-	<div class="_debobigegoPanel anocepby">
-		<span class="key"><slot name="key"></slot></span>
-		<span class="value"><slot name="value"></slot></span>
-	</div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import './debobigego.scss';
-
-export default defineComponent({
-
-});
-</script>
-
-<style lang="scss" scoped>
-.anocepby {
-	display: flex;
-	align-items: center;
-	padding: 14px var(--debobigegoContentHMargin);
-
-	> .key {
-		margin-right: 12px;
-		white-space: nowrap;
-	}
-
-	> .value {
-		margin-left: auto;
-		opacity: 0.7;
-		text-overflow: ellipsis;
-		white-space: nowrap;
-		overflow: hidden;
-	}
-}
-</style>
diff --git a/packages/client/src/components/debobigego/link.vue b/packages/client/src/components/debobigego/link.vue
deleted file mode 100644
index de463465d4..0000000000
--- a/packages/client/src/components/debobigego/link.vue
+++ /dev/null
@@ -1,103 +0,0 @@
-<template>
-<div class="qmfkfnzi _debobigegoItem">
-	<a v-if="external" class="main _button _debobigegoPanel _debobigegoClickable" :href="to" target="_blank">
-		<span class="icon"><slot name="icon"></slot></span>
-		<span class="text"><slot></slot></span>
-		<span class="right">
-			<span class="text"><slot name="suffix"></slot></span>
-			<i class="fas fa-external-link-alt icon"></i>
-		</span>
-	</a>
-	<MkA v-else class="main _button _debobigegoPanel _debobigegoClickable" :class="{ active }" :to="to" :behavior="behavior">
-		<span class="icon"><slot name="icon"></slot></span>
-		<span class="text"><slot></slot></span>
-		<span class="right">
-			<span class="text"><slot name="suffix"></slot></span>
-			<i class="fas fa-chevron-right icon"></i>
-		</span>
-	</MkA>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import './debobigego.scss';
-
-export default defineComponent({
-	props: {
-		to: {
-			type: String,
-			required: true
-		},
-		active: {
-			type: Boolean,
-			required: false
-		},
-		external: {
-			type: Boolean,
-			required: false
-		},
-		behavior: {
-			type: String,
-			required: false,
-		},
-	},
-	data() {
-		return {
-		};
-	}
-});
-</script>
-
-<style lang="scss" scoped>
-.qmfkfnzi {
-	> .main {
-		display: flex;
-		align-items: center;
-		width: 100%;
-		box-sizing: border-box;
-		padding: 14px 16px 14px 14px;
-
-		&:hover {
-			text-decoration: none;
-		}
-
-		&.active {
-			color: var(--accent);
-			background: var(--panelHighlight);
-		}
-
-		> .icon {
-			width: 32px;
-			margin-right: 2px;
-			flex-shrink: 0;
-			text-align: center;
-			opacity: 0.8;
-
-			&:empty {
-				display: none;
-
-				& + .text {
-					padding-left: 4px;
-				}
-			}
-		}
-
-		> .text {
-			white-space: nowrap;
-			text-overflow: ellipsis;
-			overflow: hidden;
-			padding-right: 12px;
-		}
-
-		> .right {
-			margin-left: auto;
-			opacity: 0.7;
-
-			> .text:not(:empty) {
-				margin-right: 0.75em;
-			}
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/components/debobigego/object-view.vue b/packages/client/src/components/debobigego/object-view.vue
deleted file mode 100644
index 68be08560b..0000000000
--- a/packages/client/src/components/debobigego/object-view.vue
+++ /dev/null
@@ -1,102 +0,0 @@
-<template>
-<FormGroup class="_debobigegoItem">
-	<template #label><slot></slot></template>
-	<div class="drooglns _debobigegoItem" :class="{ tall }">
-		<div class="input _debobigegoPanel">
-			<textarea v-model="v"
-				class="_monospace"
-				readonly
-				:spellcheck="false"
-			></textarea>
-		</div>
-	</div>
-	<template #caption><slot name="desc"></slot></template>
-</FormGroup>
-</template>
-
-<script lang="ts">
-import { defineComponent, ref, toRefs, watch } from 'vue';
-import * as JSON5 from 'json5';
-import './debobigego.scss';
-import FormGroup from './group.vue';
-
-export default defineComponent({
-	components: {
-		FormGroup,
-	},
-	props: {
-		value: {
-			required: false
-		},
-		tall: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-		pre: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-		manualSave: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-	},
-	setup(props, context) {
-		const { value } = toRefs(props);
-		const v = ref('');
-
-		watch(() => value, newValue => {
-			v.value = JSON5.stringify(newValue.value, null, '\t');
-		}, {
-			immediate: true
-		});
-
-		return {
-			v,
-		};
-	}
-});
-</script>
-
-<style lang="scss" scoped>
-.drooglns {
-	position: relative;
-
-	> .input {
-		position: relative;
-	
-		> textarea {
-			display: block;
-			width: 100%;
-			min-width: 100%;
-			max-width: 100%;
-			min-height: 130px;
-			margin: 0;
-			padding: 16px var(--debobigegoContentHMargin);
-			box-sizing: border-box;
-			font: inherit;
-			font-weight: normal;
-			font-size: 1em;
-			background: transparent;
-			border: none;
-			border-radius: 0;
-			outline: none;
-			box-shadow: none;
-			color: var(--fg);
-			tab-size: 2;
-			white-space: pre;
-		}
-	}
-
-	&.tall {
-		> .input {
-			> textarea {
-				min-height: 200px;
-			}
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/components/debobigego/pagination.vue b/packages/client/src/components/debobigego/pagination.vue
deleted file mode 100644
index 16779caa42..0000000000
--- a/packages/client/src/components/debobigego/pagination.vue
+++ /dev/null
@@ -1,42 +0,0 @@
-<template>
-<FormGroup class="uljviswt _debobigegoItem">
-	<template #label><slot name="label"></slot></template>
-	<slot :items="items"></slot>
-	<div v-if="empty" key="_empty_" class="empty">
-		<slot name="empty"></slot>
-	</div>
-	<FormButton v-show="more" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore">
-		<template v-if="!moreFetching">{{ $ts.loadMore }}</template>
-		<template v-if="moreFetching"><MkLoading inline/></template>
-	</FormButton>
-</FormGroup>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormButton from './button.vue';
-import FormGroup from './group.vue';
-import paging from '@/scripts/paging';
-
-export default defineComponent({
-	components: {
-		FormButton,
-		FormGroup,
-	},
-
-	mixins: [
-		paging({}),
-	],
-
-	props: {
-		pagination: {
-			required: true
-		},
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.uljviswt {
-}
-</style>
diff --git a/packages/client/src/components/debobigego/radios.vue b/packages/client/src/components/debobigego/radios.vue
deleted file mode 100644
index b4c5841337..0000000000
--- a/packages/client/src/components/debobigego/radios.vue
+++ /dev/null
@@ -1,112 +0,0 @@
-<script lang="ts">
-import { defineComponent, h } from 'vue';
-import MkRadio from '@/components/form/radio.vue';
-import './debobigego.scss';
-
-export default defineComponent({
-	components: {
-		MkRadio
-	},
-	props: {
-		modelValue: {
-			required: false
-		},
-	},
-	data() {
-		return {
-			value: this.modelValue,
-		}
-	},
-	watch: {
-		modelValue() {
-			this.value = this.modelValue;
-		},
-		value() {
-			this.$emit('update:modelValue', this.value);
-		}
-	},
-	render() {
-		const label = this.$slots.desc();
-		let options = this.$slots.default();
-
-		// なぜかFragmentになることがあるため
-		if (options.length === 1 && options[0].props == null) options = options[0].children;
-
-		return h('div', {
-			class: 'cnklmpwm _debobigegoItem'
-		}, [
-			h('div', {
-				class: '_debobigegoLabel',
-			}, label),
-			...options.map(option => h('button', {
-				class: '_button _debobigegoPanel _debobigegoClickable',
-				key: option.key,
-				onClick: () => this.value = option.props.value,
-			}, [h('span', {
-				class: ['check', { checked: this.value === option.props.value }],
-			}), option.children]))
-		]);
-	}
-});
-</script>
-
-<style lang="scss">
-.cnklmpwm {
-	> button {
-		display: block;
-		width: 100%;
-		box-sizing: border-box;
-		padding: 14px 18px;
-		text-align: left;
-
-		&:not(:first-of-type) {
-			border-top: none !important;
-			border-top-left-radius: 0;
-			border-top-right-radius: 0;
-		}
-
-		&:not(:last-of-type) {
-			border-bottom: solid 0.5px var(--divider);
-			border-bottom-left-radius: 0;
-			border-bottom-right-radius: 0;
-		}
-
-		> .check {
-			display: inline-block;
-			vertical-align: bottom;
-			position: relative;
-			width: 16px;
-			height: 16px;
-			margin-right: 8px;
-			background: none;
-			border: 2px solid var(--inputBorder);
-			border-radius: 100%;
-			transition: inherit;
-
-			&:after {
-				content: "";
-				display: block;
-				position: absolute;
-				top: 3px;
-				right: 3px;
-				bottom: 3px;
-				left: 3px;
-				border-radius: 100%;
-				opacity: 0;
-				transform: scale(0);
-				transition: .4s cubic-bezier(.25,.8,.25,1);
-			}
-
-			&.checked {
-				border-color: var(--accent);
-
-				&:after {
-					background-color: var(--accent);
-					transform: scale(1);
-					opacity: 1;
-				}
-			}
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/components/debobigego/range.vue b/packages/client/src/components/debobigego/range.vue
deleted file mode 100644
index dc71f25d83..0000000000
--- a/packages/client/src/components/debobigego/range.vue
+++ /dev/null
@@ -1,122 +0,0 @@
-<template>
-<div class="ifitouly _debobigegoItem" :class="{ focused, disabled }">
-	<div class="_debobigegoLabel"><slot name="label"></slot></div>
-	<div class="_debobigegoPanel main">
-		<input
-			ref="input"
-			v-model="v"
-			type="range"
-			:disabled="disabled"
-			:min="min"
-			:max="max"
-			:step="step"
-			@focus="focused = true"
-			@blur="focused = false"
-			@input="$emit('update:value', $event.target.value)"
-		/>
-	</div>
-	<div class="_debobigegoCaption"><slot name="caption"></slot></div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-
-export default defineComponent({
-	props: {
-		value: {
-			type: Number,
-			required: false,
-			default: 0
-		},
-		disabled: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-		min: {
-			type: Number,
-			required: false,
-			default: 0
-		},
-		max: {
-			type: Number,
-			required: false,
-			default: 100
-		},
-		step: {
-			type: Number,
-			required: false,
-			default: 1
-		},
-	},
-	data() {
-		return {
-			v: this.value,
-			focused: false
-		};
-	},
-	watch: {
-		value(v) {
-			this.v = parseFloat(v);
-		}
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.ifitouly {
-	position: relative;
-
-	> .main {
-		padding: 22px 16px;
-
-		> input {
-			display: block;
-			-webkit-appearance: none;
-			-moz-appearance: none;
-			appearance: none;
-			background: var(--X10);
-			height: 4px;
-			width: 100%;
-			box-sizing: border-box;
-			margin: 0;
-			outline: 0;
-			border: 0;
-			border-radius: 7px;
-
-			&.disabled {
-				opacity: 0.6;
-				cursor: not-allowed;
-			}
-
-			&::-webkit-slider-thumb {
-				-webkit-appearance: none;
-				appearance: none;
-				cursor: pointer;
-				width: 20px;
-				height: 20px;
-				display: block;
-				border-radius: 50%;
-				border: none;
-				background: var(--accent);
-				box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
-				box-sizing: content-box;
-			}
-
-			&::-moz-range-thumb {
-				-moz-appearance: none;
-				appearance: none;
-				cursor: pointer;
-				width: 20px;
-				height: 20px;
-				display: block;
-				border-radius: 50%;
-				border: none;
-				background: var(--accent);
-				box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
-			}
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/components/debobigego/select.vue b/packages/client/src/components/debobigego/select.vue
deleted file mode 100644
index 081bbfe302..0000000000
--- a/packages/client/src/components/debobigego/select.vue
+++ /dev/null
@@ -1,145 +0,0 @@
-<template>
-<div class="yrtfrpux _debobigegoItem" :class="{ disabled, inline }">
-	<div class="_debobigegoLabel"><slot name="label"></slot></div>
-	<div ref="icon" class="icon"><slot name="icon"></slot></div>
-	<div class="input _debobigegoPanel _debobigegoClickable" @click="focus">
-		<div ref="prefix" class="prefix"><slot name="prefix"></slot></div>
-		<select ref="input"
-			v-model="v"
-			:required="required"
-			:disabled="disabled"
-			@focus="focused = true"
-			@blur="focused = false"
-		>
-			<slot></slot>
-		</select>
-		<div class="suffix">
-			<i class="fas fa-chevron-down"></i>
-		</div>
-	</div>
-	<div class="_debobigegoCaption"><slot name="caption"></slot></div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import './debobigego.scss';
-
-export default defineComponent({
-	props: {
-		modelValue: {
-			required: false
-		},
-		required: {
-			type: Boolean,
-			required: false
-		},
-		disabled: {
-			type: Boolean,
-			required: false
-		},
-		inline: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-	},
-	data() {
-		return {
-		};
-	},
-	computed: {
-		v: {
-			get() {
-				return this.modelValue;
-			},
-			set(v) {
-				this.$emit('update:modelValue', v);
-			}
-		},
-	},
-	methods: {
-		focus() {
-			this.$refs.input.focus();
-		}
-	}
-});
-</script>
-
-<style lang="scss" scoped>
-.yrtfrpux {
-	position: relative;
-
-	> .icon {
-		position: absolute;
-		top: 0;
-		left: 0;
-		width: 24px;
-		text-align: center;
-		line-height: 32px;
-
-		&:not(:empty) + .input {
-			margin-left: 28px;
-		}
-	}
-
-	> .input {
-		display: flex;
-		position: relative;
-
-		> select {
-			display: block;
-			flex: 1;
-			width: 100%;
-			padding: 0 16px;
-			font: inherit;
-			font-weight: normal;
-			font-size: 1em;
-			height: 48px;
-			background: none;
-			border: none;
-			border-radius: 0;
-			outline: none;
-			box-shadow: none;
-			appearance: none;
-			-webkit-appearance: none;
-			color: var(--fg);
-
-			option,
-			optgroup {
-				color: var(--fg);
-				background: var(--bg);
-			}
-		}
-
-		> .prefix,
-		> .suffix {
-			display: block;
-			align-self: center;
-			justify-self: center;
-			font-size: 1em;
-			line-height: 32px;
-			color: var(--inputLabel);
-			pointer-events: none;
-
-			&:empty {
-				display: none;
-			}
-
-			> * {
-				display: block;
-				min-width: 16px;
-			}
-		}
-
-		> .prefix {
-			padding-right: 4px;
-		}
-
-		> .suffix {
-			padding: 0 16px 0 0;
-			opacity: 0.7;
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/components/debobigego/suspense.vue b/packages/client/src/components/debobigego/suspense.vue
deleted file mode 100644
index acb0b64424..0000000000
--- a/packages/client/src/components/debobigego/suspense.vue
+++ /dev/null
@@ -1,101 +0,0 @@
-<template>
-<transition name="fade" mode="out-in">
-	<div v-if="pending" class="_debobigegoItem">
-		<div class="_debobigegoPanel">
-			<MkLoading/>
-		</div>
-	</div>
-	<div v-else-if="resolved" class="_debobigegoItem">
-		<slot :result="result"></slot>
-	</div>
-	<div v-else class="_debobigegoItem">
-		<div class="_debobigegoPanel eiurkvay">
-			<div><i class="fas fa-exclamation-triangle"></i> {{ $ts.somethingHappened }}</div>
-			<MkButton inline class="retry" @click="retry"><i class="fas fa-redo-alt"></i> {{ $ts.retry }}</MkButton>
-		</div>
-	</div>
-</transition>
-</template>
-
-<script lang="ts">
-import { defineComponent, PropType, ref, watch } from 'vue';
-import './debobigego.scss';
-import MkButton from '@/components/ui/button.vue';
-
-export default defineComponent({
-	components: {
-		MkButton
-	},
-
-	props: {
-		p: {
-			type: Function as PropType<() => Promise<any>>,
-			required: true,
-		}
-	},
-
-	setup(props, context) {
-		const pending = ref(true);
-		const resolved = ref(false);
-		const rejected = ref(false);
-		const result = ref(null);
-
-		const process = () => {
-			if (props.p == null) {
-				return;
-			}
-			const promise = props.p();
-			pending.value = true;
-			resolved.value = false;
-			rejected.value = false;
-			promise.then((_result) => {
-				pending.value = false;
-				resolved.value = true;
-				result.value = _result;
-			});
-			promise.catch(() => {
-				pending.value = false;
-				rejected.value = true;
-			});
-		};
-
-		watch(() => props.p, () => {
-			process();
-		}, {
-			immediate: true
-		});
-
-		const retry = () => {
-			process();
-		};
-
-		return {
-			pending,
-			resolved,
-			rejected,
-			result,
-			retry,
-		};
-	}
-});
-</script>
-
-<style lang="scss" scoped>
-.fade-enter-active,
-.fade-leave-active {
-	transition: opacity 0.125s ease;
-}
-.fade-enter-from,
-.fade-leave-to {
-	opacity: 0;
-}
-
-.eiurkvay {
-	padding: 16px;
-	text-align: center;
-
-	> .retry {
-		margin-top: 16px;
-	}
-}
-</style>
diff --git a/packages/client/src/components/debobigego/switch.vue b/packages/client/src/components/debobigego/switch.vue
deleted file mode 100644
index 239140f730..0000000000
--- a/packages/client/src/components/debobigego/switch.vue
+++ /dev/null
@@ -1,132 +0,0 @@
-<template>
-<div class="ijnpvmgr _debobigegoItem">
-	<div class="main _debobigegoPanel _debobigegoClickable"
-		:class="{ disabled, checked }"
-		:aria-checked="checked"
-		:aria-disabled="disabled"
-		@click.prevent="toggle"
-	>
-		<input
-			ref="input"
-			type="checkbox"
-			:disabled="disabled"
-			@keydown.enter="toggle"
-		>
-		<span v-tooltip="checked ? $ts.itsOn : $ts.itsOff" class="button">
-			<span class="handle"></span>
-		</span>
-		<span class="label">
-			<span><slot></slot></span>
-		</span>
-	</div>
-	<div class="_debobigegoCaption"><slot name="desc"></slot></div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import './debobigego.scss';
-
-export default defineComponent({
-	props: {
-		modelValue: {
-			type: Boolean,
-			default: false
-		},
-		disabled: {
-			type: Boolean,
-			default: false
-		}
-	},
-	computed: {
-		checked(): boolean {
-			return this.modelValue;
-		}
-	},
-	methods: {
-		toggle() {
-			if (this.disabled) return;
-			this.$emit('update:modelValue', !this.checked);
-		}
-	}
-});
-</script>
-
-<style lang="scss" scoped>
-.ijnpvmgr {
-	> .main {
-		position: relative;
-		display: flex;
-		padding: 14px 16px;
-		cursor: pointer;
-
-		> * {
-			user-select: none;
-		}
-
-		> input {
-			position: absolute;
-			width: 0;
-			height: 0;
-			opacity: 0;
-			margin: 0;
-		}
-
-		> .button {
-			position: relative;
-			display: inline-block;
-			flex-shrink: 0;
-			margin: 0;
-			width: 34px;
-			height: 22px;
-			background: var(--switchBg);
-			outline: none;
-			border-radius: 999px;
-			transition: all 0.3s;
-			cursor: pointer;
-
-			> .handle {
-				position: absolute;
-				top: 0;
-				left: 3px;
-				bottom: 0;
-				margin: auto 0;
-				border-radius: 100%;
-				transition: background-color 0.3s, transform 0.3s;
-				width: 16px;
-				height: 16px;
-				background-color: #fff;
-				pointer-events: none;
-			}
-		}
-
-		> .label {
-			margin-left: 12px;
-			display: block;
-			transition: inherit;
-			color: var(--fg);
-
-			> span {
-				display: block;
-				line-height: 20px;
-				transition: inherit;
-			}
-		}
-
-		&.disabled {
-			opacity: 0.6;
-			cursor: not-allowed;
-		}
-
-		&.checked {
-			> .button {
-				background-color: var(--accent);
-
-				> .handle {
-					transform: translateX(12px);
-				}
-			}
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/components/debobigego/textarea.vue b/packages/client/src/components/debobigego/textarea.vue
deleted file mode 100644
index ca5b35c49e..0000000000
--- a/packages/client/src/components/debobigego/textarea.vue
+++ /dev/null
@@ -1,161 +0,0 @@
-<template>
-<FormGroup class="_debobigegoItem">
-	<template #label><slot></slot></template>
-	<div class="rivhosbp _debobigegoItem" :class="{ tall, pre }">
-		<div class="input _debobigegoPanel">
-			<textarea ref="input" v-model="v"
-				:class="{ code, _monospace: code }"
-				:required="required"
-				:readonly="readonly"
-				:pattern="pattern"
-				:autocomplete="autocomplete"
-				:spellcheck="!code"
-				@input="onInput"
-				@focus="focused = true"
-				@blur="focused = false"
-			></textarea>
-		</div>
-	</div>
-	<template #caption><slot name="desc"></slot></template>
-
-	<FormButton v-if="manualSave && changed" primary @click="updated"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
-</FormGroup>
-</template>
-
-<script lang="ts">
-import { defineComponent, ref, toRefs, watch } from 'vue';
-import './debobigego.scss';
-import FormButton from './button.vue';
-import FormGroup from './group.vue';
-
-export default defineComponent({
-	components: {
-		FormGroup,
-		FormButton,
-	},
-	props: {
-		modelValue: {
-			required: false
-		},
-		required: {
-			type: Boolean,
-			required: false
-		},
-		readonly: {
-			type: Boolean,
-			required: false
-		},
-		pattern: {
-			type: String,
-			required: false
-		},
-		autocomplete: {
-			type: String,
-			required: false
-		},
-		code: {
-			type: Boolean,
-			required: false
-		},
-		tall: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-		pre: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-		manualSave: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-	},
-	setup(props, context) {
-		const { modelValue } = toRefs(props);
-		const v = ref(modelValue.value);
-		const changed = ref(false);
-		const inputEl = ref(null);
-		const focus = () => inputEl.value.focus();
-		const onInput = (ev) => {
-			changed.value = true;
-			context.emit('change', ev);
-		};
-
-		const updated = () => {
-			changed.value = false;
-			context.emit('update:modelValue', v.value);
-		};
-
-		watch(modelValue.value, newValue => {
-			v.value = newValue;
-		});
-
-		watch(v, newValue => {
-			if (!props.manualSave) {
-				updated();
-			}
-		});
-		
-		return {
-			v,
-			updated,
-			changed,
-			focus,
-			onInput,
-		};
-	}
-});
-</script>
-
-<style lang="scss" scoped>
-.rivhosbp {
-	position: relative;
-
-	> .input {
-		position: relative;
-	
-		> textarea {
-			display: block;
-			width: 100%;
-			min-width: 100%;
-			max-width: 100%;
-			min-height: 130px;
-			margin: 0;
-			padding: 16px;
-			box-sizing: border-box;
-			font: inherit;
-			font-weight: normal;
-			font-size: 1em;
-			background: transparent;
-			border: none;
-			border-radius: 0;
-			outline: none;
-			box-shadow: none;
-			color: var(--fg);
-
-			&.code {
-				tab-size: 2;
-			}
-		}
-	}
-
-	&.tall {
-		> .input {
-			> textarea {
-				min-height: 200px;
-			}
-		}
-	}
-
-	&.pre {
-		> .input {
-			> textarea {
-				white-space: pre;
-			}
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/components/debobigego/tuple.vue b/packages/client/src/components/debobigego/tuple.vue
deleted file mode 100644
index 1d2a6cb55e..0000000000
--- a/packages/client/src/components/debobigego/tuple.vue
+++ /dev/null
@@ -1,36 +0,0 @@
-<template>
-<div v-size="{ max: [500] }" class="wthhikgt _debobigegoItem">
-	<slot></slot>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-
-export default defineComponent({
-});
-</script>
-
-<style lang="scss" scoped>
-.wthhikgt {
-	position: relative;
-	display: flex;
-
-	> ::v-deep(*) {
-		flex: 1;
-		margin: 0;
-
-		&:not(:last-child) {
-			margin-right: 16px;
-		}
-	}
-
-	&.max-width_500px {
-		display: block;
-
-		> ::v-deep(*) {
-			margin: inherit;
-		}
-	}
-}
-</style>
diff --git a/packages/client/src/components/form/folder.vue b/packages/client/src/components/form/folder.vue
index fe12202014..571afe50c0 100644
--- a/packages/client/src/components/form/folder.vue
+++ b/packages/client/src/components/form/folder.vue
@@ -20,8 +20,14 @@
 </template>
 
 <script lang="ts" setup>
-let opened = $ref(false);
-let openedAtLeastOnce = $ref(false);
+const props = withDefaults(defineProps<{
+	defaultOpen: boolean;
+}>(), {
+  defaultOpen: false,
+})
+
+let opened = $ref(props.defaultOpen);
+let openedAtLeastOnce = $ref(props.defaultOpen);
 
 const toggle = () => {
 	opened = !opened;
diff --git a/packages/client/src/components/object-view.vue b/packages/client/src/components/object-view.vue
new file mode 100644
index 0000000000..4334917e6b
--- /dev/null
+++ b/packages/client/src/components/object-view.vue
@@ -0,0 +1,29 @@
+<template>
+<div class="zhyxdalp">
+
+</div>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent } from 'vue';
+import number from '@/filters/number';
+
+export default defineComponent({
+	props: {
+		value: {
+			type: Object,
+			required: true,
+		},
+	},
+
+	setup(props) {
+
+	}
+});
+</script>
+
+<style lang="scss" scoped>
+.zhyxdalp {
+
+}
+</style>
diff --git a/packages/client/src/pages/admin/database.vue b/packages/client/src/pages/admin/database.vue
index fc9a3e9690..c1088afd77 100644
--- a/packages/client/src/pages/admin/database.vue
+++ b/packages/client/src/pages/admin/database.vue
@@ -1,5 +1,5 @@
 <template>
-<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
+<MkSpacer :content-max="800" :margin-min="16" :margin-max="32">
 	<FormSuspense v-slot="{ result: database }" :p="databasePromiseFactory">
 		<MkKeyValue v-for="table in database" :key="table[0]" oneline style="margin: 1em 0;">
 			<template #key>{{ table[0] }}</template>
diff --git a/packages/client/src/pages/gallery/edit.vue b/packages/client/src/pages/gallery/edit.vue
index caca6aed4b..d317da038f 100644
--- a/packages/client/src/pages/gallery/edit.vue
+++ b/packages/client/src/pages/gallery/edit.vue
@@ -1,12 +1,12 @@
 <template>
-<FormBase>
+<div>
 	<FormSuspense :p="init">
 		<FormInput v-model="title">
-			<span>{{ $ts.title }}</span>
+			<template #label>{{ $ts.title }}</template>
 		</FormInput>
 
 		<FormTextarea v-model="description" :max="500">
-			<span>{{ $ts.description }}</span>
+			<template #label>{{ $ts.description }}</template>
 		</FormTextarea>
 
 		<FormGroup>
@@ -24,19 +24,17 @@
 
 		<FormButton v-if="postId" danger @click="del"><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</FormButton>
 	</FormSuspense>
-</FormBase>
+</div>
 </template>
 
 <script lang="ts">
 import { computed, defineComponent } from 'vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormInput from '@/components/debobigego/input.vue';
-import FormTextarea from '@/components/debobigego/textarea.vue';
-import FormSwitch from '@/components/debobigego/switch.vue';
-import FormTuple from '@/components/debobigego/tuple.vue';
-import FormBase from '@/components/debobigego/base.vue';
-import FormGroup from '@/components/debobigego/group.vue';
-import FormSuspense from '@/components/debobigego/suspense.vue';
+import FormButton from '@/components/ui/button.vue';
+import FormInput from '@/components/form/input.vue';
+import FormTextarea from '@/components/form/textarea.vue';
+import FormSwitch from '@/components/form/switch.vue';
+import FormGroup from '@/components/form/group.vue';
+import FormSuspense from '@/components/form/suspense.vue';
 import { selectFiles } from '@/scripts/select-file';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
@@ -47,7 +45,6 @@ export default defineComponent({
 		FormInput,
 		FormTextarea,
 		FormSwitch,
-		FormBase,
 		FormGroup,
 		FormSuspense,
 	},
diff --git a/packages/client/src/pages/instance-info.vue b/packages/client/src/pages/instance-info.vue
index d6d72f6601..475107ab6d 100644
--- a/packages/client/src/pages/instance-info.vue
+++ b/packages/client/src/pages/instance-info.vue
@@ -89,9 +89,8 @@
 			</div>
 		</FormSection>
 
-		<FormObjectView tall :value="instance">
-			<span>Raw</span>
-		</FormObjectView>
+		<MkObjectView tall :value="instance">
+		</MkObjectView>
 
 		<FormSection>
 			<template #label>Well-known resources</template>
@@ -108,7 +107,7 @@
 <script lang="ts">
 import { defineAsyncComponent, defineComponent } from 'vue';
 import MkChart from '@/components/chart.vue';
-import FormObjectView from '@/components/debobigego/object-view.vue';
+import MkObjectView from '@/components/object-view.vue';
 import FormTextarea from '@/components/form/textarea.vue';
 import FormLink from '@/components/form/link.vue';
 import MkLink from '@/components/link.vue';
@@ -125,7 +124,7 @@ import * as symbols from '@/symbols';
 export default defineComponent({
 	components: {
 		FormTextarea,
-		FormObjectView,
+		MkObjectView,
 		FormButton,
 		FormLink,
 		FormSection,
diff --git a/packages/client/src/pages/theme-editor.vue b/packages/client/src/pages/theme-editor.vue
index f023653425..c4917e2270 100644
--- a/packages/client/src/pages/theme-editor.vue
+++ b/packages/client/src/pages/theme-editor.vue
@@ -1,61 +1,67 @@
 <template>
-<FormBase class="cwepdizn">
-	<div class="_debobigegoItem colorPicker">
-		<div class="_debobigegoLabel">{{ $ts.backgroundColor }}</div>
-		<div class="_debobigegoPanel colors">
-			<div class="row">
-				<button v-for="color in bgColors.filter(x => x.kind === 'light')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)">
-					<div class="preview" :style="{ background: color.forPreview }"></div>
-				</button>
+<MkSpacer :content-max="800" :margin-min="16" :margin-max="32">
+	<div class="cwepdizn _formRoot">
+		<FormFolder :default-open="true" class="_formBlock">
+			<template #label>{{ $ts.backgroundColor }}</template>
+			<div class="cwepdizn-colors">
+				<div class="row">
+					<button v-for="color in bgColors.filter(x => x.kind === 'light')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)">
+						<div class="preview" :style="{ background: color.forPreview }"></div>
+					</button>
+				</div>
+				<div class="row">
+					<button v-for="color in bgColors.filter(x => x.kind === 'dark')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)">
+						<div class="preview" :style="{ background: color.forPreview }"></div>
+					</button>
+				</div>
 			</div>
-			<div class="row">
-				<button v-for="color in bgColors.filter(x => x.kind === 'dark')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)">
-					<div class="preview" :style="{ background: color.forPreview }"></div>
-				</button>
-			</div>
-		</div>
-	</div>
-	<div class="_debobigegoItem colorPicker">
-		<div class="_debobigegoLabel">{{ $ts.accentColor }}</div>
-		<div class="_debobigegoPanel colors">
-			<div class="row">
-				<button v-for="color in accentColors" :key="color" class="color rounded _button" :class="{ active: theme.props.accent === color }" @click="setAccentColor(color)">
-					<div class="preview" :style="{ background: color }"></div>
-				</button>
-			</div>
-		</div>
-	</div>
-	<div class="_debobigegoItem colorPicker">
-		<div class="_debobigegoLabel">{{ $ts.textColor }}</div>
-		<div class="_debobigegoPanel colors">
-			<div class="row">
-				<button v-for="color in fgColors" :key="color" class="color char _button" :class="{ active: (theme.props.fg === color.forLight) || (theme.props.fg === color.forDark) }" @click="setFgColor(color)">
-					<div class="preview" :style="{ color: color.forPreview ? color.forPreview : theme.base === 'light' ? '#5f5f5f' : '#dadada' }">A</div>
-				</button>
-			</div>
-		</div>
-	</div>
+		</FormFolder>
 
-	<FormGroup v-if="codeEnabled">
-		<FormTextarea v-model="themeCode" tall>
-			<span>{{ $ts._theme.code }}</span>
-		</FormTextarea>
-		<FormButton primary @click="applyThemeCode">{{ $ts.apply }}</FormButton>
-	</FormGroup>
-	<FormButton v-else @click="codeEnabled = true"><i class="fas fa-code"></i> {{ $ts.editCode }}</FormButton>
+		<FormFolder :default-open="true" class="_formBlock">
+			<template #label>{{ $ts.accentColor }}</template>
+			<div class="cwepdizn-colors">
+				<div class="row">
+					<button v-for="color in accentColors" :key="color" class="color rounded _button" :class="{ active: theme.props.accent === color }" @click="setAccentColor(color)">
+						<div class="preview" :style="{ background: color }"></div>
+					</button>
+				</div>
+			</div>
+		</FormFolder>
 
-	<FormGroup v-if="descriptionEnabled">
-		<FormTextarea v-model="description">
-			<span>{{ $ts._theme.description }}</span>
-		</FormTextarea>
-	</FormGroup>
-	<FormButton v-else @click="descriptionEnabled = true">{{ $ts.addDescription }}</FormButton>
+		<FormFolder :default-open="true" class="_formBlock">
+			<template #label>{{ $ts.textColor }}</template>
+			<div class="cwepdizn-colors">
+				<div class="row">
+					<button v-for="color in fgColors" :key="color" class="color char _button" :class="{ active: (theme.props.fg === color.forLight) || (theme.props.fg === color.forDark) }" @click="setFgColor(color)">
+						<div class="preview" :style="{ color: color.forPreview ? color.forPreview : theme.base === 'light' ? '#5f5f5f' : '#dadada' }">A</div>
+					</button>
+				</div>
+			</div>
+		</FormFolder>
 
-	<FormGroup>
-		<FormButton @click="showPreview"><i class="fas fa-eye"></i> {{ $ts.preview }}</FormButton>
-		<FormButton primary @click="saveAs"><i class="fas fa-save"></i> {{ $ts.saveAs }}</FormButton>
-	</FormGroup>
-</FormBase>
+		<FormFolder :default-open="false" class="_formBlock">
+			<template #icon><i class="fas fa-code"></i></template>
+			<template #label>{{ $ts.editCode }}</template>
+
+			<div class="_formRoot">
+				<FormTextarea v-model="themeCode" tall class="_formBlock">
+					<template #label>{{ $ts._theme.code }}</template>
+				</FormTextarea>
+				<FormButton primary class="_formBlock" @click="applyThemeCode">{{ $ts.apply }}</FormButton>
+			</div>
+		</FormFolder>
+
+		<FormFolder :default-open="false" class="_formBlock">
+			<template #label>{{ $ts.addDescription }}</template>
+
+			<div class="_formRoot">
+				<FormTextarea v-model="description">
+					<template #label>{{ $ts._theme.description }}</template>
+				</FormTextarea>
+			</div>
+		</FormFolder>
+	</div>
+</MkSpacer>
 </template>
 
 <script lang="ts">
@@ -65,12 +71,11 @@ import * as tinycolor from 'tinycolor2';
 import { v4 as uuid} from 'uuid';
 import * as JSON5 from 'json5';
 
-import FormBase from '@/components/debobigego/base.vue';
-import FormButton from '@/components/debobigego/button.vue';
-import FormTextarea from '@/components/debobigego/textarea.vue';
-import FormGroup from '@/components/debobigego/group.vue';
+import FormButton from '@/components/ui/button.vue';
+import FormTextarea from '@/components/form/textarea.vue';
+import FormFolder from '@/components/form/folder.vue';
 
-import { Theme, applyTheme, validateTheme, darkTheme, lightTheme } from '@/scripts/theme';
+import { Theme, applyTheme, darkTheme, lightTheme } from '@/scripts/theme';
 import { host } from '@/config';
 import * as os from '@/os';
 import { ColdDeviceStorage } from '@/store';
@@ -79,10 +84,9 @@ import * as symbols from '@/symbols';
 
 export default defineComponent({
 	components: {
-		FormBase,
 		FormButton,
 		FormTextarea,
-		FormGroup,
+		FormFolder,
 	},
 
 	async beforeRouteLeave(to, from) {
@@ -96,13 +100,23 @@ export default defineComponent({
 			[symbols.PAGE_INFO]: {
 				title: this.$ts.themeEditor,
 				icon: 'fas fa-palette',
+				bg: 'var(--bg)',
+				actions: [{
+					asFullButton: true,
+					icon: 'fas fa-eye',
+					text: this.$ts.preview,
+					handler: this.showPreview,
+				}, {
+					asFullButton: true,
+					icon: 'fas fa-check',
+					text: this.$ts.saveAs,
+					handler: this.saveAs,
+				}],
 			},
 			theme: {
 				base: 'light',
 				props: lightTheme.props
 			} as Theme,
-			codeEnabled: false,
-			descriptionEnabled: false,
 			description: null,
 			themeCode: null,
 			bgColors: [
@@ -244,57 +258,51 @@ export default defineComponent({
 
 <style lang="scss" scoped>
 .cwepdizn {
-	max-width: 800px;
-	margin: 0 auto;
+	::v-deep(.cwepdizn-colors) {
+		text-align: center;
 
-	> .colorPicker {
-		> .colors {
-			padding: 32px;
-			text-align: center;
+		> .row {
+			> .color {
+				display: inline-block;
+				position: relative;
+				width: 64px;
+				height: 64px;
+				border-radius: 8px;
 
-			> .row {
-				> .color {
-					display: inline-block;
-					position: relative;
-					width: 64px;
-					height: 64px;
-					border-radius: 8px;
+				> .preview {
+					position: absolute;
+					top: 0;
+					left: 0;
+					right: 0;
+					bottom: 0;
+					margin: auto;
+					width: 42px;
+					height: 42px;
+					border-radius: 4px;
+					box-shadow: 0 2px 4px rgb(0 0 0 / 30%);
+					transition: transform 0.15s ease;
+				}
+
+				&:hover {
+					> .preview {
+						transform: scale(1.1);
+					}
+				}
+
+				&.active {
+					box-shadow: 0 0 0 2px var(--divider) inset;
+				}
+
+				&.rounded {
+					border-radius: 999px;
 
 					> .preview {
-						position: absolute;
-						top: 0;
-						left: 0;
-						right: 0;
-						bottom: 0;
-						margin: auto;
-						width: 42px;
-						height: 42px;
-						border-radius: 4px;
-						box-shadow: 0 2px 4px rgb(0 0 0 / 30%);
-						transition: transform 0.15s ease;
-					}
-
-					&:hover {
-						> .preview {
-							transform: scale(1.1);
-						}
-					}
-
-					&.active {
-						box-shadow: 0 0 0 2px var(--divider) inset;
-					}
-
-					&.rounded {
 						border-radius: 999px;
-
-						> .preview {
-							border-radius: 999px;
-						}
 					}
+				}
 
-					&.char {
-						line-height: 42px;
-					}
+				&.char {
+					line-height: 42px;
 				}
 			}
 		}
diff --git a/packages/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue
index e3c10c6df9..4bdc82f601 100644
--- a/packages/client/src/pages/user-info.vue
+++ b/packages/client/src/pages/user-info.vue
@@ -53,9 +53,8 @@
 				<FormButton v-if="user.host != null" class="_formBlock" @click="updateRemoteUser"><i class="fas fa-sync"></i> {{ $ts.updateRemoteUser }}</FormButton>
 			</FormSection>
 
-			<FormObjectView tall :value="user">
-				<span>Raw</span>
-			</FormObjectView>
+			<MkObjectView tall :value="user">
+			</MkObjectView>
 		</div>
 	</FormSuspense>
 </MkSpacer>
@@ -63,7 +62,7 @@
 
 <script lang="ts">
 import { computed, defineAsyncComponent, defineComponent } from 'vue';
-import FormObjectView from '@/components/debobigego/object-view.vue';
+import MkObjectView from '@/components/object-view.vue';
 import FormTextarea from '@/components/form/textarea.vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormLink from '@/components/form/link.vue';
@@ -83,7 +82,7 @@ export default defineComponent({
 		FormSection,
 		FormTextarea,
 		FormSwitch,
-		FormObjectView,
+		MkObjectView,
 		FormButton,
 		FormLink,
 		MkKeyValue,