diff --git a/.changeset/red-zoos-march.md b/.changeset/red-zoos-march.md new file mode 100644 index 000000000..3bd8a05bd --- /dev/null +++ b/.changeset/red-zoos-march.md @@ -0,0 +1,6 @@ +--- +"@codeimage/api": minor +"@codeimage/app": minor +--- + +feat(app,api) add new border type glass option diff --git a/apps/api/prisma/migrations/20231231115803_add_terminal_border_type_property/migration.sql b/apps/api/prisma/migrations/20231231115803_add_terminal_border_type_property/migration.sql new file mode 100644 index 000000000..07b42de0b --- /dev/null +++ b/apps/api/prisma/migrations/20231231115803_add_terminal_border_type_property/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "SnippetTerminal" ADD COLUMN "borderType" TEXT; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 695753c87..8883dda9b 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -62,6 +62,7 @@ model SnippetTerminal { showGlassReflection Boolean @default(false) opacity Float @default(100) alternativeTheme Boolean @default(false) + borderType String? } model SnippetEditorOptions { diff --git a/apps/api/src/common/typebox/enum.ts b/apps/api/src/common/typebox/enum.ts new file mode 100644 index 000000000..d28c3823c --- /dev/null +++ b/apps/api/src/common/typebox/enum.ts @@ -0,0 +1,10 @@ +import {TString, Type} from '@sinclair/typebox'; + +export const enumLiteral = (values: T[]): TString => { + const literals = values.map(value => Type.Literal(value)); + // TODO: validation should work but type must work as a string... + return Type.Intersect([ + Type.Union(literals), + Type.String(), + ]) as unknown as TString; +}; diff --git a/apps/api/src/modules/project/infra/prisma/prisma-project.repository.ts b/apps/api/src/modules/project/infra/prisma/prisma-project.repository.ts index c7f78f7ee..850b64260 100644 --- a/apps/api/src/modules/project/infra/prisma/prisma-project.repository.ts +++ b/apps/api/src/modules/project/infra/prisma/prisma-project.repository.ts @@ -105,6 +105,7 @@ export function makePrismaProjectRepository( showWatermark: data.terminal.showWatermark, textColor: data.terminal.textColor, type: data.terminal.type, + borderType: data.terminal.borderType, }, }, }, @@ -188,6 +189,7 @@ export function makePrismaProjectRepository( showWatermark: data.terminal.showWatermark, textColor: data.terminal.textColor, type: data.terminal.type, + borderType: data.terminal.borderType, }, }, }, diff --git a/apps/api/src/modules/project/mapper/create-project-mapper.ts b/apps/api/src/modules/project/mapper/create-project-mapper.ts index 9923d15ab..989097e82 100644 --- a/apps/api/src/modules/project/mapper/create-project-mapper.ts +++ b/apps/api/src/modules/project/mapper/create-project-mapper.ts @@ -48,6 +48,7 @@ export function createProjectRequestMapper( data.terminal.alternativeTheme ?? SnippetTerminalCreateRequestSchema.properties.alternativeTheme.default, shadow: data.terminal.shadow ?? null, + borderType: data.terminal.borderType ?? null, }, editorOptions: { fontWeight: data.editorOptions.fontWeight, diff --git a/apps/api/src/modules/project/mapper/get-project-by-id-mapper.ts b/apps/api/src/modules/project/mapper/get-project-by-id-mapper.ts index 5fe1ef6b4..1749d40bd 100644 --- a/apps/api/src/modules/project/mapper/get-project-by-id-mapper.ts +++ b/apps/api/src/modules/project/mapper/get-project-by-id-mapper.ts @@ -30,6 +30,7 @@ export function createCompleteProjectGetByIdResponseMapper( accentVisible: data.terminal.accentVisible, alternativeTheme: data.terminal.alternativeTheme, shadow: data.terminal.shadow, + borderType: data.terminal.borderType as 'glass' | null, }, editorOptions: { id: data.editorOptions.id, diff --git a/apps/api/src/modules/project/schema/project-create.schema.ts b/apps/api/src/modules/project/schema/project-create.schema.ts index 317ecd741..375ffe689 100644 --- a/apps/api/src/modules/project/schema/project-create.schema.ts +++ b/apps/api/src/modules/project/schema/project-create.schema.ts @@ -1,5 +1,6 @@ import {Static, Type} from '@sinclair/typebox'; import {Nullable} from '../../../common/typebox/nullable.js'; +import {SnippetTerminalBorderType} from './project.schema.js'; export const SnippetFrameCreateRequestSchema = Type.Object( { @@ -45,6 +46,7 @@ export const SnippetTerminalCreateRequestSchema = Type.Object( opacity: Nullable(Type.Number({minimum: 0, maximum: 100, default: 100})), showHeader: Type.Boolean(), showWatermark: Nullable(Type.Boolean({default: true})), + borderType: Nullable(SnippetTerminalBorderType), }, {title: 'SnippetTerminalCreateRequest'}, ); diff --git a/apps/api/src/modules/project/schema/project-update.schema.ts b/apps/api/src/modules/project/schema/project-update.schema.ts index 37f99a671..73eb7a8d5 100644 --- a/apps/api/src/modules/project/schema/project-update.schema.ts +++ b/apps/api/src/modules/project/schema/project-update.schema.ts @@ -1,5 +1,6 @@ import {Static, Type} from '@sinclair/typebox'; import {Nullable} from '../../../common/typebox/nullable.js'; +import {SnippetTerminalBorderType} from './project.schema.js'; export const SnippetFrameUpdateRequestSchema = Type.Object( { @@ -40,6 +41,7 @@ const SnippetTerminalUpdateRequestSchema = Type.Object( showWatermark: Type.Boolean(), textColor: Nullable(Type.String()), type: Type.String(), + borderType: Nullable(SnippetTerminalBorderType), }, {title: 'SnippetTerminalUpdateRequest'}, ); diff --git a/apps/api/src/modules/project/schema/project.schema.ts b/apps/api/src/modules/project/schema/project.schema.ts index c474b20b8..82f9ef1be 100644 --- a/apps/api/src/modules/project/schema/project.schema.ts +++ b/apps/api/src/modules/project/schema/project.schema.ts @@ -1,4 +1,5 @@ import {Type} from '@sinclair/typebox'; +import {enumLiteral} from '../../../common/typebox/enum.js'; import {Nullable} from '../../../common/typebox/nullable.js'; export const BaseProjectResponseSchema = Type.Object( @@ -34,6 +35,8 @@ export const BaseSnippetFrameSchema = Type.Object({ opacity: Type.Number(), }); +export const SnippetTerminalBorderType = enumLiteral(['glass'] as const); + export const BaseSnippetTerminalSchema = Type.Object({ id: Type.String({format: 'uuid'}), showHeader: Type.Boolean(), @@ -46,6 +49,7 @@ export const BaseSnippetTerminalSchema = Type.Object({ showGlassReflection: Type.Boolean(), opacity: Type.Number(), alternativeTheme: Type.Boolean(), + borderType: Nullable(SnippetTerminalBorderType), }); export const BaseSnippetEditorOptionsSchema = Type.Object({ diff --git a/apps/api/test/modules/project/mapper/create-project-mapper.test.ts b/apps/api/test/modules/project/mapper/create-project-mapper.test.ts index fdc4cfcf3..d9136c7c8 100644 --- a/apps/api/test/modules/project/mapper/create-project-mapper.test.ts +++ b/apps/api/test/modules/project/mapper/create-project-mapper.test.ts @@ -22,6 +22,7 @@ test('should map ProjectCreateRequest to Prisma ProjectCreateRequest with defaul shadow: null, textColor: null, accentVisible: null, + borderType: null, }, name: 'Untitled', editors: [], @@ -53,6 +54,7 @@ test('should map ProjectCreateRequest to Prisma ProjectCreateRequest with defaul showWatermark: true, opacity: 100, showHeader: true, + borderType: null, }, editors: [], name: 'Untitled', diff --git a/apps/api/test/modules/project/mapper/get-project-by-id-mapper.test.ts b/apps/api/test/modules/project/mapper/get-project-by-id-mapper.test.ts index 5a7d9d32f..270d7ef25 100644 --- a/apps/api/test/modules/project/mapper/get-project-by-id-mapper.test.ts +++ b/apps/api/test/modules/project/mapper/get-project-by-id-mapper.test.ts @@ -31,6 +31,7 @@ test('should map Prisma ProjectGetByIdResponse to schema ProjectGetByIdResponse' shadow: null, textColor: null, accentVisible: true, + borderType: 'glass', }, editorOptionsId: 'editorOptionsId', terminalId: 'terminalId', @@ -75,6 +76,7 @@ test('should map Prisma ProjectGetByIdResponse to schema ProjectGetByIdResponse' showWatermark: false, opacity: 100, showHeader: true, + borderType: 'glass', }, editorOptions: { id: 'editorOptionsId', diff --git a/apps/api/test/routes/v1/project/update.integration.test.ts b/apps/api/test/routes/v1/project/update.integration.test.ts index c660490c4..8f4b08581 100644 --- a/apps/api/test/routes/v1/project/update.integration.test.ts +++ b/apps/api/test/routes/v1/project/update.integration.test.ts @@ -72,6 +72,7 @@ test('POST /v1/project/:id [Update Project] -> 200', async context alternativeTheme: true, accentVisible: false, type: 'windows', + borderType: 'glass', }, }; @@ -125,7 +126,8 @@ test('POST /v1/project/:id [Update Project] -> 200', async context alternativeTheme: true, accentVisible: false, type: 'windows', - } as ProjectUpdateResponse['terminal'], + borderType: 'glass', + } satisfies ProjectUpdateResponse['terminal'], 'return updated terminal', ); assert.deepStrictEqual( diff --git a/apps/codeimage/src/components/FeatureBadge/FeatureBadge.css.ts b/apps/codeimage/src/components/FeatureBadge/FeatureBadge.css.ts index 0d6ab38d6..883937732 100644 --- a/apps/codeimage/src/components/FeatureBadge/FeatureBadge.css.ts +++ b/apps/codeimage/src/components/FeatureBadge/FeatureBadge.css.ts @@ -12,7 +12,7 @@ export const badge = style({ position: 'absolute', left: '100%', top: '50%', - transform: `translateX(10px) translateY(-50%)`, + transform: `translateX(2px) translateY(-50%)`, borderRadius: themeTokens.radii.lg, whiteSpace: 'nowrap', }); diff --git a/apps/codeimage/src/components/Frame/ManagedFrame.tsx b/apps/codeimage/src/components/Frame/ManagedFrame.tsx index c841ddecb..94d9352c4 100644 --- a/apps/codeimage/src/components/Frame/ManagedFrame.tsx +++ b/apps/codeimage/src/components/Frame/ManagedFrame.tsx @@ -40,6 +40,7 @@ export function ManagedFrame() { showWatermark={terminal.showWatermark} opacity={terminal.opacity} alternativeTheme={terminal.alternativeTheme} + borderType={terminal.borderType} themeId={editor.state.options.themeId} > diff --git a/apps/codeimage/src/components/Frame/PreviewFrame.tsx b/apps/codeimage/src/components/Frame/PreviewFrame.tsx index 2bdc66e66..3ee7bbdd9 100644 --- a/apps/codeimage/src/components/Frame/PreviewFrame.tsx +++ b/apps/codeimage/src/components/Frame/PreviewFrame.tsx @@ -121,6 +121,7 @@ export function PreviewFrame(props: VoidProps) { showWatermark={terminal.showWatermark} opacity={terminal.opacity} alternativeTheme={terminal.alternativeTheme} + borderType={terminal.borderType} themeId={editor.state.options.themeId} > diff --git a/apps/codeimage/src/components/Icons/SettingsIcon.tsx b/apps/codeimage/src/components/Icons/SettingsIcon.tsx new file mode 100644 index 000000000..660e0ecbf --- /dev/null +++ b/apps/codeimage/src/components/Icons/SettingsIcon.tsx @@ -0,0 +1,25 @@ +import {SvgIconProps} from '@codeimage/ui'; +import {SvgIcon} from '@codeui/kit'; + +export function SettingsIcon(props: SvgIconProps) { + return ( + + + + + + ); +} diff --git a/apps/codeimage/src/components/Presets/PresetPreview/PresetPreview.tsx b/apps/codeimage/src/components/Presets/PresetPreview/PresetPreview.tsx index f3a636e2e..4dde585c3 100644 --- a/apps/codeimage/src/components/Presets/PresetPreview/PresetPreview.tsx +++ b/apps/codeimage/src/components/Presets/PresetPreview/PresetPreview.tsx @@ -39,6 +39,7 @@ export function PresetPreview(props: PresetPreviewProps) { accentVisible={props.data.terminal.accentVisible} textColor={props.data.terminal.textColor} showHeader={props.data.terminal.showHeader} + borderType={props.data.terminal.borderType} showGlassReflection={props.data.terminal.showGlassReflection} showWatermark={false} opacity={props.data.terminal.opacity} diff --git a/apps/codeimage/src/components/PropertyEditor/WindowStyleForm.tsx b/apps/codeimage/src/components/PropertyEditor/WindowStyleForm.tsx index 1faf7c0c3..e45aa1a2f 100644 --- a/apps/codeimage/src/components/PropertyEditor/WindowStyleForm.tsx +++ b/apps/codeimage/src/components/PropertyEditor/WindowStyleForm.tsx @@ -1,11 +1,13 @@ import {useI18n} from '@codeimage/locale'; import {getTerminalState} from '@codeimage/store/editor/terminal'; +import {VersionStore} from '@codeimage/store/version/version.store'; import {createSelectOptions, Select} from '@codeui/kit'; import {shadowsLabel} from '@core/configuration/shadow'; import {getUmami} from '@core/constants/umami'; import {SegmentedField} from '@ui/SegmentedField/SegmentedField'; import {SkeletonLine} from '@ui/Skeleton/Skeleton'; import {createMemo, ParentComponent, Show} from 'solid-js'; +import {provideState} from 'statebuilder'; import {AppLocaleEntries} from '../../i18n'; import {TerminalControlField} from './controls/TerminalControlField/TerminalControlField'; import {PanelHeader} from './PanelHeader'; @@ -14,6 +16,7 @@ import {SuspenseEditorItem} from './SuspenseEditorItem'; export const WindowStyleForm: ParentComponent = () => { const terminal = getTerminalState(); + const versionStore = provideState(VersionStore); const [t] = useI18n(); const terminalShadows = createMemo( @@ -25,6 +28,17 @@ export const WindowStyleForm: ParentComponent = () => { valueKey: 'value', }); + const borderTypeSelect = createSelectOptions( + [ + {label: 'None', value: 'none'}, + {label: 'Glass', value: 'glass'}, + ], + { + key: 'label', + valueKey: 'value', + }, + ); + return ( <> @@ -151,6 +165,36 @@ export const WindowStyleForm: ParentComponent = () => { + + + } + > +