From 20e794ef451695b71e9fc8d4ddd0cb35a21a3f30 Mon Sep 17 00:00:00 2001 From: Pat Needham Date: Tue, 23 Apr 2024 14:01:49 -0400 Subject: [PATCH] panel artboard layers crud with entity fetcher strategies and actions --- .../panel/dashboard-entity-panel.actions.tsx | 9 +- .../panel/dashboard-entity-panel.header.tsx | 9 +- .../panel/dashboard-entity-panel.reorder.tsx | 83 +++++----- .../panel/dashboard-entity-panel.row.tsx | 4 +- .../panel/dashboard-entity-panel.tsx | 22 ++- app/models/layer/layer.create.server.ts | 18 +++ app/models/layer/layer.delete.server.ts | 31 ++++ app/models/layer/layer.get.server.ts | 13 +- app/models/layer/layer.update.server.ts | 144 ++++++++++++++++++ .../api.v1+/artboard-version.layer.create.tsx | 51 +++++++ .../api.v1+/artboard-version.layer.delete.tsx | 51 +++++++ .../artboard-version.layer.update.order.tsx | 59 +++++++ .../artboard-version.layer.update.visible.tsx | 51 +++++++ .../api.v1+/layer.update.description.tsx | 52 +++++++ .../resources+/api.v1+/layer.update.name.tsx | 52 +++++++ ...sidebars.panel.artboard-version.layers.tsx | 47 ++++++ .../$artboardSlug+/__components/sidebars.tsx | 4 +- app/schema/entity.ts | 34 ++++- app/schema/zod-helpers.ts | 2 +- .../artboard/version/layer/delete.service.ts | 41 +++-- .../version/layer/move-down.service.ts | 25 +-- .../artboard/version/layer/move-up.service.ts | 25 +-- .../version/layer/toggle-visible.service.ts | 23 +-- .../dashboard-panel/create-entity.strategy.ts | 24 ++- .../dashboard-panel/delete-entity.strategy.ts | 24 ++- .../update-entity-move.strategy.ts | 24 ++- .../update-entity-visible.strategy.ts | 25 ++- .../update-entity-values.layer.ts | 88 +++++++++++ .../update-entity/update-entity-values.ts | 4 +- .../validate-submission.strategy.ts | 21 +++ app/utils/routes.utils.ts | 82 ++++++++++ 31 files changed, 1012 insertions(+), 130 deletions(-) create mode 100644 app/models/layer/layer.delete.server.ts create mode 100644 app/models/layer/layer.update.server.ts create mode 100644 app/routes/resources+/api.v1+/artboard-version.layer.create.tsx create mode 100644 app/routes/resources+/api.v1+/artboard-version.layer.delete.tsx create mode 100644 app/routes/resources+/api.v1+/artboard-version.layer.update.order.tsx create mode 100644 app/routes/resources+/api.v1+/artboard-version.layer.update.visible.tsx create mode 100644 app/routes/resources+/api.v1+/layer.update.description.tsx create mode 100644 app/routes/resources+/api.v1+/layer.update.name.tsx create mode 100644 app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.panel.artboard-version.layers.tsx create mode 100644 app/strategies/component/dashboard-panel/update-entity/update-entity-values.layer.ts diff --git a/app/components/templates/panel/dashboard-entity-panel.actions.tsx b/app/components/templates/panel/dashboard-entity-panel.actions.tsx index edeffde0..476697bb 100644 --- a/app/components/templates/panel/dashboard-entity-panel.actions.tsx +++ b/app/components/templates/panel/dashboard-entity-panel.actions.tsx @@ -1,5 +1,8 @@ -import { type designParentTypeIdEnum } from '#app/schema/design' -import { type IEntityVisible, type IEntityParentType } from '#app/schema/entity' +import { + type IEntityVisible, + type IEntityParentType, + type entityParentIdTypeEnum, +} from '#app/schema/entity' import { type IDashboardPanelDeleteEntityStrategy } from '#app/strategies/component/dashboard-panel/delete-entity.strategy' import { type IDashboardPanelUpdateEntityVisibleStrategy } from '#app/strategies/component/dashboard-panel/update-entity-visible.strategy' import { SidebarPanelRowActionsContainer } from '..' @@ -13,7 +16,7 @@ export const PanelEntityRowActions = ({ strategyEntityDelete, }: { entity: IEntityVisible - parentTypeId: designParentTypeIdEnum + parentTypeId: entityParentIdTypeEnum parent: IEntityParentType strategyToggleVisible: IDashboardPanelUpdateEntityVisibleStrategy strategyEntityDelete: IDashboardPanelDeleteEntityStrategy diff --git a/app/components/templates/panel/dashboard-entity-panel.header.tsx b/app/components/templates/panel/dashboard-entity-panel.header.tsx index 4f342d8d..a3ade619 100644 --- a/app/components/templates/panel/dashboard-entity-panel.header.tsx +++ b/app/components/templates/panel/dashboard-entity-panel.header.tsx @@ -1,5 +1,8 @@ -import { type designParentTypeIdEnum } from '#app/schema/design' -import { type IEntityParentType, type IEntityType } from '#app/schema/entity' +import { + type entityParentIdTypeEnum, + type IEntityParentType, + type IEntityType, +} from '#app/schema/entity' import { type IDashboardPanelCreateEntityStrategy } from '#app/strategies/component/dashboard-panel/create-entity.strategy' import { capitalize } from '#app/utils/string-formatting' import { SidebarPanelHeader, SidebarPanelRowActionsContainer } from '..' @@ -12,7 +15,7 @@ export const PanelEntityHeader = ({ strategyEntityNew, }: { type: IEntityType - parentTypeId: designParentTypeIdEnum + parentTypeId: entityParentIdTypeEnum parent: IEntityParentType strategyEntityNew: IDashboardPanelCreateEntityStrategy }) => { diff --git a/app/components/templates/panel/dashboard-entity-panel.reorder.tsx b/app/components/templates/panel/dashboard-entity-panel.reorder.tsx index 711b4e46..1ac616ba 100644 --- a/app/components/templates/panel/dashboard-entity-panel.reorder.tsx +++ b/app/components/templates/panel/dashboard-entity-panel.reorder.tsx @@ -1,49 +1,52 @@ -import { type designParentTypeIdEnum } from '#app/schema/design' -import { type IEntity, type IEntityParentType } from '#app/schema/entity' +import { + type entityParentIdTypeEnum, + type IEntity, + type IEntityParentType, +} from '#app/schema/entity' import { type IDashboardPanelUpdateEntityOrderStrategy } from '#app/strategies/component/dashboard-panel/update-entity-move.strategy' import { SidebarPanelRowReorderContainer } from '..' import { FormFetcherMoveIcon } from '../form/fetcher/move-icon' export const PanelEntityRowReorder = ({ - entity, - parentTypeId, - parent, - entityCount, - entityIndex, - strategyReorder, + entity, + parentTypeId, + parent, + entityCount, + entityIndex, + strategyReorder, }: { - entity: IEntity - parentTypeId: designParentTypeIdEnum - parent: IEntityParentType - entityCount: number - entityIndex: number - strategyReorder: IDashboardPanelUpdateEntityOrderStrategy + entity: IEntity + parentTypeId: entityParentIdTypeEnum + parent: IEntityParentType + entityCount: number + entityIndex: number + strategyReorder: IDashboardPanelUpdateEntityOrderStrategy }) => { - const atTop = entityIndex === 0 - const atBottom = entityIndex === entityCount - 1 + const atTop = entityIndex === 0 + const atBottom = entityIndex === entityCount - 1 - return ( - - - - - ) + return ( + + + + + ) } diff --git a/app/components/templates/panel/dashboard-entity-panel.row.tsx b/app/components/templates/panel/dashboard-entity-panel.row.tsx index 3b750a3a..0e3ebba9 100644 --- a/app/components/templates/panel/dashboard-entity-panel.row.tsx +++ b/app/components/templates/panel/dashboard-entity-panel.row.tsx @@ -1,8 +1,8 @@ -import { type designParentTypeIdEnum } from '#app/schema/design' import { type IEntityVisible, type IEntity, type IEntityParentType, + type entityParentIdTypeEnum, } from '#app/schema/entity' import { type IDashboardPanelDeleteEntityStrategy } from '#app/strategies/component/dashboard-panel/delete-entity.strategy' import { type IDashboardPanelUpdateEntityOrderStrategy } from '#app/strategies/component/dashboard-panel/update-entity-move.strategy' @@ -23,7 +23,7 @@ export const PanelEntityRow = ({ children, }: { entity: IEntity - parentTypeId: designParentTypeIdEnum + parentTypeId: entityParentIdTypeEnum parent: IEntityParentType entityCount: number entityIndex: number diff --git a/app/components/templates/panel/dashboard-entity-panel.tsx b/app/components/templates/panel/dashboard-entity-panel.tsx index 221b4fb6..411d9c0a 100644 --- a/app/components/templates/panel/dashboard-entity-panel.tsx +++ b/app/components/templates/panel/dashboard-entity-panel.tsx @@ -1,9 +1,9 @@ -import { type IArtboardVersionWithDesignsAndLayers } from '#app/models/artboard-version/artboard-version.server' -import { type IDesignWithType } from '#app/models/design.server' import { - type designParentTypeIdEnum, - type designTypeEnum, -} from '#app/schema/design' + type IEntity, + type IEntityParentType, + type IEntityType, + type entityParentIdTypeEnum, +} from '#app/schema/entity' import { type IDashboardPanelCreateEntityStrategy } from '#app/strategies/component/dashboard-panel/create-entity.strategy' import { type IDashboardPanelDeleteEntityStrategy } from '#app/strategies/component/dashboard-panel/delete-entity.strategy' import { type IDashboardPanelUpdateEntityValuesStrategy } from '#app/strategies/component/dashboard-panel/update-entity/update-entity-values' @@ -14,10 +14,6 @@ import { PanelEntityHeader } from './dashboard-entity-panel.header' import { PanelEntityRow } from './dashboard-entity-panel.row' import { PanelEntityValues } from './dashboard-entity-panel.values' -type PanelEntities = IDesignWithType[] -type PanelEntityType = designTypeEnum -type PanelEntityParent = IArtboardVersionWithDesignsAndLayers - export const DashboardEntityPanel = ({ type, parentTypeId, @@ -29,10 +25,10 @@ export const DashboardEntityPanel = ({ strategyToggleVisible, strategyEntityDelete, }: { - type: PanelEntityType - parentTypeId: designParentTypeIdEnum - parent: PanelEntityParent - entities: PanelEntities + type: IEntityType + parentTypeId: entityParentIdTypeEnum + parent: IEntityParentType + entities: IEntity[] strategyEntityNew: IDashboardPanelCreateEntityStrategy strategyReorder: IDashboardPanelUpdateEntityOrderStrategy strategyEntityValues: IDashboardPanelUpdateEntityValuesStrategy diff --git a/app/models/layer/layer.create.server.ts b/app/models/layer/layer.create.server.ts index 43ecc57e..603916ee 100644 --- a/app/models/layer/layer.create.server.ts +++ b/app/models/layer/layer.create.server.ts @@ -1,3 +1,7 @@ +import { type IntentActionArgs } from '#app/definitions/intent-action-args' +import { NewArtboardVersionLayerSchema } from '#app/schema/layer-artboard-version' +import { ValidateArtboardVersionParentSubmissionStrategy } from '#app/strategies/validate-submission.strategy' +import { validateEntitySubmission } from '#app/utils/conform-utils' import { prisma } from '#app/utils/db.server' import { type ILayer } from '../layer.server' @@ -7,6 +11,20 @@ export interface ILayerCreatedResponse { createdLayer?: ILayer } +export const validateArtboardVersionNewLayerSubmission = async ({ + userId, + formData, +}: IntentActionArgs) => { + const strategy = new ValidateArtboardVersionParentSubmissionStrategy() + + return await validateEntitySubmission({ + userId, + formData, + schema: NewArtboardVersionLayerSchema, + strategy, + }) +} + export const createLayer = async ({ data, }: { diff --git a/app/models/layer/layer.delete.server.ts b/app/models/layer/layer.delete.server.ts new file mode 100644 index 00000000..85270177 --- /dev/null +++ b/app/models/layer/layer.delete.server.ts @@ -0,0 +1,31 @@ +import { type IntentActionArgs } from '#app/definitions/intent-action-args' +import { DeleteArtboardVersionLayerSchema } from '#app/schema/layer-artboard-version' +import { ValidateArtboardVersionParentSubmissionStrategy } from '#app/strategies/validate-submission.strategy' +import { validateEntitySubmission } from '#app/utils/conform-utils' +import { prisma } from '#app/utils/db.server' +import { type ILayer } from '../layer.server' + +export interface ILayerDeletedResponse { + success: boolean + message?: string +} + +export const validateArtboardVersionDeleteLayerSubmission = async ({ + userId, + formData, +}: IntentActionArgs) => { + const strategy = new ValidateArtboardVersionParentSubmissionStrategy() + + return await validateEntitySubmission({ + userId, + formData, + schema: DeleteArtboardVersionLayerSchema, + strategy, + }) +} + +export const deleteLayer = ({ id }: { id: ILayer['id'] }) => { + return prisma.layer.delete({ + where: { id }, + }) +} diff --git a/app/models/layer/layer.get.server.ts b/app/models/layer/layer.get.server.ts index 2708f795..a25e616a 100644 --- a/app/models/layer/layer.get.server.ts +++ b/app/models/layer/layer.get.server.ts @@ -1,6 +1,6 @@ import { type whereArgsType } from '#app/schema/layer' import { prisma } from '#app/utils/db.server' -import { type ILayerWithDesigns } from '../layer.server' +import { type ILayer, type ILayerWithDesigns } from '../layer.server' export const getLayersWithDesigns = async ({ where, @@ -26,3 +26,14 @@ export const getLayersWithDesigns = async ({ }) return layers } + +export const getLayer = async ({ + where, +}: { + where: whereArgsType +}): Promise => { + const layer = await prisma.layer.findFirst({ + where, + }) + return layer +} diff --git a/app/models/layer/layer.update.server.ts b/app/models/layer/layer.update.server.ts new file mode 100644 index 00000000..057564a9 --- /dev/null +++ b/app/models/layer/layer.update.server.ts @@ -0,0 +1,144 @@ +import { type IntentActionArgs } from '#app/definitions/intent-action-args' +import { + EditLayerDescriptionSchema, + EditLayerNameSchema, +} from '#app/schema/layer' +import { + ReorderArtboardVersionLayerSchema, + ToggleVisibleArtboardVersionLayerSchema, +} from '#app/schema/layer-artboard-version' +import { + ValidateArtboardVersionParentSubmissionStrategy, + ValidateLayerSubmissionStrategy, +} from '#app/strategies/validate-submission.strategy' +import { validateEntitySubmission } from '#app/utils/conform-utils' +import { findFirstLayerInstance } from '#app/utils/prisma-extensions-layer' +import { type ILayer } from '../layer.server' + +export interface ILayerUpdatedResponse { + success: boolean + message?: string + updatedLayer?: ILayer +} + +export const validateLayerNameSubmission = async ({ + userId, + formData, +}: IntentActionArgs) => { + const strategy = new ValidateLayerSubmissionStrategy() + + return await validateEntitySubmission({ + userId, + formData, + schema: EditLayerNameSchema, + strategy, + }) +} + +export const validateLayerDescriptionSubmission = async ({ + userId, + formData, +}: IntentActionArgs) => { + const strategy = new ValidateLayerSubmissionStrategy() + + return await validateEntitySubmission({ + userId, + formData, + schema: EditLayerDescriptionSchema, + strategy, + }) +} + +export const validateArtboardVersionToggleVisibeLayerSubmission = async ({ + userId, + formData, +}: IntentActionArgs) => { + const strategy = new ValidateArtboardVersionParentSubmissionStrategy() + + return await validateEntitySubmission({ + userId, + formData, + schema: ToggleVisibleArtboardVersionLayerSchema, + strategy, + }) +} + +export const validateArtboardVersionReorderLayerSubmission = async ({ + userId, + formData, +}: IntentActionArgs) => { + const strategy = new ValidateArtboardVersionParentSubmissionStrategy() + + return await validateEntitySubmission({ + userId, + formData, + schema: ReorderArtboardVersionLayerSchema, + strategy, + }) +} + +const getLayerInstance = async ({ id }: { id: ILayer['id'] }) => { + return await findFirstLayerInstance({ + where: { id }, + }) +} + +// updating instance instead of regular prism update +// this may not be easier, but it's more explicit +export const updateLayerName = async ({ + id, + name, +}: { + id: ILayer['id'] + name: number +}): Promise => { + const layer = await getLayerInstance({ id }) + if (!layer) return { success: false } + + try { + const data = EditLayerNameSchema.parse({ id, name }) + layer.name = data.name + layer.updatedAt = new Date() + await layer.save() + + return { success: true, updatedLayer: layer } + } catch (error) { + // consider how to handle this error where this is called + console.log('updateLayerName error:', error) + const errorType = error instanceof Error + const errorMessage = errorType ? error.message : 'An unknown error occurred' + return { + success: false, + message: errorMessage, + } + } +} + +export const updateLayerDescription = async ({ + id, + description, +}: { + id: ILayer['id'] + description: number +}): Promise => { + const layer = await getLayerInstance({ id }) + if (!layer) return { success: false } + + try { + const data = EditLayerDescriptionSchema.parse({ id, description }) + layer.description = data.description ?? '' + layer.updatedAt = new Date() + await layer.save() + + return { success: true, updatedLayer: layer } + } catch (error) { + // consider how to handle this error where this is called + console.log('updateLayerDescription error:', error) + const errorType = error instanceof Error + const errorMessage = errorType ? error.message : 'An unknown error occurred' + return { + success: false, + message: errorMessage, + } + } +} diff --git a/app/routes/resources+/api.v1+/artboard-version.layer.create.tsx b/app/routes/resources+/api.v1+/artboard-version.layer.create.tsx new file mode 100644 index 00000000..3255c8e2 --- /dev/null +++ b/app/routes/resources+/api.v1+/artboard-version.layer.create.tsx @@ -0,0 +1,51 @@ +import { + type LoaderFunctionArgs, + json, + type DataFunctionArgs, +} from '@remix-run/node' +import { redirectBack } from 'remix-utils/redirect-back' +import { validateArtboardVersionNewLayerSubmission } from '#app/models/layer/layer.create.server' +import { validateNoJS } from '#app/schema/form-data' +import { artboardVersionLayerCreateService } from '#app/services/artboard/version/layer/create.service' +import { requireUserId } from '#app/utils/auth.server' + +// https://www.epicweb.dev/full-stack-components + +export async function loader({ request }: LoaderFunctionArgs) { + await requireUserId(request) + return json({}) +} + +export async function action({ request }: DataFunctionArgs) { + const userId = await requireUserId(request) + const formData = await request.formData() + const noJS = validateNoJS({ formData }) + + let createSuccess = false + const { status, submission } = + await validateArtboardVersionNewLayerSubmission({ + userId, + formData, + }) + + if (status === 'success') { + const { success } = await artboardVersionLayerCreateService({ + userId, + ...submission.value, + }) + createSuccess = success + } + + if (noJS) { + throw redirectBack(request, { + fallback: '/', + }) + } + + return json( + { status, submission }, + { + status: status === 'error' || !createSuccess ? 422 : 201, + }, + ) +} diff --git a/app/routes/resources+/api.v1+/artboard-version.layer.delete.tsx b/app/routes/resources+/api.v1+/artboard-version.layer.delete.tsx new file mode 100644 index 00000000..2980fc28 --- /dev/null +++ b/app/routes/resources+/api.v1+/artboard-version.layer.delete.tsx @@ -0,0 +1,51 @@ +import { + type LoaderFunctionArgs, + json, + type DataFunctionArgs, +} from '@remix-run/node' +import { redirectBack } from 'remix-utils/redirect-back' +import { validateArtboardVersionDeleteLayerSubmission } from '#app/models/layer/layer.delete.server' +import { validateNoJS } from '#app/schema/form-data' +import { artboardVersionDesignDeleteService } from '#app/services/artboard/version/design/delete.service' +import { requireUserId } from '#app/utils/auth.server' + +// https://www.epicweb.dev/full-stack-components + +export async function loader({ request }: LoaderFunctionArgs) { + await requireUserId(request) + return json({}) +} + +export async function action({ request }: DataFunctionArgs) { + const userId = await requireUserId(request) + const formData = await request.formData() + const noJS = validateNoJS({ formData }) + + let createSuccess = false + const { status, submission } = + await validateArtboardVersionDeleteLayerSubmission({ + userId, + formData, + }) + + if (status === 'success') { + const { success } = await artboardVersionDesignDeleteService({ + userId, + ...submission.value, + }) + createSuccess = success + } + + if (noJS) { + throw redirectBack(request, { + fallback: '/', + }) + } + + return json( + { status, submission }, + { + status: status === 'error' || !createSuccess ? 422 : 201, + }, + ) +} diff --git a/app/routes/resources+/api.v1+/artboard-version.layer.update.order.tsx b/app/routes/resources+/api.v1+/artboard-version.layer.update.order.tsx new file mode 100644 index 00000000..548a8b0c --- /dev/null +++ b/app/routes/resources+/api.v1+/artboard-version.layer.update.order.tsx @@ -0,0 +1,59 @@ +import { + type LoaderFunctionArgs, + json, + type DataFunctionArgs, +} from '@remix-run/node' +import { redirectBack } from 'remix-utils/redirect-back' +import { validateArtboardVersionReorderLayerSubmission } from '#app/models/layer/layer.update.server' +import { validateNoJS } from '#app/schema/form-data' +import { artboardVersionLayerMoveDownService } from '#app/services/artboard/version/layer/move-down.service' +import { artboardVersionLayerMoveUpService } from '#app/services/artboard/version/layer/move-up.service' +import { requireUserId } from '#app/utils/auth.server' + +// https://www.epicweb.dev/full-stack-components + +export async function loader({ request }: LoaderFunctionArgs) { + await requireUserId(request) + return json({}) +} + +export async function action({ request }: DataFunctionArgs) { + const userId = await requireUserId(request) + const formData = await request.formData() + const noJS = validateNoJS({ formData }) + + let updateSuccess = false + const { status, submission } = + await validateArtboardVersionReorderLayerSubmission({ + userId, + formData, + }) + + if (status === 'success') { + const { direction } = submission.value + const { success } = + direction === 'up' + ? await artboardVersionLayerMoveUpService({ + userId, + ...submission.value, + }) + : await artboardVersionLayerMoveDownService({ + userId, + ...submission.value, + }) + updateSuccess = success + } + + if (noJS) { + throw redirectBack(request, { + fallback: '/', + }) + } + + return json( + { status, submission }, + { + status: status === 'error' || !updateSuccess ? 404 : 200, + }, + ) +} diff --git a/app/routes/resources+/api.v1+/artboard-version.layer.update.visible.tsx b/app/routes/resources+/api.v1+/artboard-version.layer.update.visible.tsx new file mode 100644 index 00000000..8301976d --- /dev/null +++ b/app/routes/resources+/api.v1+/artboard-version.layer.update.visible.tsx @@ -0,0 +1,51 @@ +import { + type LoaderFunctionArgs, + json, + type DataFunctionArgs, +} from '@remix-run/node' +import { redirectBack } from 'remix-utils/redirect-back' +import { validateArtboardVersionToggleVisibeLayerSubmission } from '#app/models/layer/layer.update.server' +import { validateNoJS } from '#app/schema/form-data' +import { artboardVersionLayerToggleVisibleService } from '#app/services/artboard/version/layer/toggle-visible.service' +import { requireUserId } from '#app/utils/auth.server' + +// https://www.epicweb.dev/full-stack-components + +export async function loader({ request }: LoaderFunctionArgs) { + await requireUserId(request) + return json({}) +} + +export async function action({ request }: DataFunctionArgs) { + const userId = await requireUserId(request) + const formData = await request.formData() + const noJS = validateNoJS({ formData }) + + let updateSuccess = false + const { status, submission } = + await validateArtboardVersionToggleVisibeLayerSubmission({ + userId, + formData, + }) + + if (status === 'success') { + const { success } = await artboardVersionLayerToggleVisibleService({ + userId, + ...submission.value, + }) + updateSuccess = success + } + + if (noJS) { + throw redirectBack(request, { + fallback: '/', + }) + } + + return json( + { status, submission }, + { + status: status === 'error' || !updateSuccess ? 404 : 200, + }, + ) +} diff --git a/app/routes/resources+/api.v1+/layer.update.description.tsx b/app/routes/resources+/api.v1+/layer.update.description.tsx new file mode 100644 index 00000000..64a92238 --- /dev/null +++ b/app/routes/resources+/api.v1+/layer.update.description.tsx @@ -0,0 +1,52 @@ +import { + type LoaderFunctionArgs, + json, + type DataFunctionArgs, +} from '@remix-run/node' +import { redirectBack } from 'remix-utils/redirect-back' +import { + updateLayerDescription, + validateLayerDescriptionSubmission, +} from '#app/models/layer/layer.update.server' +import { validateNoJS } from '#app/schema/form-data' +import { requireUserId } from '#app/utils/auth.server' + +// https://www.epicweb.dev/full-stack-components + +export async function loader({ request }: LoaderFunctionArgs) { + await requireUserId(request) + return json({}) +} + +export async function action({ request }: DataFunctionArgs) { + const userId = await requireUserId(request) + const formData = await request.formData() + const noJS = validateNoJS({ formData }) + + let updateSuccess = false + const { status, submission } = await validateLayerDescriptionSubmission({ + userId, + formData, + }) + + if (status === 'success') { + const { success } = await updateLayerDescription({ + userId, + ...submission.value, + }) + updateSuccess = success + } + + if (noJS) { + throw redirectBack(request, { + fallback: '/', + }) + } + + return json( + { status, submission }, + { + status: status === 'error' || !updateSuccess ? 404 : 200, + }, + ) +} diff --git a/app/routes/resources+/api.v1+/layer.update.name.tsx b/app/routes/resources+/api.v1+/layer.update.name.tsx new file mode 100644 index 00000000..01823335 --- /dev/null +++ b/app/routes/resources+/api.v1+/layer.update.name.tsx @@ -0,0 +1,52 @@ +import { + type LoaderFunctionArgs, + json, + type DataFunctionArgs, +} from '@remix-run/node' +import { redirectBack } from 'remix-utils/redirect-back' +import { + updateLayerName, + validateLayerNameSubmission, +} from '#app/models/layer/layer.update.server' +import { validateNoJS } from '#app/schema/form-data' +import { requireUserId } from '#app/utils/auth.server' + +// https://www.epicweb.dev/full-stack-components + +export async function loader({ request }: LoaderFunctionArgs) { + await requireUserId(request) + return json({}) +} + +export async function action({ request }: DataFunctionArgs) { + const userId = await requireUserId(request) + const formData = await request.formData() + const noJS = validateNoJS({ formData }) + + let updateSuccess = false + const { status, submission } = await validateLayerNameSubmission({ + userId, + formData, + }) + + if (status === 'success') { + const { success } = await updateLayerName({ + userId, + ...submission.value, + }) + updateSuccess = success + } + + if (noJS) { + throw redirectBack(request, { + fallback: '/', + }) + } + + return json( + { status, submission }, + { + status: status === 'error' || !updateSuccess ? 404 : 200, + }, + ) +} diff --git a/app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.panel.artboard-version.layers.tsx b/app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.panel.artboard-version.layers.tsx new file mode 100644 index 00000000..3cab1666 --- /dev/null +++ b/app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.panel.artboard-version.layers.tsx @@ -0,0 +1,47 @@ +import { DashboardEntityPanel } from '#app/components/templates/panel/dashboard-entity-panel' +import { type IArtboardVersionWithDesignsAndLayers } from '#app/models/artboard-version/artboard-version.server' +import { DesignParentTypeIdEnum } from '#app/schema/design' +import { DashboardPanelCreateArtboardVersionLayerStrategy } from '#app/strategies/component/dashboard-panel/create-entity.strategy' +import { DashboardPanelDeleteArtboardVersionLayerStrategy } from '#app/strategies/component/dashboard-panel/delete-entity.strategy' +import { DashboardPanelUpdateLayerValuesStrategy } from '#app/strategies/component/dashboard-panel/update-entity/update-entity-values.layer' +import { DashboardPanelUpdateArtboardVersionLayerTypeOrderStrategy } from '#app/strategies/component/dashboard-panel/update-entity-move.strategy' +import { DashboardPanelUpdateArtboardVersionLayerTypeVisibleStrategy } from '#app/strategies/component/dashboard-panel/update-entity-visible.strategy' + +export const PanelArtboardVersionLayers = ({ + version, +}: { + version: IArtboardVersionWithDesignsAndLayers +}) => { + // const orderedDesigns = filterAndOrderDesignsByType({ + // designs: version.designs, + // }) + // const designTypePanels = designsByTypeToPanelArray({ + // designs: orderedDesigns, + // }) + + const strategyEntityNew = + new DashboardPanelCreateArtboardVersionLayerStrategy() + const strategyReorder = + new DashboardPanelUpdateArtboardVersionLayerTypeOrderStrategy() + const strategyToggleVisible = + new DashboardPanelUpdateArtboardVersionLayerTypeVisibleStrategy() + const strategyEntityDelete = + new DashboardPanelDeleteArtboardVersionLayerStrategy() + const strategyEntityValues = new DashboardPanelUpdateLayerValuesStrategy() + + return ( +
+ +
+ ) +} diff --git a/app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.tsx b/app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.tsx index 49883746..49e06dbf 100644 --- a/app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.tsx +++ b/app/routes/sketch+/projects+/$projectSlug_+/artboards+/$artboardSlug+/__components/sidebars.tsx @@ -2,6 +2,7 @@ import { Sidebar } from '#app/components/layout' import { SidebarTabs, SidebarTabsContent } from '#app/components/templates' import { type IArtboardVersionWithDesignsAndLayers } from '#app/models/artboard-version/artboard-version.server' import { PanelArtboardVersion } from './sidebars.panel.artboard-version' +import { PanelArtboardVersionLayers } from './sidebars.panel.artboard-version.layers' export const SidebarLeft = ({ version, @@ -12,8 +13,7 @@ export const SidebarLeft = ({ - artboard layrs - {/* */} + Add assets like images here diff --git a/app/schema/entity.ts b/app/schema/entity.ts index 3be660f1..740662c4 100644 --- a/app/schema/entity.ts +++ b/app/schema/entity.ts @@ -10,9 +10,22 @@ import { type ISize } from '#app/models/size.server' import { type IStroke } from '#app/models/stroke.server' import { type ITemplate } from '#app/models/template.server' import { type ObjectValues } from '#app/utils/typescript-helpers' -import { type designTypeEnum } from './design' +import { + type ReorderDesignSchemaType, + type NewDesignSchemaType, + type designTypeEnum, + type ToggleVisibleDesignSchemaType, + type DeleteDesignSchemaType, +} from './design' +import { + type ReorderLayerSchemaType, + type NewLayerSchemaType, + type ToggleVisibleLayerSchemaType, + type DeleteLayerSchemaType, +} from './layer' export type IEntity = + | ILayer | IDesign | IDesignWithType | IArtboardVersion @@ -28,6 +41,7 @@ export type IEntity = export type IEntityVisible = IDesign | IDesignWithType | ILayer export type IEntityId = + | ILayer['id'] | IDesign['id'] | IDesignWithType['id'] | IArtboardVersion['id'] @@ -40,7 +54,7 @@ export type IEntityId = | ILayout['id'] | ITemplate['id'] -export type IEntityType = designTypeEnum +export type IEntityType = designTypeEnum | 'layer' export type IEntityParentType = IDesignWithType | IArtboardVersion @@ -57,10 +71,12 @@ export type entityParentIdTypeEnum = ObjectValues export const EntityFormType = { HEX: 'hex', + TEXT: 'text', NUMBER: 'number', ICON: 'icon', MOVE_ICON: 'move-icon', SELECT: 'select', + TEXTAREA: 'textarea', MULTIPLE: 'multiple', // add more form types here } as const @@ -69,3 +85,17 @@ export type entityFormTypeEnum = ObjectValues export type IEntityEnumSelectOption = { [x: string]: string } + +export type NewEntitySchemaType = NewDesignSchemaType | NewLayerSchemaType + +export type ReorderEntitySchemaType = + | ReorderDesignSchemaType + | ReorderLayerSchemaType + +export type ToggleVisibleEntitySchemaType = + | ToggleVisibleDesignSchemaType + | ToggleVisibleLayerSchemaType + +export type DeleteEntitySchemaType = + | DeleteDesignSchemaType + | DeleteLayerSchemaType diff --git a/app/schema/zod-helpers.ts b/app/schema/zod-helpers.ts index 0a6ab470..a21cb8fd 100644 --- a/app/schema/zod-helpers.ts +++ b/app/schema/zod-helpers.ts @@ -7,5 +7,5 @@ export type defaultValueNumber = { [key: string]: number } export type defaultValueStringOrNumber = { - [key: string]: string | number + [key: string]: string | number | null | undefined } diff --git a/app/services/artboard/version/layer/delete.service.ts b/app/services/artboard/version/layer/delete.service.ts index aa4bf709..60b39ee5 100644 --- a/app/services/artboard/version/layer/delete.service.ts +++ b/app/services/artboard/version/layer/delete.service.ts @@ -1,21 +1,25 @@ -import { type User, type Layer, type Artboard } from '@prisma/client' +import { type IArtboardVersion } from '#app/models/artboard-version/artboard-version.server' +import { type ILayerDeletedResponse } from '#app/models/layer/layer.delete.server' import { findFirstLayer, connectPrevAndNextLayers, updateLayerToHead, updateLayerToTail, + type ILayer, } from '#app/models/layer.server' +import { type IUser } from '#app/models/user/user.server' import { prisma } from '#app/utils/db.server' -export const artboardLayerDeleteService = async ({ +// TODO: move logic to direct layer service +export const artboardVersionLayerDeleteService = async ({ userId, id, - artboardId, + artboardVersionId, }: { - userId: User['id'] - id: Layer['id'] - artboardId: Artboard['id'] -}) => { + userId: IUser['id'] + id: ILayer['id'] + artboardVersionId: IArtboardVersion['id'] +}): Promise => { try { // Step 1: get the layer const layer = await getLayer({ @@ -45,8 +49,13 @@ export const artboardLayerDeleteService = async ({ return { success: true } } catch (error) { - console.log(error) - return { error: true } + console.log('artboardVersionLayerDeleteService error:', error) + const errorType = error instanceof Error + const errorMessage = errorType ? error.message : 'An unknown error occurred' + return { + success: false, + message: errorMessage, + } } } @@ -54,8 +63,8 @@ const getLayer = async ({ id, userId, }: { - id: Layer['id'] - userId: User['id'] + id: ILayer['id'] + userId: IUser['id'] }) => { const layer = await findFirstLayer({ where: { id, ownerId: userId }, @@ -70,8 +79,8 @@ const getAdjacentLayers = async ({ userId, layer, }: { - userId: User['id'] - layer: Layer + userId: IUser['id'] + layer: ILayer }) => { const { nextId, prevId } = layer @@ -92,7 +101,7 @@ const getAdjacentLayers = async ({ return { nextLayer, prevLayer } } -const deleteLayer = ({ id }: { id: Layer['id'] }) => { +const deleteLayer = ({ id }: { id: ILayer['id'] }) => { return prisma.layer.delete({ where: { id }, }) @@ -106,9 +115,9 @@ const updateLayerNodes = ({ prevLayer, }: { nextId: string | null - nextLayer: Layer | null + nextLayer: ILayer | null prevId: string | null - prevLayer: Layer | null + prevLayer: ILayer | null }) => { const updateLayerNodesPromises = [] diff --git a/app/services/artboard/version/layer/move-down.service.ts b/app/services/artboard/version/layer/move-down.service.ts index 8dc0f517..400fac88 100644 --- a/app/services/artboard/version/layer/move-down.service.ts +++ b/app/services/artboard/version/layer/move-down.service.ts @@ -1,21 +1,21 @@ -import { type User } from '@prisma/client' -import { type IArtboard } from '#app/models/artboard.server' +import { type IArtboardVersion } from '#app/models/artboard-version/artboard-version.server' import { findFirstLayer, updateLayerRemoveNodes, type ILayer, updateLayerNodes, } from '#app/models/layer.server' +import { type IUser } from '#app/models/user/user.server' import { prisma } from '#app/utils/db.server' -export const artboardLayerMoveDownService = async ({ +export const artboardVersionLayerMoveDownService = async ({ userId, id, - artboardId, + artboardVersionId, }: { - userId: User['id'] + userId: IUser['id'] id: ILayer['id'] - artboardId: IArtboard['id'] + artboardVersionId: IArtboardVersion['id'] }) => { try { const moveDownLayerPromises = [] @@ -59,8 +59,13 @@ export const artboardLayerMoveDownService = async ({ return { success: true } } catch (error) { - console.log(error) - return { error: true } + console.log('artboardVersionLayerMoveDownService error:', error) + const errorType = error instanceof Error + const errorMessage = errorType ? error.message : 'An unknown error occurred' + return { + success: false, + message: errorMessage, + } } } @@ -69,7 +74,7 @@ const getLayer = async ({ userId, }: { id: ILayer['id'] - userId: User['id'] + userId: IUser['id'] }) => { const layer = await findFirstLayer({ where: { id, ownerId: userId }, @@ -85,7 +90,7 @@ const getAdjacentLayers = async ({ nextNextId, prevId, }: { - userId: User['id'] + userId: IUser['id'] nextNextId: string | null prevId: string | null }) => { diff --git a/app/services/artboard/version/layer/move-up.service.ts b/app/services/artboard/version/layer/move-up.service.ts index 96e289f8..9d074d50 100644 --- a/app/services/artboard/version/layer/move-up.service.ts +++ b/app/services/artboard/version/layer/move-up.service.ts @@ -1,21 +1,21 @@ -import { type User } from '@prisma/client' -import { type IArtboard } from '#app/models/artboard.server' +import { type IArtboardVersion } from '#app/models/artboard-version/artboard-version.server' import { findFirstLayer, updateLayerRemoveNodes, type ILayer, updateLayerNodes, } from '#app/models/layer.server' +import { type IUser } from '#app/models/user/user.server' import { prisma } from '#app/utils/db.server' -export const artboardLayerMoveUpService = async ({ +export const artboardVersionLayerMoveUpService = async ({ userId, id, - artboardId, + artboardVersionId, }: { - userId: User['id'] + userId: IUser['id'] id: ILayer['id'] - artboardId: IArtboard['id'] + artboardVersionId: IArtboardVersion['id'] }) => { try { const moveUpLayerPromises = [] @@ -59,8 +59,13 @@ export const artboardLayerMoveUpService = async ({ return { success: true } } catch (error) { - console.log(error) - return { error: true } + console.log('artboardVersionLayerMoveUpService error:', error) + const errorType = error instanceof Error + const errorMessage = errorType ? error.message : 'An unknown error occurred' + return { + success: false, + message: errorMessage, + } } } @@ -69,7 +74,7 @@ const getLayer = async ({ userId, }: { id: ILayer['id'] - userId: User['id'] + userId: IUser['id'] }) => { const layer = await findFirstLayer({ where: { id, ownerId: userId }, @@ -85,7 +90,7 @@ const getAdjacentLayers = async ({ prevPrevId, nextId, }: { - userId: User['id'] + userId: IUser['id'] prevPrevId: string | null nextId: string | null }) => { diff --git a/app/services/artboard/version/layer/toggle-visible.service.ts b/app/services/artboard/version/layer/toggle-visible.service.ts index 4ac7a86b..89040f7c 100644 --- a/app/services/artboard/version/layer/toggle-visible.service.ts +++ b/app/services/artboard/version/layer/toggle-visible.service.ts @@ -1,20 +1,20 @@ -import { type User } from '@prisma/client' -import { type IArtboard } from '#app/models/artboard.server' +import { type IArtboardVersion } from '#app/models/artboard-version/artboard-version.server' import { findFirstLayer, updateLayerVisible, type ILayer, } from '#app/models/layer.server' +import { type IUser } from '#app/models/user/user.server' import { prisma } from '#app/utils/db.server' -export const artboardLayerToggleVisibleService = async ({ +export const artboardVersionLayerToggleVisibleService = async ({ userId, id, - artboardId, + artboardVersionId, }: { - userId: User['id'] + userId: IUser['id'] id: ILayer['id'] - artboardId: IArtboard['id'] + artboardVersionId: IArtboardVersion['id'] }) => { try { // Step 1: get the design @@ -30,8 +30,13 @@ export const artboardLayerToggleVisibleService = async ({ return { success: true } } catch (error) { - console.log(error) - return { error: true } + console.log('artboardVersionLayerToggleVisibleService error:', error) + const errorType = error instanceof Error + const errorMessage = errorType ? error.message : 'An unknown error occurred' + return { + success: false, + message: errorMessage, + } } } @@ -40,7 +45,7 @@ const getLayer = async ({ userId, }: { id: ILayer['id'] - userId: User['id'] + userId: IUser['id'] }) => { const layer = await findFirstLayer({ where: { id, ownerId: userId }, diff --git a/app/strategies/component/dashboard-panel/create-entity.strategy.ts b/app/strategies/component/dashboard-panel/create-entity.strategy.ts index 8d1fe936..02bd1042 100644 --- a/app/strategies/component/dashboard-panel/create-entity.strategy.ts +++ b/app/strategies/component/dashboard-panel/create-entity.strategy.ts @@ -1,16 +1,21 @@ import { DesignParentTypeIdEnum, - type designParentTypeIdEnum, type NewDesignSchemaType, } from '#app/schema/design' import { NewArtboardVersionDesignSchema } from '#app/schema/design-artboard-version' +import { + type entityParentIdTypeEnum, + type NewEntitySchemaType, +} from '#app/schema/entity' +import { type NewLayerSchemaType } from '#app/schema/layer' +import { NewArtboardVersionLayerSchema } from '#app/schema/layer-artboard-version' import { Routes, type RoutePath } from '#app/utils/routes.utils' export interface IDashboardPanelCreateEntityStrategy { route: RoutePath - parentTypeId: designParentTypeIdEnum + parentTypeId: entityParentIdTypeEnum formId: string - schema: NewDesignSchemaType // could also be layer + schema: NewEntitySchemaType iconText: string } @@ -18,9 +23,20 @@ export class DashboardPanelCreateArtboardVersionDesignTypeStrategy implements IDashboardPanelCreateEntityStrategy { route: RoutePath = Routes.RESOURCES.API.V1.ARTBOARD_VERSION.DESIGN.CREATE - parentTypeId: designParentTypeIdEnum = + parentTypeId: entityParentIdTypeEnum = DesignParentTypeIdEnum.ARTBOARD_VERSION_ID formId: string = 'artboard-version-design-create' schema: NewDesignSchemaType = NewArtboardVersionDesignSchema iconText = 'Add New Design' } + +export class DashboardPanelCreateArtboardVersionLayerStrategy + implements IDashboardPanelCreateEntityStrategy +{ + route: RoutePath = Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER.CREATE + parentTypeId: entityParentIdTypeEnum = + DesignParentTypeIdEnum.ARTBOARD_VERSION_ID + formId: string = 'artboard-version-layer-create' + schema: NewLayerSchemaType = NewArtboardVersionLayerSchema + iconText = 'Add New Layer' +} diff --git a/app/strategies/component/dashboard-panel/delete-entity.strategy.ts b/app/strategies/component/dashboard-panel/delete-entity.strategy.ts index 3205c94e..7d92c98e 100644 --- a/app/strategies/component/dashboard-panel/delete-entity.strategy.ts +++ b/app/strategies/component/dashboard-panel/delete-entity.strategy.ts @@ -1,16 +1,21 @@ import { type DeleteDesignSchemaType, DesignParentTypeIdEnum, - type designParentTypeIdEnum, } from '#app/schema/design' import { DeleteArtboardVersionDesignSchema } from '#app/schema/design-artboard-version' +import { + type entityParentIdTypeEnum, + type DeleteEntitySchemaType, +} from '#app/schema/entity' +import { type DeleteLayerSchemaType } from '#app/schema/layer' +import { DeleteArtboardVersionLayerSchema } from '#app/schema/layer-artboard-version' import { Routes, type RoutePath } from '#app/utils/routes.utils' export interface IDashboardPanelDeleteEntityStrategy { route: RoutePath - parentTypeId: designParentTypeIdEnum + parentTypeId: entityParentIdTypeEnum formId: string - schema: DeleteDesignSchemaType // could also be layer + schema: DeleteEntitySchemaType iconText: string } @@ -18,9 +23,20 @@ export class DashboardPanelDeleteArtboardVersionDesignTypeStrategy implements IDashboardPanelDeleteEntityStrategy { route: RoutePath = Routes.RESOURCES.API.V1.ARTBOARD_VERSION.DESIGN.DELETE - parentTypeId: designParentTypeIdEnum = + parentTypeId: entityParentIdTypeEnum = DesignParentTypeIdEnum.ARTBOARD_VERSION_ID formId: string = 'artboard-version-design-delete' schema: DeleteDesignSchemaType = DeleteArtboardVersionDesignSchema iconText = 'Delete Design' } + +export class DashboardPanelDeleteArtboardVersionLayerStrategy + implements IDashboardPanelDeleteEntityStrategy +{ + route: RoutePath = Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER.DELETE + parentTypeId: entityParentIdTypeEnum = + DesignParentTypeIdEnum.ARTBOARD_VERSION_ID + formId: string = 'artboard-version-layer-delete' + schema: DeleteLayerSchemaType = DeleteArtboardVersionLayerSchema + iconText = 'Add New Layer' +} diff --git a/app/strategies/component/dashboard-panel/update-entity-move.strategy.ts b/app/strategies/component/dashboard-panel/update-entity-move.strategy.ts index ad7314f0..e51b0b29 100644 --- a/app/strategies/component/dashboard-panel/update-entity-move.strategy.ts +++ b/app/strategies/component/dashboard-panel/update-entity-move.strategy.ts @@ -1,16 +1,21 @@ import { DesignParentTypeIdEnum, type ReorderDesignSchemaType, - type designParentTypeIdEnum, } from '#app/schema/design' import { ReorderArtboardVersionDesignSchema } from '#app/schema/design-artboard-version' +import { + type ReorderEntitySchemaType, + type entityParentIdTypeEnum, +} from '#app/schema/entity' +import { type ReorderLayerSchemaType } from '#app/schema/layer' +import { ReorderArtboardVersionLayerSchema } from '#app/schema/layer-artboard-version' import { Routes, type RoutePath } from '#app/utils/routes.utils' export interface IDashboardPanelUpdateEntityOrderStrategy { route: RoutePath - parentTypeId: designParentTypeIdEnum + parentTypeId: entityParentIdTypeEnum formId: string - schema: ReorderDesignSchemaType // could also be layer + schema: ReorderEntitySchemaType iconText: string } @@ -19,9 +24,20 @@ export class DashboardPanelUpdateArtboardVersionDesignTypeOrderStrategy { route: RoutePath = Routes.RESOURCES.API.V1.ARTBOARD_VERSION.DESIGN.UPDATE.ORDER - parentTypeId: designParentTypeIdEnum = + parentTypeId: entityParentIdTypeEnum = DesignParentTypeIdEnum.ARTBOARD_VERSION_ID formId: string = 'artboard-version-design-update-order' schema: ReorderDesignSchemaType = ReorderArtboardVersionDesignSchema iconText = 'Move' } + +export class DashboardPanelUpdateArtboardVersionLayerTypeOrderStrategy + implements IDashboardPanelUpdateEntityOrderStrategy +{ + route: RoutePath = Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER.UPDATE.ORDER + parentTypeId: entityParentIdTypeEnum = + DesignParentTypeIdEnum.ARTBOARD_VERSION_ID + formId: string = 'artboard-version-layer-update-order' + schema: ReorderLayerSchemaType = ReorderArtboardVersionLayerSchema + iconText = 'Move' +} diff --git a/app/strategies/component/dashboard-panel/update-entity-visible.strategy.ts b/app/strategies/component/dashboard-panel/update-entity-visible.strategy.ts index 39d0c1c0..341bb4c8 100644 --- a/app/strategies/component/dashboard-panel/update-entity-visible.strategy.ts +++ b/app/strategies/component/dashboard-panel/update-entity-visible.strategy.ts @@ -1,16 +1,21 @@ import { DesignParentTypeIdEnum, - type designParentTypeIdEnum, type ToggleVisibleDesignSchemaType, } from '#app/schema/design' import { ToggleVisibleArtboardVersionDesignSchema } from '#app/schema/design-artboard-version' +import { + type ToggleVisibleEntitySchemaType, + type entityParentIdTypeEnum, +} from '#app/schema/entity' +import { type ToggleVisibleLayerSchemaType } from '#app/schema/layer' +import { ToggleVisibleArtboardVersionLayerSchema } from '#app/schema/layer-artboard-version' import { Routes, type RoutePath } from '#app/utils/routes.utils' export interface IDashboardPanelUpdateEntityVisibleStrategy { route: RoutePath - parentTypeId: designParentTypeIdEnum + parentTypeId: entityParentIdTypeEnum formId: string - schema: ToggleVisibleDesignSchemaType // could also be layer + schema: ToggleVisibleEntitySchemaType iconText: string } @@ -19,10 +24,22 @@ export class DashboardPanelUpdateArtboardVersionDesignTypeVisibleStrategy { route: RoutePath = Routes.RESOURCES.API.V1.ARTBOARD_VERSION.DESIGN.UPDATE.VISIBLE - parentTypeId: designParentTypeIdEnum = + parentTypeId: entityParentIdTypeEnum = DesignParentTypeIdEnum.ARTBOARD_VERSION_ID formId: string = 'artboard-version-design-update-visible' schema: ToggleVisibleDesignSchemaType = ToggleVisibleArtboardVersionDesignSchema iconText = 'Design' } + +export class DashboardPanelUpdateArtboardVersionLayerTypeVisibleStrategy + implements IDashboardPanelUpdateEntityVisibleStrategy +{ + route: RoutePath = + Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER.UPDATE.VISIBLE + parentTypeId: entityParentIdTypeEnum = + DesignParentTypeIdEnum.ARTBOARD_VERSION_ID + formId: string = 'artboard-version-layer-update-visible' + schema: ToggleVisibleLayerSchemaType = ToggleVisibleArtboardVersionLayerSchema + iconText = 'Design' +} diff --git a/app/strategies/component/dashboard-panel/update-entity/update-entity-values.layer.ts b/app/strategies/component/dashboard-panel/update-entity/update-entity-values.layer.ts new file mode 100644 index 00000000..3af57afb --- /dev/null +++ b/app/strategies/component/dashboard-panel/update-entity/update-entity-values.layer.ts @@ -0,0 +1,88 @@ +import { type ILayer } from '#app/models/layer.server' +import { EntityFormType, EntityParentIdType } from '#app/schema/entity' +import { + EditLayerDescriptionSchema, + EditLayerNameSchema, +} from '#app/schema/layer' +import { Routes } from '#app/utils/routes.utils' +import { + type IPanelEntityFormArgsOptionalMultiple, + type IDashboardPanelUpdateEntityValuesStrategy, + type IPanelEntityFormArgs, +} from './update-entity-values' + +const baseRoute = Routes.RESOURCES.API.V1.LAYER.UPDATE +const globalEntityFormArgs = { + parentTypeId: EntityParentIdType.ARTBOARD_VERSION_ID, +} +const globalLayerNameArgs = { + route: baseRoute.NAME, + formType: EntityFormType.TEXT, + formId: 'layer-update-name', + schema: EditLayerNameSchema, + label: 'Name', +} +const globalLayerDescriptionArgs = { + route: baseRoute.DESCRIPTION, + formType: EntityFormType.TEXTAREA, + formId: 'layer-update-description', + schema: EditLayerDescriptionSchema, + label: 'Description', +} + +export class DashboardPanelUpdateLayerValuesStrategy + implements IDashboardPanelUpdateEntityValuesStrategy +{ + getMainPanelForm({ + entity, + }: { + entity: ILayer + }): IPanelEntityFormArgsOptionalMultiple { + const { name } = entity + const sharedEntityFormArgs = { + ...globalEntityFormArgs, + entityId: entity.id, + } + + const layerNameArgs = { + ...sharedEntityFormArgs, + ...globalLayerNameArgs, + defaultValue: { name }, + } + return layerNameArgs + } + + getPopoverForms({ entity }: { entity: ILayer }): IPanelEntityFormArgs[] { + const { name, description } = entity + const sharedEntityFormArgs = { + ...globalEntityFormArgs, + entityId: entity.id, + } + + const layerNameArgs = { + ...sharedEntityFormArgs, + ...globalLayerNameArgs, + defaultValue: { name }, + } + + const layerDescriptionArgs = { + ...sharedEntityFormArgs, + ...globalLayerDescriptionArgs, + defaultValue: { description }, + } + + return [layerNameArgs, layerDescriptionArgs] + } + + getPopoverTriggerColor({ entity }: { entity: ILayer }): undefined { + return undefined + } + + getPanelFormatIcon({ entity }: { entity: ILayer }): undefined { + return undefined + } + + getPanelBasisIcon({ entity }: { entity: ILayer }): undefined { + return undefined + } +} diff --git a/app/strategies/component/dashboard-panel/update-entity/update-entity-values.ts b/app/strategies/component/dashboard-panel/update-entity/update-entity-values.ts index 5e91efc8..660864be 100644 --- a/app/strategies/component/dashboard-panel/update-entity/update-entity-values.ts +++ b/app/strategies/component/dashboard-panel/update-entity/update-entity-values.ts @@ -16,8 +16,8 @@ export interface IPanelEntityFormArgs { formType: entityFormTypeEnum defaultValue: defaultValueStringOrNumber entityId: IEntityId - parentId: IEntityParentId - parentTypeId: entityParentIdTypeEnum + parentId?: IEntityParentId + parentTypeId?: entityParentIdTypeEnum formId: string schema: z.ZodSchema label?: string diff --git a/app/strategies/validate-submission.strategy.ts b/app/strategies/validate-submission.strategy.ts index 7e8c74cc..5e84e9ad 100644 --- a/app/strategies/validate-submission.strategy.ts +++ b/app/strategies/validate-submission.strategy.ts @@ -2,6 +2,7 @@ import { type User } from '@prisma/client' import { type z } from 'zod' import { getArtboardVersion } from '#app/models/artboard-version/artboard-version.get.server' import { getDesign } from '#app/models/design/design.get.server' +import { getLayer } from '#app/models/layer/layer.get.server' import { addNotFoundIssue } from '#app/utils/conform-utils' export interface IValidateSubmissionStrategy { @@ -71,3 +72,23 @@ export class ValidateDesignParentSubmissionStrategy if (!design) ctx.addIssue(addNotFoundIssue('Design')) } } + +export class ValidateLayerSubmissionStrategy + implements IValidateSubmissionStrategy +{ + async validateFormDataEntity({ + userId, + data, + ctx, + }: { + userId: User['id'] + data: any + ctx: any + }): Promise { + const { id } = data + const layer = await getLayer({ + where: { id, ownerId: userId }, + }) + if (!layer) ctx.addIssue(addNotFoundIssue('Artboard')) + } +} diff --git a/app/utils/routes.utils.ts b/app/utils/routes.utils.ts index 30ff7b28..77b16b25 100644 --- a/app/utils/routes.utils.ts +++ b/app/utils/routes.utils.ts @@ -15,6 +15,22 @@ import { loader as apiV1ArtboardVersionDesignUpdateVisibleLoader, action as apiV1ArtboardVersionDesignUpdateVisibleAction, } from '#app/routes/resources+/api.v1+/artboard-version.design.update.visible' +import { + loader as apiV1ArtboardVersionLayerCreateLoader, + action as apiV1ArtboardVersionLayerCreateAction, +} from '#app/routes/resources+/api.v1+/artboard-version.layer.create' +import { + loader as apiV1ArtboardVersionLayerDeleteLoader, + action as apiV1ArtboardVersionLayerDeleteAction, +} from '#app/routes/resources+/api.v1+/artboard-version.layer.delete' +import { + loader as apiV1ArtboardVersionLayerUpdateOrderLoader, + action as apiV1ArtboardVersionLayerUpdateOrderAction, +} from '#app/routes/resources+/api.v1+/artboard-version.layer.update.order' +import { + loader as apiV1ArtboardVersionLayerUpdateVisibleLoader, + action as apiV1ArtboardVersionLayerUpdateVisibleAction, +} from '#app/routes/resources+/api.v1+/artboard-version.layer.update.visible' import { loader as apiV1ArtboardVersionUpdateBackgroundLoader, action as apiV1ArtboardVersionUpdateBackgroundAction, @@ -107,6 +123,14 @@ import { loader as apiV1DesignTypeTemplateStyleLoader, action as apiV1DesignTypeTemplateStyleAction, } from '#app/routes/resources+/api.v1+/design.type.template.update.style' +import { + loader as apiV1LayerUpdateDescriptionLoader, + action as apiV1LayerUpdateDescriptionAction, +} from '#app/routes/resources+/api.v1+/layer.update.description' +import { + loader as apiV1LayerUpdateNameLoader, + action as apiV1LayerUpdateNameAction, +} from '#app/routes/resources+/api.v1+/layer.update.name' import { type ExtractStringValues } from './typescript-helpers' export type RoutePath = ExtractStringValues @@ -130,6 +154,14 @@ export const Routes = { ORDER: `${pathBase}/artboard-version/design/update/order`, }, }, + LAYER: { + CREATE: `${pathBase}/artboard-version/layer/create`, + DELETE: `${pathBase}/artboard-version/layer/delete`, + UPDATE: { + VISIBLE: `${pathBase}/artboard-version/layer/update/visible`, + ORDER: `${pathBase}/artboard-version/layer/update/order`, + }, + }, }, DESIGN: { TYPE: { @@ -187,6 +219,12 @@ export const Routes = { }, }, }, + LAYER: { + UPDATE: { + DESCRIPTION: `${pathBase}/layer/update/description`, + NAME: `${pathBase}/layer/update/name`, + }, + }, }, }, }, @@ -207,6 +245,17 @@ export interface ApiRouteLoaders { .VISIBLE]: typeof apiV1ArtboardVersionDesignUpdateVisibleLoader [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.DESIGN.UPDATE .ORDER]: typeof apiV1ArtboardVersionDesignUpdateOrderLoader + [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER + .CREATE]: typeof apiV1ArtboardVersionLayerCreateLoader + [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER + .DELETE]: typeof apiV1ArtboardVersionLayerDeleteLoader + [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER.UPDATE + .VISIBLE]: typeof apiV1ArtboardVersionLayerUpdateVisibleLoader + [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER.UPDATE + .ORDER]: typeof apiV1ArtboardVersionLayerUpdateOrderLoader + [Routes.RESOURCES.API.V1.LAYER.UPDATE + .DESCRIPTION]: typeof apiV1LayerUpdateDescriptionLoader + [Routes.RESOURCES.API.V1.LAYER.UPDATE.NAME]: typeof apiV1LayerUpdateNameLoader [Routes.RESOURCES.API.V1.DESIGN.TYPE.LAYOUT.UPDATE .COUNT]: typeof apiV1DesignTypeLayoutCountLoader [Routes.RESOURCES.API.V1.DESIGN.TYPE.LAYOUT.UPDATE @@ -264,6 +313,17 @@ export const loaders: ApiRouteLoaders = { apiV1ArtboardVersionDesignUpdateVisibleLoader, [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.DESIGN.UPDATE.ORDER]: apiV1ArtboardVersionDesignUpdateOrderLoader, + [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER.CREATE]: + apiV1ArtboardVersionLayerCreateLoader, + [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER.DELETE]: + apiV1ArtboardVersionLayerDeleteLoader, + [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER.UPDATE.VISIBLE]: + apiV1ArtboardVersionLayerUpdateVisibleLoader, + [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER.UPDATE.ORDER]: + apiV1ArtboardVersionLayerUpdateOrderLoader, + [Routes.RESOURCES.API.V1.LAYER.UPDATE.DESCRIPTION]: + apiV1LayerUpdateDescriptionLoader, + [Routes.RESOURCES.API.V1.LAYER.UPDATE.NAME]: apiV1LayerUpdateNameLoader, [Routes.RESOURCES.API.V1.DESIGN.TYPE.LAYOUT.UPDATE.COUNT]: apiV1DesignTypeLayoutCountLoader, [Routes.RESOURCES.API.V1.DESIGN.TYPE.LAYOUT.UPDATE.ROWS]: @@ -329,6 +389,17 @@ export interface ApiRouteActions { .VISIBLE]: typeof apiV1ArtboardVersionDesignUpdateVisibleAction [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.DESIGN.UPDATE .ORDER]: typeof apiV1ArtboardVersionDesignUpdateOrderAction + [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER + .CREATE]: typeof apiV1ArtboardVersionLayerCreateAction + [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER + .DELETE]: typeof apiV1ArtboardVersionLayerDeleteAction + [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER.UPDATE + .VISIBLE]: typeof apiV1ArtboardVersionLayerUpdateVisibleAction + [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER.UPDATE + .ORDER]: typeof apiV1ArtboardVersionLayerUpdateOrderAction + [Routes.RESOURCES.API.V1.LAYER.UPDATE + .DESCRIPTION]: typeof apiV1LayerUpdateDescriptionAction + [Routes.RESOURCES.API.V1.LAYER.UPDATE.NAME]: typeof apiV1LayerUpdateNameAction [Routes.RESOURCES.API.V1.DESIGN.TYPE.LAYOUT.UPDATE .COUNT]: typeof apiV1DesignTypeLayoutCountAction [Routes.RESOURCES.API.V1.DESIGN.TYPE.LAYOUT.UPDATE @@ -386,6 +457,17 @@ export const actions: ApiRouteActions = { apiV1ArtboardVersionDesignUpdateVisibleAction, [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.DESIGN.UPDATE.ORDER]: apiV1ArtboardVersionDesignUpdateOrderAction, + [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER.CREATE]: + apiV1ArtboardVersionLayerCreateAction, + [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER.DELETE]: + apiV1ArtboardVersionLayerDeleteAction, + [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER.UPDATE.VISIBLE]: + apiV1ArtboardVersionLayerUpdateVisibleAction, + [Routes.RESOURCES.API.V1.ARTBOARD_VERSION.LAYER.UPDATE.ORDER]: + apiV1ArtboardVersionLayerUpdateOrderAction, + [Routes.RESOURCES.API.V1.LAYER.UPDATE.DESCRIPTION]: + apiV1LayerUpdateDescriptionAction, + [Routes.RESOURCES.API.V1.LAYER.UPDATE.NAME]: apiV1LayerUpdateNameAction, [Routes.RESOURCES.API.V1.DESIGN.TYPE.LAYOUT.UPDATE.COUNT]: apiV1DesignTypeLayoutCountAction, [Routes.RESOURCES.API.V1.DESIGN.TYPE.LAYOUT.UPDATE.ROWS]: