ChunDe Claude commited on
Commit
a3e6a1a
·
1 Parent(s): b9c2e4b

feat: Add HuggingFace Spaces README and fix TypeScript build issues

Browse files

- Created comprehensive README.md with YAML frontmatter for HF Spaces
- Fixed TypeScript errors by refactoring setObjects callbacks to direct calls
- Removed unused variables (rect, dropX, dropY)
- Fixed boolean type coercion issues with ?? operator
- Successfully built production bundle (505KB)
- Ready for deployment to HuggingFace Spaces

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>

Files changed (2) hide show
  1. README.md +90 -0
  2. src/App.tsx +16 -26
README.md ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: HF Thumbnail Crafter
3
+ emoji: 🎨
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: static
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
11
+ # HF Thumbnail Crafter
12
+
13
+ A web-based thumbnail creator for HuggingFace users. Create quick, professional thumbnails with pre-made layouts and visual assets (Huggys).
14
+
15
+ ## Features
16
+
17
+ - **4 Pre-designed Layouts**: Serious Collab, Fun Collab, Sandwich, and Docs layouts
18
+ - **44+ Huggy Mascot Assets**: Loaded directly from HuggingFace dataset
19
+ - **Custom Text Editing**: Support for Inter & IBM Plex Mono fonts with bold/italic styling
20
+ - **Image Upload**: Drag-and-drop support for local images (PNG, JPG, WebP)
21
+ - **Layer Management**: Use `[` and `]` keyboard shortcuts to reorder objects
22
+ - **Smart Canvas Tools**:
23
+ - Multi-select with drag-to-select box or Shift+Click
24
+ - Arrow key movement (1px or 10px with Shift)
25
+ - Shift-constrained drag (horizontal/vertical lock)
26
+ - Smart snapping to canvas center lines
27
+ - **Export**: High-quality PNG export with custom filename
28
+ - **Undo/Redo**: Full history support (Ctrl+Z / Ctrl+Shift+Z)
29
+
30
+ ## Usage
31
+
32
+ 1. **Select a Layout**: Click the Layout button in the sidebar to choose from 4 pre-designed templates
33
+ 2. **Customize**:
34
+ - Add Huggys from the Huggy menu
35
+ - Upload your own images
36
+ - Edit text directly on canvas (double-click)
37
+ - Adjust colors using the background selector
38
+ 3. **Arrange Objects**:
39
+ - Drag to reposition
40
+ - Use `[` and `]` keys to change layer order
41
+ - Transform with scaling and rotation
42
+ 4. **Export**: Click the export button and download your thumbnail as PNG
43
+
44
+ ## Keyboard Shortcuts
45
+
46
+ - **`T`**: Activate text creation mode
47
+ - **`Delete/Backspace`**: Delete selected objects
48
+ - **`Ctrl+Z`**: Undo
49
+ - **`Ctrl+Shift+Z`**: Redo
50
+ - **`Ctrl+D`**: Duplicate selected objects
51
+ - **`Arrow Keys`**: Move selection 1px (hold Shift for 10px)
52
+ - **`[`**: Send backward (decrease layer order)
53
+ - **`]`**: Bring forward (increase layer order)
54
+ - **`Shift + Drag`**: Constrain to horizontal/vertical axis
55
+
56
+ ## Canvas Sizes
57
+
58
+ - **1200×675**: Default size (X/Twitter)
59
+ - **1200×627**: LinkedIn size
60
+ - **1160×580**: HuggingFace custom size
61
+
62
+ ## Tech Stack
63
+
64
+ - **Frontend**: React 18 + TypeScript
65
+ - **Build Tool**: Vite
66
+ - **Canvas**: react-konva + Konva.js
67
+ - **Styling**: Tailwind CSS
68
+ - **Icons**: Custom Figma-exported SVGs
69
+ - **Fonts**: Inter, IBM Plex Mono, Bison Bold
70
+
71
+ ## Development
72
+
73
+ ```bash
74
+ # Install dependencies
75
+ npm install
76
+
77
+ # Run dev server
78
+ npm run dev
79
+
80
+ # Build for production
81
+ npx vite build
82
+ ```
83
+
84
+ ## License
85
+
86
+ MIT
87
+
88
+ ---
89
+
90
+ 🤖 Built with [Claude Code](https://claude.com/claude-code)
src/App.tsx CHANGED
@@ -8,7 +8,7 @@ import LayoutSwitchConfirmation from './components/Layout/LayoutSwitchConfirmati
8
  import TextToolbar from './components/TextToolbar/TextToolbar';
9
  import { useCanvasState } from './hooks/useCanvasState';
10
  import { useViewportScale } from './hooks/useViewportScale';
11
- import { LayoutType, TextObject } from './types/canvas.types';
12
  import { getLayoutById } from './data/layouts';
13
  import { getCanvasDimensions, generateId, getNextZIndex, sortByZIndex } from './utils/canvas.utils';
14
  import { Huggy } from './data/huggys';
@@ -111,7 +111,7 @@ function App() {
111
  // Get the maximum zIndex from existing objects
112
  const maxZIndex = objects.length > 0 ? Math.max(...objects.map(obj => obj.zIndex)) : 0;
113
 
114
- const layoutObjects = layout.objects.map((obj, index) => {
115
  const baseObj = {
116
  ...obj,
117
  id: `${obj.id}-${Date.now()}`,
@@ -337,12 +337,6 @@ function App() {
337
  // Get the canvas container element to calculate relative position
338
  const canvasContainer = document.querySelector('.canvas-container');
339
  if (canvasContainer) {
340
- const rect = canvasContainer.getBoundingClientRect();
341
-
342
- // Calculate position relative to canvas
343
- const dropX = e.clientX - rect.left;
344
- const dropY = e.clientY - rect.top;
345
-
346
  // Find the actual canvas stage element (the white/dark canvas area)
347
  const canvasStage = canvasContainer.querySelector('.konvajs-content');
348
  if (canvasStage) {
@@ -775,8 +769,8 @@ function App() {
775
  const textObj = selectedTextObjects[0];
776
  const fontFamily = textObj?.fontFamily || 'Inter';
777
  const fill = textObj?.fill || '#000000';
778
- const bold = textObj?.bold || false;
779
- const italic = textObj?.italic || false;
780
 
781
  // Get canvas dimensions
782
  const dimensions = getCanvasDimensions(canvasSize);
@@ -793,8 +787,7 @@ function App() {
793
  scale={scale}
794
  stageRef={stageRef}
795
  onFontFamilyChange={(font) => {
796
- setObjects(prevObjects =>
797
- prevObjects.map(o => {
798
  if (o.type === 'text' && selectedTextObjects.some(selected => selected.id === o.id)) {
799
  // Recalculate text dimensions with new font
800
  try {
@@ -816,21 +809,19 @@ function App() {
816
  }
817
  }
818
  return o;
819
- })
820
- );
821
  }}
822
  onFillChange={(color) => {
823
- setObjects(prevObjects =>
824
- prevObjects.map(o =>
825
  o.type === 'text' && selectedTextObjects.some(selected => selected.id === o.id)
826
  ? { ...o, fill: color }
827
  : o
828
- )
829
- );
830
  }}
831
  onBoldToggle={() => {
832
- setObjects(prevObjects =>
833
- prevObjects.map(o => {
834
  if (o.type === 'text' && selectedTextObjects.some(selected => selected.id === o.id)) {
835
  const newBold = !o.bold;
836
  // Recalculate text dimensions with new bold state
@@ -853,12 +844,11 @@ function App() {
853
  }
854
  }
855
  return o;
856
- })
857
- );
858
  }}
859
  onItalicToggle={() => {
860
- setObjects(prevObjects =>
861
- prevObjects.map(o => {
862
  if (o.type === 'text' && selectedTextObjects.some(selected => selected.id === o.id)) {
863
  const newItalic = !o.italic;
864
  // Recalculate text dimensions with new italic state
@@ -881,8 +871,8 @@ function App() {
881
  }
882
  }
883
  return o;
884
- })
885
- );
886
  }}
887
  />
888
  );
 
8
  import TextToolbar from './components/TextToolbar/TextToolbar';
9
  import { useCanvasState } from './hooks/useCanvasState';
10
  import { useViewportScale } from './hooks/useViewportScale';
11
+ import { LayoutType, TextObject, CanvasObject } from './types/canvas.types';
12
  import { getLayoutById } from './data/layouts';
13
  import { getCanvasDimensions, generateId, getNextZIndex, sortByZIndex } from './utils/canvas.utils';
14
  import { Huggy } from './data/huggys';
 
111
  // Get the maximum zIndex from existing objects
112
  const maxZIndex = objects.length > 0 ? Math.max(...objects.map(obj => obj.zIndex)) : 0;
113
 
114
+ const layoutObjects = layout.objects.map((obj) => {
115
  const baseObj = {
116
  ...obj,
117
  id: `${obj.id}-${Date.now()}`,
 
337
  // Get the canvas container element to calculate relative position
338
  const canvasContainer = document.querySelector('.canvas-container');
339
  if (canvasContainer) {
 
 
 
 
 
 
340
  // Find the actual canvas stage element (the white/dark canvas area)
341
  const canvasStage = canvasContainer.querySelector('.konvajs-content');
342
  if (canvasStage) {
 
769
  const textObj = selectedTextObjects[0];
770
  const fontFamily = textObj?.fontFamily || 'Inter';
771
  const fill = textObj?.fill || '#000000';
772
+ const bold = textObj?.bold ?? false;
773
+ const italic = textObj?.italic ?? false;
774
 
775
  // Get canvas dimensions
776
  const dimensions = getCanvasDimensions(canvasSize);
 
787
  scale={scale}
788
  stageRef={stageRef}
789
  onFontFamilyChange={(font) => {
790
+ const updatedObjects = objects.map((o: CanvasObject) => {
 
791
  if (o.type === 'text' && selectedTextObjects.some(selected => selected.id === o.id)) {
792
  // Recalculate text dimensions with new font
793
  try {
 
809
  }
810
  }
811
  return o;
812
+ });
813
+ setObjects(updatedObjects);
814
  }}
815
  onFillChange={(color) => {
816
+ const updatedObjects = objects.map((o: CanvasObject) =>
 
817
  o.type === 'text' && selectedTextObjects.some(selected => selected.id === o.id)
818
  ? { ...o, fill: color }
819
  : o
820
+ );
821
+ setObjects(updatedObjects);
822
  }}
823
  onBoldToggle={() => {
824
+ const updatedObjects = objects.map((o: CanvasObject) => {
 
825
  if (o.type === 'text' && selectedTextObjects.some(selected => selected.id === o.id)) {
826
  const newBold = !o.bold;
827
  // Recalculate text dimensions with new bold state
 
844
  }
845
  }
846
  return o;
847
+ });
848
+ setObjects(updatedObjects);
849
  }}
850
  onItalicToggle={() => {
851
+ const updatedObjects = objects.map((o: CanvasObject) => {
 
852
  if (o.type === 'text' && selectedTextObjects.some(selected => selected.id === o.id)) {
853
  const newItalic = !o.italic;
854
  // Recalculate text dimensions with new italic state
 
871
  }
872
  }
873
  return o;
874
+ });
875
+ setObjects(updatedObjects);
876
  }}
877
  />
878
  );