Skip to content

Commit cd028dc

Browse files
liszt01Hiroshiba
andauthored
エディターのメイン画面でアップデートを通知する (#1616)
Co-authored-by: Hiroshiba <[email protected]>
1 parent 88330de commit cd028dc

File tree

10 files changed

+194
-44
lines changed

10 files changed

+194
-44
lines changed

.env.production

+2
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ VITE_DEFAULT_ENGINE_INFOS=`[
99
"host": "http://127.0.0.1:50021"
1010
}
1111
]`
12+
VITE_OFFICIAL_WEBSITE_URL=https://voicevox.hiroshiba.jp/
13+
VITE_LATEST_UPDATE_INFOS_URL=https://raw.githubusercontent.com/VOICEVOX/voicevox_blog/master/src/data/updateInfos.json
1214
VITE_GTM_CONTAINER_ID=GTM-DUMMY

.env.test

+2
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ VITE_DEFAULT_ENGINE_INFOS=`[
99
"host": "http://127.0.0.1:50021"
1010
}
1111
]`
12+
VITE_OFFICIAL_WEBSITE_URL=https://voicevox.hiroshiba.jp/
13+
VITE_LATEST_UPDATE_INFOS_URL=https://raw.githubusercontent.com/VOICEVOX/voicevox_blog/master/src/data/updateInfos.json
1214
VITE_GTM_CONTAINER_ID=GTM-DUMMY
+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<template>
2+
<q-dialog v-model="modelValueComputed">
3+
<q-card class="q-py-sm q-px-md">
4+
<q-card-section class="q-pb-sm" align="center">
5+
<div class="text-h6">
6+
<q-icon name="info" color="primary" />アップデート通知
7+
</div>
8+
</q-card-section>
9+
<q-card-section class="q-pt-sm" align="center">
10+
<div class="text-body1">
11+
最新バージョン {{ props.latestVersion }} が利用可能です。<br />
12+
公式サイトから最新バージョンをダウンロードできます。
13+
</div>
14+
</q-card-section>
15+
<q-card-section class="q-py-none scrollable-area">
16+
<template
17+
v-for="(info, infoIndex) of props.newUpdateInfos"
18+
:key="infoIndex"
19+
>
20+
<div class="text-h6">バージョン {{ info.version }}</div>
21+
<ul>
22+
<template
23+
v-for="(item, descriptionIndex) of info.descriptions"
24+
:key="descriptionIndex"
25+
>
26+
<li>{{ item }}</li>
27+
</template>
28+
</ul>
29+
</template>
30+
</q-card-section>
31+
<q-card-actions align="center" class="button-area">
32+
<q-btn
33+
padding="xs md"
34+
label="キャンセル"
35+
unelevated
36+
color="surface"
37+
text-color="display"
38+
class="q-mt-sm"
39+
@click="closeUpdateNotificationDialog()"
40+
/>
41+
<q-btn
42+
padding="xs md"
43+
label="公式サイトを開く"
44+
unelevated
45+
color="primary"
46+
text-color="display-on-primary"
47+
class="q-mt-sm"
48+
@click="
49+
openOfficialWebsite();
50+
closeUpdateNotificationDialog();
51+
"
52+
/>
53+
</q-card-actions>
54+
</q-card>
55+
</q-dialog>
56+
</template>
57+
58+
<script setup lang="ts">
59+
import { computed } from "vue";
60+
import { UpdateInfo } from "@/type/preload";
61+
62+
const props =
63+
defineProps<{
64+
modelValue: boolean;
65+
latestVersion: string;
66+
newUpdateInfos: UpdateInfo[];
67+
}>();
68+
const emit =
69+
defineEmits<{
70+
(e: "update:modelValue", value: boolean): void;
71+
}>();
72+
73+
const modelValueComputed = computed({
74+
get: () => props.modelValue,
75+
set: (val) => emit("update:modelValue", val),
76+
});
77+
78+
const closeUpdateNotificationDialog = () => {
79+
modelValueComputed.value = false;
80+
};
81+
82+
const openOfficialWebsite = () => {
83+
window.open(import.meta.env.VITE_OFFICIAL_WEBSITE_URL, "_blank");
84+
};
85+
</script>
86+
87+
<style scoped lang="scss">
88+
@use '@/styles/colors' as colors;
89+
90+
.scrollable-area {
91+
overflow-y: auto;
92+
max-height: 250px;
93+
}
94+
95+
.scrollable-area h5 {
96+
margin: 10px 0;
97+
}
98+
99+
.scrollable-area h6 {
100+
margin: 15px 0;
101+
}
102+
103+
.button-area {
104+
border-top: 1px solid colors.$splitter;
105+
/* ボタン領域の上部に線を引く */
106+
}
107+
</style>

src/components/help/HelpDialog.vue

+3-44
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@
8686

8787
<script setup lang="ts">
8888
import { computed, ref, type Component } from "vue";
89-
import semver from "semver";
9089
import HelpPolicy from "./HelpPolicy.vue";
9190
import LibraryPolicy from "./LibraryPolicy.vue";
9291
import HowToUse from "./HowToUse.vue";
@@ -97,6 +96,7 @@ import QAndA from "./QAndA.vue";
9796
import ContactInfo from "./ContactInfo.vue";
9897
import { UpdateInfo as UpdateInfoObject } from "@/type/preload";
9998
import { useStore } from "@/store";
99+
import { useFetchNewUpdateInfos } from "@/composables/useFetchNewUpdateInfos";
100100
101101
type PageItem = {
102102
type: "item";
@@ -131,48 +131,7 @@ const store = useStore();
131131
const updateInfos = ref<UpdateInfoObject[]>();
132132
store.dispatch("GET_UPDATE_INFOS").then((obj) => (updateInfos.value = obj));
133133
134-
const isCheckingFinished = ref<boolean>(false);
135-
136-
// 最新版があるか調べる
137-
const currentVersion = ref("");
138-
const latestVersion = ref("");
139-
window.electron
140-
.getAppInfos()
141-
.then((obj) => {
142-
currentVersion.value = obj.version;
143-
})
144-
.then(() => {
145-
fetch("https://api.github.com/repos/VOICEVOX/voicevox/releases", {
146-
method: "GET",
147-
headers: {
148-
"Content-Type": "application/json",
149-
Accept: "application/json",
150-
},
151-
})
152-
.then((response) => {
153-
if (!response.ok) throw new Error("Network response was not ok.");
154-
return response.json();
155-
})
156-
.then((json) => {
157-
const newerVersion = json.find(
158-
(item: { prerelease: boolean; tag_name: string }) => {
159-
return (
160-
!item.prerelease &&
161-
semver.valid(currentVersion.value) &&
162-
semver.valid(item.tag_name) &&
163-
semver.lt(currentVersion.value, item.tag_name)
164-
);
165-
}
166-
);
167-
if (newerVersion) {
168-
latestVersion.value = newerVersion.tag_name;
169-
}
170-
isCheckingFinished.value = true;
171-
})
172-
.catch((err) => {
173-
throw new Error(err);
174-
});
175-
});
134+
const { isCheckingFinished, latestVersion } = useFetchNewUpdateInfos();
176135
177136
const isUpdateAvailable = computed(() => {
178137
return isCheckingFinished.value && latestVersion.value !== "";
@@ -223,7 +182,7 @@ const pagedata = computed(() => {
223182
name: "アップデート情報",
224183
component: UpdateInfo,
225184
props: {
226-
downloadLink: "https://voicevox.hiroshiba.jp/",
185+
downloadLink: import.meta.env.VITE_OFFICIAL_WEBSITE_URL,
227186
updateInfos: updateInfos.value,
228187
isUpdateAvailable: isUpdateAvailable.value,
229188
latestVersion: latestVersion.value,
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { ref } from "vue";
2+
import semver from "semver";
3+
import { UpdateInfo } from "@/type/preload";
4+
5+
// 最新版があるか調べる
6+
// 現バージョンより新しい最新版があれば`latestVersion`に代入される
7+
export const useFetchNewUpdateInfos = () => {
8+
const isCheckingFinished = ref<boolean>(false);
9+
const currentVersion = ref("");
10+
const latestVersion = ref("");
11+
const newUpdateInfos = ref<UpdateInfo[]>([]);
12+
13+
window.electron
14+
.getAppInfos()
15+
.then((obj) => {
16+
currentVersion.value = obj.version;
17+
})
18+
.then(() => {
19+
const url: string | undefined = import.meta.env
20+
.VITE_LATEST_UPDATE_INFOS_URL;
21+
if (!url) {
22+
throw new Error(
23+
"VITE_LATEST_UPDATE_INFOS_URLが未設定です。.env内に記載してください。"
24+
);
25+
}
26+
fetch(url)
27+
.then((response) => {
28+
if (!response.ok) throw new Error("Network response was not ok.");
29+
return response.json();
30+
})
31+
.then((json) => {
32+
newUpdateInfos.value = json.filter((item: UpdateInfo) => {
33+
return semver.lt(currentVersion.value, item.version);
34+
});
35+
if (newUpdateInfos.value?.length) {
36+
latestVersion.value = newUpdateInfos.value[0].version;
37+
}
38+
isCheckingFinished.value = true;
39+
});
40+
});
41+
42+
return {
43+
isCheckingFinished,
44+
latestVersion,
45+
newUpdateInfos,
46+
};
47+
};

src/store/type.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,7 @@ export type UiStoreState = {
11661166
isAcceptTermsDialogOpen: boolean;
11671167
isDictionaryManageDialogOpen: boolean;
11681168
isEngineManageDialogOpen: boolean;
1169+
isUpdateNotificationDialogOpen: boolean;
11691170
isMaximized: boolean;
11701171
isPinned: boolean;
11711172
isFullscreen: boolean;
@@ -1231,6 +1232,7 @@ export type UiStoreTypes = {
12311232
isToolbarSettingDialogOpen?: boolean;
12321233
isCharacterOrderDialogOpen?: boolean;
12331234
isEngineManageDialogOpen?: boolean;
1235+
isUpdateNotificationDialogOpen?: boolean;
12341236
};
12351237
action(payload: {
12361238
isDefaultStyleSelectDialogOpen?: boolean;
@@ -1243,6 +1245,7 @@ export type UiStoreTypes = {
12431245
isToolbarSettingDialogOpen?: boolean;
12441246
isCharacterOrderDialogOpen?: boolean;
12451247
isEngineManageDialogOpen?: boolean;
1248+
isUpdateNotificationDialogOpen?: boolean;
12461249
}): void;
12471250
};
12481251

src/store/ui.ts

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export const uiStoreState: UiStoreState = {
6060
isAcceptTermsDialogOpen: false,
6161
isDictionaryManageDialogOpen: false,
6262
isEngineManageDialogOpen: false,
63+
isUpdateNotificationDialogOpen: false,
6364
isMaximized: false,
6465
isPinned: false,
6566
isFullscreen: false,
@@ -169,6 +170,7 @@ export const uiStore = createPartialStore<UiStoreTypes>({
169170
isToolbarSettingDialogOpen?: boolean;
170171
isCharacterOrderDialogOpen?: boolean;
171172
isEngineManageDialogOpen?: boolean;
173+
isUpdateNotificationDialogOpen?: boolean;
172174
}
173175
) {
174176
for (const [key, value] of Object.entries(dialogState)) {

src/views/EditorHome.vue

+25
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@
176176
v-model="isAcceptRetrieveTelemetryDialogOpenComputed"
177177
/>
178178
<accept-terms-dialog v-model="isAcceptTermsDialogOpenComputed" />
179+
<update-notification-dialog
180+
v-model="isUpdateNotificationDialogOpenComputed"
181+
:latest-version="latestVersion"
182+
:new-update-infos="newUpdateInfos"
183+
/>
179184
</template>
180185

181186
<script setup lang="ts">
@@ -203,6 +208,8 @@ import AcceptTermsDialog from "@/components/AcceptTermsDialog.vue";
203208
import DictionaryManageDialog from "@/components/DictionaryManageDialog.vue";
204209
import EngineManageDialog from "@/components/EngineManageDialog.vue";
205210
import ProgressDialog from "@/components/ProgressDialog.vue";
211+
import UpdateNotificationDialog from "@/components/UpdateNotificationDialog.vue";
212+
import { useFetchNewUpdateInfos } from "@/composables/useFetchNewUpdateInfos";
206213
import { AudioItem, EngineState } from "@/store/type";
207214
import {
208215
AudioKey,
@@ -541,6 +548,13 @@ watch(userOrderedCharacterInfos, (userOrderedCharacterInfos) => {
541548
}
542549
});
543550
551+
// エディタのアップデート確認
552+
const { isCheckingFinished, latestVersion, newUpdateInfos } =
553+
useFetchNewUpdateInfos();
554+
const isUpdateAvailable = computed(() => {
555+
return isCheckingFinished.value && latestVersion.value !== "";
556+
});
557+
544558
// ソフトウェアを初期化
545559
const isCompletedInitialStartup = ref(false);
546560
onMounted(async () => {
@@ -623,6 +637,8 @@ onMounted(async () => {
623637
import.meta.env.MODE !== "development" &&
624638
store.state.acceptTerms !== "Accepted";
625639
640+
isUpdateNotificationDialogOpenComputed.value = isUpdateAvailable.value;
641+
626642
isCompletedInitialStartup.value = true;
627643
});
628644
@@ -798,6 +814,15 @@ const isAcceptRetrieveTelemetryDialogOpenComputed = computed({
798814
}),
799815
});
800816
817+
// アップデート通知
818+
const isUpdateNotificationDialogOpenComputed = computed({
819+
get: () => store.state.isUpdateNotificationDialogOpen,
820+
set: (val) =>
821+
store.dispatch("SET_DIALOG_OPEN", {
822+
isUpdateNotificationDialogOpen: val,
823+
}),
824+
});
825+
801826
// ドラッグ&ドロップ
802827
const dragEventCounter = ref(0);
803828
const loadDraggedFile = (event: { dataTransfer: DataTransfer | null }) => {

src/vite-env.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ interface ImportMetaEnv {
55
readonly VITE_APP_NAME: string;
66
readonly VITE_APP_VERSION: string;
77
readonly VITE_DEFAULT_ENGINE_INFOS: string;
8+
readonly VITE_OFFICIAL_WEBSITE_URL: string;
9+
readonly VITE_LATEST_UPDATE_INFOS_URL: string;
810
readonly VITE_GTM_CONTAINER_ID: string;
911
readonly VITE_TARGET: "electron" | "browser";
1012
}

tests/unit/store/Vuex.spec.ts

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ describe("store/vuex.js test", () => {
5151
isDefaultStyleSelectDialogOpen: false,
5252
isDictionaryManageDialogOpen: false,
5353
isEngineManageDialogOpen: false,
54+
isUpdateNotificationDialogOpen: false,
5455
isAcceptRetrieveTelemetryDialogOpen: false,
5556
isAcceptTermsDialogOpen: false,
5657
isMaximized: false,

0 commit comments

Comments
 (0)