커스텀 필요한 항목들 모두 커스텀 가능하도록 수정.
This commit is contained in:
@@ -1,30 +1,54 @@
|
||||
export type DiceFace = 1 | 2 | 3 | 4 | 5 | 6;
|
||||
export type DiceSlot = 1 | 2;
|
||||
export type DiceCount = 1 | 2;
|
||||
export type DiceFaceMap = Record<DiceFace, string>;
|
||||
|
||||
export type DrinkmarbleAssets = {
|
||||
background: string;
|
||||
chessman: string;
|
||||
dice: Record<DiceFace, string>;
|
||||
diceCount: DiceCount;
|
||||
/** 1번 주사위 면 */
|
||||
dice: DiceFaceMap;
|
||||
/** 2번 주사위 면 */
|
||||
dice2: DiceFaceMap;
|
||||
/** 주사위판 바닥 색 (이미지가 없을 때) */
|
||||
diceFloorColor: string;
|
||||
/** 주사위판 바닥 이미지 (있으면 색 대신 사용) */
|
||||
diceFloorImage: string | null;
|
||||
};
|
||||
|
||||
const STORAGE_KEY = 'drinkmarble.assets';
|
||||
const DICE_FACES: DiceFace[] = [1, 2, 3, 4, 5, 6];
|
||||
|
||||
const DEFAULT_DICE: DiceFaceMap = {
|
||||
1: '/dice/1.png',
|
||||
2: '/dice/2.png',
|
||||
3: '/dice/3.png',
|
||||
4: '/dice/4.png',
|
||||
5: '/dice/5.png',
|
||||
6: '/dice/6.png',
|
||||
};
|
||||
|
||||
export const DEFAULT_DICE_FLOOR_COLOR = '#2563eb';
|
||||
|
||||
export const DEFAULT_ASSETS: DrinkmarbleAssets = {
|
||||
background: '/background/default.png',
|
||||
chessman: '/chessman/default.png',
|
||||
dice: {
|
||||
1: '/dice/1.png',
|
||||
2: '/dice/2.png',
|
||||
3: '/dice/3.png',
|
||||
4: '/dice/4.png',
|
||||
5: '/dice/5.png',
|
||||
6: '/dice/6.png',
|
||||
},
|
||||
diceCount: 1,
|
||||
dice: { ...DEFAULT_DICE },
|
||||
dice2: { ...DEFAULT_DICE },
|
||||
diceFloorColor: DEFAULT_DICE_FLOOR_COLOR,
|
||||
diceFloorImage: null,
|
||||
};
|
||||
|
||||
type StoredAssets = Partial<{
|
||||
background: string;
|
||||
chessman: string;
|
||||
dice: Partial<Record<DiceFace, string>>;
|
||||
diceCount: DiceCount | number;
|
||||
dice: Partial<DiceFaceMap>;
|
||||
dice2: Partial<DiceFaceMap>;
|
||||
diceFloorColor: string;
|
||||
diceFloorImage: string | null;
|
||||
}>;
|
||||
|
||||
function readStored(): StoredAssets {
|
||||
@@ -52,20 +76,47 @@ function writeStored(next: StoredAssets) {
|
||||
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
|
||||
}
|
||||
|
||||
function mergeDiceFaces(
|
||||
stored: Partial<DiceFaceMap> | undefined,
|
||||
fallback: DiceFaceMap,
|
||||
): DiceFaceMap {
|
||||
return {
|
||||
1: stored?.[1] || fallback[1],
|
||||
2: stored?.[2] || fallback[2],
|
||||
3: stored?.[3] || fallback[3],
|
||||
4: stored?.[4] || fallback[4],
|
||||
5: stored?.[5] || fallback[5],
|
||||
6: stored?.[6] || fallback[6],
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeDiceCount(value: unknown): DiceCount {
|
||||
return value === 2 ? 2 : 1;
|
||||
}
|
||||
|
||||
function normalizeFloorColor(value: unknown): string {
|
||||
if (typeof value !== 'string') return DEFAULT_DICE_FLOOR_COLOR;
|
||||
const trimmed = value.trim();
|
||||
if (/^#[0-9a-fA-F]{6}$/.test(trimmed) || /^#[0-9a-fA-F]{3}$/.test(trimmed)) {
|
||||
return trimmed;
|
||||
}
|
||||
return DEFAULT_DICE_FLOOR_COLOR;
|
||||
}
|
||||
|
||||
/** 저장된 값이 있으면 쓰고, 없으면 서버 public 기본 경로를 쓴다. */
|
||||
export function getAssets(): DrinkmarbleAssets {
|
||||
const stored = readStored();
|
||||
return {
|
||||
background: stored.background || DEFAULT_ASSETS.background,
|
||||
chessman: stored.chessman || DEFAULT_ASSETS.chessman,
|
||||
dice: {
|
||||
1: stored.dice?.[1] || DEFAULT_ASSETS.dice[1],
|
||||
2: stored.dice?.[2] || DEFAULT_ASSETS.dice[2],
|
||||
3: stored.dice?.[3] || DEFAULT_ASSETS.dice[3],
|
||||
4: stored.dice?.[4] || DEFAULT_ASSETS.dice[4],
|
||||
5: stored.dice?.[5] || DEFAULT_ASSETS.dice[5],
|
||||
6: stored.dice?.[6] || DEFAULT_ASSETS.dice[6],
|
||||
},
|
||||
diceCount: normalizeDiceCount(stored.diceCount),
|
||||
dice: mergeDiceFaces(stored.dice, DEFAULT_ASSETS.dice),
|
||||
dice2: mergeDiceFaces(stored.dice2, DEFAULT_ASSETS.dice2),
|
||||
diceFloorColor: normalizeFloorColor(stored.diceFloorColor),
|
||||
diceFloorImage:
|
||||
typeof stored.diceFloorImage === 'string' && stored.diceFloorImage.length > 0
|
||||
? stored.diceFloorImage
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -79,14 +130,36 @@ export function setChessmanAsset(src: string) {
|
||||
writeStored({ ...stored, chessman: src });
|
||||
}
|
||||
|
||||
export function setDiceFaceAsset(face: DiceFace, src: string) {
|
||||
export function setDiceCount(count: DiceCount) {
|
||||
const stored = readStored();
|
||||
writeStored({ ...stored, diceCount: normalizeDiceCount(count) });
|
||||
}
|
||||
|
||||
export function setDiceFaceAsset(slot: DiceSlot, face: DiceFace, src: string) {
|
||||
const stored = readStored();
|
||||
if (slot === 2) {
|
||||
writeStored({
|
||||
...stored,
|
||||
dice2: { ...stored.dice2, [face]: src },
|
||||
});
|
||||
return;
|
||||
}
|
||||
writeStored({
|
||||
...stored,
|
||||
dice: { ...stored.dice, [face]: src },
|
||||
});
|
||||
}
|
||||
|
||||
export function setDiceFloorColor(color: string) {
|
||||
const stored = readStored();
|
||||
writeStored({ ...stored, diceFloorColor: normalizeFloorColor(color) });
|
||||
}
|
||||
|
||||
export function setDiceFloorImage(src: string) {
|
||||
const stored = readStored();
|
||||
writeStored({ ...stored, diceFloorImage: src });
|
||||
}
|
||||
|
||||
export function clearBackgroundAsset() {
|
||||
const stored = readStored();
|
||||
const { background: _removed, ...rest } = stored;
|
||||
@@ -99,31 +172,51 @@ export function clearChessmanAsset() {
|
||||
writeStored(rest);
|
||||
}
|
||||
|
||||
export function clearDiceFaceAsset(face: DiceFace) {
|
||||
export function clearDiceFaceAsset(slot: DiceSlot, face: DiceFace) {
|
||||
const stored = readStored();
|
||||
if (slot === 2) {
|
||||
if (!stored.dice2) return;
|
||||
const dice2 = { ...stored.dice2 };
|
||||
delete dice2[face];
|
||||
writeStored({ ...stored, dice2 });
|
||||
return;
|
||||
}
|
||||
if (!stored.dice) return;
|
||||
const dice = { ...stored.dice };
|
||||
delete dice[face];
|
||||
writeStored({ ...stored, dice });
|
||||
}
|
||||
|
||||
export function clearDiceFloorColor() {
|
||||
const stored = readStored();
|
||||
const { diceFloorColor: _removed, ...rest } = stored;
|
||||
writeStored(rest);
|
||||
}
|
||||
|
||||
export function clearDiceFloorImage() {
|
||||
const stored = readStored();
|
||||
const { diceFloorImage: _removed, ...rest } = stored;
|
||||
writeStored(rest);
|
||||
}
|
||||
|
||||
export function clearAllAssets() {
|
||||
if (typeof window === 'undefined') return;
|
||||
window.localStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
|
||||
/** boxGeometry material 순서: +X,-X,+Y,-Y,+Z,-Z = 2,5,1,6,3,4 */
|
||||
export function getDiceFaceTextureList(assets: DrinkmarbleAssets = getAssets()): string[] {
|
||||
return [
|
||||
assets.dice[2],
|
||||
assets.dice[5],
|
||||
assets.dice[1],
|
||||
assets.dice[6],
|
||||
assets.dice[3],
|
||||
assets.dice[4],
|
||||
];
|
||||
export function getDiceFaceTextureList(
|
||||
faces: DiceFaceMap = getAssets().dice,
|
||||
): string[] {
|
||||
return [faces[2], faces[5], faces[1], faces[6], faces[3], faces[4]];
|
||||
}
|
||||
|
||||
export function getDiceFaceMap(slot: DiceSlot, assets: DrinkmarbleAssets = getAssets()) {
|
||||
return slot === 2 ? assets.dice2 : assets.dice;
|
||||
}
|
||||
|
||||
export { DICE_FACES };
|
||||
|
||||
export function isCustomAsset(src: string, fallback: string) {
|
||||
return src !== fallback && src.startsWith('data:');
|
||||
}
|
||||
@@ -133,6 +226,8 @@ export const ASSET_ASPECT = {
|
||||
background: { w: 16, h: 9, label: '16:9' },
|
||||
chessman: { w: 1, h: 1, label: '1:1' },
|
||||
dice: { w: 1, h: 1, label: '1:1' },
|
||||
cell: { w: 1, h: 1, label: '1:1' },
|
||||
diceFloor: { w: 1, h: 1, label: '1:1' },
|
||||
} as const;
|
||||
|
||||
export type AssetKind = keyof typeof ASSET_ASPECT;
|
||||
|
||||
Reference in New Issue
Block a user