Skip to content
Merged
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
1 change: 0 additions & 1 deletion .npmrc

This file was deleted.

3 changes: 1 addition & 2 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
"type": "module",
"scripts": {
"dev": "bun run --watch src/index.ts",
"start": "bun run src/index.ts",
"build": "bun build ./src/index.ts --compile --target bun --outfile ./dist/shadowave-api --minify-whitespace --minify-syntax",
"lint": "eslint . --max-warnings 0"
},
"dependencies": {
"@elysiajs/cors": "^1.2.0",
"@workspace/schema": "workspace:*",
"elysia": "^1.2.11",
"elysia": "^1.2.12",
"puppeteer-core": "^24.2.0"
},
"devDependencies": {
Expand Down
27 changes: 12 additions & 15 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,38 @@
"dependencies": {
"@elysiajs/eden": "^1.2.0",
"@lingui/core": "^5.2.0",
"@lingui/macro": "^5.2.0",
"@lingui/react": "^5.2.0",
"@radix-ui/react-alert-dialog": "^1.1.5",
"@radix-ui/react-dialog": "^1.1.5",
"@radix-ui/react-slot": "^1.1.1",
"@workspace/schema": "workspace:*",
"@workspace/ui": "workspace:*",
"browser-fs-access": "^0.35.0",
"clsx": "^2.1.1",
"idb-keyval": "^6.2.1",
"jotai": "^2.11.3",
"jotai": "^2.12.0",
"jotai-optics": "^0.4.0",
"lucide-react": "^0.474.0",
"lucide-react": "^0.475.0",
"optics-ts": "^2.4.1",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react-dom": "^19.0.0",
"react-hook-form": "^7.54.2"
},
"devDependencies": {
"@lingui/cli": "^5.2.0",
"@lingui/conf": "^5.2.0",
"@lingui/swc-plugin": "5.0.2",
"@lingui/swc-plugin": "5.1.0",
"@lingui/vite-plugin": "^5.2.0",
"@tailwindcss/postcss": "^4.0.6",
"@tailwindcss/vite": "^4.0.6",
"@testing-library/react": "^16.2.0",
"@tsconfig/vite-react": "^3.4.0",
"@types/bun": "^1.2.2",
"@types/node": "^22.13.0",
"@types/node": "^22.13.1",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@vitejs/plugin-react-swc": "3.7.2",
"@vitejs/plugin-react-swc": "3.8.0",
"@workspace/eslint-config": "workspace:*",
"autoprefixer": "^10.4.20",
"elysia": "^1.2.11",
"happy-dom": "^16.8.1",
"postcss": "^8.5.1",
"tailwindcss": "^3.4.17",
"elysia": "^1.2.12",
"happy-dom": "^17.0.4",
"tailwindcss": "^4.0.6",
"vite-tsconfig-paths": "^5.1.4"
}
}
3 changes: 1 addition & 2 deletions apps/web/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
autoprefixer: {}
'@tailwindcss/postcss': {},
}
};

Expand Down
9 changes: 7 additions & 2 deletions apps/web/src/atoms/areImagesReversedAtom.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { atomWithToggle } from '@/utils/atomWithToggle';
import { atomWithToggleAndStorage } from '@/utils/atomWithToggleAndStorage';

export const areImagesReversedAtom = atomWithToggle(false);
export const areImagesReversedAtom = atomWithToggleAndStorage(
'areImagesReversed',
false,
undefined,
{ getOnInit: true }
);
5 changes: 1 addition & 4 deletions apps/web/src/components/AtomSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ import { Label } from '@workspace/ui/components/label';
import { Slider } from '@workspace/ui/components/slider';

