Update
This commit is contained in:
9
package-lock.json
generated
9
package-lock.json
generated
@@ -4,7 +4,6 @@
|
|||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "memeaigen",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^2.2.0",
|
"@headlessui/react": "^2.2.0",
|
||||||
"@inertiajs/react": "^2.0.0",
|
"@inertiajs/react": "^2.0.0",
|
||||||
@@ -41,7 +40,7 @@
|
|||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
"vite": "^6.0",
|
"vite": "^6.0",
|
||||||
"zustand": "^5.0.3"
|
"zustand": "^5.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.19.0",
|
"@eslint/js": "^9.19.0",
|
||||||
@@ -7809,9 +7808,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zustand": {
|
"node_modules/zustand": {
|
||||||
"version": "5.0.3",
|
"version": "5.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.5.tgz",
|
||||||
"integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==",
|
"integrity": "sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.20.0"
|
"node": ">=12.20.0"
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
"vite": "^6.0",
|
"vite": "^6.0",
|
||||||
"zustand": "^5.0.3"
|
"zustand": "^5.0.5"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.9.5",
|
"@rollup/rollup-linux-x64-gnu": "4.9.5",
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { useState, useEffect, useLayoutEffect } from "react"
|
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||||
|
|
||||||
import EditSidebar from "./partials/edit-sidebar"
|
import useLocalSettingsStore from '@/stores/localSettingsStore';
|
||||||
import EditorCanvas from "./partials/editor-canvas"
|
import { Volume2Icon, VolumeOffIcon } from 'lucide-react';
|
||||||
import EditorHeader from "./partials/editor-header"
|
import EditNavSidebar from './partials/edit-nav-sidebar';
|
||||||
import EditorControls from "./partials/editor-controls"
|
import EditSidebar from './partials/edit-sidebar';
|
||||||
import { calculateOptimalMaxWidth, calculateResponsiveWidth } from "./utils/layout-constants"
|
import EditorCanvas from './partials/editor-canvas';
|
||||||
|
import EditorControls from './partials/editor-controls';
|
||||||
|
import EditorHeader from './partials/editor-header';
|
||||||
|
import { calculateOptimalMaxWidth, calculateResponsiveWidth } from './utils/layout-constants';
|
||||||
|
|
||||||
// Hook to detect if viewport is below minimum width
|
// Hook to detect if viewport is below minimum width
|
||||||
const useViewportDetection = (minWidth = 320) => {
|
const useViewportDetection = (minWidth = 320) => {
|
||||||
@@ -43,7 +46,7 @@ const useViewportDetection = (minWidth = 320) => {
|
|||||||
const useResponsiveDimensions = () => {
|
const useResponsiveDimensions = () => {
|
||||||
const [dimensions, setDimensions] = useState(() => ({
|
const [dimensions, setDimensions] = useState(() => ({
|
||||||
maxWidth: calculateOptimalMaxWidth(),
|
maxWidth: calculateOptimalMaxWidth(),
|
||||||
responsiveWidth: calculateResponsiveWidth()
|
responsiveWidth: calculateResponsiveWidth(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
@@ -51,7 +54,7 @@ const useResponsiveDimensions = () => {
|
|||||||
const newResponsiveWidth = calculateResponsiveWidth();
|
const newResponsiveWidth = calculateResponsiveWidth();
|
||||||
setDimensions({
|
setDimensions({
|
||||||
maxWidth: newMaxWidth,
|
maxWidth: newMaxWidth,
|
||||||
responsiveWidth: newResponsiveWidth
|
responsiveWidth: newResponsiveWidth,
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -61,7 +64,7 @@ const useResponsiveDimensions = () => {
|
|||||||
const newResponsiveWidth = calculateResponsiveWidth();
|
const newResponsiveWidth = calculateResponsiveWidth();
|
||||||
setDimensions({
|
setDimensions({
|
||||||
maxWidth: newMaxWidth,
|
maxWidth: newMaxWidth,
|
||||||
responsiveWidth: newResponsiveWidth
|
responsiveWidth: newResponsiveWidth,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -81,7 +84,7 @@ const useResponsiveDimensions = () => {
|
|||||||
});
|
});
|
||||||
mutationObserver.observe(document.documentElement, {
|
mutationObserver.observe(document.documentElement, {
|
||||||
attributes: true,
|
attributes: true,
|
||||||
attributeFilter: ['style']
|
attributeFilter: ['style'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,39 +100,85 @@ const useResponsiveDimensions = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Editor = () => {
|
const Editor = () => {
|
||||||
const [isEditSidebarOpen, setIsEditSidebarOpen] = useState(false)
|
const { getSetting } = useLocalSettingsStore();
|
||||||
|
|
||||||
|
const [isEditNavSidebarOpen, setIsEditNavSidebarOpen] = useState(false);
|
||||||
|
const [isEditSidebarOpen, setIsEditSidebarOpen] = useState(false);
|
||||||
|
const [isMuted, setIsMuted] = useState(true); // Video starts muted by default
|
||||||
const { maxWidth, responsiveWidth } = useResponsiveDimensions();
|
const { maxWidth, responsiveWidth } = useResponsiveDimensions();
|
||||||
const isBelowMinWidth = useViewportDetection(320);
|
const isBelowMinWidth = useViewportDetection(320);
|
||||||
|
|
||||||
const handleEditClick = () => {
|
const handleEditNavClick = () => {
|
||||||
setIsEditSidebarOpen(!isEditSidebarOpen)
|
setIsEditNavSidebarOpen(!isEditNavSidebarOpen);
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleCloseSidebar = () => {
|
const handleEditNavClose = () => {
|
||||||
setIsEditSidebarOpen(false)
|
setIsEditNavSidebarOpen(false);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const handleEditClick = () => {
|
||||||
|
setIsEditSidebarOpen(!isEditSidebarOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditClose = () => {
|
||||||
|
setIsEditSidebarOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Toggle mute functionality
|
||||||
|
const handleToggleMute = () => {
|
||||||
|
setIsMuted(!isMuted);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="relative mx-auto flex min-h-screen flex-col space-y-2 py-4" style={{ width: `${responsiveWidth}px` }}>
|
||||||
className="mx-auto min-h-screen flex flex-col relative space-y-2 py-4"
|
<EditSidebar isOpen={isEditSidebarOpen} onClose={handleEditClose} />
|
||||||
style={{ width: `${responsiveWidth}px` }}
|
<EditNavSidebar isOpen={isEditNavSidebarOpen} onClose={handleEditNavClose} />
|
||||||
>
|
|
||||||
<EditSidebar isOpen={isEditSidebarOpen} onClose={handleCloseSidebar} />
|
|
||||||
|
|
||||||
<EditorHeader className="mx-auto" style={{ width: `${responsiveWidth}px` }} />
|
<EditorHeader
|
||||||
|
className="mx-auto"
|
||||||
|
style={{ width: `${responsiveWidth}px` }}
|
||||||
|
onNavClick={handleEditNavClick}
|
||||||
|
isNavActive={isEditNavSidebarOpen}
|
||||||
|
/>
|
||||||
|
|
||||||
{isBelowMinWidth ? (
|
{isBelowMinWidth ? (
|
||||||
<div className="aspect-[9/16]">
|
<div className="aspect-[9/16]">
|
||||||
<div className="flex-1 flex items-center justify-center p-6 bg-white h-full rounded-lg border shadow-lg ">
|
<div className="flex h-full flex-1 items-center justify-center rounded-lg border bg-white p-6 shadow-lg">
|
||||||
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<video className="mx-auto" width="100" height="100%" src="https://cdn.memeaigen.com/videos/cat%20asking%20for%20food.webm" autoPlay muted loop />
|
<div className="relative">
|
||||||
<div className="text-center">
|
<video
|
||||||
<div className="text-xl
|
className="mx-auto"
|
||||||
font-bold">YIKES 🥔</div>
|
width="100"
|
||||||
|
height="100%"
|
||||||
|
src="https://cdn.memeaigen.com/videos/cat%20asking%20for%20food.webm"
|
||||||
|
autoPlay
|
||||||
|
muted={isMuted}
|
||||||
|
loop
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full space-y-2 text-center">
|
||||||
|
<div className="text-xl font-bold">{getSetting('genAlphaSlang') ? 'SHEESH' : 'YIKES'} 🥔</div>
|
||||||
<p className="text-muted-foreground text-sm leading-relaxed">
|
<p className="text-muted-foreground text-sm leading-relaxed">
|
||||||
You seem to be using a potato-sized screen. Please continue with desktop for a more refined experience!
|
{getSetting('genAlphaSlang')
|
||||||
|
? 'Not gonna lie, using on a potato screen is giving L vibes. Desktop hits different - that experience is straight fire, bet!'
|
||||||
|
: 'You seem to be using a potato-sized screen. Please continue with desktop for a more refined experience!'}
|
||||||
</p>
|
</p>
|
||||||
|
<div class="text-muted-foreground text-xs italic">
|
||||||
|
Note: You can turn {getSetting('genAlphaSlang') ? 'off' : 'on'} gen alpha slang in Settings.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex w-full justify-center">
|
||||||
|
<button
|
||||||
|
onClick={handleToggleMute}
|
||||||
|
className="bg-opacity-50 hover:bg-opacity-70 mx-auto rounded-full bg-black p-2 text-white transition-opacity"
|
||||||
|
title={isMuted ? 'Unmute video' : 'Mute video'}
|
||||||
|
>
|
||||||
|
{isMuted ? <VolumeOffIcon className="h-4 w-4" /> : <Volume2Icon className="h-4 w-4" />}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -146,7 +195,7 @@ const Editor = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Editor;
|
export default Editor;
|
||||||
|
|||||||
59
resources/js/modules/editor/partials/edit-nav-sidebar.tsx
Normal file
59
resources/js/modules/editor/partials/edit-nav-sidebar.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
||||||
|
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet';
|
||||||
|
import useLocalSettingsStore from '@/stores/localSettingsStore';
|
||||||
|
import { SettingsIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
interface EditNavSidebarProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EditNavSidebar({ isOpen, onClose }: EditNavSidebarProps) {
|
||||||
|
const { getSetting, setSetting } = useLocalSettingsStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sheet open={isOpen} onOpenChange={(open) => !open && onClose()}>
|
||||||
|
<SheetContent side="left" className="w-50 overflow-y-auto">
|
||||||
|
<SheetHeader>
|
||||||
|
<SheetTitle className="flex items-center gap-3">
|
||||||
|
<div className="font-display ml-0 text-lg tracking-wide md:ml-3 md:text-xl">MEMEAIGEN</div>
|
||||||
|
</SheetTitle>
|
||||||
|
</SheetHeader>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button variant="link">
|
||||||
|
<SettingsIcon className="h-6 w-6" /> Settings
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Settings</DialogTitle>
|
||||||
|
<DialogDescription>Change your settings here.</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="genAlphaSlang"
|
||||||
|
checked={getSetting('genAlphaSlang')}
|
||||||
|
onCheckedChange={() => setSetting('genAlphaSlang', !getSetting('genAlphaSlang'))}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="genAlphaSlang"
|
||||||
|
className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
Use gen alpha slang
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter></DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,14 +1,9 @@
|
|||||||
import { Edit3, Plus, Coins } from "lucide-react"
|
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet';
|
||||||
import {
|
import { Coins, Edit3, Plus } from 'lucide-react';
|
||||||
Sheet,
|
|
||||||
SheetContent,
|
|
||||||
SheetHeader,
|
|
||||||
SheetTitle,
|
|
||||||
} from "@/components/ui/sheet"
|
|
||||||
|
|
||||||
interface EditSidebarProps {
|
interface EditSidebarProps {
|
||||||
isOpen: boolean
|
isOpen: boolean;
|
||||||
onClose: () => void
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function EditSidebar({ isOpen, onClose }: EditSidebarProps) {
|
export default function EditSidebar({ isOpen, onClose }: EditSidebarProps) {
|
||||||
@@ -25,26 +20,26 @@ export default function EditSidebar({ isOpen, onClose }: EditSidebarProps) {
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Background and Meme Selection */}
|
{/* Background and Meme Selection */}
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="border-2 border-gray-300 rounded-lg p-3 text-center">
|
<div className="rounded-lg border-2 border-gray-300 p-3 text-center">
|
||||||
<div className="w-full h-16 bg-blue-600 rounded mb-2 overflow-hidden">
|
<div className="mb-2 h-16 w-full overflow-hidden rounded bg-blue-600">
|
||||||
<img
|
<img
|
||||||
src="/placeholder.svg?height=64&width=120"
|
src="/placeholder.svg?height=64&width=120"
|
||||||
alt="Gaming background"
|
alt="Gaming background"
|
||||||
width={120}
|
width={120}
|
||||||
height={64}
|
height={64}
|
||||||
className="w-full h-full object-cover"
|
className="h-full w-full object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm font-medium">Background</span>
|
<span className="text-sm font-medium">Background</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-2 border-gray-300 rounded-lg p-3 text-center">
|
<div className="rounded-lg border-2 border-gray-300 p-3 text-center">
|
||||||
<div className="w-full h-16 bg-gray-200 rounded mb-2 overflow-hidden">
|
<div className="mb-2 h-16 w-full overflow-hidden rounded bg-gray-200">
|
||||||
<img
|
<img
|
||||||
src="/placeholder.svg?height=64&width=120"
|
src="/placeholder.svg?height=64&width=120"
|
||||||
alt="Meme character"
|
alt="Meme character"
|
||||||
width={120}
|
width={120}
|
||||||
height={64}
|
height={64}
|
||||||
className="w-full h-full object-cover"
|
className="h-full w-full object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm font-medium">Meme</span>
|
<span className="text-sm font-medium">Meme</span>
|
||||||
@@ -53,16 +48,16 @@ export default function EditSidebar({ isOpen, onClose }: EditSidebarProps) {
|
|||||||
|
|
||||||
{/* AI Background Search */}
|
{/* AI Background Search */}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-medium mb-4">Search for backgrounds using AI</h3>
|
<h3 className="mb-4 text-lg font-medium">Search for backgrounds using AI</h3>
|
||||||
|
|
||||||
<div className="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center mb-4">
|
<div className="mb-4 rounded-lg border-2 border-dashed border-gray-300 p-6 text-center">
|
||||||
<div className="w-12 h-12 border-2 border-gray-400 rounded-full flex items-center justify-center mx-auto mb-3">
|
<div className="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-full border-2 border-gray-400">
|
||||||
<Plus className="h-6 w-6" />
|
<Plus className="h-6 w-6" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm font-medium mb-2">Generate a background with AI</p>
|
<p className="mb-2 text-sm font-medium">Generate a background with AI</p>
|
||||||
<div className="flex items-center justify-center gap-1">
|
<div className="flex items-center justify-center gap-1">
|
||||||
<span className="text-lg font-bold">1</span>
|
<span className="text-lg font-bold">1</span>
|
||||||
<div className="w-5 h-5 bg-yellow-400 rounded-full flex items-center justify-center">
|
<div className="flex h-5 w-5 items-center justify-center rounded-full bg-yellow-400">
|
||||||
<Coins className="h-3 w-3 text-yellow-800" />
|
<Coins className="h-3 w-3 text-yellow-800" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -71,68 +66,68 @@ export default function EditSidebar({ isOpen, onClose }: EditSidebarProps) {
|
|||||||
|
|
||||||
{/* Meme Templates Grid */}
|
{/* Meme Templates Grid */}
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="aspect-square bg-gray-100 rounded-lg overflow-hidden">
|
<div className="aspect-square overflow-hidden rounded-lg bg-gray-100">
|
||||||
<img
|
<img
|
||||||
src="/placeholder.svg?height=150&width=150"
|
src="/placeholder.svg?height=150&width=150"
|
||||||
alt="Creepy face meme"
|
alt="Creepy face meme"
|
||||||
width={150}
|
width={150}
|
||||||
height={150}
|
height={150}
|
||||||
className="w-full h-full object-cover"
|
className="h-full w-full object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="aspect-square bg-gray-100 rounded-lg overflow-hidden relative">
|
<div className="relative aspect-square overflow-hidden rounded-lg bg-gray-100">
|
||||||
<img
|
<img
|
||||||
src="/placeholder.svg?height=150&width=150"
|
src="/placeholder.svg?height=150&width=150"
|
||||||
alt="Confused person meme"
|
alt="Confused person meme"
|
||||||
width={150}
|
width={150}
|
||||||
height={150}
|
height={150}
|
||||||
className="w-full h-full object-cover"
|
className="h-full w-full object-cover"
|
||||||
/>
|
/>
|
||||||
<div className="absolute top-2 right-2 text-black font-bold text-sm">???</div>
|
<div className="absolute top-2 right-2 text-sm font-bold text-black">???</div>
|
||||||
<div className="absolute bottom-2 left-2 text-black font-bold text-sm">???</div>
|
<div className="absolute bottom-2 left-2 text-sm font-bold text-black">???</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="aspect-square bg-gray-100 rounded-lg overflow-hidden">
|
<div className="aspect-square overflow-hidden rounded-lg bg-gray-100">
|
||||||
<img
|
<img
|
||||||
src="/placeholder.svg?height=150&width=150"
|
src="/placeholder.svg?height=150&width=150"
|
||||||
alt="Woody meme"
|
alt="Woody meme"
|
||||||
width={150}
|
width={150}
|
||||||
height={150}
|
height={150}
|
||||||
className="w-full h-full object-cover"
|
className="h-full w-full object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="aspect-square bg-gray-100 rounded-lg overflow-hidden">
|
<div className="aspect-square overflow-hidden rounded-lg bg-gray-100">
|
||||||
<img
|
<img
|
||||||
src="/placeholder.svg?height=150&width=150"
|
src="/placeholder.svg?height=150&width=150"
|
||||||
alt="Doge meme"
|
alt="Doge meme"
|
||||||
width={150}
|
width={150}
|
||||||
height={150}
|
height={150}
|
||||||
className="w-full h-full object-cover"
|
className="h-full w-full object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="aspect-square bg-blue-900 rounded-lg overflow-hidden relative">
|
<div className="relative aspect-square overflow-hidden rounded-lg bg-blue-900">
|
||||||
<img
|
<img
|
||||||
src="/placeholder.svg?height=150&width=150"
|
src="/placeholder.svg?height=150&width=150"
|
||||||
alt="Stock market meme"
|
alt="Stock market meme"
|
||||||
width={150}
|
width={150}
|
||||||
height={150}
|
height={150}
|
||||||
className="w-full h-full object-cover"
|
className="h-full w-full object-cover"
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 bg-blue-900/50 flex items-center justify-center">
|
<div className="absolute inset-0 flex items-center justify-center bg-blue-900/50">
|
||||||
<span className="text-white text-xs">📈 28%</span>
|
<span className="text-xs text-white">📈 28%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="aspect-square bg-yellow-100 rounded-lg overflow-hidden">
|
<div className="aspect-square overflow-hidden rounded-lg bg-yellow-100">
|
||||||
<img
|
<img
|
||||||
src="/placeholder.svg?height=150&width=150"
|
src="/placeholder.svg?height=150&width=150"
|
||||||
alt="Room meme"
|
alt="Room meme"
|
||||||
width={150}
|
width={150}
|
||||||
height={150}
|
height={150}
|
||||||
className="w-full h-full object-cover"
|
className="h-full w-full object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,53 @@
|
|||||||
import { Button } from "@/components/ui/button"
|
import { Button } from '@/components/ui/button';
|
||||||
import { cn } from "@/lib/utils"
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
import CoinIcon from "@/reusables/coin-icon"
|
import { cn } from '@/lib/utils';
|
||||||
import { Menu, Coins } from "lucide-react"
|
import CoinIcon from '@/reusables/coin-icon';
|
||||||
|
import useLocalSettingsStore from '@/stores/localSettingsStore';
|
||||||
|
import { Menu } from 'lucide-react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
const EditorHeader = ({ className = '', onNavClick = () => {}, isNavActive = false }) => {
|
||||||
|
const { getSetting } = useLocalSettingsStore();
|
||||||
|
|
||||||
|
const [openCoinDialog, setOpenCoinDialog] = useState(false);
|
||||||
|
|
||||||
const EditorHeader = (
|
|
||||||
{className = ''}
|
|
||||||
) => {
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("bg-white rounded-xl p-2 flex items-center justify-between shadow-sm w-full", className)}>
|
<div className={cn('flex w-full items-center justify-between rounded-xl bg-white p-2 shadow-sm', className)}>
|
||||||
<Button variant="outline" size="icon" className="rounded">
|
<Button onClick={onNavClick} variant="outline" size="icon" className="rounded">
|
||||||
<Menu className="h-8 w-8" />
|
<Menu className="h-8 w-8" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<h1 className="text-lg md:text-xl font-display tracking-wide ml-0 md:ml-3">MEMEAIGEN</h1>
|
<h1 className="font-display ml-0 text-lg tracking-wide md:ml-3 md:text-xl">MEMEAIGEN</h1>
|
||||||
|
|
||||||
<Button variant="outline" className="rounded inline-flex gap-1">
|
<Button variant="outline" className="inline-flex gap-1 rounded" onClick={() => setOpenCoinDialog(true)}>
|
||||||
<span className="text-sm font-semibold">100</span>
|
<span className="text-sm font-semibold">0</span>
|
||||||
<CoinIcon className="w-8 h-8" />
|
<CoinIcon className="h-8 w-8" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<Dialog open={openCoinDialog} onOpenChange={(open) => setOpenCoinDialog(open)}>
|
||||||
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{getSetting('genAlphaSlang') ? 'Bruh' : 'Chill'}</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{getSetting('genAlphaSlang')
|
||||||
|
? "No cap, soon you'll be able to get AI cooking memes that absolutely slay! But lowkey fam, we gotta focus on making these core features bussin' first."
|
||||||
|
: "Soon you'll be able to prompt AI to generate memes! Let us focus on nailing the core features first."}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<div className="flex justify-between gap-1">
|
||||||
|
<span class="text-muted-foreground text-xs italic">
|
||||||
|
Note: You can turn {getSetting('genAlphaSlang') ? 'off' : 'on'} gen alpha slang in Settings.
|
||||||
|
</span>
|
||||||
|
<Button variant="outline" onClick={() => setOpenCoinDialog(false)}>
|
||||||
|
{getSetting('genAlphaSlang') ? 'Bet' : 'Okay'}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)
|
</DialogFooter>
|
||||||
}
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default EditorHeader;
|
export default EditorHeader;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import Editor from "@/modules/editor/editor";
|
import Editor from '@/modules/editor/editor';
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
return (
|
return (
|
||||||
<div class="bg-neutral-50 min-h-screen">
|
<div className="min-h-screen bg-neutral-50">
|
||||||
<Editor />
|
<Editor />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,17 +7,32 @@ interface CoinIconProps {
|
|||||||
const CoinIcon: React.FC<CoinIconProps> = ({ className }) => {
|
const CoinIcon: React.FC<CoinIconProps> = ({ className }) => {
|
||||||
return (
|
return (
|
||||||
<svg className={className} width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg className={className} width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g clip-path="url(#clip0_23_2)">
|
<g clipPath="url(#clip0_23_2)">
|
||||||
<path d="M100 200C44.8582 200 0 155.139 0 100C0 44.8605 44.8582 0 100 0C155.142 0 200 44.8605 200 100C200 155.139 155.142 200 100 200Z" fill="#FFE14D"/>
|
<path
|
||||||
<path d="M200 100C200 44.8605 155.142 0 100 0V200C155.142 200 200 155.139 200 100Z" fill="#FFCC33"/>
|
d="M100 200C44.8582 200 0 155.139 0 100C0 44.8605 44.8582 0 100 0C155.142 0 200 44.8605 200 100C200 155.139 155.142 200 100 200Z"
|
||||||
<path d="M100 13.0435C52.0509 13.0435 13.0435 52.0533 13.0435 100C13.0435 147.947 52.0509 186.957 100 186.957C147.949 186.957 186.957 147.947 186.957 100C186.957 52.0533 147.949 13.0435 100 13.0435ZM100 173.913C59.2435 173.913 26.0872 140.756 26.0872 100C26.0872 59.2439 59.2435 26.0872 100 26.0872C140.757 26.0872 173.913 59.2435 173.913 100C173.913 140.757 140.757 173.913 100 173.913Z" fill="#FF9F19"/>
|
fill="#FFE14D"
|
||||||
<path d="M173.913 100C173.913 140.757 140.756 173.913 100 173.913V186.956C147.949 186.956 186.957 147.947 186.957 99.9997C186.957 52.0525 147.949 13.0435 100 13.0435V26.0868C140.757 26.0872 173.913 59.2435 173.913 100Z" fill="#F28618"/>
|
/>
|
||||||
<path d="M81.8546 60.7827C83.0432 60.7827 84.0092 61.037 84.7521 61.5454C85.5691 62.0537 86.3121 62.8885 86.9806 64.0503L97.0099 81.8071C97.3813 82.4607 97.6786 82.9336 97.9015 83.2241C98.1986 83.5143 98.5702 83.6596 99.0157 83.6596H99.5167V112.31H97.0099C95.5984 112.31 94.4092 112.056 93.4435 111.547C92.5522 110.966 91.7722 110.058 91.1036 108.824L84.5294 96.9497V135.296C84.5293 136.676 84.195 137.693 83.5265 138.346C82.9322 138.927 81.9292 139.217 80.5177 139.217H63.9142C62.5029 139.217 61.4626 138.927 60.7941 138.346C60.2 137.693 59.9035 136.676 59.9034 135.296V64.7046C59.9034 63.3249 60.2 62.3442 60.7941 61.7632C61.4626 61.1096 62.5029 60.7827 63.9142 60.7827H81.8546Z" fill="#FF9F19"/>
|
<path d="M200 100C200 44.8605 155.142 0 100 0V200C155.142 200 200 155.139 200 100Z" fill="#FFCC33" />
|
||||||
<path d="M135.119 60.7827C136.53 60.7827 137.533 61.1096 138.127 61.7632C138.796 62.3442 139.13 63.3247 139.13 64.7046V135.296C139.13 136.676 138.796 137.693 138.127 138.346C137.533 138.927 136.53 139.217 135.119 139.217H117.958C116.547 139.217 115.507 138.927 114.838 138.346C114.244 137.693 113.947 136.676 113.947 135.296V96.9497L107.372 108.824C106.704 110.058 105.887 110.966 104.921 111.547C104.03 112.056 102.878 112.31 101.467 112.31H99.517V83.6596H100.018C100.463 83.6596 100.798 83.5143 101.021 83.2241C101.318 82.9336 101.652 82.4607 102.024 81.8071L111.942 64.0503C112.61 62.8885 113.316 62.0537 114.059 61.5454C114.876 61.0371 115.878 60.7827 117.067 60.7827H135.119Z" fill="#F28618"/>
|
<path
|
||||||
|
d="M100 13.0435C52.0509 13.0435 13.0435 52.0533 13.0435 100C13.0435 147.947 52.0509 186.957 100 186.957C147.949 186.957 186.957 147.947 186.957 100C186.957 52.0533 147.949 13.0435 100 13.0435ZM100 173.913C59.2435 173.913 26.0872 140.756 26.0872 100C26.0872 59.2439 59.2435 26.0872 100 26.0872C140.757 26.0872 173.913 59.2435 173.913 100C173.913 140.757 140.757 173.913 100 173.913Z"
|
||||||
|
fill="#FF9F19"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M173.913 100C173.913 140.757 140.756 173.913 100 173.913V186.956C147.949 186.956 186.957 147.947 186.957 99.9997C186.957 52.0525 147.949 13.0435 100 13.0435V26.0868C140.757 26.0872 173.913 59.2435 173.913 100Z"
|
||||||
|
fill="#F28618"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M81.8546 60.7827C83.0432 60.7827 84.0092 61.037 84.7521 61.5454C85.5691 62.0537 86.3121 62.8885 86.9806 64.0503L97.0099 81.8071C97.3813 82.4607 97.6786 82.9336 97.9015 83.2241C98.1986 83.5143 98.5702 83.6596 99.0157 83.6596H99.5167V112.31H97.0099C95.5984 112.31 94.4092 112.056 93.4435 111.547C92.5522 110.966 91.7722 110.058 91.1036 108.824L84.5294 96.9497V135.296C84.5293 136.676 84.195 137.693 83.5265 138.346C82.9322 138.927 81.9292 139.217 80.5177 139.217H63.9142C62.5029 139.217 61.4626 138.927 60.7941 138.346C60.2 137.693 59.9035 136.676 59.9034 135.296V64.7046C59.9034 63.3249 60.2 62.3442 60.7941 61.7632C61.4626 61.1096 62.5029 60.7827 63.9142 60.7827H81.8546Z"
|
||||||
|
fill="#FF9F19"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M135.119 60.7827C136.53 60.7827 137.533 61.1096 138.127 61.7632C138.796 62.3442 139.13 63.3247 139.13 64.7046V135.296C139.13 136.676 138.796 137.693 138.127 138.346C137.533 138.927 136.53 139.217 135.119 139.217H117.958C116.547 139.217 115.507 138.927 114.838 138.346C114.244 137.693 113.947 136.676 113.947 135.296V96.9497L107.372 108.824C106.704 110.058 105.887 110.966 104.921 111.547C104.03 112.056 102.878 112.31 101.467 112.31H99.517V83.6596H100.018C100.463 83.6596 100.798 83.5143 101.021 83.2241C101.318 82.9336 101.652 82.4607 102.024 81.8071L111.942 64.0503C112.61 62.8885 113.316 62.0537 114.059 61.5454C114.876 61.0371 115.878 60.7827 117.067 60.7827H135.119Z"
|
||||||
|
fill="#F28618"
|
||||||
|
/>
|
||||||
</g>
|
</g>
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="clip0_23_2">
|
<clipPath id="clip0_23_2">
|
||||||
<rect width="200" height="200" fill="white"/>
|
<rect width="200" height="200" fill="white" />
|
||||||
</clipPath>
|
</clipPath>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
80
resources/js/stores/localSettingsStore.ts
Normal file
80
resources/js/stores/localSettingsStore.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { persist } from 'zustand/middleware';
|
||||||
|
|
||||||
|
// Immutable default settings
|
||||||
|
const defaultSettings = {
|
||||||
|
genAlphaSlang: true,
|
||||||
|
// Add more settings here
|
||||||
|
};
|
||||||
|
|
||||||
|
const useLocalSettingsStore = create(
|
||||||
|
persist(
|
||||||
|
(set, get) => ({
|
||||||
|
settings: { ...defaultSettings }, // clone to avoid shared reference
|
||||||
|
|
||||||
|
// Get a setting by key
|
||||||
|
getSetting: (key) => {
|
||||||
|
const currentSettings = get().settings;
|
||||||
|
return key in currentSettings ? currentSettings[key] : defaultSettings[key] ?? null;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Set or update a specific setting
|
||||||
|
setSetting: (key, value) => {
|
||||||
|
console.log(`Updating setting ${key} to`, value); // <-- Debug log
|
||||||
|
set((state) => ({
|
||||||
|
settings: {
|
||||||
|
...state.settings,
|
||||||
|
[key]: value,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// Reset all settings to default
|
||||||
|
resetSettings: () => {
|
||||||
|
set({ settings: { ...defaultSettings } }); // create new object reference
|
||||||
|
},
|
||||||
|
|
||||||
|
// Reset a specific setting to its default
|
||||||
|
resetSetting: (key) => {
|
||||||
|
set((state) => ({
|
||||||
|
settings: {
|
||||||
|
...state.settings,
|
||||||
|
[key]: defaultSettings[key],
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
// Merge default settings with current ones (useful on load)
|
||||||
|
initializeSettings: () => {
|
||||||
|
set((state) => ({
|
||||||
|
settings: {
|
||||||
|
...defaultSettings,
|
||||||
|
...state.settings,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: 'local-settings-storage',
|
||||||
|
// Ensure only the 'settings' key is stored
|
||||||
|
partialize: (state) => ({ settings: state.settings }),
|
||||||
|
// Explicit localStorage usage (for compatibility)
|
||||||
|
storage: {
|
||||||
|
getItem: (name) => {
|
||||||
|
const stored = localStorage.getItem(name);
|
||||||
|
return stored ? JSON.parse(stored) : null;
|
||||||
|
},
|
||||||
|
setItem: (name, value) => {
|
||||||
|
localStorage.setItem(name, JSON.stringify(value));
|
||||||
|
},
|
||||||
|
removeItem: (name) => {
|
||||||
|
localStorage.removeItem(name);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export default useLocalSettingsStore;
|
||||||
Reference in New Issue
Block a user