Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/image/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^12.23.24",
"i18next": "^26.0.6",
"lucide-react": "^0.555.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^17.0.4",
"tailwind-merge": "^3.4.0",
"uuid": "^13.0.0",
"zustand": "^4.5.2"
Expand Down
58 changes: 30 additions & 28 deletions apps/image/src/components/editor/SettingsDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState } from 'react';
import { useState, type ReactNode } from 'react';
import { X, Settings, Grid3X3, MousePointer, Save, Palette, Monitor } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useUIStore } from '../../stores/ui-store';
import { Slider } from '@openreel/ui';

Expand All @@ -12,6 +13,7 @@ type SettingsTab = 'canvas' | 'snapping' | 'appearance';

export function SettingsDialog({ isOpen, onClose }: Props) {
const [activeTab, setActiveTab] = useState<SettingsTab>('canvas');
const { t } = useTranslation();

const {
showGrid,
Expand All @@ -32,10 +34,10 @@ export function SettingsDialog({ isOpen, onClose }: Props) {

if (!isOpen) return null;

const tabs: { id: SettingsTab; label: string; icon: React.ReactNode }[] = [
{ id: 'canvas', label: 'Canvas', icon: <Grid3X3 size={16} /> },
{ id: 'snapping', label: 'Snapping', icon: <MousePointer size={16} /> },
{ id: 'appearance', label: 'Appearance', icon: <Palette size={16} /> },
const tabs: { id: SettingsTab; label: string; icon: ReactNode }[] = [
{ id: 'canvas', label: t('settings:tabs.canvas'), icon: <Grid3X3 size={16} /> },
{ id: 'snapping', label: t('settings:tabs.snapping'), icon: <MousePointer size={16} /> },
{ id: 'appearance', label: t('settings:tabs.appearance'), icon: <Palette size={16} /> },
];

return (
Expand All @@ -45,7 +47,7 @@ export function SettingsDialog({ isOpen, onClose }: Props) {
<div className="flex items-center justify-between px-6 py-4 border-b border-border">
<div className="flex items-center gap-3">
<Settings size={20} className="text-primary" />
<h2 className="text-lg font-semibold">Settings</h2>
<h2 className="text-lg font-semibold">{t('settings:dialog.title')}</h2>
</div>
<button
onClick={onClose}
Expand Down Expand Up @@ -76,33 +78,33 @@ export function SettingsDialog({ isOpen, onClose }: Props) {
<div className="flex-1 p-6 min-h-[300px]">
{activeTab === 'canvas' && (
<div className="space-y-6">
<h3 className="text-sm font-medium text-foreground mb-4">Canvas Options</h3>
<h3 className="text-sm font-medium text-foreground mb-4">{t('settings:canvas.title')}</h3>

<div className="space-y-4">
<ToggleOption
label="Show Grid"
description="Display grid overlay on canvas"
label={t('settings:canvas.showGrid')}
description={t('settings:canvas.showGridDescription')}
checked={showGrid}
onChange={toggleGrid}
/>

<ToggleOption
label="Show Guides"
description="Display alignment guides"
label={t('settings:canvas.showGuides')}
description={t('settings:canvas.showGuidesDescription')}
checked={showGuides}
onChange={toggleGuides}
/>

<ToggleOption
label="Show Rulers"
description="Display rulers on edges"
label={t('settings:canvas.showRulers')}
description={t('settings:canvas.showRulersDescription')}
checked={showRulers}
onChange={toggleRulers}
/>

<div className="pt-2">
<div className="flex items-center justify-between mb-2">
<label className="text-sm text-foreground">Grid Size</label>
<label className="text-sm text-foreground">{t('settings:canvas.gridSize')}</label>
<span className="text-sm text-muted-foreground">{gridSize}px</span>
</div>
<Slider
Expand All @@ -119,26 +121,26 @@ export function SettingsDialog({ isOpen, onClose }: Props) {

{activeTab === 'snapping' && (
<div className="space-y-6">
<h3 className="text-sm font-medium text-foreground mb-4">Snap Options</h3>
<h3 className="text-sm font-medium text-foreground mb-4">{t('settings:snapping.title')}</h3>

<div className="space-y-4">
<ToggleOption
label="Snap to Grid"
description="Snap objects to grid intersections"
label={t('settings:snapping.snapToGrid')}
description={t('settings:snapping.snapToGridDescription')}
checked={snapToGrid}
onChange={toggleSnapToGrid}
/>

<ToggleOption
label="Snap to Guides"
description="Snap objects to guide lines"
label={t('settings:snapping.snapToGuides')}
description={t('settings:snapping.snapToGuidesDescription')}
checked={snapToGuides}
onChange={toggleSnapToGuides}
/>

<ToggleOption
label="Snap to Objects"
description="Snap objects to other objects"
label={t('settings:snapping.snapToObjects')}
description={t('settings:snapping.snapToObjectsDescription')}
checked={snapToObjects}
onChange={toggleSnapToObjects}
/>
Expand All @@ -148,32 +150,32 @@ export function SettingsDialog({ isOpen, onClose }: Props) {

{activeTab === 'appearance' && (
<div className="space-y-6">
<h3 className="text-sm font-medium text-foreground mb-4">Appearance</h3>
<h3 className="text-sm font-medium text-foreground mb-4">{t('settings:appearance.title')}</h3>

<div className="space-y-4">
<div className="flex items-center justify-between p-3 bg-secondary/50 rounded-lg">
<div className="flex items-center gap-3">
<Monitor size={18} className="text-muted-foreground" />
<div>
<p className="text-sm font-medium">Theme</p>
<p className="text-xs text-muted-foreground">Interface appearance</p>
<p className="text-sm font-medium">{t('settings:appearance.theme')}</p>
<p className="text-xs text-muted-foreground">{t('settings:appearance.themeDescription')}</p>
</div>
</div>
<div className="px-3 py-1.5 text-xs bg-primary/20 text-primary rounded-md">
Dark (System)
{t('settings:appearance.themeValue')}
</div>
</div>

<div className="p-3 bg-secondary/50 rounded-lg">
<div className="flex items-center gap-3 mb-3">
<Save size={18} className="text-muted-foreground" />
<div>
<p className="text-sm font-medium">Auto Save</p>
<p className="text-xs text-muted-foreground">Automatically save projects</p>
<p className="text-sm font-medium">{t('settings:appearance.autoSave')}</p>
<p className="text-xs text-muted-foreground">{t('settings:appearance.autoSaveDescription')}</p>
</div>
</div>
<p className="text-xs text-muted-foreground">
Projects are automatically saved to browser storage every 30 seconds.
{t('settings:appearance.autoSaveSummary')}
</p>
</div>
</div>
Expand Down
30 changes: 16 additions & 14 deletions apps/image/src/components/editor/inspector/TransformSection.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FlipHorizontal2, FlipVertical2, RotateCw, RotateCcw } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useProjectStore } from '../../../stores/project-store';
import type { Layer } from '../../../types/project';

Expand All @@ -7,6 +8,7 @@ interface Props {
}

export function TransformSection({ layer }: Props) {
const { t } = useTranslation();
const { updateLayer, updateLayerTransform } = useProjectStore();

const { x, y, width, height, rotation, skewX, skewY, opacity } = layer.transform;
Expand Down Expand Up @@ -34,12 +36,12 @@ export function TransformSection({ layer }: Props) {
return (
<div className="space-y-3">
<h4 className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
Transform
{t('inspector:transform.title')}
</h4>

<div className="grid grid-cols-2 gap-2">
<div>
<label className="block text-[10px] text-muted-foreground mb-1">X</label>
<label className="block text-[10px] text-muted-foreground mb-1">{t('inspector:transform.x')}</label>
<input
type="number"
value={Math.round(x)}
Expand All @@ -48,7 +50,7 @@ export function TransformSection({ layer }: Props) {
/>
</div>
<div>
<label className="block text-[10px] text-muted-foreground mb-1">Y</label>
<label className="block text-[10px] text-muted-foreground mb-1">{t('inspector:transform.y')}</label>
<input
type="number"
value={Math.round(y)}
Expand All @@ -60,7 +62,7 @@ export function TransformSection({ layer }: Props) {

<div className="grid grid-cols-2 gap-2">
<div>
<label className="block text-[10px] text-muted-foreground mb-1">Width</label>
<label className="block text-[10px] text-muted-foreground mb-1">{t('inspector:transform.width')}</label>
<input
type="number"
value={Math.round(width)}
Expand All @@ -70,7 +72,7 @@ export function TransformSection({ layer }: Props) {
/>
</div>
<div>
<label className="block text-[10px] text-muted-foreground mb-1">Height</label>
<label className="block text-[10px] text-muted-foreground mb-1">{t('inspector:transform.height')}</label>
<input
type="number"
value={Math.round(height)}
Expand All @@ -83,19 +85,19 @@ export function TransformSection({ layer }: Props) {

<div className="grid grid-cols-2 gap-2">
<div>
<label className="block text-[10px] text-muted-foreground mb-1">Rotation</label>
<label className="block text-[10px] text-muted-foreground mb-1">{t('inspector:transform.rotation')}</label>
<div className="flex items-center gap-1">
<input
type="number"
value={Math.round(rotation)}
onChange={(e) => handleChange('rotation', Number(e.target.value))}
className="flex-1 px-2 py-1.5 text-xs bg-background border border-input rounded-md focus:outline-none focus:ring-1 focus:ring-primary"
/>
<span className="text-xs text-muted-foreground">°</span>
<span className="text-xs text-muted-foreground">deg</span>
</div>
</div>
<div>
<label className="block text-[10px] text-muted-foreground mb-1">Opacity</label>
<label className="block text-[10px] text-muted-foreground mb-1">{t('inspector:transform.opacity')}</label>
<div className="flex items-center gap-1">
<input
type="number"
Expand All @@ -112,7 +114,7 @@ export function TransformSection({ layer }: Props) {

<div className="grid grid-cols-2 gap-2">
<div>
<label className="block text-[10px] text-muted-foreground mb-1">Skew X</label>
<label className="block text-[10px] text-muted-foreground mb-1">{t('inspector:transform.skewX')}</label>
<div className="flex items-center gap-1">
<input
type="number"
Expand All @@ -126,7 +128,7 @@ export function TransformSection({ layer }: Props) {
</div>
</div>
<div>
<label className="block text-[10px] text-muted-foreground mb-1">Skew Y</label>
<label className="block text-[10px] text-muted-foreground mb-1">{t('inspector:transform.skewY')}</label>
<div className="flex items-center gap-1">
<input
type="number"
Expand All @@ -149,7 +151,7 @@ export function TransformSection({ layer }: Props) {
? 'bg-primary/20 border-primary text-primary'
: 'bg-secondary/50 border-border text-muted-foreground hover:text-foreground hover:bg-secondary'
}`}
title="Flip Horizontal"
title={t('inspector:transform.flipHorizontal')}
>
<FlipHorizontal2 size={14} />
</button>
Expand All @@ -161,23 +163,23 @@ export function TransformSection({ layer }: Props) {
? 'bg-primary/20 border-primary text-primary'
: 'bg-secondary/50 border-border text-muted-foreground hover:text-foreground hover:bg-secondary'
}`}
title="Flip Vertical"
title={t('inspector:transform.flipVertical')}
>
<FlipVertical2 size={14} />
</button>

<button
onClick={() => handleRotate(-90)}
className="flex items-center justify-center gap-1 p-2 rounded-md bg-secondary/50 border border-border text-muted-foreground hover:text-foreground hover:bg-secondary transition-all"
title="Rotate 90° Counter-clockwise"
title={t('inspector:transform.rotateCounterClockwise')}
>
<RotateCcw size={14} />
</button>

<button
onClick={() => handleRotate(90)}
className="flex items-center justify-center gap-1 p-2 rounded-md bg-secondary/50 border border-border text-muted-foreground hover:text-foreground hover:bg-secondary transition-all"
title="Rotate 90° Clockwise"
title={t('inspector:transform.rotateClockwise')}
>
<RotateCw size={14} />
</button>
Expand Down
30 changes: 30 additions & 0 deletions apps/image/src/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import common from "./locales/en/common.json";
import editor from "./locales/en/editor.json";
import inspector from "./locales/en/inspector.json";
import settings from "./locales/en/settings.json";

const resources = {
en: {
common,
editor,
inspector,
settings,
},
} as const;

if (!i18n.isInitialized) {
void i18n.use(initReactI18next).init({
resources,
lng: "en",
fallbackLng: "en",
ns: ["common", "editor", "inspector", "settings"],
defaultNS: "common",
interpolation: {
escapeValue: false,
},
});
}

export default i18n;
5 changes: 5 additions & 0 deletions apps/image/src/locales/en/common.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"buttons": {
"close": "Close"
}
}
1 change: 1 addition & 0 deletions apps/image/src/locales/en/editor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
17 changes: 17 additions & 0 deletions apps/image/src/locales/en/inspector.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"transform": {
"title": "Transform",
"x": "X",
"y": "Y",
"width": "Width",
"height": "Height",
"rotation": "Rotation",
"opacity": "Opacity",
"skewX": "Skew X",
"skewY": "Skew Y",
"flipHorizontal": "Flip Horizontal",
"flipVertical": "Flip Vertical",
"rotateCounterClockwise": "Rotate 90 degrees Counter-clockwise",
"rotateClockwise": "Rotate 90 degrees Clockwise"
}
}
38 changes: 38 additions & 0 deletions apps/image/src/locales/en/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"dialog": {
"title": "Settings"
},
"tabs": {
"canvas": "Canvas",
"snapping": "Snapping",
"appearance": "Appearance"
},
"canvas": {
"title": "Canvas Options",
"showGrid": "Show Grid",
"showGridDescription": "Display grid overlay on canvas",
"showGuides": "Show Guides",
"showGuidesDescription": "Display alignment guides",
"showRulers": "Show Rulers",
"showRulersDescription": "Display rulers on edges",
"gridSize": "Grid Size"
},
"snapping": {
"title": "Snap Options",
"snapToGrid": "Snap to Grid",
"snapToGridDescription": "Snap objects to grid intersections",
"snapToGuides": "Snap to Guides",
"snapToGuidesDescription": "Snap objects to guide lines",
"snapToObjects": "Snap to Objects",
"snapToObjectsDescription": "Snap objects to other objects"
},
"appearance": {
"title": "Appearance",
"theme": "Theme",
"themeDescription": "Interface appearance",
"themeValue": "Dark (System)",
"autoSave": "Auto Save",
"autoSaveDescription": "Automatically save projects",
"autoSaveSummary": "Projects are automatically saved to browser storage every 30 seconds."
}
}
Loading