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,76 +8,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="500">
|
<MkSpacer :contentMax="500">
|
||||||
<MkLoading v-if="uiPhase === 'fetching'"/>
|
<MkLoading v-if="uiPhase === 'fetching'"/>
|
||||||
<div v-else-if="uiPhase === 'confirm' && data" class="_gaps_m" :class="$style.extInstallerRoot">
|
<MkExtensionInstaller v-else-if="uiPhase === 'confirm' && data" :extension="data" @confirm="install()">
|
||||||
<div :class="$style.extInstallerIconWrapper">
|
<template #additionalInfo>
|
||||||
<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>
|
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<template #label>{{ i18n.ts._externalResourceInstaller._vendorInfo.title }}</template>
|
<template #label>{{ i18n.ts._externalResourceInstaller._vendorInfo.title }}</template>
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkKeyValue>
|
<MkKeyValue>
|
||||||
<template #key>{{ i18n.ts._externalResourceInstaller._vendorInfo.endpoint }}</template>
|
<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>
|
||||||
<MkKeyValue>
|
<MkKeyValue>
|
||||||
<template #key>{{ i18n.ts._externalResourceInstaller._vendorInfo.hashVerify }}</template>
|
<template #key>{{ i18n.ts._externalResourceInstaller._vendorInfo.hashVerify }}</template>
|
||||||
<template #value>
|
<template #value>
|
||||||
<!--この画面が出ている時点でハッシュの検証には成功している-->
|
<!-- この画面が出ている時点でハッシュの検証には成功している -->
|
||||||
<i class="ti ti-check" style="color: var(--accent)"></i>
|
<i class="ti ti-check" style="color: var(--accent)"></i>
|
||||||
</template>
|
</template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
</div>
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
<div class="_buttonsCenter">
|
</template>
|
||||||
<MkButton primary @click="install()"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton>
|
</MkExtensionInstaller>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="uiPhase === 'error'" class="_gaps_m" :class="[$style.extInstallerRoot, $style.error]">
|
<div v-else-if="uiPhase === 'error'" class="_gaps_m" :class="[$style.extInstallerRoot, $style.error]">
|
||||||
<div :class="$style.extInstallerIconWrapper">
|
<div :class="$style.extInstallerIconWrapper">
|
||||||
<i class="ti ti-circle-x"></i>
|
<i class="ti ti-circle-x"></i>
|
||||||
|
@ -96,14 +46,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, onActivated, onDeactivated, nextTick } from 'vue';
|
import { ref, computed, onActivated, onDeactivated, nextTick } from 'vue';
|
||||||
import MkLoading from '@/components/global/MkLoading.vue';
|
import MkLoading from '@/components/global/MkLoading.vue';
|
||||||
|
import MkExtensionInstaller, { type Extension } from '@/components/MkExtensionInstaller.vue';
|
||||||
import MkButton from '@/components/MkButton.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 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 * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { AiScriptPluginMeta, parsePluginMeta, installPlugin } from '@/scripts/install-plugin.js';
|
import { AiScriptPluginMeta, parsePluginMeta, installPlugin } from '@/scripts/install-plugin.js';
|
||||||
|
@ -124,24 +71,7 @@ const errorKV = ref<{
|
||||||
const url = ref<string | null>(null);
|
const url = ref<string | null>(null);
|
||||||
const hash = ref<string | null>(null);
|
const hash = ref<string | null>(null);
|
||||||
|
|
||||||
const data = ref<{
|
const data = ref<Extension | null>(null);
|
||||||
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);
|
|
||||||
|
|
||||||
function goBack(): void {
|
function goBack(): void {
|
||||||
history.back();
|
history.back();
|
||||||
|
@ -227,7 +157,7 @@ async function fetch() {
|
||||||
data.value = {
|
data.value = {
|
||||||
type: 'theme',
|
type: 'theme',
|
||||||
meta: {
|
meta: {
|
||||||
description,
|
// description, // 使用されていない
|
||||||
...meta,
|
...meta,
|
||||||
},
|
},
|
||||||
raw: res.data,
|
raw: res.data,
|
||||||
|
@ -353,9 +283,4 @@ definePageMetadata(() => ({
|
||||||
.extInstallerNormDesc {
|
.extInstallerNormDesc {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.extInstallerKVList {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in a new issue