mirror of
https://github.com/mii443/vrclipboard-ime-gui.git
synced 2025-08-22 08:05:32 +00:00
fix updater
This commit is contained in:
10
package-lock.json
generated
10
package-lock.json
generated
@ -9,6 +9,7 @@
|
||||
"version": "1.11.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-process": "^2.2.0",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0",
|
||||
"@tauri-apps/plugin-updater": "^2.6.0",
|
||||
"react": "^18.2.0",
|
||||
@ -1267,6 +1268,15 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-process": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-process/-/plugin-process-2.2.0.tgz",
|
||||
"integrity": "sha512-uypN2Crmyop9z+KRJr3zl71OyVFgTuvHFjsJ0UxxQ/J5212jVa5w4nPEYjIewcn8bUEXacRebwE6F7owgrbhSw==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-shell": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.0.0.tgz",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "vrclipboard-ime-gui",
|
||||
"private": true,
|
||||
"version": "1.11.0",
|
||||
"version": "1.11.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@ -11,6 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-process": "^2.2.0",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0",
|
||||
"@tauri-apps/plugin-updater": "^2.6.0",
|
||||
"react": "^18.2.0",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"url": "https://r2-vrime.mii.dev/releases/vrclipboard-ime-gui_1.11.0_x64_ja-JP.msi.zip",
|
||||
"version": "1.11.0",
|
||||
"notes": "UIの変更, TSF再変換機能の正式リリース",
|
||||
"url": "https://r2-vrime.mii.dev/releases/vrclipboard-ime-gui_1.11.1_x64_ja-JP.msi.zip",
|
||||
"version": "1.11.1",
|
||||
"notes": "UIの変更, TSF再変換機能の正式リリース, アップデート機能の修正",
|
||||
"pub_date": "2025-03-11T19:14:02+09:00",
|
||||
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVTTStkVlpUR0NpcVcvYitzcmhYakFkOTBWZVo5RHFwVk9xbjdzNlg3VHMrZ3NBaHpxS2VacEg4ckE3V3ZZSGZETzFyZE1Qc0JoRTltUGhvREdsUzF2Ry91c2RtWnlqOXdrPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzQxNjg4MzA3CWZpbGU6dnJjbGlwYm9hcmQtaW1lLWd1aV8xLjExLjBfeDY0X2phLUpQLm1zaS56aXAKVWt0b0t5YkR5MlczM1FRYXV2THRHYjQwWVFxRGR2OW1KTExJNGFpeGcyN2k2Q1hrU3Yrb3dxU3NvY2FLNTk4bDd1Q1lFanpaQktIM0UxdTBzNmd3Q3c9PQo="
|
||||
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVTTStkVlpUR0NpcVlBbURsaEpHUmtuS3N5dUJzM0RaMnBCK2F4K1J5dFJYR0NoaWlvRUtJK203NEd6aDFXZjE5MFF6MUZqZDBwd0pVdWFpOW14ZDJaTDJKL0d4dGd0K1FrPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzQxNjk2MTEwCWZpbGU6dnJjbGlwYm9hcmQtaW1lLWd1aV8xLjExLjFfeDY0X2phLUpQLm1zaS56aXAKTmljczR2U0ZmSlZwbUZ2eEFuSmNUZEJ0L0ZHeSs0SmlRUjRRMU95OWpLTWZ5QktLSWRZdDgvMkpEV2hDQ00wSytWYm9WSHhpQm5naWlUQTBhQkhKRGc9PQo="
|
||||
}
|
||||
|
15
src-tauri/capabilities/default.json
Normal file
15
src-tauri/capabilities/default.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"identifier": "desktop-capability",
|
||||
"platforms": [
|
||||
"macOS",
|
||||
"windows",
|
||||
"linux"
|
||||
],
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"updater:allow-check",
|
||||
"updater:allow-download",
|
||||
"updater:allow-install",
|
||||
"updater:allow-download-and-install"
|
||||
]
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"identifier": "desktop-capability",
|
||||
"platforms": [
|
||||
"macOS",
|
||||
"windows",
|
||||
"linux"
|
||||
],
|
||||
"permissions": [
|
||||
"updater:default",
|
||||
"updater:default"
|
||||
]
|
||||
}
|
@ -14,20 +14,21 @@ mod tauri_emit_subscriber;
|
||||
mod tsf_availability;
|
||||
mod dictionary;
|
||||
|
||||
use std::sync::Mutex;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{Manager, State};
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
|
||||
use clipboard_master::Master;
|
||||
use com::Com;
|
||||
use config::Config;
|
||||
use handler::ConversionHandler;
|
||||
use tauri_emit_subscriber::TauriEmitSubscriber;
|
||||
use tauri_plugin_updater::UpdaterExt;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
use tsf_availability::check_tsf_availability;
|
||||
use tracing::debug;
|
||||
use tracing::{debug, error};
|
||||
use dictionary::Dictionary;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
@ -44,6 +45,7 @@ struct AppState {
|
||||
|
||||
static STATE: Lazy<Mutex<Config>> = Lazy::new(|| Mutex::new(Config::load().unwrap()));
|
||||
static DICTIONARY: Lazy<Mutex<Dictionary>> = Lazy::new(|| Mutex::new(Dictionary::load().unwrap()));
|
||||
static APP_HANDLE: OnceLock<AppHandle> = OnceLock::new();
|
||||
|
||||
#[tauri::command]
|
||||
fn load_settings(state: State<AppState>) -> Result<Config, String> {
|
||||
@ -102,6 +104,29 @@ fn open_ms_settings_regionlanguage_jpnime() -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn check_update() -> Result<bool, String> {
|
||||
if let Some(app_handle) = APP_HANDLE.get() {
|
||||
match app_handle.updater() {
|
||||
Ok(updater) => match updater.check().await {
|
||||
Ok(Some(_)) => Ok(true),
|
||||
Ok(None) => Ok(false),
|
||||
Err(e) => {
|
||||
error!("Failed to check for updates: {}", e);
|
||||
Err(format!("Failed to check for updates: {}", e))
|
||||
},
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Updater not available: {}", e);
|
||||
Err("Updater not available".to_string())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!("App handle not set");
|
||||
Err("App handle not set".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
@ -116,8 +141,18 @@ fn main() {
|
||||
Dictionary::load().expect("Failed to load default dictionary")
|
||||
})),
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![load_settings, save_settings, check_tsf_availability_command, open_ms_settings_regionlanguage_jpnime, load_dictionary, save_dictionary])
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
load_settings,
|
||||
save_settings,
|
||||
check_tsf_availability_command,
|
||||
open_ms_settings_regionlanguage_jpnime,
|
||||
load_dictionary,
|
||||
save_dictionary,
|
||||
check_update
|
||||
])
|
||||
.setup(|app| {
|
||||
APP_HANDLE.set(app.app_handle().to_owned()).unwrap();
|
||||
|
||||
let _span = tracing::span!(tracing::Level::INFO, "main");
|
||||
app.manage(STATE.lock().unwrap().clone());
|
||||
app.manage(DICTIONARY.lock().unwrap().clone());
|
||||
@ -128,6 +163,15 @@ fn main() {
|
||||
});
|
||||
registry.init();
|
||||
|
||||
let update_handle = app_handle.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let _ = update_handle.updater()
|
||||
.unwrap()
|
||||
.check()
|
||||
.await
|
||||
.map_err(|e| tracing::error!("Failed to check for updates: {}", e));
|
||||
});
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let _com = Com::new().unwrap();
|
||||
|
||||
@ -142,4 +186,4 @@ fn main() {
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
}
|
@ -1,12 +1,191 @@
|
||||
import React from 'react';
|
||||
import { Github, ExternalLink, Coffee } from 'lucide-react';
|
||||
import React, { useState } from 'react';
|
||||
import { Github, ExternalLink, Coffee, RefreshCw, Check, AlertCircle, Download, X } from 'lucide-react';
|
||||
import { check } from '@tauri-apps/plugin-updater';
|
||||
import { relaunch } from '@tauri-apps/plugin-process';
|
||||
|
||||
const AboutComponent: React.FC = () => {
|
||||
const [updateStatus, setUpdateStatus] = useState<'idle' | 'checking' | 'available' | 'downloading' | 'notAvailable' | 'error'>('idle');
|
||||
const [downloadProgress, setDownloadProgress] = useState<number>(0);
|
||||
const [contentLength, setContentLength] = useState<number>(0);
|
||||
const [updateInfo, setUpdateInfo] = useState<{version: string, date: string | undefined, body: string | undefined} | null>(null);
|
||||
const [showUpdateDialog, setShowUpdateDialog] = useState(false);
|
||||
|
||||
const openLink = (url: string) => {
|
||||
// ブラウザの標準APIを使用してリンクを開く
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
const checkForUpdates = async () => {
|
||||
try {
|
||||
setUpdateStatus('checking');
|
||||
const update = await check();
|
||||
|
||||
if (update) {
|
||||
console.log(`更新が見つかりました: ${update.version} (${update.date}) - ${update.body}`);
|
||||
setUpdateStatus('available');
|
||||
setUpdateInfo({
|
||||
version: update.version,
|
||||
date: update.date,
|
||||
body: update.body
|
||||
});
|
||||
setShowUpdateDialog(true);
|
||||
} else {
|
||||
setUpdateStatus('notAvailable');
|
||||
setTimeout(() => setUpdateStatus('idle'), 3000);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Update check error:', error);
|
||||
setUpdateStatus('error');
|
||||
setTimeout(() => setUpdateStatus('idle'), 3000);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInstallUpdate = async () => {
|
||||
try {
|
||||
setUpdateStatus('downloading');
|
||||
const update = await check();
|
||||
if (!update) {
|
||||
setUpdateStatus('error');
|
||||
setTimeout(() => setUpdateStatus('idle'), 3000);
|
||||
return;
|
||||
}
|
||||
|
||||
let downloaded = 0;
|
||||
|
||||
// 更新をダウンロードしてインストール
|
||||
await update.downloadAndInstall((event: any) => {
|
||||
switch (event.event) {
|
||||
case 'Started':
|
||||
setContentLength(event.data.contentLength);
|
||||
console.log(`ダウンロード開始: ${event.data.contentLength} bytes`);
|
||||
break;
|
||||
case 'Progress':
|
||||
downloaded += event.data.chunkLength;
|
||||
const progress = (downloaded / contentLength) * 100;
|
||||
setDownloadProgress(progress);
|
||||
console.log(`ダウンロード中: ${downloaded} / ${contentLength} (${progress.toFixed(1)}%)`);
|
||||
break;
|
||||
case 'Finished':
|
||||
console.log('ダウンロード完了');
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
console.log('更新がインストールされました');
|
||||
// アプリを再起動
|
||||
await relaunch();
|
||||
} catch (error) {
|
||||
console.error('Update installation error:', error);
|
||||
setUpdateStatus('error');
|
||||
setTimeout(() => setUpdateStatus('idle'), 3000);
|
||||
}
|
||||
};
|
||||
|
||||
const getUpdateButtonContent = () => {
|
||||
switch (updateStatus) {
|
||||
case 'idle':
|
||||
return (
|
||||
<>
|
||||
<RefreshCw size={14} className="mr-1.5" />
|
||||
更新を確認
|
||||
</>
|
||||
);
|
||||
case 'checking':
|
||||
return (
|
||||
<>
|
||||
<RefreshCw size={14} className="mr-1.5 animate-spin" />
|
||||
確認中...
|
||||
</>
|
||||
);
|
||||
case 'available':
|
||||
return (
|
||||
<>
|
||||
<Download size={14} className="mr-1.5" />
|
||||
更新が見つかりました
|
||||
</>
|
||||
);
|
||||
case 'downloading':
|
||||
return (
|
||||
<>
|
||||
<Download size={14} className="mr-1.5 animate-pulse" />
|
||||
ダウンロード中... {Math.round(downloadProgress)}%
|
||||
</>
|
||||
);
|
||||
case 'notAvailable':
|
||||
return (
|
||||
<>
|
||||
<Check size={14} className="mr-1.5" />
|
||||
最新バージョンです
|
||||
</>
|
||||
);
|
||||
case 'error':
|
||||
return (
|
||||
<>
|
||||
<AlertCircle size={14} className="mr-1.5" />
|
||||
エラーが発生しました
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// 更新ダイアログの表示
|
||||
const UpdateDialog = () => {
|
||||
if (!showUpdateDialog || !updateInfo) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-5 max-w-md w-full">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<h3 className="text-lg font-medium text-gray-800 dark:text-gray-200">
|
||||
アップデートが利用可能です
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => setShowUpdateDialog(false)}
|
||||
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
>
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300 w-20">バージョン:</span>
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400">{updateInfo.version}</span>
|
||||
</div>
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300 w-20">リリース日:</span>
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400">{updateInfo.date}</span>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300 block mb-1">変更内容:</span>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400 bg-gray-50 dark:bg-gray-700 p-2 rounded border border-gray-200 dark:border-gray-600">
|
||||
{updateInfo.body}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-3">
|
||||
<button
|
||||
onClick={() => setShowUpdateDialog(false)}
|
||||
className="px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded hover:bg-gray-300 dark:hover:bg-gray-600 text-sm"
|
||||
>
|
||||
後で
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowUpdateDialog(false);
|
||||
handleInstallUpdate();
|
||||
}}
|
||||
className="px-4 py-2 bg-indigo-500 text-white rounded hover:bg-indigo-600 text-sm flex items-center"
|
||||
>
|
||||
<Download size={14} className="mr-1.5" />
|
||||
今すぐ更新
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full">
|
||||
<h2 className="text-base font-medium mb-4 text-gray-700 dark:text-gray-200 flex items-center transition-colors">
|
||||
@ -18,7 +197,7 @@ const AboutComponent: React.FC = () => {
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center mb-4">
|
||||
<div className="font-semibold text-lg text-indigo-600 dark:text-indigo-400 mr-3">VRClipboard-IME</div>
|
||||
<div className="bg-indigo-100 dark:bg-indigo-900/50 text-indigo-700 dark:text-indigo-300 text-xs py-1 px-2 rounded">
|
||||
v1.10.0
|
||||
v1.11.0
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -30,7 +209,7 @@ const AboutComponent: React.FC = () => {
|
||||
<div className="text-sm">
|
||||
<div className="flex items-center mb-1">
|
||||
<span className="text-gray-700 dark:text-gray-300 w-20">バージョン:</span>
|
||||
<span className="text-gray-600 dark:text-gray-400">1.11.0</span>
|
||||
<span className="text-gray-600 dark:text-gray-400">1.11.1</span>
|
||||
</div>
|
||||
<div className="flex items-center mb-1">
|
||||
<span className="text-gray-700 dark:text-gray-300 w-20">ライセンス:</span>
|
||||
@ -92,10 +271,33 @@ const AboutComponent: React.FC = () => {
|
||||
<ExternalLink size={14} className="mr-1.5" />
|
||||
ウェブサイト
|
||||
</button>
|
||||
<button
|
||||
onClick={checkForUpdates}
|
||||
disabled={updateStatus === 'checking' || updateStatus === 'downloading'}
|
||||
className={`flex items-center text-gray-600 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 px-3 py-1.5 rounded text-sm ${
|
||||
(updateStatus === 'checking' || updateStatus === 'downloading') ? 'opacity-70 cursor-not-allowed' : ''
|
||||
}`}
|
||||
>
|
||||
{getUpdateButtonContent()}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{updateStatus === 'downloading' && (
|
||||
<div className="mt-2">
|
||||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded h-2 overflow-hidden">
|
||||
<div
|
||||
className="bg-indigo-500 h-full rounded transition-all duration-300"
|
||||
style={{ width: `${downloadProgress}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 更新ダイアログ */}
|
||||
<UpdateDialog />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
140
src/App.tsx
140
src/App.tsx
@ -1,7 +1,9 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { List, Settings, Terminal, Bug, Info, Check } from 'lucide-react';
|
||||
import { List, Settings, Terminal, Bug, Info, Check, Download, X } from 'lucide-react';
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { check } from '@tauri-apps/plugin-updater';
|
||||
import { relaunch } from '@tauri-apps/plugin-process';
|
||||
import "./App.css";
|
||||
import TitleBar from "./TitleBar";
|
||||
import SettingsComponent from "./SettingsComponent";
|
||||
@ -26,6 +28,13 @@ const AppContent = () => {
|
||||
const [currentSettings, setCurrentSettings] = useState<Config | null>(null);
|
||||
const [_isTsfAvailable, setIsTsfAvailable] = useState<boolean | null>(null);
|
||||
const [showTsfSuccessMessage, setShowTsfSuccessMessage] = useState(false);
|
||||
|
||||
// 更新関連のstate
|
||||
const [updateAvailable, setUpdateAvailable] = useState(false);
|
||||
const [updateDownloading, setUpdateDownloading] = useState(false);
|
||||
const [updateProgress, setUpdateProgress] = useState(0);
|
||||
const [contentLength, setContentLength] = useState<number>(0);
|
||||
const [updateInfo, setUpdateInfo] = useState<{version: string, date: string | undefined, body: string | undefined} | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = listen<Log>('addLog', (event) => {
|
||||
@ -60,6 +69,33 @@ const AppContent = () => {
|
||||
|
||||
checkTsfSettings();
|
||||
}, []);
|
||||
|
||||
// アプリ起動時に更新を自動チェック
|
||||
useEffect(() => {
|
||||
const checkForUpdates = async () => {
|
||||
try {
|
||||
const update = await check();
|
||||
if (update) {
|
||||
console.log(`更新が見つかりました: ${update.version} (${update.date}) - ${update.body}`);
|
||||
setUpdateAvailable(true);
|
||||
setUpdateInfo({
|
||||
version: update.version,
|
||||
date: update.date,
|
||||
body: update.body
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新チェックエラー:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// アプリ起動から少し遅らせて更新チェック
|
||||
const timer = setTimeout(() => {
|
||||
checkForUpdates();
|
||||
}, 3000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
// 設定保存関数
|
||||
const saveSettings = async (config: Config) => {
|
||||
@ -70,6 +106,51 @@ const AppContent = () => {
|
||||
console.error('設定保存エラー:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 更新処理
|
||||
const handleInstallUpdate = async () => {
|
||||
try {
|
||||
setUpdateDownloading(true);
|
||||
setUpdateAvailable(false);
|
||||
|
||||
const update = await check();
|
||||
if (!update) {
|
||||
setUpdateDownloading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let downloaded = 0;
|
||||
|
||||
await update.downloadAndInstall((event: any) => {
|
||||
switch (event.event) {
|
||||
case 'Started':
|
||||
setContentLength(event.data.contentLength);
|
||||
console.log(`ダウンロード開始: ${event.data.contentLength} bytes`);
|
||||
break;
|
||||
case 'Progress':
|
||||
downloaded += event.data.chunkLength;
|
||||
const progress = contentLength > 0 ? (downloaded / contentLength) * 100 : 0;
|
||||
setUpdateProgress(progress);
|
||||
console.log(`ダウンロード中: ${downloaded} / ${contentLength} (${progress.toFixed(1)}%)`);
|
||||
break;
|
||||
case 'Finished':
|
||||
console.log('ダウンロード完了');
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
console.log('更新がインストールされました');
|
||||
// アプリを再起動
|
||||
await relaunch();
|
||||
} catch (error) {
|
||||
console.error('更新インストールエラー:', error);
|
||||
setUpdateDownloading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const dismissUpdate = () => {
|
||||
setUpdateAvailable(false);
|
||||
};
|
||||
|
||||
const renderLogEntry = (log: { time: string; original: string; converted: string }, index: number) => (
|
||||
<div key={index} className="mb-2 p-2 bg-white/90 dark:bg-gray-800 rounded border border-gray-100 dark:border-gray-700 text-sm transition-colors">
|
||||
@ -187,6 +268,63 @@ const AppContent = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 更新通知 */}
|
||||
{updateAvailable && !updateDownloading && updateInfo && (
|
||||
<div className="fixed bottom-4 right-4 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 p-4 rounded shadow-lg flex flex-col max-w-xs z-50">
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<h3 className="text-sm font-medium text-gray-800 dark:text-gray-200">アップデートが利用可能です</h3>
|
||||
<button onClick={dismissUpdate} className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200">
|
||||
<X size={16} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-xs text-gray-600 dark:text-gray-400 mb-3">
|
||||
<div className="mb-1">
|
||||
<span className="font-medium">バージョン: </span>
|
||||
{updateInfo.version}
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<span className="font-medium">リリース日: </span>
|
||||
{updateInfo.date}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">変更内容: </span>
|
||||
<p className="mt-1 bg-gray-50 dark:bg-gray-700 p-2 rounded border border-gray-200 dark:border-gray-600">
|
||||
{updateInfo.body}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
onClick={dismissUpdate}
|
||||
className="flex-1 text-gray-600 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 py-1.5 rounded text-sm"
|
||||
>
|
||||
後で
|
||||
</button>
|
||||
<button
|
||||
onClick={handleInstallUpdate}
|
||||
className="flex-1 bg-indigo-500 hover:bg-indigo-600 text-white py-1.5 rounded text-sm flex items-center justify-center"
|
||||
>
|
||||
<Download size={14} className="mr-1.5" />
|
||||
更新する
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ダウンロード中ダイアログ */}
|
||||
{updateDownloading && (
|
||||
<div className="fixed bottom-4 right-4 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 p-4 rounded shadow-lg flex flex-col max-w-xs z-50">
|
||||
<h3 className="text-sm font-medium text-gray-800 dark:text-gray-200 mb-2">ダウンロード中...</h3>
|
||||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded h-2 overflow-hidden">
|
||||
<div
|
||||
className="bg-indigo-500 h-full rounded transition-all duration-300"
|
||||
style={{ width: `${updateProgress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1 text-right">{Math.round(updateProgress)}%</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user