Skip to content

Commit 0f8938c

Browse files
wernerbihlCopilotgithub-code-quality[bot]
authored
Feature/2026 04 29 (#13)
* Refactor autotile dialog and pattern group card to enhance paint tile functionality and update selection instructions * feat: enhance autotile functionality with blob patterns and UI improvements - Added support for a new "blob-47" preset, allowing for 47-tile blob patterns. - Updated AutotilePatternDiagram to include a center cell button for better interaction. - Refactored AutotilePatternTileCard to utilize the new center cell feature. - Improved AutotileTerrainSidebar and AutotileDialog to reflect changes in terminology and functionality. - Introduced accordion UI for pattern tiles in AutotileDialog for better organization. - Enhanced autotile preset rules to include blob pattern definitions and their respective groups. - Updated tests to cover new blob pattern functionality and ensure correctness. - Adjusted Content Security Policy in vite.config.ts to include Google Analytics. - Added Google Analytics initialization script for tracking. Co-authored-by: Copilot <copilot@github.com> * docs: update README to enhance clarity on features and workflows * refactor: update project saving mechanism and improve error handling - Replaced `markEditorSaved` and `saveProject` with `saveProjectAndNotify` in AppShell, TilesetPanel, and keyboard shortcuts for better error handling and user feedback. - Introduced `saveProjectAndNotify` function to handle project saving with notifications on success or failure. - Updated event listeners to use `project-save-success` for improved event handling. - Added new tests for project saving and terrain functionalities to ensure reliability. - Enhanced utility functions for generating unique identifiers and managing class names. - Added new tests for image layer editor context and tile editor context to ensure proper state management. * feat: add toggle component and tileset animation types; update favicon and config files Co-authored-by: Copilot <copilot@github.com> * feat: add tileset animation support with configuration and utilities - Implemented tileset animations in the map editor, including creation, normalization, and retrieval functions. - Added types for animation placement, drag payloads, and animation frame resolution. - Introduced utility functions for resolving animated tile references and handling animation conflicts. - Created tests for animation export guards and conversion functions to ensure compatibility with Tiled formats. - Enhanced map canvas rendering types to accommodate animation rendering. Co-authored-by: Copilot <copilot@github.com> * feat: implement animation placement resolution and integrate into map editor components Co-authored-by: Copilot <copilot@github.com> * Potential fix for pull request finding 'Useless conditional' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
1 parent 1120496 commit 0f8938c

81 files changed

Lines changed: 4680 additions & 354 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

TODO.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
More advanced autotile
22
Custom Animations i.e. water moving
3+
More advanced autotile: Wang Tile (Look at https://github.com/ruccho/FangAutoTile)
34

45
Support Tiled Project Import/Export
56
Godot project Import/Export

index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
></script>
99
<script src="/gtag-init.js"></script>
1010
<meta charset="UTF-8" />
11-
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
12-
<link rel="apple-touch-icon" href="/icon.svg" />
11+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
12+
<link rel="apple-touch-icon" href="/favicon.svg" />
1313
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
1414
<meta name="theme-color" content="#000000" />
1515
<meta name="description" content="A 2D tile map editor" />

public/favicon.svg

Lines changed: 6 additions & 0 deletions
Loading

public/icon.svg

Lines changed: 0 additions & 14 deletions
This file was deleted.

src/App.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {
1414
loadProjectPrefs,
1515
loadLastProjectId,
1616
} from "@/services/db";
17-
import {} from "@/services/db";
1817
import {
1918
hydrateZoomStoreForProject,
2019
saveCurrentProjectPrefs,

src/components/dialogs/SettingsDialog.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ function ApiKeyRow({ id, label, url, placeholder }: KeyRowProps) {
3535
const [saved, setSaved] = useState(false);
3636
const [hasSaved, setHasSaved] = useState(false);
3737
const savedTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
38+
const inputId = `api-key-${id}`;
3839

3940
useEffect(() => {
4041
setHasSaved(hasApiKey(id));
@@ -60,7 +61,9 @@ function ApiKeyRow({ id, label, url, placeholder }: KeyRowProps) {
6061
return (
6162
<div className="space-y-2 rounded-lg border p-3">
6263
<div className="flex items-center justify-between">
63-
<Label className="text-sm font-medium">{label}</Label>
64+
<Label htmlFor={inputId} className="text-sm font-medium">
65+
{label}
66+
</Label>
6467
<a
6568
href={url}
6669
target="_blank"
@@ -79,6 +82,8 @@ function ApiKeyRow({ id, label, url, placeholder }: KeyRowProps) {
7982
<div className="flex gap-2">
8083
<div className="relative flex-1">
8184
<input
85+
id={inputId}
86+
name={inputId}
8287
type={visible ? "text" : "password"}
8388
placeholder={hasSaved ? "Enter new key to replace" : placeholder}
8489
value={value}
@@ -92,6 +97,9 @@ function ApiKeyRow({ id, label, url, placeholder }: KeyRowProps) {
9297
type="button"
9398
onClick={() => setVisible((v) => !v)}
9499
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
100+
aria-label={
101+
visible ? `Hide ${label} API key` : `Show ${label} API key`
102+
}
95103
tabIndex={-1}
96104
>
97105
{visible ? (
@@ -117,6 +125,7 @@ function ApiKeyRow({ id, label, url, placeholder }: KeyRowProps) {
117125
className="h-8 px-2 text-muted-foreground hover:text-destructive"
118126
onClick={handleDelete}
119127
title="Remove key"
128+
aria-label={`Remove ${label} API key`}
120129
>
121130
<Trash2 className="h-3.5 w-3.5" />
122131
</Button>
@@ -180,9 +189,9 @@ export function SettingsDialog({ open, onOpenChange }: SettingsDialogProps) {
180189
<AccordionContent>
181190
<div className="space-y-3">
182191
<p className="text-[11px] text-muted-foreground leading-relaxed">
183-
Keys are encrypted with AES-GCM and stored locally in your
184-
browser — they are never sent to any server other than the
185-
provider&apos;s own API.
192+
Keys are obfuscated locally in your browser. They are never
193+
sent to any server other than the provider&apos;s own API, but
194+
any script running on this origin can still access them.
186195
</p>
187196
{API_KEY_PROVIDERS.map((p) => (
188197
<ApiKeyRow

src/components/ui/Toggle.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import * as React from "react";
2+
import { cva, type VariantProps } from "class-variance-authority";
3+
import { Toggle as TogglePrimitive } from "radix-ui";
4+
5+
import { cn } from "@/utils/cn";
6+
7+
const toggleVariants = cva(
8+
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
9+
{
10+
variants: {
11+
variant: {
12+
default: "bg-transparent",
13+
outline:
14+
"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
15+
},
16+
size: {
17+
default: "h-9 min-w-9 px-2",
18+
sm: "h-8 min-w-8 px-1.5",
19+
lg: "h-10 min-w-10 px-2.5",
20+
},
21+
},
22+
defaultVariants: {
23+
variant: "default",
24+
size: "default",
25+
},
26+
},
27+
);
28+
29+
function Toggle({
30+
className,
31+
variant,
32+
size,
33+
...props
34+
}: React.ComponentProps<typeof TogglePrimitive.Root> &
35+
VariantProps<typeof toggleVariants>) {
36+
return (
37+
<TogglePrimitive.Root
38+
data-slot="toggle"
39+
className={cn(toggleVariants({ variant, size, className }))}
40+
{...props}
41+
/>
42+
);
43+
}
44+
45+
export { Toggle };

src/features/ai-assets/components/Generator.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,6 @@ export function Generator() {
404404
return;
405405
}
406406

407-
console.log("[AiAssets] Prompt:", finalPrompt);
408-
409407
setIsGenerating(true);
410408
setImages(
411409
Array.from({ length: count }, () => ({ status: "loading" as const })),

src/features/ai-assets/components/ImageUpload.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@ import { useRef, useState } from "react";
22
import { X, Upload } from "lucide-react";
33
import { Button } from "@/components/ui/Button";
44
import { Label } from "@/components/ui/Label";
5+
import type { ImageUploadProps } from "@/types/integrations/ai-assets";
56

67
async function resizeImage(
78
file: File,
89
maxSize: number = 1280,
910
): Promise<string> {
1011
return new Promise((resolve, reject) => {
1112
const image = new Image();
13+
const objectUrl = URL.createObjectURL(file);
1214
image.onload = () => {
15+
URL.revokeObjectURL(objectUrl);
1316
let { width, height } = image;
1417
if (width > maxSize || height > maxSize) {
1518
if (width > height) {
@@ -28,8 +31,11 @@ async function resizeImage(
2831
context.drawImage(image, 0, 0, width, height);
2932
resolve(canvas.toDataURL(file.type));
3033
};
31-
image.onerror = reject;
32-
image.src = URL.createObjectURL(file);
34+
image.onerror = () => {
35+
URL.revokeObjectURL(objectUrl);
36+
reject(new Error("Failed to load uploaded image."));
37+
};
38+
image.src = objectUrl;
3339
});
3440
}
3541

@@ -39,13 +45,7 @@ export function ImageUpload({
3945
value,
4046
onChange,
4147
label,
42-
}: {
43-
id: string;
44-
name: string;
45-
value: string | null;
46-
onChange: (value: string | null) => void;
47-
label: string;
48-
}) {
48+
}: ImageUploadProps) {
4949
const [isDragging, setIsDragging] = useState(false);
5050
const inputRef = useRef<HTMLInputElement>(null);
5151

@@ -128,4 +128,4 @@ export function ImageUpload({
128128
</button>
129129
</div>
130130
);
131-
}
131+
}

src/features/app-shell/components/AppShell.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,11 @@ import {
2222
EditorWorkspaceDrawer,
2323
} from "@/features/map-editor/components/Layout/EditorLayouts";
2424
import { useEditorStore } from "@/hooks/use-editor-store";
25-
import { saveProject } from "@/services/db";
2625
import { clearTileEditorContext } from "@/features/map-editor/lib/tile-editor-context";
2726
import type { AppShellProps } from "@/features/app-shell/types";
2827
import type { EditorWorkspaceTab } from "@/features/map-editor/types/editor-layout";
2928
import type { ImportExportDialogMode } from "@/features/import-export/types";
30-
import { markEditorSaved } from "@/store/editor-store";
29+
import { saveProjectAndNotify } from "@/features/project-management/lib/project-save";
3130

3231
const SettingsDialog = lazy(() =>
3332
import("@/components/dialogs/SettingsDialog").then((module) => ({
@@ -305,8 +304,9 @@ export function AppShell({
305304
function handleSaveEnd() {
306305
toast.success("Project saved");
307306
}
308-
window.addEventListener("project-save-end", handleSaveEnd);
309-
return () => window.removeEventListener("project-save-end", handleSaveEnd);
307+
window.addEventListener("project-save-success", handleSaveEnd);
308+
return () =>
309+
window.removeEventListener("project-save-success", handleSaveEnd);
310310
}, []);
311311

312312
const handleMapQuickExport = useEffectEvent(() => {
@@ -337,8 +337,7 @@ export function AppShell({
337337
onSaveProject={() => {
338338
const project = state.project;
339339
if (project) {
340-
markEditorSaved();
341-
void saveProject({ ...project, updatedAt: Date.now() });
340+
void saveProjectAndNotify(project);
342341
}
343342
}}
344343
onOpenImportDialog={handleOpenImportDialog}

0 commit comments

Comments
 (0)