mirror of
				https://codeberg.org/yeentown/barkey.git
				synced 2025-11-04 07:24:13 +00:00 
			
		
		
		
	refactor: 拡張機能インストールのページの一部をコンポーネントとして分離 (#14654)
* create MkExtensionInstaller.vue * annotation * add fallbacks * storybook * update annotations * Update MkExtensionInstaller.vue * use additonalInfo slot
This commit is contained in:
		
							parent
							
								
									03fb688073
								
							
						
					
					
						commit
						ed89b4bd94
					
				
					 3 changed files with 250 additions and 96 deletions
				
			
		| 
						 | 
				
			
			@ -0,0 +1,83 @@
 | 
			
		|||
/*
 | 
			
		||||
 * SPDX-FileCopyrightText: syuilo and misskey-project
 | 
			
		||||
 * SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { StoryObj } from '@storybook/vue3';
 | 
			
		||||
import MkExtensionInstaller from './MkExtensionInstaller.vue';
 | 
			
		||||
import lightTheme from '@@/themes/_light.json5';
 | 
			
		||||
 | 
			
		||||
export const Plugin = {
 | 
			
		||||
	render(args) {
 | 
			
		||||
		return {
 | 
			
		||||
			components: {
 | 
			
		||||
				MkExtensionInstaller,
 | 
			
		||||
			},
 | 
			
		||||
			setup() {
 | 
			
		||||
				return {
 | 
			
		||||
					args,
 | 
			
		||||
				};
 | 
			
		||||
			},
 | 
			
		||||
			computed: {
 | 
			
		||||
				props() {
 | 
			
		||||
					return {
 | 
			
		||||
						...this.args,
 | 
			
		||||
					};
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			template: '<MkExtensionInstaller v-bind="props" />',
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	args: {
 | 
			
		||||
		extension: {
 | 
			
		||||
			type: 'plugin',
 | 
			
		||||
			raw: '"do nothing"',
 | 
			
		||||
			meta: {
 | 
			
		||||
				name: 'do nothing plugin',
 | 
			
		||||
				version: '1.0',
 | 
			
		||||
				author: 'syuilo and misskey-project',
 | 
			
		||||
				description: 'a plugin that does nothing',
 | 
			
		||||
				permissions: ['read:account'],
 | 
			
		||||
				config: {
 | 
			
		||||
					'doNothing': true,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	parameters: {
 | 
			
		||||
		layout: 'centered',
 | 
			
		||||
	},
 | 
			
		||||
} satisfies StoryObj<typeof MkExtensionInstaller>;
 | 
			
		||||
 | 
			
		||||
export const Theme = {
 | 
			
		||||
	render(args) {
 | 
			
		||||
		return {
 | 
			
		||||
			components: {
 | 
			
		||||
				MkExtensionInstaller,
 | 
			
		||||
			},
 | 
			
		||||
			setup() {
 | 
			
		||||
				return {
 | 
			
		||||
					args,
 | 
			
		||||
				};
 | 
			
		||||
			},
 | 
			
		||||
			computed: {
 | 
			
		||||
				props() {
 | 
			
		||||
					return {
 | 
			
		||||
						...this.args,
 | 
			
		||||
					};
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			template: '<MkExtensionInstaller v-bind="props" />',
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	args: {
 | 
			
		||||
		extension: {
 | 
			
		||||
			type: 'theme',
 | 
			
		||||
			raw: JSON.stringify(lightTheme),
 | 
			
		||||
			meta: lightTheme,
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	parameters: {
 | 
			
		||||
		layout: 'centered',
 | 
			
		||||
	},
 | 
			
		||||
} satisfies StoryObj<typeof MkExtensionInstaller>;
 | 
			
		||||
							
								
								
									
										146
									
								
								packages/frontend/src/components/MkExtensionInstaller.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								packages/frontend/src/components/MkExtensionInstaller.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,146 @@
 | 
			
		|||
<!--
 | 
			
		||||
SPDX-FileCopyrightText: syuilo and misskey-project
 | 
			
		||||
SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div class="_gaps_m" :class="$style.extInstallerRoot">
 | 
			
		||||
	<div :class="$style.extInstallerIconWrapper">
 | 
			
		||||
		<i v-if="isPlugin" class="ti ti-plug"></i>
 | 
			
		||||
		<i v-else-if="isTheme" class="ti ti-palette"></i>
 | 
			
		||||
		<!-- 拡張用? -->
 | 
			
		||||
		<i v-else class="ti ti-download"></i>
 | 
			
		||||
	</div>
 | 
			
		||||
	<h2 :class="$style.extInstallerTitle">{{ i18n.ts._externalResourceInstaller[`_${extension.type}`].title }}</h2>
 | 
			
		||||
	<div :class="$style.extInstallerNormDesc">{{ i18n.ts._externalResourceInstaller.checkVendorBeforeInstall }}</div>
 | 
			
		||||
	<MkInfo v-if="isPlugin" :warn="true">{{ i18n.ts._plugin.installWarn }}</MkInfo>
 | 
			
		||||
	<FormSection>
 | 
			
		||||
		<template #label>{{ i18n.ts._externalResourceInstaller[`_${extension.type}`].metaTitle }}</template>
 | 
			
		||||
		<div class="_gaps_s">
 | 
			
		||||
			<FormSplit>
 | 
			
		||||
				<MkKeyValue>
 | 
			
		||||
					<template #key>{{ i18n.ts.name }}</template>
 | 
			
		||||
					<template #value>{{ extension.meta.name }}</template>
 | 
			
		||||
				</MkKeyValue>
 | 
			
		||||
				<MkKeyValue>
 | 
			
		||||
					<template #key>{{ i18n.ts.author }}</template>
 | 
			
		||||
					<template #value>{{ extension.meta.author }}</template>
 | 
			
		||||
				</MkKeyValue>
 | 
			
		||||
			</FormSplit>
 | 
			
		||||
			<MkKeyValue v-if="isPlugin">
 | 
			
		||||
				<template #key>{{ i18n.ts.description }}</template>
 | 
			
		||||
				<template #value>{{ extension.meta.description ?? i18n.ts.none }}</template>
 | 
			
		||||
			</MkKeyValue>
 | 
			
		||||
			<MkKeyValue v-if="isPlugin">
 | 
			
		||||
				<template #key>{{ i18n.ts.version }}</template>
 | 
			
		||||
				<template #value>{{ extension.meta.version }}</template>
 | 
			
		||||
			</MkKeyValue>
 | 
			
		||||
			<MkKeyValue v-if="isPlugin">
 | 
			
		||||
				<template #key>{{ i18n.ts.permission }}</template>
 | 
			
		||||
				<template #value>
 | 
			
		||||
					<ul v-if="extension.meta.permissions && extension.meta.permissions.length > 0" :class="$style.extInstallerKVList">
 | 
			
		||||
						<li v-for="permission in extension.meta.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li>
 | 
			
		||||
					</ul>
 | 
			
		||||
					<template v-else>{{ i18n.ts.none }}</template>
 | 
			
		||||
				</template>
 | 
			
		||||
			</MkKeyValue>
 | 
			
		||||
			<MkKeyValue v-if="isTheme">
 | 
			
		||||
				<template #key>{{ i18n.ts._externalResourceInstaller._meta.base }}</template>
 | 
			
		||||
				<template #value>{{ i18n.ts[extension.meta.base ?? 'none'] }}</template>
 | 
			
		||||
			</MkKeyValue>
 | 
			
		||||
			<MkFolder>
 | 
			
		||||
				<template #icon><i class="ti ti-code"></i></template>
 | 
			
		||||
				<template #label>{{ i18n.ts._plugin.viewSource }}</template>
 | 
			
		||||
 | 
			
		||||
				<MkCode :code="extension.raw"/>
 | 
			
		||||
			</MkFolder>
 | 
			
		||||
		</div>
 | 
			
		||||
	</FormSection>
 | 
			
		||||
	<slot name="additionalInfo"/>
 | 
			
		||||
	<div class="_buttonsCenter">
 | 
			
		||||
		<MkButton primary @click="emits('confirm')"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
export type Extension = {
 | 
			
		||||
	type: 'plugin';
 | 
			
		||||
	raw: string;
 | 
			
		||||
	meta: {
 | 
			
		||||
		name: string;
 | 
			
		||||
		version: string;
 | 
			
		||||
		author: string;
 | 
			
		||||
		description?: string;
 | 
			
		||||
		permissions?: string[];
 | 
			
		||||
		config?: Record<string, any>;
 | 
			
		||||
	};
 | 
			
		||||
} | {
 | 
			
		||||
	type: 'theme';
 | 
			
		||||
	raw: string;
 | 
			
		||||
	meta: {
 | 
			
		||||
		name: string;
 | 
			
		||||
		author: string;
 | 
			
		||||
		base?: 'light' | 'dark';
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import MkButton from '@/components/MkButton.vue';
 | 
			
		||||
import FormSection from '@/components/form/section.vue';
 | 
			
		||||
import FormSplit from '@/components/form/split.vue';
 | 
			
		||||
import MkCode from '@/components/MkCode.vue';
 | 
			
		||||
import MkInfo from '@/components/MkInfo.vue';
 | 
			
		||||
import MkFolder from '@/components/MkFolder.vue';
 | 
			
		||||
import MkKeyValue from '@/components/MkKeyValue.vue';
 | 
			
		||||
import { i18n } from '@/i18n.js';
 | 
			
		||||
 | 
			
		||||
const isPlugin = computed(() => props.extension.type === 'plugin');
 | 
			
		||||
const isTheme = computed(() => props.extension.type === 'theme');
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
	extension: Extension;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits<{
 | 
			
		||||
	(ev: 'confirm'): void;
 | 
			
		||||
}>();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" module>
 | 
			
		||||
.extInstallerRoot {
 | 
			
		||||
	border-radius: var(--radius);
 | 
			
		||||
	background: var(--panel);
 | 
			
		||||
	padding: 1.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.extInstallerIconWrapper {
 | 
			
		||||
	width: 48px;
 | 
			
		||||
	height: 48px;
 | 
			
		||||
	font-size: 24px;
 | 
			
		||||
	line-height: 48px;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	border-radius: 50%;
 | 
			
		||||
	margin-left: auto;
 | 
			
		||||
	margin-right: auto;
 | 
			
		||||
 | 
			
		||||
	background-color: var(--accentedBg);
 | 
			
		||||
	color: var(--accent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.extInstallerTitle {
 | 
			
		||||
	font-size: 1.2rem;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
	margin: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.extInstallerNormDesc {
 | 
			
		||||
	text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.extInstallerKVList {
 | 
			
		||||
	margin-top: 0;
 | 
			
		||||
	margin-bottom: 0;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -8,62 +8,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		|||
	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
 | 
			
		||||
	<MkSpacer :contentMax="500">
 | 
			
		||||
		<MkLoading v-if="uiPhase === 'fetching'"/>
 | 
			
		||||
		<div v-else-if="uiPhase === 'confirm' && data" class="_gaps_m" :class="$style.extInstallerRoot">
 | 
			
		||||
			<div :class="$style.extInstallerIconWrapper">
 | 
			
		||||
				<i v-if="data.type === 'plugin'" class="ti ti-plug"></i>
 | 
			
		||||
				<i v-else-if="data.type === 'theme'" class="ti ti-palette"></i>
 | 
			
		||||
				<i v-else class="ti ti-download"></i>
 | 
			
		||||
			</div>
 | 
			
		||||
			<h2 :class="$style.extInstallerTitle">{{ i18n.ts._externalResourceInstaller[`_${data.type}`].title }}</h2>
 | 
			
		||||
			<div :class="$style.extInstallerNormDesc">{{ i18n.ts._externalResourceInstaller.checkVendorBeforeInstall }}</div>
 | 
			
		||||
			<MkInfo v-if="data.type === 'plugin'" :warn="true">{{ i18n.ts._plugin.installWarn }}</MkInfo>
 | 
			
		||||
			<FormSection>
 | 
			
		||||
				<template #label>{{ i18n.ts._externalResourceInstaller[`_${data.type}`].metaTitle }}</template>
 | 
			
		||||
				<div class="_gaps_s">
 | 
			
		||||
					<FormSplit>
 | 
			
		||||
						<MkKeyValue>
 | 
			
		||||
							<template #key>{{ i18n.ts.name }}</template>
 | 
			
		||||
							<template #value>{{ data.meta?.name }}</template>
 | 
			
		||||
						</MkKeyValue>
 | 
			
		||||
						<MkKeyValue>
 | 
			
		||||
							<template #key>{{ i18n.ts.author }}</template>
 | 
			
		||||
							<template #value>{{ data.meta?.author }}</template>
 | 
			
		||||
						</MkKeyValue>
 | 
			
		||||
					</FormSplit>
 | 
			
		||||
					<MkKeyValue v-if="data.type === 'plugin'">
 | 
			
		||||
						<template #key>{{ i18n.ts.description }}</template>
 | 
			
		||||
						<template #value>{{ data.meta?.description }}</template>
 | 
			
		||||
					</MkKeyValue>
 | 
			
		||||
					<MkKeyValue v-if="data.type === 'plugin'">
 | 
			
		||||
						<template #key>{{ i18n.ts.version }}</template>
 | 
			
		||||
						<template #value>{{ data.meta?.version }}</template>
 | 
			
		||||
					</MkKeyValue>
 | 
			
		||||
					<MkKeyValue v-if="data.type === 'plugin'">
 | 
			
		||||
						<template #key>{{ i18n.ts.permission }}</template>
 | 
			
		||||
						<template #value>
 | 
			
		||||
							<ul :class="$style.extInstallerKVList">
 | 
			
		||||
								<li v-for="permission in data.meta?.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li>
 | 
			
		||||
							</ul>
 | 
			
		||||
						</template>
 | 
			
		||||
					</MkKeyValue>
 | 
			
		||||
					<MkKeyValue v-if="data.type === 'theme' && data.meta?.base">
 | 
			
		||||
						<template #key>{{ i18n.ts._externalResourceInstaller._meta.base }}</template>
 | 
			
		||||
						<template #value>{{ i18n.ts[data.meta.base] }}</template>
 | 
			
		||||
					</MkKeyValue>
 | 
			
		||||
					<MkFolder>
 | 
			
		||||
						<template #icon><i class="ti ti-code"></i></template>
 | 
			
		||||
						<template #label>{{ i18n.ts._plugin.viewSource }}</template>
 | 
			
		||||
 | 
			
		||||
						<MkCode :code="data.raw ?? ''"/>
 | 
			
		||||
					</MkFolder>
 | 
			
		||||
				</div>
 | 
			
		||||
			</FormSection>
 | 
			
		||||
		<MkExtensionInstaller v-else-if="uiPhase === 'confirm' && data" :extension="data" @confirm="install()">
 | 
			
		||||
			<template #additionalInfo>
 | 
			
		||||
				<FormSection>
 | 
			
		||||
					<template #label>{{ i18n.ts._externalResourceInstaller._vendorInfo.title }}</template>
 | 
			
		||||
					<div class="_gaps_s">
 | 
			
		||||
						<MkKeyValue>
 | 
			
		||||
							<template #key>{{ i18n.ts._externalResourceInstaller._vendorInfo.endpoint }}</template>
 | 
			
		||||
						<template #value><MkUrl :url="url ?? ''" :showUrlPreview="false"></MkUrl></template>
 | 
			
		||||
							<template #value><MkUrl :url="url" :showUrlPreview="false"></MkUrl></template>
 | 
			
		||||
						</MkKeyValue>
 | 
			
		||||
						<MkKeyValue>
 | 
			
		||||
							<template #key>{{ i18n.ts._externalResourceInstaller._vendorInfo.hashVerify }}</template>
 | 
			
		||||
| 
						 | 
				
			
			@ -74,10 +26,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		|||
						</MkKeyValue>
 | 
			
		||||
					</div>
 | 
			
		||||
				</FormSection>
 | 
			
		||||
			<div class="_buttonsCenter">
 | 
			
		||||
				<MkButton primary @click="install()"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
			</template>
 | 
			
		||||
		</MkExtensionInstaller>
 | 
			
		||||
		<div v-else-if="uiPhase === 'error'" class="_gaps_m" :class="[$style.extInstallerRoot, $style.error]">
 | 
			
		||||
			<div :class="$style.extInstallerIconWrapper">
 | 
			
		||||
				<i class="ti ti-circle-x"></i>
 | 
			
		||||
| 
						 | 
				
			
			@ -96,14 +46,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		|||
<script lang="ts" setup>
 | 
			
		||||
import { ref, computed, onActivated, onDeactivated, nextTick } from 'vue';
 | 
			
		||||
import MkLoading from '@/components/global/MkLoading.vue';
 | 
			
		||||
import MkExtensionInstaller, { type Extension } from '@/components/MkExtensionInstaller.vue';
 | 
			
		||||
import MkButton from '@/components/MkButton.vue';
 | 
			
		||||
import FormSection from '@/components/form/section.vue';
 | 
			
		||||
import FormSplit from '@/components/form/split.vue';
 | 
			
		||||
import MkCode from '@/components/MkCode.vue';
 | 
			
		||||
import MkUrl from '@/components/global/MkUrl.vue';
 | 
			
		||||
import MkInfo from '@/components/MkInfo.vue';
 | 
			
		||||
import MkFolder from '@/components/MkFolder.vue';
 | 
			
		||||
import MkKeyValue from '@/components/MkKeyValue.vue';
 | 
			
		||||
import MkUrl from '@/components/global/MkUrl.vue';
 | 
			
		||||
import FormSection from '@/components/form/section.vue';
 | 
			
		||||
import * as os from '@/os.js';
 | 
			
		||||
import { misskeyApi } from '@/scripts/misskey-api.js';
 | 
			
		||||
import { AiScriptPluginMeta, parsePluginMeta, installPlugin } from '@/scripts/install-plugin.js';
 | 
			
		||||
| 
						 | 
				
			
			@ -124,24 +71,7 @@ const errorKV = ref<{
 | 
			
		|||
const url = ref<string | null>(null);
 | 
			
		||||
const hash = ref<string | null>(null);
 | 
			
		||||
 | 
			
		||||
const data = ref<{
 | 
			
		||||
	type: 'plugin' | 'theme';
 | 
			
		||||
	raw: string;
 | 
			
		||||
	meta?: {
 | 
			
		||||
		// Plugin & Theme Common
 | 
			
		||||
		name: string;
 | 
			
		||||
		author: string;
 | 
			
		||||
 | 
			
		||||
		// Plugin
 | 
			
		||||
		description?: string;
 | 
			
		||||
		version?: string;
 | 
			
		||||
		permissions?: string[];
 | 
			
		||||
		config?: Record<string, any>;
 | 
			
		||||
 | 
			
		||||
		// Theme
 | 
			
		||||
		base?: 'light' | 'dark';
 | 
			
		||||
	};
 | 
			
		||||
} | null>(null);
 | 
			
		||||
const data = ref<Extension | null>(null);
 | 
			
		||||
 | 
			
		||||
function goBack(): void {
 | 
			
		||||
	history.back();
 | 
			
		||||
| 
						 | 
				
			
			@ -227,7 +157,7 @@ async function fetch() {
 | 
			
		|||
				data.value = {
 | 
			
		||||
					type: 'theme',
 | 
			
		||||
					meta: {
 | 
			
		||||
						description,
 | 
			
		||||
						// description, // 使用されていない
 | 
			
		||||
						...meta,
 | 
			
		||||
					},
 | 
			
		||||
					raw: res.data,
 | 
			
		||||
| 
						 | 
				
			
			@ -353,9 +283,4 @@ definePageMetadata(() => ({
 | 
			
		|||
.extInstallerNormDesc {
 | 
			
		||||
	text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.extInstallerKVList {
 | 
			
		||||
	margin-top: 0;
 | 
			
		||||
	margin-bottom: 0;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue