Skip to content

Commit

Permalink
refactor: 拡張機能インストールのページの一部をコンポーネントとして分離 (#14654)
Browse files Browse the repository at this point in the history
* create MkExtensionInstaller.vue

* annotation

* add fallbacks

* storybook

* update annotations

* Update MkExtensionInstaller.vue

* use additonalInfo slot
  • Loading branch information
FineArchs authored Oct 7, 2024
1 parent 03fb688 commit ed89b4b
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 96 deletions.
Original file line number Diff line number Diff line change
@@ -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 changes: 146 additions & 0 deletions packages/frontend/src/components/MkExtensionInstaller.vue
Original file line number Diff line number Diff line change
@@ -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>
117 changes: 21 additions & 96 deletions packages/frontend/src/pages/install-extensions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,76 +8,26 @@ 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>
<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.name }}</template>
<template #value>{{ data.meta?.name }}</template>
<template #key>{{ i18n.ts._externalResourceInstaller._vendorInfo.endpoint }}</template>
<template #value><MkUrl :url="url" :showUrlPreview="false"></MkUrl></template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.author }}</template>
<template #value>{{ data.meta?.author }}</template>
<template #key>{{ i18n.ts._externalResourceInstaller._vendorInfo.hashVerify }}</template>
<template #value>
<!-- この画面が出ている時点でハッシュの検証には成功している -->
<i class="ti ti-check" style="color: var(--accent)"></i>
</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>
<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>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts._externalResourceInstaller._vendorInfo.hashVerify }}</template>
<template #value>
<!--この画面が出ている時点でハッシュの検証には成功している-->
<i class="ti ti-check" style="color: var(--accent)"></i>
</template>
</MkKeyValue>
</div>
</FormSection>
<div class="_buttonsCenter">
<MkButton primary @click="install()"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton>
</div>
</div>
</div>
</FormSection>
</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>
Expand All @@ -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';
Expand All @@ -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();
Expand Down Expand Up @@ -227,7 +157,7 @@ async function fetch() {
data.value = {
type: 'theme',
meta: {
description,
// description, // 使用されていない
...meta,
},
raw: res.data,
Expand Down Expand Up @@ -353,9 +283,4 @@ definePageMetadata(() => ({
.extInstallerNormDesc {
text-align: center;
}

.extInstallerKVList {
margin-top: 0;
margin-bottom: 0;
}
</style>

1 comment on commit ed89b4b

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chromatic detects changes. Please review the changes on Chromatic.

Please sign in to comment.