feat(images): default frontend assets to image service

This commit is contained in:
2026-08-08 09:30:58 +00:00
parent bda76b7888
commit 4374c490ac
28 changed files with 228 additions and 61 deletions
@@ -20,7 +20,7 @@
--sammo-button-info-border: #2f89c5;
--sammo-button-navigation-bg: #00582c;
--sammo-button-navigation-border: #004f28;
--sammo-texture-walnut: url('/image/game/back_walnut.jpg');
--sammo-texture-green: url('/image/game/back_green.jpg');
--sammo-texture-blue: url('/image/game/back_blue.jpg');
--sammo-texture-walnut: url('https://sam-image.hided.net/game/back_walnut.jpg');
--sammo-texture-green: url('https://sam-image.hided.net/game/back_green.jpg');
--sammo-texture-blue: url('https://sam-image.hided.net/game/back_blue.jpg');
}
@@ -7,6 +7,7 @@ import MapCityBasic from './MapCityBasic.vue';
import MapCityDetail from './MapCityDetail.vue';
import { useMapViewerStore } from '../../stores/mapViewer';
import { buildAssetUrl } from '../../utils/mapAssets';
import { configuredGameAssetUrl } from '../../utils/imageAssets';
interface MapSummary {
year: number;
@@ -102,7 +103,7 @@ const resolveStateClass = (state: number): CityStateClass => {
return 'wrong';
};
const assetBaseUrl = computed(() => import.meta.env.VITE_GAME_ASSET_URL?.trim() || '/image/game');
const assetBaseUrl = computed(configuredGameAssetUrl);
const resolveAsset = (path: string) => buildAssetUrl(assetBaseUrl.value, path);
const nationById = computed(() => {
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
import type { MessageType } from '@sammo-ts/logic';
import { resolveMessageGeneralIconUrl, useDefaultGeneralIcon } from '../../utils/generalIcon';
import { DEFAULT_GENERAL_ICON_URL, resolveMessageGeneralIconUrl, useDefaultGeneralIcon } from '../../utils/generalIcon';
interface MessageTarget {
generalId: number;
@@ -48,7 +48,7 @@ const destination = computed<MessageTarget>(
nationId: 0,
nationName: '재야',
color: '#000000',
icon: '/image/icons/default.jpg',
icon: DEFAULT_GENERAL_ICON_URL,
}
);
+1
View File
@@ -12,6 +12,7 @@ interface ImportMetaEnv {
readonly VITE_GAME_API_URL?: string;
readonly VITE_GAME_SSE_URL?: string;
readonly VITE_GAME_ASSET_URL?: string;
readonly VITE_IMAGE_PUBLIC_URL?: string;
readonly VITE_GAME_PROFILE?: string;
readonly VITE_GATEWAY_WEB_URL?: string;
readonly VITE_GATEWAY_USER_ICON_BASE_URL?: string;
+3
View File
@@ -3,6 +3,9 @@ import { createPinia } from 'pinia';
import App from './App.vue';
import router from './router';
import './assets/main.css';
import { installImageAssetCssVariables } from './utils/imageAssets';
installImageAssetCssVariables();
const app = createApp(App);
+18 -8
View File
@@ -1,5 +1,12 @@
export const DEFAULT_GENERAL_ICON_URL = '/image/icons/default.jpg';
export const DEFAULT_GATEWAY_USER_ICON_BASE_URL = '/gateway/api/user-icons';
import {
configuredSharedIconPublicUrl,
configuredUserIconPublicUrl,
DEFAULT_USER_ICON_PUBLIC_URL,
externalizeLegacyImageUrl,
} from './imageAssets.ts';
export const DEFAULT_GENERAL_ICON_URL = `${configuredSharedIconPublicUrl()}/default.jpg`;
export const DEFAULT_GATEWAY_USER_ICON_BASE_URL = DEFAULT_USER_ICON_PUBLIC_URL;
export type GeneralIconSource = {
picture?: string | null;
@@ -23,16 +30,16 @@ const encodeLegacyIconPath = (value: string): string =>
})
.join('/');
const configuredUserIconBaseUrl = (): string =>
import.meta.env?.VITE_GATEWAY_USER_ICON_BASE_URL?.trim() || DEFAULT_GATEWAY_USER_ICON_BASE_URL;
export const resolveGeneralIconUrl = (
source: GeneralIconSource,
{ legacyBaseUrl = '/image/icons', userIconBaseUrl = configuredUserIconBaseUrl() }: GeneralIconOptions = {}
{
legacyBaseUrl = configuredSharedIconPublicUrl(),
userIconBaseUrl = configuredUserIconPublicUrl(),
}: GeneralIconOptions = {}
): string => {
const picture = source.picture?.trim() || 'default.jpg';
const baseUrl = source.imageServer ? userIconBaseUrl : legacyBaseUrl;
const encodedPicture = source.imageServer ? encodeURIComponent(picture) : encodeLegacyIconPath(picture);
const encodedPicture = encodeLegacyIconPath(picture);
return `${trimTrailingSlashes(baseUrl)}/${encodedPicture}`;
};
@@ -43,7 +50,7 @@ export const resolveGeneralIconBackgroundImage = (source: GeneralIconSource, opt
export const resolveMessageGeneralIconUrl = (
icon: string | null | undefined,
userIconBaseUrl = configuredUserIconBaseUrl()
userIconBaseUrl = configuredUserIconPublicUrl()
): string => {
const normalized = icon?.trim();
if (!normalized) {
@@ -55,6 +62,9 @@ export const resolveMessageGeneralIconUrl = (
return resolveGeneralIconUrl({ picture: userIconMatch[1], imageServer: 1 }, { userIconBaseUrl });
}
if (normalized.startsWith('/image/')) {
return externalizeLegacyImageUrl(normalized);
}
if (normalized.startsWith('/') || /^https?:\/\//iu.test(normalized)) {
return normalized;
}
@@ -0,0 +1,49 @@
export const DEFAULT_IMAGE_PUBLIC_URL = 'https://sam-image.hided.net';
export const DEFAULT_SHARED_ICON_PUBLIC_URL = `${DEFAULT_IMAGE_PUBLIC_URL}/icons`;
export const DEFAULT_USER_ICON_PUBLIC_URL = DEFAULT_SHARED_ICON_PUBLIC_URL;
const trimTrailingSlashes = (value: string): string => value.replace(/\/+$/u, '');
const configuredValue = (value: string | undefined, fallback: string): string => {
const normalized = value?.trim();
return trimTrailingSlashes(normalized || fallback);
};
export const configuredImagePublicUrl = (): string =>
configuredValue(import.meta.env?.VITE_IMAGE_PUBLIC_URL, DEFAULT_IMAGE_PUBLIC_URL);
export const configuredSharedIconPublicUrl = (): string =>
`${configuredImagePublicUrl()}/icons`;
export const configuredUserIconPublicUrl = (): string =>
configuredValue(
import.meta.env?.VITE_GATEWAY_USER_ICON_BASE_URL,
`${configuredImagePublicUrl()}/icons`
);
export const configuredGameAssetUrl = (): string => {
const explicit = import.meta.env?.VITE_GAME_ASSET_URL?.trim();
if (!explicit) {
return `${configuredImagePublicUrl()}/game`;
}
const normalized = trimTrailingSlashes(explicit);
return normalized.endsWith('/image') ? `${normalized}/game` : normalized;
};
export const externalizeLegacyImageUrl = (value: string): string => {
if (value.startsWith('/image/general/')) {
return `${configuredSharedIconPublicUrl()}/${value.slice('/image/general/'.length)}`;
}
if (value.startsWith('/image/')) {
return `${configuredImagePublicUrl()}/${value.slice('/image/'.length)}`;
}
return value;
};
export const installImageAssetCssVariables = (): void => {
const root = document.documentElement.style;
const gameAssetUrl = configuredGameAssetUrl();
root.setProperty('--sammo-texture-walnut', `url(${JSON.stringify(`${gameAssetUrl}/back_walnut.jpg`)})`);
root.setProperty('--sammo-texture-green', `url(${JSON.stringify(`${gameAssetUrl}/back_green.jpg`)})`);
root.setProperty('--sammo-texture-blue', `url(${JSON.stringify(`${gameAssetUrl}/back_blue.jpg`)})`);
};
@@ -229,10 +229,7 @@ const nationStyle = (color: string) => ({
const signerIcon = (signer: DiplomacyLetter['src'] | DiplomacyLetter['dest']): string | null => {
if (signer.generalIcon) return signer.generalIcon;
if (!signer.generalPicture) return null;
return resolveGeneralIconUrl(
{ picture: signer.generalPicture, imageServer: signer.generalImageServer },
{ legacyBaseUrl: '/image/general' }
);
return resolveGeneralIconUrl({ picture: signer.generalPicture, imageServer: signer.generalImageServer });
};
const toggleHistory = (letterId: number) => {
@@ -48,7 +48,7 @@ const loadDirectory = async () => {
}
};
const imageUrl = (general: General): string => resolveGeneralIconUrl(general, { legacyBaseUrl: '/image/general' });
const imageUrl = (general: General): string => resolveGeneralIconUrl(general);
const injuredStat = (value: number, injury: number): number => Math.trunc((value * (100 - injury)) / 100);
onMounted(() => {