export interface AtomSliderProps
extends Omit<
React.ComponentPropsWithoutRef<typeof Slider>,
'value' | 'onValueCHange'
> {
extends Omit<React.ComponentProps<typeof Slider>, 'value' | 'onValueCHange'> {
atom: PrimitiveAtom<number>;
label: React.ReactNode;
LabelProps?: Partial<LabelProps>;
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/ChangeWaveFunction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export function ChangeWaveFunction() {
return (
<ToggleGroup
type="single"
variant="outline"
value={waveFunction}
onValueChange={handleWaveFunctionChange}
disabled={!images.length}
Expand Down
3 changes: 1 addition & 2 deletions apps/web/src/components/ChooseFiles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { CoreFileOptions, fileOpen, FileWithHandle } from 'browser-fs-access';
import { composeEventHandlers } from '@radix-ui/primitive';
import { Button } from '@workspace/ui/components/button';

export interface ChooseFilesProps
extends React.ComponentPropsWithoutRef<typeof Button> {
export interface ChooseFilesProps extends React.ComponentProps<typeof Button> {
onFilesChange?: (files: FileWithHandle[]) => void;
options?: CoreFileOptions;
}
Expand Down
7 changes: 5 additions & 2 deletions apps/web/src/components/CopyImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ import { useAtomValue } from 'jotai';
import { Copy } from 'lucide-react';
import { Trans } from '@lingui/react/macro';
import { cn } from '@workspace/ui/lib/utils';
import { Button, ButtonProps } from '@workspace/ui/components/button';
import { Button } from '@workspace/ui/components/button';
import { Tooltip } from '@workspace/ui/components/tooltip';
import { graphicsAtom } from '@/atoms/graphicsAtom';
import { isClipboardSupported, useClipboard } from '@/hooks/useClipboard';
import { LOADABLE_STATE, MIME_TYPES } from '@/constants';
import { rasterize } from '@/utils/rasterize';
import { LoadableIcon } from './LoadableIcon';

export type CopyImageProps = Omit<ButtonProps, 'onClick' | 'disabled'>;
export type CopyImageProps = Omit<
React.ComponentProps<typeof Button>,
'onClick' | 'disabled'
>;

export function CopyImage(props: CopyImageProps) {
const graphics = useAtomValue(graphicsAtom);
Expand Down
12 changes: 6 additions & 6 deletions apps/web/src/components/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { OptimizeWaveform } from '@/components/OptimizeWaveform';
import { useSetAtom } from 'jotai';
import { graphicsAtom } from '@/atoms/graphicsAtom';

export type EditorProps = React.ComponentPropsWithoutRef<'div'>;
export type EditorProps = React.ComponentProps<'div'>;

export function Editor(props: EditorProps) {
const { className, ...other } = props;
Expand Down Expand Up @@ -59,12 +59,12 @@ export function Editor(props: EditorProps) {

<div className="flex flex-col justify-between gap-8 p-6">
<div className="flex flex-col gap-4">
<div className="bg-primary flex gap-1 rounded-md">
<ReverseImages />
<ImportImages className="grow" />
<RemoveImages />
<div className="flex">
<ReverseImages className="disabled:text-accent/50 rounded-none rounded-l-md shadow-none disabled:opacity-100" />
<ImportImages className="grow rounded-none shadow-none" />
<RemoveImages className="disabled:text-accent/50 rounded-none rounded-r-md shadow-none disabled:opacity-100" />
</div>
<div className="flex flex-row justify-between">
<div className="flex justify-between">
<ChangeWaveFunction />
<OptimizeWaveform />
</div>
Expand Down
18 changes: 6 additions & 12 deletions apps/web/src/components/ExportImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ import {
exportFileHandleAtom
} from '@/atoms/exportAtoms';
import { Button } from '@workspace/ui/components/button';
import { Separator } from '@workspace/ui/components/separator';
import { Tooltip } from '@workspace/ui/components/tooltip';
import { LoadableIcon } from '@/components/LoadableIcon';
import { LOADABLE_STATE } from '@/constants';

export type ExportImageProps = React.ComponentPropsWithoutRef<'div'>;
export type ExportImageProps = React.ComponentProps<'div'>;

export function ExportImage(props: ExportImageProps) {
const { className, ...other } = props;
Expand All @@ -31,16 +30,12 @@ export function ExportImage(props: ExportImageProps) {
};

return (
<div
className={cn(
'bg-secondary text-secondary-foreground flex items-center space-x-1 overflow-hidden rounded-md',
className
)}
{...other}
>
<div className={cn('flex overflow-hidden', className)} {...other}>
<Tooltip title={<Trans>Save to</Trans>}>
<Button
className="min-w-0 grow"
className={cn('min-w-0 grow', {
'justify-start rounded-none rounded-l-md': !!exportFileHandle
})}
variant="secondary"
onClick={createExportClickHandler()}
disabled={disabled}
Expand All @@ -59,11 +54,10 @@ export function ExportImage(props: ExportImageProps) {
</Tooltip>
{exportFileHandle && (
<>
<Separator orientation="vertical" className="h-[20px]" />
<Tooltip title={<Trans>Choose file</Trans>}>
<Button
variant="secondary"
className="shrink-0"
className="shrink-0 rounded-none rounded-r-md"
size="icon"
onClick={createExportClickHandler(null)}
disabled={disabled}
Expand Down
3 changes: 1 addition & 2 deletions apps/web/src/components/Graphics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ declare module 'react' {
}
}

export interface GraphicsProps extends React.ComponentPropsWithoutRef<'svg'> {
export interface GraphicsProps extends React.ComponentProps<'svg'> {
fallback?: React.ReactNode;
ref?: React.Ref<SVGSVGElement>;
}

export function Graphics(props: GraphicsProps) {
Expand Down
5 changes: 2 additions & 3 deletions apps/web/src/components/ImportImages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Camera, Monitor, Smartphone, Tablet } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { Trans, Plural, useLingui } from '@lingui/react/macro';
import { cn } from '@workspace/ui/lib/utils';
import { Button, ButtonProps } from '@workspace/ui/components/button';
import { Button } from '@workspace/ui/components/button';
import {
Dialog,
DialogContent,
Expand Down Expand Up @@ -35,7 +35,7 @@ import { isValidationError } from '@/utils/client';
import { LOADABLE_STATE } from '@/constants';
import { Site } from '@/types';

export type ImportImagesProps = ButtonProps;
export type ImportImagesProps = React.ComponentProps<typeof Button>;

export function ImportImages(props: ImportImagesProps) {
const { t } = useLingui();
Expand Down Expand Up @@ -145,7 +145,6 @@ export function ImportImages(props: ImportImagesProps) {
onValueChange={handleDeviceTypeChange(field.onChange)}
type="single"
variant="outline"
size="sm"
>
<ToggleGroupItem
value="desktop"
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/OptimizeWaveform.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function OptimizeWaveform() {
disabled={!largestImage}
aria-label={t`Optimize`}
>
<WandSparkles className="transition-transform ease-in-out group-active:rotate-45" />
<WandSparkles className="transition-transform ease-in-out group-active:rotate-30" />
</Button>
</Tooltip>
);
Expand Down
5 changes: 4 additions & 1 deletion apps/web/src/components/RemoveImages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import {
import { imagesAtom, unwrappedImagesAtom } from '@/atoms/imagesAtom';
import { waveformAtom } from '@/atoms/waveformAtoms';

export function RemoveImages() {
export type RemoveImagesProps = React.ComponentProps<typeof Button>;

export function RemoveImages(props: RemoveImagesProps) {
const { t } = useLingui();
const [isPending, startTransition] = useTransition();
const images = useAtomValue(unwrappedImagesAtom);
Expand All @@ -39,6 +41,7 @@ export function RemoveImages() {
size="icon"
disabled={!images.length || isPending}
aria-label={t`Remove images`}
{...props}
>
<Trash2 />
</Button>
Expand Down
5 changes: 4 additions & 1 deletion apps/web/src/components/ReverseImages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { Button } from '@workspace/ui/components/button';
import { Tooltip } from '@workspace/ui/components/tooltip';
import { unwrappedImagesAtom } from '@/atoms/imagesAtom';

export function ReverseImages() {
export type ReverseImagesProps = React.ComponentProps<typeof Button>;

export function ReverseImages(props: ReverseImagesProps) {
const { t } = useLingui();
const images = useAtomValue(unwrappedImagesAtom);
const [areImagesReversed, toggleImagesReversed] = useAtom(
Expand All @@ -27,6 +29,7 @@ export function ReverseImages() {
onClick={handleClick}
disabled={images.length < 2}
aria-label={t`Reverse images`}
{...props}
>
{areImagesReversed ? <ArrowDownUp /> : <ArrowUpDown />}
</Button>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/Waveform.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { AtomSlider } from '@/components/AtomSlider';
import { unwrappedImagesAtom } from '@/atoms/imagesAtom';

export type WaveformProps = React.ComponentPropsWithoutRef<'div'>;
export type WaveformProps = React.ComponentProps<'div'>;

export function Waveform(props: WaveformProps) {
const { t } = useLingui();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { describe, it, expect } from 'vitest';
import { getDefaultStore } from 'jotai';
import { atomWithToggle } from '@/utils/atomWithToggle';
import { atomWithToggleAndStorage } from '@/utils/atomWithToggleAndStorage';

describe('atomWithToggle', () => {
describe('atomWithToggleAndStorage', () => {
let store: ReturnType<typeof getDefaultStore>;

beforeEach(() => {
store = getDefaultStore();
});

it('should initialize with the given value', () => {
const atom = atomWithToggle(true);
const atom = atomWithToggleAndStorage('value', true);
expect(store.get(atom)).toBe(true);
});

it('should toggle the value when no argument is provided', () => {
const atom = atomWithToggle(false);
const atom = atomWithToggleAndStorage('value', false);

store.set(atom);
expect(store.get(atom)).toBe(true);
Expand All @@ -25,23 +25,12 @@ describe('atomWithToggle', () => {
});

it('should set the value to the provided argument', () => {
const atom = atomWithToggle(true);
const atom = atomWithToggleAndStorage('value', true);

store.set(atom, false);
expect(store.get(atom)).toBe(false);

store.set(atom, true);
expect(store.get(atom)).toBe(true);
});

it('should toggle the value correctly from the initial undefined state', () => {
const atom = atomWithToggle();
expect(store.get(atom)).toBe(undefined);

store.set(atom);
expect(store.get(atom)).toBe(true);

store.set(atom);
expect(store.get(atom)).toBe(false);
});
});
10 changes: 0 additions & 10 deletions apps/web/src/utils/atomWithToggle.ts

This file was deleted.

17 changes: 17 additions & 0 deletions apps/web/src/utils/atomWithToggleAndStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { WritableAtom, atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';

export function atomWithToggleAndStorage(
...args: Parameters<typeof atomWithStorage<boolean>>
): WritableAtom<boolean, [boolean?], void> {
const anAtom = atomWithStorage(...args);
const derivedAtom = atom(
get => get(anAtom),
(get, set, nextValue?: boolean) => {
const update = nextValue ?? !get(anAtom);
void set(anAtom, update);
}
);

return derivedAtom as WritableAtom<boolean, [boolean?], void>;
}
Loading