Skip to content

Commit

Permalink
Merge pull request #32 from goodeats/31-create-size-design
Browse files Browse the repository at this point in the history
size design
  • Loading branch information
goodeats authored Mar 5, 2024
2 parents 2332b9c + bf29503 commit 8895fe5
Show file tree
Hide file tree
Showing 20 changed files with 590 additions and 8 deletions.
1 change: 1 addition & 0 deletions app/components/ui/icons/name.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type IconName =
| 'file-minus'
| 'file-plus'
| 'file-text'
| 'gear'
| 'github-logo'
| 'height'
| 'instagram-logo'
Expand Down
7 changes: 7 additions & 0 deletions app/components/ui/icons/sprite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions app/models/design.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from '#app/schema/design'
import { prisma } from '#app/utils/db.server'
import { type IPalette } from './palette.server'
import { type ISize } from './size.server'

export interface IDesignWithType {
id: string
Expand All @@ -17,12 +18,17 @@ export interface IDesignWithType {
ownerId: string
artboardId: string | null
palette: IPalette | null
size: ISize | null
}

export interface IDesignWithPalette extends IDesignWithType {
palette: IPalette
}

export interface IDesignWithSize extends IDesignWithType {
size: ISize
}

export const findManyDesignsWithType = async ({
where,
}: {
Expand All @@ -32,6 +38,7 @@ export const findManyDesignsWithType = async ({
where,
include: {
palette: true,
size: true,
},
orderBy: {
type: 'asc',
Expand Down
9 changes: 9 additions & 0 deletions app/models/size.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface ISize {
id: string
format: string
value: number
basis: string
createdAt: Date | string
updatedAt: Date | string
designId: string
}
114 changes: 114 additions & 0 deletions app/routes/sketch+/artboards+/$slug_+/actions/artboard-design-size.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { json } from '@remix-run/node'
import { type IntentActionArgs } from '#app/definitions/intent-action-args'
import { NewArtboardDesignSchema, designSchema } from '#app/schema/design'
import { EditArtboardSizeSchema } from '#app/schema/size'
import {
notSubmissionResponse,
submissionErrorResponse,
} from '#app/utils/conform-utils'
import { prisma } from '#app/utils/db.server'
import { findFirstSizeInstance } from '#app/utils/prisma-extensions-size'
import { parseArtboardDesignSubmission } from './utils'

export async function artboardDesignNewSizeAction({
userId,
formData,
}: IntentActionArgs) {
// validation
const submission = await parseArtboardDesignSubmission({
userId,
formData,
schema: NewArtboardDesignSchema,
})
if (submission.intent !== 'submit') {
return notSubmissionResponse(submission)
}
if (!submission.value) {
return submissionErrorResponse(submission)
}

// changes
const { artboardId } = submission.value

// start transaction so we can create design and size together
// size is 1:1 with design which belongs to an artboard
try {
await prisma.$transaction(async prisma => {
// new designs are appended to the end of the list
// find the last design in the list by type
// we know the artboard already exists for the user by this point
const lastArtboardDesignSize = await prisma.design.findFirst({
where: { type: 'size', artboardId, nextId: null },
})

// create design before size
const designData = designSchema.parse({
type: 'size',
ownerId: userId,
artboardId,
})
const design = await prisma.design.create({
data: designData,
})

// then create size after design
await prisma.size.create({
data: {
designId: design.id,
},
})

// if the artboard already has a size
// link the new size to the last one
// and the last one to the new one
if (lastArtboardDesignSize) {
await prisma.design.update({
where: { id: design.id },
data: { prevId: lastArtboardDesignSize.id },
})

await prisma.design.update({
where: { id: lastArtboardDesignSize.id },
data: { nextId: design.id },
})
}
})
} catch (error) {
console.log(error)
return submissionErrorResponse(submission)
}

return json({ status: 'success', submission } as const)
}

export async function artboardDesignEditSizeAction({
userId,
formData,
}: IntentActionArgs) {
// validation
const submission = await parseArtboardDesignSubmission({
userId,
formData,
schema: EditArtboardSizeSchema,
})

if (submission.intent !== 'submit') {
return notSubmissionResponse(submission)
}
if (!submission.value) {
return submissionErrorResponse(submission)
}

// changes
const { id, value } = submission.value
const size = await findFirstSizeInstance({
where: { id },
})
if (!size) return submissionErrorResponse(submission)

size.value = value
size.updatedAt = new Date()
await size.save()

return json({ status: 'success', submission } as const)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {
Panel,
PanelHeader,
PanelRow,
PanelRowContainer,
PanelRowIconContainer,
PanelRowOrderContainer,
PanelRowValueContainer,
PanelTitle,
} from '#app/components/shared'
import { type IDesignWithSize } from '#app/models/design.server'
import { type PickedArtboardType } from '../queries'
import { PanelFormArtboardDesignDelete } from './panel-form-artboard-design-delete'
import { PanelFormArtboardDesignEditSize } from './panel-form-artboard-design-edit-size'
import { PanelFormArtboardDesignNewSize } from './panel-form-artboard-design-new-size'
import { PanelFormArtboardDesignReorder } from './panel-form-artboard-design-reorder'
import { PanelFormArtboardDesignToggleVisibility } from './panel-form-artboard-design-toggle-visibility'
import { PanelPopoverArtboardDesignSize } from './panel-popover-artboard-design-size'

export const PanelContentArtboardDesignSize = ({
artboard,
designSizes,
}: {
artboard: PickedArtboardType
designSizes: IDesignWithSize[]
}) => {
const orderedDesignSizes = designSizes.reduce(
(acc: IDesignWithSize[], designSize) => {
if (!designSize.prevId) {
acc.unshift(designSize) // Add the head of the list to the start
} else {
let currentDesignIndex = acc.findIndex(d => d.id === designSize.prevId)
if (currentDesignIndex !== -1) {
// Insert the designSize right after its predecessor
acc.splice(currentDesignIndex + 1, 0, designSize)
} else {
// If predecessor is not found, add it to the end as a fallback
acc.push(designSize)
}
}
return acc
},
[],
)

const designCount = designSizes.length
return (
<Panel>
<PanelHeader>
<PanelTitle>Size</PanelTitle>
<div className="flex flex-shrink">
<PanelFormArtboardDesignNewSize artboardId={artboard.id} />
</div>
</PanelHeader>
{orderedDesignSizes.map((designSize, index) => {
const { id, visible, size } = designSize
return (
<PanelRow key={size.id}>
<PanelRowOrderContainer>
<PanelFormArtboardDesignReorder
id={id}
artboardId={artboard.id}
panelCount={designCount}
panelIndex={index}
direction="up"
/>
<PanelFormArtboardDesignReorder
id={id}
artboardId={artboard.id}
panelCount={designCount}
panelIndex={index}
direction="down"
/>
</PanelRowOrderContainer>
<PanelRowContainer>
<PanelRowValueContainer>
<PanelPopoverArtboardDesignSize size={size} />
<PanelFormArtboardDesignEditSize
artboardId={artboard.id}
size={size}
/>
</PanelRowValueContainer>
<PanelRowIconContainer>
<PanelFormArtboardDesignToggleVisibility
id={id}
artboardId={artboard.id}
visible={visible}
/>
<PanelFormArtboardDesignDelete
id={id}
artboardId={artboard.id}
/>
</PanelRowIconContainer>
</PanelRowContainer>
</PanelRow>
)
})}
</Panel>
)
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import {
type IDesignWithType,
type IDesignWithPalette,
type IDesignWithSize,
} from '#app/models/design.server'
import { type IPalette } from '#app/models/palette.server'
import { type ISize } from '#app/models/size.server'
import { type PickedArtboardType } from '../queries'
import { PanelContentArtboardBackgroundColor } from './panel-content-artboard-background-color'
import { PanelContentArtboardDesignPalette } from './panel-content-artboard-design-palette'
import { PanelContentArtboardDesignSize } from './panel-content-artboard-design-size'
import { PanelContentArtboardFrame } from './panel-content-artboard-frame'

export const PanelContentArtboard = ({
Expand All @@ -19,6 +22,10 @@ export const PanelContentArtboard = ({
.filter(design => design.type === 'palette' && design.palette !== null)
.map(design => ({ ...design, palette: design.palette as IPalette }))

const designSizes: IDesignWithSize[] = artboardDesigns
.filter(design => design.type === 'size' && design.size !== null)
.map(design => ({ ...design, size: design.size as ISize }))

return (
<div>
<PanelContentArtboardFrame artboard={artboard} />
Expand All @@ -27,6 +34,10 @@ export const PanelContentArtboard = ({
artboard={artboard}
designPalettes={designPalettes}
/>
<PanelContentArtboardDesignSize
artboard={artboard}
designSizes={designSizes}
/>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { conform, useForm } from '@conform-to/react'
import { getFieldsetConstraint } from '@conform-to/zod'
import { type Artboard } from '@prisma/client'
import { useActionData, useFetcher } from '@remix-run/react'
import { type FocusEvent } from 'react'
import { AuthenticityTokenInput } from 'remix-utils/csrf/react'
import { Input } from '#app/components/ui/input'
import { type ISize } from '#app/models/size.server'
import { EditArtboardPaletteSchema } from '#app/schema/palette'
import { useIsPending } from '#app/utils/misc'
import { INTENT } from '../intent'
import { type action } from '../route'

export const PanelFormArtboardDesignEditSize = ({
artboardId,
size,
}: {
artboardId: Artboard['id']
size: ISize
}) => {
const fetcher = useFetcher<typeof action>()
const actionData = useActionData<typeof action>()
const isPending = useIsPending()

const [form, fields] = useForm({
id: `panel-form-artboard-design-edit-size-${size.id}`,
constraint: getFieldsetConstraint(EditArtboardPaletteSchema),
lastSubmission: actionData?.submission,
defaultValue: {
...size,
},
})

const handleSubmit = (event: FocusEvent<HTMLInputElement>) => {
fetcher.submit(event.currentTarget.form, {
method: 'POST',
})
}

return (
<fetcher.Form method="POST" {...form.props}>
<AuthenticityTokenInput />

<input type="hidden" name="id" value={size.id} />
<input type="hidden" name="artboardId" value={artboardId} />
<input type="hidden" name="designId" value={size.designId} />
<input
type="hidden"
name="intent"
value={INTENT.artboardUpdateDesignSize}
/>
<Input
type="number"
className={'flex h-8'}
onBlur={e => handleSubmit(e)}
disabled={isPending}
{...conform.input(fields.value, {
ariaAttributes: true,
})}
/>
</fetcher.Form>
)
}
Loading

0 comments on commit 8895fe5

Please sign in to comment.