修复创作弹窗与生成器引用缓存
未开放工具弹窗补充平台主题背景 画布生成器缓存引用按当前账号过滤 新画布工具栏引导改用文档图片 补充弹窗、引导图和引用归属回归测试
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 383 KiB |
@@ -262,6 +262,7 @@ describe('CreationLandingView', () => {
|
||||
const dialog = screen.getByRole('dialog', { name: '抱歉' });
|
||||
expect(dialog.textContent).toContain('功能还在调试中');
|
||||
expect(dialog.textContent).toContain('暂未开放');
|
||||
expect(dialog.closest('.platform-theme--light')).toBeTruthy();
|
||||
expect(createEditorProjectMock).not.toHaveBeenCalled();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '期待一下' }));
|
||||
|
||||
@@ -762,7 +762,7 @@ export function CreationLandingView({
|
||||
showHeader={false}
|
||||
showCloseButton={false}
|
||||
size="sm"
|
||||
overlayClassName="platform-mobile-home-welcome-overlay !items-center !p-4"
|
||||
overlayClassName="platform-theme platform-theme--light platform-mobile-home-welcome-overlay !items-center !p-4"
|
||||
panelClassName="platform-remap-surface platform-mobile-home-welcome-dialog"
|
||||
bodyClassName="platform-mobile-home-welcome-dialog__body"
|
||||
footerClassName="platform-mobile-home-welcome-dialog__footer"
|
||||
|
||||
@@ -642,6 +642,45 @@ describe('ImageCanvasEditorModel', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('drops restored generator references owned by another user', () => {
|
||||
const dialog: CanvasGenerationDialogState = {
|
||||
id: 'generation-dialog-owner',
|
||||
mode: 'generate',
|
||||
prompt: '不应复用别人素材',
|
||||
status: 'idle',
|
||||
composerOpen: true,
|
||||
generationReferences: [
|
||||
{
|
||||
id: 'foreign-reference',
|
||||
label: '别人账号的素材',
|
||||
src: 'data:image/png;base64,foreign',
|
||||
resourceId: 'resource-foreign',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { generationDialogs } = splitCanvasLayoutItems(
|
||||
serializeCanvasLayout({
|
||||
layers: [],
|
||||
canvasGenerationDialogs: [dialog],
|
||||
}),
|
||||
new Map([
|
||||
[
|
||||
'resource-foreign',
|
||||
{
|
||||
imageSrc: '/read/foreign.png',
|
||||
objectKey: 'generated/foreign.png',
|
||||
ownerUserId: 'user-b',
|
||||
},
|
||||
],
|
||||
]),
|
||||
'user-a',
|
||||
);
|
||||
|
||||
expect(generationDialogs).toHaveLength(1);
|
||||
expect(generationDialogs[0]?.generationReferences).toEqual([]);
|
||||
});
|
||||
|
||||
it('snaps moving layers to nearby canvas and layer guides', () => {
|
||||
const movingLayer: CanvasLayer = {
|
||||
id: 'moving',
|
||||
|
||||
@@ -315,6 +315,18 @@ function isPersistedReferenceAssetId(assetId: string | null | undefined) {
|
||||
return Boolean(normalizedAssetId && !normalizedAssetId.startsWith('upload-'));
|
||||
}
|
||||
|
||||
function normalizedCurrentUserId(userId: string | null | undefined) {
|
||||
return userId?.trim() || null;
|
||||
}
|
||||
|
||||
function isReferencePointerSrc(src: string | null | undefined) {
|
||||
const normalizedSrc = src?.trim() ?? '';
|
||||
return (
|
||||
normalizedSrc.startsWith('ref:project-resource:') ||
|
||||
normalizedSrc.startsWith('ref:asset:')
|
||||
);
|
||||
}
|
||||
|
||||
function resolveReferencePointer(reference: CharacterReferenceImage) {
|
||||
const resourceId = reference.resourceId?.trim();
|
||||
if (isPersistedReferenceResourceId(resourceId)) {
|
||||
@@ -435,6 +447,7 @@ export function isCanvasGenerationDialogLayoutItem(
|
||||
export function splitCanvasLayoutItems(
|
||||
items: EditorProjectLayerSnapshot[],
|
||||
resourcesById: Map<string, CanvasLayerResourceMetadata> = new Map(),
|
||||
currentUserId?: string | null,
|
||||
): {
|
||||
layerItems: EditorProjectLayerSnapshot[];
|
||||
generationDialogs: CanvasGenerationDialogState[];
|
||||
@@ -444,7 +457,11 @@ export function splitCanvasLayoutItems(
|
||||
|
||||
items.forEach((item) => {
|
||||
if (isCanvasGenerationDialogLayoutItem(item)) {
|
||||
const dialog = hydrateCanvasGenerationDialog(item.dialog, resourcesById);
|
||||
const dialog = hydrateCanvasGenerationDialog(
|
||||
item.dialog,
|
||||
resourcesById,
|
||||
currentUserId,
|
||||
);
|
||||
if (dialog) {
|
||||
generationDialogs.push(dialog);
|
||||
}
|
||||
@@ -459,6 +476,7 @@ export function splitCanvasLayoutItems(
|
||||
export function hydrateCanvasGenerationDialog(
|
||||
value: unknown,
|
||||
resourcesById: Map<string, CanvasLayerResourceMetadata> = new Map(),
|
||||
currentUserId?: string | null,
|
||||
): CanvasGenerationDialogState | null {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return null;
|
||||
@@ -488,22 +506,27 @@ export function hydrateCanvasGenerationDialog(
|
||||
specReference: hydrateCharacterReference(
|
||||
snapshot.specReference,
|
||||
resourcesById,
|
||||
currentUserId,
|
||||
),
|
||||
generationReferences: hydrateCharacterReferences(
|
||||
snapshot.generationReferences,
|
||||
resourcesById,
|
||||
currentUserId,
|
||||
),
|
||||
characterSpecReference: hydrateCharacterReference(
|
||||
snapshot.characterSpecReference,
|
||||
resourcesById,
|
||||
currentUserId,
|
||||
),
|
||||
characterReferences: hydrateCharacterReferences(
|
||||
snapshot.characterReferences,
|
||||
resourcesById,
|
||||
currentUserId,
|
||||
),
|
||||
iconSpecReference: hydrateCharacterReference(
|
||||
snapshot.iconSpecReference,
|
||||
resourcesById,
|
||||
currentUserId,
|
||||
),
|
||||
iconDescriptions: Array.isArray(snapshot.iconDescriptions)
|
||||
? snapshot.iconDescriptions.filter(
|
||||
@@ -520,10 +543,12 @@ export function hydrateCanvasGenerationDialog(
|
||||
publicationReferences: hydrateCharacterReferences(
|
||||
snapshot.publicationReferences,
|
||||
resourcesById,
|
||||
currentUserId,
|
||||
),
|
||||
uiDesignSpecReference: hydrateCharacterReference(
|
||||
snapshot.uiDesignSpecReference,
|
||||
resourcesById,
|
||||
currentUserId,
|
||||
),
|
||||
imageModel: stringOrUndefined(snapshot.imageModel),
|
||||
videoModel:
|
||||
@@ -775,6 +800,7 @@ function inferAudioAssetKindFromLabel(label: string): CanvasAssetKind {
|
||||
|
||||
export type CanvasLayerResourceMetadata = {
|
||||
resourceId?: string | null;
|
||||
ownerUserId?: string | null;
|
||||
imageSrc: string;
|
||||
objectKey?: string | null;
|
||||
assetObjectId?: string | null;
|
||||
@@ -1171,9 +1197,16 @@ function hydrateSpecFormValues(
|
||||
function resolveHydratedReferenceResource(
|
||||
resourceId: string | undefined,
|
||||
resourcesById: Map<string, CanvasLayerResourceMetadata>,
|
||||
currentUserId?: string | null,
|
||||
) {
|
||||
if (resourceId) {
|
||||
return resourcesById.get(resourceId);
|
||||
const resource = resourcesById.get(resourceId);
|
||||
const currentUser = normalizedCurrentUserId(currentUserId);
|
||||
const ownerUserId = resource?.ownerUserId?.trim() || null;
|
||||
if (currentUser && ownerUserId && ownerUserId !== currentUser) {
|
||||
return null;
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -1181,6 +1214,7 @@ function resolveHydratedReferenceResource(
|
||||
function hydrateCharacterReference(
|
||||
value: unknown,
|
||||
resourcesById: Map<string, CanvasLayerResourceMetadata> = new Map(),
|
||||
currentUserId?: string | null,
|
||||
): CharacterReferenceImage | null {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return null;
|
||||
@@ -1199,7 +1233,18 @@ function hydrateCharacterReference(
|
||||
const sizeBytes = numberFromSnapshot(snapshot.sizeBytes, 0) || undefined;
|
||||
const durationSeconds =
|
||||
numberFromSnapshot(snapshot.durationSeconds, 0) || undefined;
|
||||
const resource = resolveHydratedReferenceResource(resourceId, resourcesById);
|
||||
const resource = resolveHydratedReferenceResource(
|
||||
resourceId,
|
||||
resourcesById,
|
||||
currentUserId,
|
||||
);
|
||||
if (
|
||||
resource === null ||
|
||||
(currentUserId && resourceId && !resource) ||
|
||||
(!resource && isReferencePointerSrc(src))
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const objectKey =
|
||||
stringOrUndefined(snapshot.objectKey) ?? stringOrUndefined(resource?.objectKey);
|
||||
const assetObjectId =
|
||||
@@ -1226,10 +1271,15 @@ function hydrateCharacterReference(
|
||||
function hydrateCharacterReferences(
|
||||
value: unknown,
|
||||
resourcesById: Map<string, CanvasLayerResourceMetadata> = new Map(),
|
||||
currentUserId?: string | null,
|
||||
): CharacterReferenceImage[] | undefined {
|
||||
return Array.isArray(value)
|
||||
? value.flatMap((reference) => {
|
||||
const hydrated = hydrateCharacterReference(reference, resourcesById);
|
||||
const hydrated = hydrateCharacterReference(
|
||||
reference,
|
||||
resourcesById,
|
||||
currentUserId,
|
||||
);
|
||||
return hydrated ? [hydrated] : [];
|
||||
})
|
||||
: undefined;
|
||||
|
||||
@@ -147,6 +147,11 @@ describe('ImageCanvasEditorView', () => {
|
||||
expect(
|
||||
await screen.findByText('工具栏在这里,快试着随便做点什么'),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
document
|
||||
.querySelector('.image-canvas-editor__toolbar-guide-art')
|
||||
?.getAttribute('src'),
|
||||
).toBe('/branding/taonier-toolbar-guide-bubble.png');
|
||||
const bottomToolbar = screen.getByRole('toolbar', {
|
||||
name: 'AI画布工具栏',
|
||||
});
|
||||
|
||||
@@ -308,6 +308,7 @@ export function ImageCanvasEditorView() {
|
||||
const startupIntentConsumedRef = useRef(false);
|
||||
const selectedLayerIdRef = useRef<string | null>(null);
|
||||
const showRechargeEntry = shouldShowRechargeEntry();
|
||||
const currentEditorUserId = authUi?.user?.id ?? null;
|
||||
const [isToolbarGuideVisible, setIsToolbarGuideVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -993,6 +994,7 @@ export function ImageCanvasEditorView() {
|
||||
viewport,
|
||||
isViewportInteracting,
|
||||
canAccessProtectedData: authUi ? authUi.canAccessProtectedData : true,
|
||||
currentUserId: currentEditorUserId,
|
||||
openEditorLoginModal,
|
||||
});
|
||||
const applyGeneratedProjectSnapshot = useCallback(
|
||||
@@ -1127,6 +1129,7 @@ export function ImageCanvasEditorView() {
|
||||
persistGeneratedAsset,
|
||||
persistUpdatedLayerResource,
|
||||
projectId,
|
||||
currentUserId: currentEditorUserId,
|
||||
assetFolderId: activeUploadFolderId,
|
||||
upsertGeneratedAsset,
|
||||
applyProjectSnapshot: applyGeneratedProjectSnapshot,
|
||||
|
||||
@@ -453,9 +453,9 @@ export function ImageCanvasStageView({
|
||||
aria-live="polite"
|
||||
>
|
||||
<img
|
||||
src="/branding/mobile-home-welcome-taonier-ip.png"
|
||||
src="/branding/taonier-toolbar-guide-bubble.png"
|
||||
alt=""
|
||||
className="image-canvas-editor__toolbar-guide-ip"
|
||||
className="image-canvas-editor__toolbar-guide-art"
|
||||
/>
|
||||
<span>工具栏在这里,快试着随便做点什么</span>
|
||||
</div>
|
||||
|
||||
@@ -96,6 +96,7 @@ type ImageCanvasGenerationSurfaceOptions = {
|
||||
persistGeneratedAsset?: (layer: CanvasLayer) => void;
|
||||
persistUpdatedLayerResource?: (layer: CanvasLayer) => void;
|
||||
projectId?: string | null;
|
||||
currentUserId?: string | null;
|
||||
assetFolderId?: string | null;
|
||||
upsertGeneratedAsset?: (asset: EditorAssetSnapshot) => void;
|
||||
applyProjectSnapshot?: (project: EditorProjectSnapshot) => void;
|
||||
@@ -235,6 +236,7 @@ export function useImageCanvasGenerationSurface({
|
||||
persistGeneratedAsset,
|
||||
persistUpdatedLayerResource,
|
||||
projectId,
|
||||
currentUserId,
|
||||
assetFolderId,
|
||||
upsertGeneratedAsset,
|
||||
applyProjectSnapshot,
|
||||
@@ -271,6 +273,7 @@ export function useImageCanvasGenerationSurface({
|
||||
persistGeneratedAsset,
|
||||
persistUpdatedLayerResource,
|
||||
projectId,
|
||||
currentUserId,
|
||||
assetFolderId,
|
||||
upsertGeneratedAsset,
|
||||
applyProjectSnapshot,
|
||||
|
||||
@@ -102,11 +102,13 @@ function GenerationWorkflowHarness({
|
||||
initialLayers = [createLayer()],
|
||||
initialViewport = { x: 10, y: 20, scale: 2 },
|
||||
projectId,
|
||||
currentUserId,
|
||||
applyProjectSnapshot,
|
||||
}: {
|
||||
initialLayers?: CanvasLayer[];
|
||||
initialViewport?: { x: number; y: number; scale: number };
|
||||
projectId?: string;
|
||||
currentUserId?: string;
|
||||
applyProjectSnapshot?: Parameters<
|
||||
typeof useImageCanvasGenerationWorkflow
|
||||
>[0]['applyProjectSnapshot'];
|
||||
@@ -156,6 +158,7 @@ function GenerationWorkflowHarness({
|
||||
setMetadataLayer,
|
||||
setImageContextMenu,
|
||||
projectId,
|
||||
currentUserId,
|
||||
applyProjectSnapshot,
|
||||
});
|
||||
|
||||
@@ -1295,6 +1298,33 @@ describe('useImageCanvasGenerationWorkflow', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('does not reuse cached spec references written by another user', () => {
|
||||
const firstRender = render(
|
||||
<GenerationWorkflowHarness currentUserId="user-a" />,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开角色生成' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '选择角色规范' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开图标生成' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '选择图标规范' }));
|
||||
|
||||
firstRender.unmount();
|
||||
render(<GenerationWorkflowHarness currentUserId="user-b" />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开角色生成' }));
|
||||
expect(screen.getByTestId('character-spec-reference').textContent).toBe(
|
||||
'-',
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开图标生成' }));
|
||||
expect(screen.getByTestId('icon-spec-reference').textContent).toBe('-');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开UI设计生成' }));
|
||||
expect(screen.getByTestId('ui-design-spec-reference').textContent).toBe(
|
||||
'-',
|
||||
);
|
||||
});
|
||||
|
||||
it('reuses uploaded character and icon specs when opening matching material dialogs again', async () => {
|
||||
render(<GenerationWorkflowHarness />);
|
||||
|
||||
|
||||
@@ -283,6 +283,7 @@ function readOptionalCachedNullableString(
|
||||
|
||||
function readCachedGenerationReference(
|
||||
key: string,
|
||||
currentUserId?: string | null,
|
||||
): CharacterReferenceImage | null {
|
||||
const storage = getLocalGenerationReferenceStorage();
|
||||
if (!storage) {
|
||||
@@ -298,6 +299,13 @@ function readCachedGenerationReference(
|
||||
return null;
|
||||
}
|
||||
const record = parsedValue as Record<string, unknown>;
|
||||
const normalizedCurrentUserId = currentUserId?.trim();
|
||||
const ownerUserId =
|
||||
typeof record.ownerUserId === 'string' ? record.ownerUserId.trim() : '';
|
||||
if (normalizedCurrentUserId && ownerUserId !== normalizedCurrentUserId) {
|
||||
storage.removeItem(key);
|
||||
return null;
|
||||
}
|
||||
const id = readOptionalCachedString(record, 'id');
|
||||
const label = readOptionalCachedString(record, 'label');
|
||||
const src = readOptionalCachedString(record, 'src');
|
||||
@@ -345,13 +353,18 @@ function readCachedGenerationReference(
|
||||
function writeCachedGenerationReference(
|
||||
key: string,
|
||||
reference: CharacterReferenceImage,
|
||||
currentUserId?: string | null,
|
||||
) {
|
||||
const storage = getLocalGenerationReferenceStorage();
|
||||
if (!storage) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
storage.setItem(key, JSON.stringify(reference));
|
||||
const ownerUserId = currentUserId?.trim();
|
||||
storage.setItem(
|
||||
key,
|
||||
JSON.stringify(ownerUserId ? { ...reference, ownerUserId } : reference),
|
||||
);
|
||||
} catch {
|
||||
// 中文注释:本地缓存只提升下次新建素材的便捷性,写入失败不阻断生成流程。
|
||||
}
|
||||
@@ -448,6 +461,7 @@ type GenerationWorkflowOptions = {
|
||||
persistGeneratedAsset?: (layer: CanvasLayer) => void;
|
||||
persistUpdatedLayerResource?: (layer: CanvasLayer) => void;
|
||||
projectId?: string | null;
|
||||
currentUserId?: string | null;
|
||||
assetFolderId?: string | null;
|
||||
upsertGeneratedAsset?: (asset: EditorAssetSnapshot) => void;
|
||||
applyProjectSnapshot?: (project: EditorProjectSnapshot) => void;
|
||||
@@ -481,6 +495,7 @@ export function useImageCanvasGenerationWorkflow({
|
||||
persistGeneratedAsset,
|
||||
persistUpdatedLayerResource,
|
||||
projectId,
|
||||
currentUserId,
|
||||
assetFolderId,
|
||||
upsertGeneratedAsset,
|
||||
applyProjectSnapshot,
|
||||
@@ -564,11 +579,19 @@ export function useImageCanvasGenerationWorkflow({
|
||||
);
|
||||
const [initialCharacterSpecReference] =
|
||||
useState<CharacterReferenceImage | null>(() =>
|
||||
readCachedGenerationReference(LAST_CHARACTER_SPEC_REFERENCE_CACHE_KEY),
|
||||
readCachedGenerationReference(
|
||||
LAST_CHARACTER_SPEC_REFERENCE_CACHE_KEY,
|
||||
currentUserId,
|
||||
),
|
||||
);
|
||||
const [initialIconSpecReference] = useState<CharacterReferenceImage | null>(
|
||||
() => readCachedGenerationReference(LAST_ICON_SPEC_REFERENCE_CACHE_KEY),
|
||||
() =>
|
||||
readCachedGenerationReference(
|
||||
LAST_ICON_SPEC_REFERENCE_CACHE_KEY,
|
||||
currentUserId,
|
||||
),
|
||||
);
|
||||
const currentUserIdRef = useRef(currentUserId);
|
||||
const lastCharacterSpecReferenceRef = useRef<CharacterReferenceImage | null>(
|
||||
initialCharacterSpecReference,
|
||||
);
|
||||
@@ -589,6 +612,18 @@ export function useImageCanvasGenerationWorkflow({
|
||||
cropExpandPanelRef.current = cropExpandPanel;
|
||||
quickEditSelectionStateRef.current = quickEditSelectionState;
|
||||
|
||||
useEffect(() => {
|
||||
currentUserIdRef.current = currentUserId;
|
||||
lastCharacterSpecReferenceRef.current = readCachedGenerationReference(
|
||||
LAST_CHARACTER_SPEC_REFERENCE_CACHE_KEY,
|
||||
currentUserId,
|
||||
);
|
||||
lastIconSpecReferenceRef.current = readCachedGenerationReference(
|
||||
LAST_ICON_SPEC_REFERENCE_CACHE_KEY,
|
||||
currentUserId,
|
||||
);
|
||||
}, [currentUserId]);
|
||||
|
||||
const updateQuickEditSelectionState = useCallback(
|
||||
(
|
||||
updater: (
|
||||
@@ -613,6 +648,7 @@ export function useImageCanvasGenerationWorkflow({
|
||||
writeCachedGenerationReference(
|
||||
LAST_CHARACTER_SPEC_REFERENCE_CACHE_KEY,
|
||||
generateDialog.characterSpecReference,
|
||||
currentUserIdRef.current,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -621,6 +657,7 @@ export function useImageCanvasGenerationWorkflow({
|
||||
writeCachedGenerationReference(
|
||||
LAST_ICON_SPEC_REFERENCE_CACHE_KEY,
|
||||
generateDialog.iconSpecReference,
|
||||
currentUserIdRef.current,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -632,6 +669,7 @@ export function useImageCanvasGenerationWorkflow({
|
||||
writeCachedGenerationReference(
|
||||
LAST_ICON_SPEC_REFERENCE_CACHE_KEY,
|
||||
generateDialog.uiDesignSpecReference,
|
||||
currentUserIdRef.current,
|
||||
);
|
||||
}
|
||||
}, [generateDialog]);
|
||||
|
||||
@@ -282,6 +282,7 @@ export function useImageCanvasProjectPersistence({
|
||||
viewport,
|
||||
isViewportInteracting,
|
||||
canAccessProtectedData,
|
||||
currentUserId,
|
||||
openEditorLoginModal,
|
||||
}: {
|
||||
refs: ImageCanvasProjectPersistenceRefs;
|
||||
@@ -291,6 +292,7 @@ export function useImageCanvasProjectPersistence({
|
||||
viewport: CanvasViewport;
|
||||
isViewportInteracting: boolean;
|
||||
canAccessProtectedData: boolean;
|
||||
currentUserId?: string | null;
|
||||
openEditorLoginModal: (postLoginAction?: (() => void) | null) => void;
|
||||
}) {
|
||||
const projectIdRef = useRef<string | null>(null);
|
||||
@@ -590,6 +592,7 @@ export function useImageCanvasProjectPersistence({
|
||||
{
|
||||
imageSrc: resource.imageSrc,
|
||||
resourceId: resource.resourceId,
|
||||
ownerUserId: resource.ownerUserId,
|
||||
objectKey: resource.objectKey,
|
||||
assetObjectId: resource.assetObjectId,
|
||||
width: resource.width,
|
||||
@@ -612,6 +615,7 @@ export function useImageCanvasProjectPersistence({
|
||||
const { layerItems, generationDialogs } = splitCanvasLayoutItems(
|
||||
project.layers,
|
||||
resourcesById,
|
||||
currentUserId,
|
||||
);
|
||||
const hydratedLayers = layerItems
|
||||
.map((layer) => hydrateLayer(layer, resourcesById))
|
||||
@@ -625,6 +629,7 @@ export function useImageCanvasProjectPersistence({
|
||||
},
|
||||
[
|
||||
clearPendingProjectLayoutSave,
|
||||
currentUserId,
|
||||
refs,
|
||||
restoreCanvasGenerationDialogs,
|
||||
selectSingleLayer,
|
||||
|
||||
+19
-36
@@ -6837,46 +6837,36 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
||||
.image-canvas-editor__toolbar-guide {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 4.45rem;
|
||||
bottom: 3.65rem;
|
||||
z-index: 12;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 0;
|
||||
max-width: min(28rem, calc(100% - 2rem));
|
||||
width: min(38rem, calc(100% - 2rem));
|
||||
aspect-ratio: 2 / 1;
|
||||
color: #ffffff;
|
||||
pointer-events: none;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.image-canvas-editor__toolbar-guide-ip {
|
||||
width: clamp(5.4rem, 12vw, 8.4rem);
|
||||
height: clamp(5.4rem, 12vw, 8.4rem);
|
||||
.image-canvas-editor__toolbar-guide-art {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 16px 22px rgba(112, 57, 30, 0.18));
|
||||
}
|
||||
|
||||
.image-canvas-editor__toolbar-guide span {
|
||||
position: relative;
|
||||
margin-bottom: 0.9rem;
|
||||
margin-left: -0.55rem;
|
||||
border-radius: 0.9rem;
|
||||
background: #f28a2e;
|
||||
padding: 1rem 1.25rem;
|
||||
font-size: 0.92rem;
|
||||
position: absolute;
|
||||
top: 49%;
|
||||
right: 7%;
|
||||
left: 36%;
|
||||
margin: 0;
|
||||
color: #ffffff;
|
||||
font-size: 1rem;
|
||||
font-weight: 850;
|
||||
line-height: 1.45;
|
||||
box-shadow: 0 16px 34px rgba(150, 78, 26, 0.2);
|
||||
}
|
||||
|
||||
.image-canvas-editor__toolbar-guide span::before {
|
||||
position: absolute;
|
||||
top: 1.35rem;
|
||||
left: -0.62rem;
|
||||
width: 1.1rem;
|
||||
height: 1.1rem;
|
||||
background: #f28a2e;
|
||||
content: "";
|
||||
transform: rotate(45deg);
|
||||
text-align: center;
|
||||
text-shadow: 0 2px 7px rgba(127, 65, 25, 0.2);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.image-canvas-editor__bottom-toolbar button[aria-pressed='true'] {
|
||||
@@ -9236,19 +9226,12 @@ button.image-canvas-editor__reference-chip:disabled {
|
||||
.image-canvas-editor__toolbar-guide {
|
||||
right: 0.85rem;
|
||||
left: 0.85rem;
|
||||
bottom: 4.75rem;
|
||||
max-width: none;
|
||||
bottom: 4.25rem;
|
||||
width: auto;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.image-canvas-editor__toolbar-guide-ip {
|
||||
width: 5.25rem;
|
||||
height: 5.25rem;
|
||||
}
|
||||
|
||||
.image-canvas-editor__toolbar-guide span {
|
||||
margin-bottom: 0.65rem;
|
||||
padding: 0.82rem 0.95rem;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user