앱 버전표시
This commit is contained in:
@@ -4,12 +4,24 @@ use tauri::{
|
|||||||
AppHandle, Manager, RunEvent,
|
AppHandle, Manager, RunEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn format_main_window_title(version: &str) -> String {
|
||||||
|
format!("GoldenChart (v{version})")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_main_window_title(app: &AppHandle) {
|
||||||
|
let title = format_main_window_title(&app.package_info().version.to_string());
|
||||||
|
if let Some(w) = app.get_webview_window("main") {
|
||||||
|
let _ = w.set_title(&title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn show_main_window(app: &AppHandle) {
|
fn show_main_window(app: &AppHandle) {
|
||||||
if let Some(w) = app.get_webview_window("main") {
|
if let Some(w) = app.get_webview_window("main") {
|
||||||
let _ = w.show();
|
let _ = w.show();
|
||||||
let _ = w.unminimize();
|
let _ = w.unminimize();
|
||||||
let _ = w.set_focus();
|
let _ = w.set_focus();
|
||||||
}
|
}
|
||||||
|
apply_main_window_title(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
@@ -21,11 +33,7 @@ pub fn run() {
|
|||||||
.plugin(tauri_plugin_process::init())
|
.plugin(tauri_plugin_process::init())
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
let version = app.package_info().version.to_string();
|
apply_main_window_title(app.handle());
|
||||||
let main_title = format!("GoldenChart (v{version})");
|
|
||||||
if let Some(w) = app.get_webview_window("main") {
|
|
||||||
let _ = w.set_title(&main_title);
|
|
||||||
}
|
|
||||||
|
|
||||||
let show_i = MenuItemBuilder::with_id("show", "GoldenChart 열기").build(app)?;
|
let show_i = MenuItemBuilder::with_id("show", "GoldenChart 열기").build(app)?;
|
||||||
let quit_i = MenuItemBuilder::with_id("quit", "종료").build(app)?;
|
let quit_i = MenuItemBuilder::with_id("quit", "종료").build(app)?;
|
||||||
@@ -63,10 +71,19 @@ pub fn run() {
|
|||||||
show_main_window(app.handle());
|
show_main_window(app.handle());
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
.on_page_load(|webview, _payload| {
|
||||||
|
if webview.label() != "main" {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
apply_main_window_title(webview.app_handle());
|
||||||
|
})
|
||||||
.on_window_event(|window, event| {
|
.on_window_event(|window, event| {
|
||||||
if window.label() != "main" {
|
if window.label() != "main" {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if matches!(event, tauri::WindowEvent::Focused(true)) {
|
||||||
|
apply_main_window_title(window.app_handle());
|
||||||
|
}
|
||||||
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
||||||
api.prevent_close();
|
api.prevent_close();
|
||||||
let _ = window.hide();
|
let _ = window.hide();
|
||||||
@@ -75,6 +92,7 @@ pub fn run() {
|
|||||||
.build(tauri::generate_context!())
|
.build(tauri::generate_context!())
|
||||||
.expect("error while building tauri application")
|
.expect("error while building tauri application")
|
||||||
.run(|app_handle, event| match event {
|
.run(|app_handle, event| match event {
|
||||||
|
RunEvent::Ready => apply_main_window_title(&app_handle),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
RunEvent::Reopen { .. } => show_main_window(&app_handle),
|
RunEvent::Reopen { .. } => show_main_window(&app_handle),
|
||||||
RunEvent::ExitRequested { api, .. } => {
|
RunEvent::ExitRequested { api, .. } => {
|
||||||
|
|||||||
@@ -1,32 +1,60 @@
|
|||||||
import { getVersion } from '@tauri-apps/api/app';
|
import { getVersion } from '@tauri-apps/api/app';
|
||||||
import { getCurrentWindow } from '@tauri-apps/api/window';
|
import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||||
|
import {
|
||||||
|
formatDesktopWindowTitle,
|
||||||
|
readBakedDesktopAppVersion,
|
||||||
|
} from '@frontend/utils/desktopAppVersion';
|
||||||
|
|
||||||
const APP_NAME = 'GoldenChart';
|
export { formatDesktopWindowTitle } from '@frontend/utils/desktopAppVersion';
|
||||||
|
|
||||||
export function formatDesktopWindowTitle(version: string): string {
|
|
||||||
const v = version.trim();
|
|
||||||
return v ? `${APP_NAME} (v${v})` : APP_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** macOS/Windows 네이티브 창 타이틀 — GoldenChart (vX.Y.Z) */
|
/** macOS/Windows 네이티브 창 타이틀 — GoldenChart (vX.Y.Z) */
|
||||||
export async function syncMainWindowTitle(): Promise<string> {
|
export async function syncMainWindowTitle(): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const version = await getVersion();
|
const version = (await getVersion()) || readBakedDesktopAppVersion() || '';
|
||||||
const title = formatDesktopWindowTitle(version);
|
const title = formatDesktopWindowTitle(version);
|
||||||
await getCurrentWindow().setTitle(title);
|
await getCurrentWindow().setTitle(title);
|
||||||
if (typeof document !== 'undefined') {
|
if (typeof document !== 'undefined' && document.title !== title) {
|
||||||
document.title = title;
|
document.title = title;
|
||||||
}
|
}
|
||||||
return title;
|
return title;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
const fallback = formatDesktopWindowTitle(readBakedDesktopAppVersion());
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
document.title = fallback;
|
||||||
|
}
|
||||||
console.warn('[desktop] syncMainWindowTitle failed:', e);
|
console.warn('[desktop] syncMainWindowTitle failed:', e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let titleGuardInstalled = false;
|
||||||
|
|
||||||
|
/** macOS WebKit — document.title 변경 시 네이티브 타이틀을 다시 맞춤 */
|
||||||
|
export function installDocumentTitleGuard(): void {
|
||||||
|
if (titleGuardInstalled || typeof document === 'undefined') return;
|
||||||
|
titleGuardInstalled = true;
|
||||||
|
|
||||||
|
const rebalance = () => {
|
||||||
|
void syncMainWindowTitle().catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const titleEl = document.querySelector('title');
|
||||||
|
if (titleEl) {
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
if (!document.title.includes('(v')) rebalance();
|
||||||
|
});
|
||||||
|
observer.observe(titleEl, { childList: true, characterData: true, subtree: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
window.setInterval(() => {
|
||||||
|
if (!document.title.includes('(v')) rebalance();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
/** WebView 로드 후 macOS가 document.title로 타이틀을 덮어쓸 수 있어 재시도 */
|
/** WebView 로드 후 macOS가 document.title로 타이틀을 덮어쓸 수 있어 재시도 */
|
||||||
export function scheduleMainWindowTitleSync(): void {
|
export function scheduleMainWindowTitleSync(): void {
|
||||||
const delays = [0, 150, 600, 2000];
|
installDocumentTitleGuard();
|
||||||
|
const delays = [0, 50, 150, 400, 1000, 2500, 5000];
|
||||||
for (const ms of delays) {
|
for (const ms of delays) {
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
void syncMainWindowTitle().catch(() => {});
|
void syncMainWindowTitle().catch(() => {});
|
||||||
|
|||||||
+19
-1
@@ -20,6 +20,24 @@ function readDesktopAppVersion(): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const desktopVersion = readDesktopAppVersion();
|
||||||
|
const desktopWindowTitle = `GoldenChart (v${desktopVersion})`;
|
||||||
|
|
||||||
|
function desktopHtmlTitlePlugin() {
|
||||||
|
return {
|
||||||
|
name: 'desktop-html-title',
|
||||||
|
transformIndexHtml(html: string, ctx: { filename: string }) {
|
||||||
|
if (!ctx.filename.endsWith('index.html')) return html;
|
||||||
|
return html
|
||||||
|
.replace(/<title>[^<]*<\/title>/i, `<title>${desktopWindowTitle}</title>`)
|
||||||
|
.replace(
|
||||||
|
'<script>var global = globalThis;</script>',
|
||||||
|
`<script>var global = globalThis;window.__GC_APP_VERSION__="${desktopVersion}";</script>`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
/** Tauri 패키지 앱 — 절대 경로(/assets) 사용 시 빈 화면 */
|
/** Tauri 패키지 앱 — 절대 경로(/assets) 사용 시 빈 화면 */
|
||||||
base: './',
|
base: './',
|
||||||
@@ -32,7 +50,7 @@ export default defineConfig({
|
|||||||
__DESKTOP_APP_VERSION__: JSON.stringify(readDesktopAppVersion()),
|
__DESKTOP_APP_VERSION__: JSON.stringify(readDesktopAppVersion()),
|
||||||
},
|
},
|
||||||
envDir: __dirname,
|
envDir: __dirname,
|
||||||
plugins: [react()],
|
plugins: [react(), desktopHtmlTitlePlugin()],
|
||||||
resolve: {
|
resolve: {
|
||||||
dedupe: ['react', 'react-dom', 'lightweight-charts'],
|
dedupe: ['react', 'react-dom', 'lightweight-charts'],
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
@@ -211,6 +211,14 @@ html.desktop-client .tmb-logo-text {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html.desktop-client .tmb-logo-version {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text3);
|
||||||
|
letter-spacing: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
/* 구분선 */
|
/* 구분선 */
|
||||||
.tmb-divider {
|
.tmb-divider {
|
||||||
width: 1px;
|
width: 1px;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import type { AuthSession } from '../utils/auth';
|
|||||||
import type { TradeAlertPopupLayout, TradeAlertPopupPosition } from '../utils/tradeAlertPopupLayout';
|
import type { TradeAlertPopupLayout, TradeAlertPopupPosition } from '../utils/tradeAlertPopupLayout';
|
||||||
import { canAccessMenu } from '../utils/permissions';
|
import { canAccessMenu } from '../utils/permissions';
|
||||||
import { isDesktop } from '../utils/platform';
|
import { isDesktop } from '../utils/platform';
|
||||||
import { useDesktopAppTitle, formatDesktopAppLabel } from '../hooks/useDesktopAppTitle';
|
import { useDesktopAppTitle } from '../hooks/useDesktopAppTitle';
|
||||||
import DesktopUpdateModal from './DesktopUpdateModal';
|
import DesktopUpdateModal from './DesktopUpdateModal';
|
||||||
import TopMenuBarNavGroups from './TopMenuBarNavGroups';
|
import TopMenuBarNavGroups from './TopMenuBarNavGroups';
|
||||||
|
|
||||||
@@ -319,9 +319,10 @@ export const TopMenuBar = memo(function TopMenuBar({
|
|||||||
<rect width="20" height="20" rx="4" fill="#2196f3"/>
|
<rect width="20" height="20" rx="4" fill="#2196f3"/>
|
||||||
<polyline points="3,14 7,9 10,12 14,6 17,6" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
|
<polyline points="3,14 7,9 10,12 14,6 17,6" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span className="tmb-logo-text">
|
<span className="tmb-logo-text">GoldenChart</span>
|
||||||
{desktopClient ? formatDesktopAppLabel(desktopAppVersion) : 'GoldenChart'}
|
{desktopClient && desktopAppVersion ? (
|
||||||
</span>
|
<span className="tmb-logo-version">(v{desktopAppVersion})</span>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="tmb-divider" />
|
<div className="tmb-divider" />
|
||||||
|
|||||||
@@ -1,26 +1,19 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { isDesktop } from '../utils/platform';
|
import { isDesktop } from '../utils/platform';
|
||||||
import { getDesktopAppVersion, syncDesktopMainWindowTitle } from '../utils/desktopBridge';
|
import { getDesktopAppVersion, syncDesktopMainWindowTitle } from '../utils/desktopBridge';
|
||||||
|
import { readBakedDesktopAppVersion } from '../utils/desktopAppVersion';
|
||||||
declare const __DESKTOP_APP_VERSION__: string | undefined;
|
|
||||||
|
|
||||||
function readBakedDesktopVersion(): string | null {
|
|
||||||
if (typeof __DESKTOP_APP_VERSION__ === 'undefined') return null;
|
|
||||||
const v = __DESKTOP_APP_VERSION__.trim();
|
|
||||||
return v || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Desktop — 네이티브 창 타이틀·document.title을 GoldenChart (vX.Y.Z) 로 맞춤 */
|
/** Desktop — 네이티브 창 타이틀·document.title을 GoldenChart (vX.Y.Z) 로 맞춤 */
|
||||||
export function useDesktopAppTitle(): string | null {
|
export function useDesktopAppTitle(): string | null {
|
||||||
const [version, setVersion] = useState<string | null>(() =>
|
const [version, setVersion] = useState<string | null>(() =>
|
||||||
isDesktop() ? readBakedDesktopVersion() : null,
|
isDesktop() ? readBakedDesktopAppVersion() : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isDesktop()) return;
|
if (!isDesktop()) return;
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
void (async () => {
|
void (async () => {
|
||||||
const v = (await getDesktopAppVersion()) ?? readBakedDesktopVersion();
|
const v = (await getDesktopAppVersion()) ?? readBakedDesktopAppVersion();
|
||||||
if (cancelled || !v) return;
|
if (cancelled || !v) return;
|
||||||
setVersion(v);
|
setVersion(v);
|
||||||
await syncDesktopMainWindowTitle();
|
await syncDesktopMainWindowTitle();
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
declare const __DESKTOP_APP_VERSION__: string | undefined;
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
__GC_APP_VERSION__?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Desktop Vite 빌드·index.html 인라인 스크립트에 주입된 앱 버전 */
|
||||||
|
export function readBakedDesktopAppVersion(): string | null {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const fromWindow = window.__GC_APP_VERSION__?.trim();
|
||||||
|
if (fromWindow) return fromWindow;
|
||||||
|
}
|
||||||
|
if (typeof __DESKTOP_APP_VERSION__ !== 'undefined') {
|
||||||
|
const fromDefine = __DESKTOP_APP_VERSION__.trim();
|
||||||
|
if (fromDefine) return fromDefine;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatDesktopWindowTitle(version: string | null | undefined): string {
|
||||||
|
const v = version?.trim();
|
||||||
|
return v ? `GoldenChart (v${v})` : 'GoldenChart';
|
||||||
|
}
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
export type RuntimePlatform = 'web' | 'desktop' | 'mobile';
|
export type RuntimePlatform = 'web' | 'desktop' | 'mobile';
|
||||||
|
|
||||||
export function isDesktop(): boolean {
|
export function isDesktop(): boolean {
|
||||||
return typeof window !== 'undefined' && '__TAURI__' in window;
|
if (typeof window === 'undefined') return false;
|
||||||
|
if ('__TAURI__' in window || '__TAURI_INTERNALS__' in window) return true;
|
||||||
|
return document.documentElement.classList.contains('desktop-client');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isMobileCapacitor(): boolean {
|
export function isMobileCapacitor(): boolean {
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
export type RuntimePlatform = 'web' | 'desktop' | 'mobile';
|
export type RuntimePlatform = 'web' | 'desktop' | 'mobile';
|
||||||
|
|
||||||
export function isDesktop(): boolean {
|
export function isDesktop(): boolean {
|
||||||
return typeof window !== 'undefined' && '__TAURI__' in window;
|
if (typeof window === 'undefined') return false;
|
||||||
|
if ('__TAURI__' in window || '__TAURI_INTERNALS__' in window) return true;
|
||||||
|
return document.documentElement.classList.contains('desktop-client');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isMobileCapacitor(): boolean {
|
export function isMobileCapacitor(): boolean {
|
||||||
|
|||||||
@@ -64,6 +64,17 @@ fi
|
|||||||
VERSION="$(node -p "require('$CONF').version")"
|
VERSION="$(node -p "require('$CONF').version")"
|
||||||
echo "Version: $VERSION"
|
echo "Version: $VERSION"
|
||||||
|
|
||||||
|
node -e "
|
||||||
|
const fs = require('fs');
|
||||||
|
const confPath = process.argv[1];
|
||||||
|
const version = process.argv[2];
|
||||||
|
const title = 'GoldenChart (v' + version + ')';
|
||||||
|
const conf = JSON.parse(fs.readFileSync(confPath, 'utf8'));
|
||||||
|
if (conf.app?.windows?.[0]) conf.app.windows[0].title = title;
|
||||||
|
fs.writeFileSync(confPath, JSON.stringify(conf, null, 2) + '\n');
|
||||||
|
console.log('window title →', title);
|
||||||
|
" "$CONF" "$VERSION"
|
||||||
|
|
||||||
if [[ -f "$ROOT/scripts/patch-tauri-updater-pubkey.sh" ]]; then
|
if [[ -f "$ROOT/scripts/patch-tauri-updater-pubkey.sh" ]]; then
|
||||||
chmod +x "$ROOT/scripts/patch-tauri-updater-pubkey.sh"
|
chmod +x "$ROOT/scripts/patch-tauri-updater-pubkey.sh"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user