Loading src/app/components.d.ts +5 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,9 @@ export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { AlignCenterIcon: typeof import('./../shared/icons/AlignCenterIcon.vue')['default'] AlignLeftIcon: typeof import('./../shared/icons/AlignLeftIcon.vue')['default'] AlignRightIcon: typeof import('./../shared/icons/AlignRightIcon.vue')['default'] App: typeof import('./App.vue')['default'] AuthorizationForm: typeof import('./../modules/authorization/AuthorizationForm.vue')['default'] BaseLoader: typeof import('./../shared/BaseLoader.vue')['default'] Loading Loading @@ -35,6 +38,7 @@ declare module 'vue' { ImageMenu: typeof import('./../components/entities/settings/ImageMenu.vue')['default'] ImagePositionMenu: typeof import('./../components/entities/image/ImagePositionMenu.vue')['default'] ImageSettings: typeof import('./../components/entities/settings/ImageSettings.vue')['default'] ImageSettingsList: typeof import('./../components/entities/settings/lists/ImageSettingsList.vue')['default'] ImageSizeMenu: typeof import('./../components/entities/image/ImageSizeMenu.vue')['default'] ImageStateMenu: typeof import('./../components/entities/image/ImageStateMenu.vue')['default'] LogoAndLabel: typeof import('./../components/LogoAndLabel.vue')['default'] Loading Loading @@ -68,6 +72,7 @@ declare module 'vue' { TextPositionMenu: typeof import('./../components/entities/text/TextPositionMenu.vue')['default'] TextSettings: typeof import('./../components/entities/settings/TextSettings.vue')['default'] TextStateMenu: typeof import('./../components/entities/text/TextStateMenu.vue')['default'] ToggleButton: typeof import('./../shared/ui/ToggleButton.vue')['default'] ToggleSwitch: typeof import('./../shared/ui/ToggleSwitch.vue')['default'] TrashIcon: typeof import('./../shared/icons/TrashIcon.vue')['default'] Tree: typeof import('./../shared/ui/Tree.vue')['default'] Loading src/app/helpers/index.ts +2 −1 Original line number Diff line number Diff line Loading @@ -54,11 +54,12 @@ export const editEntity = (newState: IEntity) => { export const deleteEntity = (entityUuid: string) => { const dataStore = useDataStore(); const websocketStore = useWebsocketStore(); const page_uuid = cookies.get('current_page_uuid'); const entities = dataStore.entities; const entityToDelete = entities.find((entity) => entity.entity_uuid === entityUuid); const data = { event: 'deleteEntity', body: { ...entityToDelete } body: { ...entityToDelete, page_uuid } }; websocketStore.sendData(data); }; Loading src/app/interfaces/ui.ts 0 → 100644 +15 −0 Original line number Diff line number Diff line import type { TTheme } from '@/app/interfaces/environment'; export interface IToggleButtonItem { label: string; textColor?: TTheme; backgroundColor?: TTheme; isLabelHidden?: boolean; iconPos?: string; textStyle?: 'bold' | 'italic'; } export interface ISliderOption { label: string; value: number; color?: string; } src/components/entities/settings/ImageSettings.vue +254 −64 Original line number Diff line number Diff line <script setup lang="ts"> import { useVModel } from '@vueuse/core'; import { useDataStore } from '@/app/stores/data'; import type { IImage } from '@/app/interfaces/entities'; import { deleteEntity, editEntity } from '@/app/helpers'; import { convertThemeToColorWhiteDefault, deleteEntity, editEntity } from '@/app/helpers'; import type { TTheme } from '@/app/interfaces/environment'; import cookies from '@/app/plugins/Cookie'; import type { IToggleButtonItem } from '@/app/interfaces/ui'; interface Props { entityData: IImage; } const props = defineProps<Props>(); const emit = defineEmits(['update:entityData', 'openCropImageModal']); const entityData = useVModel(props, 'entityData', emit); const emit = defineEmits(['saveChanges']); const entityData = computed(() => props.entityData); const newEntityData = ref({ ...entityData.value }); watch(entityData, () => (newEntityData.value = entityData.value)); const isModal = ref<boolean>(false); const dataStore = useDataStore(); const entities = computed(() => dataStore.entities); const addTitle = () => { editEntity({ ...entityData.value, title: 'Title', entity_title_position: 'center', font_size: entityData.value.font_size ?? '24' }); entityData.value = { ...entityData.value, title: 'Title', entity_title_position: 'center' }; }; const removeTitle = () => { const newState = { ...entityData.value }; newState.title = null; editEntity({ ...newState }); entityData.value = newState; }; const addText = () => { editEntity({ ...entityData.value, Loading @@ -37,7 +23,7 @@ const addText = () => { font_size: entityData.value.font_size ?? '24', paragraph_size: 'full' }); entityData.value = { ...entityData.value, text: 'Text' }; newEntityData.value = { ...entityData.value, text: 'Text' }; }; const removeText = () => { const newState = { ...entityData.value }; Loading @@ -45,55 +31,259 @@ const removeText = () => { newState[item] = null; }); editEntity({ ...newState }); entityData.value = newState; }; const editPosition = (position: 'left' | 'center' | 'right') => { entityData.value.entity_position = position; editEntity({ ...entityData.value, entity_position: position }); }; const editTitlePosition = (position: 'left' | 'center' | 'right') => { entityData.value.entity_title_position = position; editEntity({ ...entityData.value, entity_title_position: position }); }; const editTextPosition = (position: 'left' | 'right') => { entityData.value.text_position = position; editEntity({ ...entityData.value, text_position: position }); }; const editParagraphWidth = (widthMode: 'full' | 'half') => { entityData.value.paragraph_size = widthMode; editEntity({ ...entityData.value, paragraph_size: widthMode }); newEntityData.value = newState; }; const changeFontSize = (newSize: '16' | '20' | '24' | '40' | '64') => { entityData.value.font_size = newSize; editEntity({ ...entityData.value, font_size: newSize }); }; const themeColor: TTheme = cookies.get('favorite_color'); const themeColorConverted = convertThemeToColorWhiteDefault(themeColor); const isTitle = ref(!!entityData.value.title); const isText = ref(!!entityData.value.text); const isEntityWidthFull = ref(entityData.value.paragraph_size === 'full'); const maxLines = computed(() => { if (isTitle.value) { return Math.floor(168 / 24); } else { return Math.floor(240 / 24); } }); const entityIsTitleOptions = ref([ { label: 'Off', value: false, textStyle: 'bold' }, { label: 'On', value: true, textStyle: 'bold' } ]); const entityIsTextOptions = ref([ { label: 'Off', value: false, textStyle: 'bold' }, { label: 'On', value: true, textStyle: 'bold' } ]); const entityPositionOptions = ref([ { label: 'left', isLabelHidden: true }, { label: 'center', isLabelHidden: true }, { label: 'right', isLabelHidden: true } ]); const entityTitlePositionOptions = ref([ { label: 'left', isLabelHidden: true }, { label: 'center', isLabelHidden: true }, { label: 'right', isLabelHidden: true } ]); const entityTextPositionOptions = ref([ { label: 'Left', value: 'left', textStyle: 'bold' }, { label: 'Right', value: 'right', textStyle: 'bold' } ]); const entityParagraphSizeOptions = ref([ { label: 'Half', value: 'half', textStyle: 'bold' }, { label: 'Full', value: 'full', textStyle: 'bold' } ]); const imageScaleOptions = ref([ { label: 'x0.25', value: 0, color: 'var(--purple-700)' }, { label: 'x0.5', value: 1, color: 'var(--indigo-500)' }, { label: 'x0.75', value: 2, color: 'var(--sky-500)' }, { label: 'x1', value: 3, color: 'var(--green-500)' }, { label: 'x1.25', value: 4, color: 'var(--yellow-500)' }, { label: 'x1.5', value: 5, color: 'var(--orange-500)' }, { label: 'x1.75', value: 6, color: 'var(--red-500)' }, { label: 'x2', value: 7, color: 'var(--red-800)' } ]); const saveChanges = () => { const entityPosition = isEntityWidthFull.value ? 'full' : 'half'; if (entityPosition !== entityData.value.entity_position) { newEntityData.value.paragraph_size = entityPosition; } if (isTitle.value !== !!entityData.value.title) { if (isTitle.value) { newEntityData.value.title = 'Title'; } else { newEntityData.value.title = null; } } if (isText.value !== !!entityData.value.title) { if (isText.value) { newEntityData.value.title = 'Text'; } else { newEntityData.value.title = null; } } if (JSON.stringify(entityData) !== JSON.stringify(newEntityData.value)) { emit('saveChanges', newEntityData.value); } isModal.value = false; }; </script> <template> <section style="height: 146px" class="speedDial absolute left-2 top-0 transition-all select-none"> <ImageStateMenu :entityData="entityData" class="h-12" @deleteEntity="deleteEntity" @addTitle="addTitle" @removeTitle="removeTitle" @addText="addText" @removeText="removeText" @openCropImageModal="$emit('openCropImageModal')" <button :style="`background-color: ${themeColorConverted}`" class="settings absolute left-2 top-0 select-none size-10 hover:brightness-75 transition-all cursor-pointer" @click.prevent="isModal = true" > <SettingsIcon color="white" size="25" /> </button> <Modal v-model:isVisible="isModal" theme="black" width="90%" ><template #header><h3 class="w-max mx-auto">Edit paragraph</h3></template> <div class="p-10 flex gap-16 items-center"> <ImageSettingsList v-model:newEntityData="newEntityData" v-model:isTitle="isTitle" v-model:isText="isText" v-model:isEntityWidthFull="isEntityWidthFull" :themeColor="themeColor" :entityIsTitleOptions="entityIsTitleOptions" :entityIsTextOptions="entityIsTextOptions" :entityPositionOptions="entityPositionOptions" :entityTitlePositionOptions="entityTitlePositionOptions" :entityTextPositionOptions="entityTextPositionOptions" :entityParagraphSizeOptions="entityParagraphSizeOptions" :imageScaleOptions="imageScaleOptions" /> <div v-if="entityData?.text || entityData?.title"> <TextFontMenu :entityData="entityData" class="h-12" @changeFontSize="changeFontSize" /> <section :style="`border-color: var(--${themeColor}-200); height: 450px`" class="grow flex flex-col gap-4 p-4 min-h-full border-2 border-slate-100 border-dashed rounded-2xl" > <div :style="`justify-content: ${newEntityData.entity_position};`" class="flex"> <div v-show="isTitle" :style="`border-color: var(--${themeColor}-800); justify-content: ${newEntityData.entity_title_position}; width: ${isEntityWidthFull ? '100%' : '50%'}`" class="flex text-2xl font-bold text-center px-2 py-4 border-2 border-dashed rounded-2xl" > <h3 class="w-2/3 overflow-ellipsis overflow-hidden whitespace-nowrap"> {{ newEntityData.title ?? 'Title' }} </h3> </div> <div v-if="entities.length > 1"> <ImagePositionMenu :entityData="entityData" @editPosition="editPosition" @editTitlePosition="editTitlePosition" @editTextPosition="editTextPosition" @editParagraphWidth="editParagraphWidth" /> </div> <div class="flex"> <div :style="`justify-content: ${newEntityData.entity_position}`" class="flex"> <div v-show="isText" :style="`border-color: var(--${themeColor}-400); width: ${isEntityWidthFull ? '100%' : '50%'};`" class="h-full p-4 pb-2 border-2 border-dashed rounded-2xl overflow-hidden" > <p class="pb-0 overflow-hidden contain-inline-size text"> {{ newEntityData.text ?? 'Text' }} </p> </div> </div> <img class="h-max" :src="newEntityData.imageUrl" alt="" /> </div> </section> <div class="absolute top-4 right-16 z-10 hover:brightness-80 transition-all"> <Button label="Delete" textColor="white" theme="red" textStyle="bold" size="medium"> <template #icon> <TrashIcon color="white" size="25" /> </template> </Button> </div> <div class="absolute top-4 left-4 z-10 hover:brightness-80 transition-all" @click.prevent="saveChanges" > <Button label="Save" textColor="white" :theme="themeColor" textStyle="bold" size="medium"> <template #icon> <SaveIcon color="white" size="25" /> </template> </Button> </div> </div> </Modal> </template> <style scoped></style> <style scoped> .text { --max-lines: v-bind(maxLines); overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: var(--max-lines); } .settings { display: flex; justify-content: center; align-items: center; border-radius: 50%; } </style> src/components/entities/settings/ParagraphSettings.vue +78 −25 Original line number Diff line number Diff line <script setup lang="ts"> import { convertThemeToColorWhiteDefault, editEntity } from '@/app/helpers'; import { convertThemeToColorWhiteDefault, deleteEntity, editEntity } from '@/app/helpers'; import type { IParagraph } from '@/app/interfaces/entities'; import type { TTheme } from '@/app/interfaces/environment'; import cookies from '@/app/plugins/Cookie'; Loading @@ -9,18 +9,19 @@ interface Props { } const props = defineProps<Props>(); const emit = defineEmits(['saveChanges']); const entityData = props.entityData; let newEntityData = ref({ ...entityData }); const entityData = computed(() => props.entityData); const newEntityData = ref({ ...entityData.value }); watch(entityData, () => (newEntityData.value = entityData.value)); const isModal = ref<boolean>(false); const isModalToDeleteParagraph = ref<boolean>(false); const changeFontSize = (newSize: '16' | '20' | '24' | '40' | '64') => { entityData.font_size = newSize; editEntity({ ...entityData, font_size: newSize }); entityData.value.font_size = newSize; editEntity({ ...entityData.value, font_size: newSize }); }; const themeColor: TTheme = cookies.get('favorite_color'); const themeColorConverted = convertThemeToColorWhiteDefault(themeColor); const isTitle = ref(!!entityData.title); const isEntityWidthFull = ref(entityData.paragraph_size === 'full'); const isTitle = ref(!!entityData.value.title); const isEntityWidthFull = ref(entityData.value.paragraph_size === 'full'); const maxLines = computed(() => { if (isTitle.value) { Loading @@ -29,51 +30,81 @@ const maxLines = computed(() => { return Math.floor(240 / 24); } }); const entityPositionOptions = [ const entityIsTitleOptions = ref([ { label: 'Off', value: false, textStyle: 'bold' }, { label: 'On', value: true, textStyle: 'bold' } ]); const isEntityWidthFullOptions = ref([ { label: 'Half', value: false, textStyle: 'bold' }, { label: 'Full', value: true, textStyle: 'bold' } ]); const entityPositionOptions = ref([ { label: 'left', value: 0 isLabelHidden: true }, { label: 'center', value: 1 isLabelHidden: true }, { label: 'right', value: 2 isLabelHidden: true } ]; const entityTitlePositionOptions = [ ]); const entityTitlePositionOptions = ref([ { label: 'left', value: 0 isLabelHidden: true }, { label: 'center', value: 1 isLabelHidden: true }, { label: 'right', value: 2 isLabelHidden: true } ]; ]); const saveChanges = () => { const entityPosition = isEntityWidthFull.value ? 'full' : 'half'; if (entityPosition !== entityData.entity_position) { if (entityPosition !== entityData.value.entity_position) { newEntityData.value.paragraph_size = entityPosition; } if (isTitle.value !== !!entityData.title) { if (isTitle.value !== !!entityData.value.title) { if (isTitle.value) { newEntityData.value.title = 'Title'; } else { newEntityData.value.title = null; } } if (JSON.stringify(entityData) !== JSON.stringify(newEntityData.value)) { console.log('they are different, I will save it. New data:', newEntityData.value); if (JSON.stringify(entityData.value) !== JSON.stringify(newEntityData.value)) { emit('saveChanges', newEntityData.value); } console.log('newEntityData.value :', newEntityData.value); isModal.value = false; }; const toggleConfirmToDeleteParagraph = () => { isModalToDeleteParagraph.value = !isModalToDeleteParagraph.value; }; const deleteParagraph = () => { deleteEntity(entityData.value.entity_uuid); isModalToDeleteParagraph.value = false; isModal.value = false; }; </script> Loading @@ -86,14 +117,33 @@ const saveChanges = () => { > <SettingsIcon color="white" size="25" /> </button> <Modal v-model:isVisible="isModal" theme="black" width="70%" <Modal v-model:isVisible="isModal" theme="black" width="90%" ><template #header><h3 class="w-max mx-auto">Edit paragraph</h3></template> <Modal v-model:isVisible="isModalToDeleteParagraph" theme="black" width="30%" ><p class="font-bold pt-4 mb-4 text-center">Are you sure you want to delete this element?</p> <div class="flex justify-between"> <Button label="Yes, delete" theme="red" textColor="white" textStyle="bold" @click.prevent="deleteParagraph" /> <Button label="Cancel" theme="white" textColor="black" @click.prevent="toggleConfirmToDeleteParagraph" /></div ></Modal> <div class="p-10 flex gap-16 items-center"> <ParagraphSettingsList v-model:newEntityData="newEntityData" v-model:isTitle="isTitle" v-model:isEntityWidthFull="isEntityWidthFull" :themeColor="themeColor" :entityIsTitleOptions="entityIsTitleOptions" :isEntityWidthFullOptions="isEntityWidthFullOptions" :entityPositionOptions="entityPositionOptions" :entityTitlePositionOptions="entityTitlePositionOptions" /> Loading Loading @@ -121,7 +171,10 @@ const saveChanges = () => { </div> </div> </section> <div class="absolute top-4 right-16 z-10 hover:brightness-80 transition-all"> <div class="absolute top-4 right-16 z-10 hover:brightness-80 transition-all" @click.prevent="toggleConfirmToDeleteParagraph" > <Button label="Delete" textColor="white" theme="red" textStyle="bold" size="medium"> <template #icon> <TrashIcon color="white" size="25" /> Loading Loading
src/app/components.d.ts +5 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,9 @@ export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { AlignCenterIcon: typeof import('./../shared/icons/AlignCenterIcon.vue')['default'] AlignLeftIcon: typeof import('./../shared/icons/AlignLeftIcon.vue')['default'] AlignRightIcon: typeof import('./../shared/icons/AlignRightIcon.vue')['default'] App: typeof import('./App.vue')['default'] AuthorizationForm: typeof import('./../modules/authorization/AuthorizationForm.vue')['default'] BaseLoader: typeof import('./../shared/BaseLoader.vue')['default'] Loading Loading @@ -35,6 +38,7 @@ declare module 'vue' { ImageMenu: typeof import('./../components/entities/settings/ImageMenu.vue')['default'] ImagePositionMenu: typeof import('./../components/entities/image/ImagePositionMenu.vue')['default'] ImageSettings: typeof import('./../components/entities/settings/ImageSettings.vue')['default'] ImageSettingsList: typeof import('./../components/entities/settings/lists/ImageSettingsList.vue')['default'] ImageSizeMenu: typeof import('./../components/entities/image/ImageSizeMenu.vue')['default'] ImageStateMenu: typeof import('./../components/entities/image/ImageStateMenu.vue')['default'] LogoAndLabel: typeof import('./../components/LogoAndLabel.vue')['default'] Loading Loading @@ -68,6 +72,7 @@ declare module 'vue' { TextPositionMenu: typeof import('./../components/entities/text/TextPositionMenu.vue')['default'] TextSettings: typeof import('./../components/entities/settings/TextSettings.vue')['default'] TextStateMenu: typeof import('./../components/entities/text/TextStateMenu.vue')['default'] ToggleButton: typeof import('./../shared/ui/ToggleButton.vue')['default'] ToggleSwitch: typeof import('./../shared/ui/ToggleSwitch.vue')['default'] TrashIcon: typeof import('./../shared/icons/TrashIcon.vue')['default'] Tree: typeof import('./../shared/ui/Tree.vue')['default'] Loading
src/app/helpers/index.ts +2 −1 Original line number Diff line number Diff line Loading @@ -54,11 +54,12 @@ export const editEntity = (newState: IEntity) => { export const deleteEntity = (entityUuid: string) => { const dataStore = useDataStore(); const websocketStore = useWebsocketStore(); const page_uuid = cookies.get('current_page_uuid'); const entities = dataStore.entities; const entityToDelete = entities.find((entity) => entity.entity_uuid === entityUuid); const data = { event: 'deleteEntity', body: { ...entityToDelete } body: { ...entityToDelete, page_uuid } }; websocketStore.sendData(data); }; Loading
src/app/interfaces/ui.ts 0 → 100644 +15 −0 Original line number Diff line number Diff line import type { TTheme } from '@/app/interfaces/environment'; export interface IToggleButtonItem { label: string; textColor?: TTheme; backgroundColor?: TTheme; isLabelHidden?: boolean; iconPos?: string; textStyle?: 'bold' | 'italic'; } export interface ISliderOption { label: string; value: number; color?: string; }
src/components/entities/settings/ImageSettings.vue +254 −64 Original line number Diff line number Diff line <script setup lang="ts"> import { useVModel } from '@vueuse/core'; import { useDataStore } from '@/app/stores/data'; import type { IImage } from '@/app/interfaces/entities'; import { deleteEntity, editEntity } from '@/app/helpers'; import { convertThemeToColorWhiteDefault, deleteEntity, editEntity } from '@/app/helpers'; import type { TTheme } from '@/app/interfaces/environment'; import cookies from '@/app/plugins/Cookie'; import type { IToggleButtonItem } from '@/app/interfaces/ui'; interface Props { entityData: IImage; } const props = defineProps<Props>(); const emit = defineEmits(['update:entityData', 'openCropImageModal']); const entityData = useVModel(props, 'entityData', emit); const emit = defineEmits(['saveChanges']); const entityData = computed(() => props.entityData); const newEntityData = ref({ ...entityData.value }); watch(entityData, () => (newEntityData.value = entityData.value)); const isModal = ref<boolean>(false); const dataStore = useDataStore(); const entities = computed(() => dataStore.entities); const addTitle = () => { editEntity({ ...entityData.value, title: 'Title', entity_title_position: 'center', font_size: entityData.value.font_size ?? '24' }); entityData.value = { ...entityData.value, title: 'Title', entity_title_position: 'center' }; }; const removeTitle = () => { const newState = { ...entityData.value }; newState.title = null; editEntity({ ...newState }); entityData.value = newState; }; const addText = () => { editEntity({ ...entityData.value, Loading @@ -37,7 +23,7 @@ const addText = () => { font_size: entityData.value.font_size ?? '24', paragraph_size: 'full' }); entityData.value = { ...entityData.value, text: 'Text' }; newEntityData.value = { ...entityData.value, text: 'Text' }; }; const removeText = () => { const newState = { ...entityData.value }; Loading @@ -45,55 +31,259 @@ const removeText = () => { newState[item] = null; }); editEntity({ ...newState }); entityData.value = newState; }; const editPosition = (position: 'left' | 'center' | 'right') => { entityData.value.entity_position = position; editEntity({ ...entityData.value, entity_position: position }); }; const editTitlePosition = (position: 'left' | 'center' | 'right') => { entityData.value.entity_title_position = position; editEntity({ ...entityData.value, entity_title_position: position }); }; const editTextPosition = (position: 'left' | 'right') => { entityData.value.text_position = position; editEntity({ ...entityData.value, text_position: position }); }; const editParagraphWidth = (widthMode: 'full' | 'half') => { entityData.value.paragraph_size = widthMode; editEntity({ ...entityData.value, paragraph_size: widthMode }); newEntityData.value = newState; }; const changeFontSize = (newSize: '16' | '20' | '24' | '40' | '64') => { entityData.value.font_size = newSize; editEntity({ ...entityData.value, font_size: newSize }); }; const themeColor: TTheme = cookies.get('favorite_color'); const themeColorConverted = convertThemeToColorWhiteDefault(themeColor); const isTitle = ref(!!entityData.value.title); const isText = ref(!!entityData.value.text); const isEntityWidthFull = ref(entityData.value.paragraph_size === 'full'); const maxLines = computed(() => { if (isTitle.value) { return Math.floor(168 / 24); } else { return Math.floor(240 / 24); } }); const entityIsTitleOptions = ref([ { label: 'Off', value: false, textStyle: 'bold' }, { label: 'On', value: true, textStyle: 'bold' } ]); const entityIsTextOptions = ref([ { label: 'Off', value: false, textStyle: 'bold' }, { label: 'On', value: true, textStyle: 'bold' } ]); const entityPositionOptions = ref([ { label: 'left', isLabelHidden: true }, { label: 'center', isLabelHidden: true }, { label: 'right', isLabelHidden: true } ]); const entityTitlePositionOptions = ref([ { label: 'left', isLabelHidden: true }, { label: 'center', isLabelHidden: true }, { label: 'right', isLabelHidden: true } ]); const entityTextPositionOptions = ref([ { label: 'Left', value: 'left', textStyle: 'bold' }, { label: 'Right', value: 'right', textStyle: 'bold' } ]); const entityParagraphSizeOptions = ref([ { label: 'Half', value: 'half', textStyle: 'bold' }, { label: 'Full', value: 'full', textStyle: 'bold' } ]); const imageScaleOptions = ref([ { label: 'x0.25', value: 0, color: 'var(--purple-700)' }, { label: 'x0.5', value: 1, color: 'var(--indigo-500)' }, { label: 'x0.75', value: 2, color: 'var(--sky-500)' }, { label: 'x1', value: 3, color: 'var(--green-500)' }, { label: 'x1.25', value: 4, color: 'var(--yellow-500)' }, { label: 'x1.5', value: 5, color: 'var(--orange-500)' }, { label: 'x1.75', value: 6, color: 'var(--red-500)' }, { label: 'x2', value: 7, color: 'var(--red-800)' } ]); const saveChanges = () => { const entityPosition = isEntityWidthFull.value ? 'full' : 'half'; if (entityPosition !== entityData.value.entity_position) { newEntityData.value.paragraph_size = entityPosition; } if (isTitle.value !== !!entityData.value.title) { if (isTitle.value) { newEntityData.value.title = 'Title'; } else { newEntityData.value.title = null; } } if (isText.value !== !!entityData.value.title) { if (isText.value) { newEntityData.value.title = 'Text'; } else { newEntityData.value.title = null; } } if (JSON.stringify(entityData) !== JSON.stringify(newEntityData.value)) { emit('saveChanges', newEntityData.value); } isModal.value = false; }; </script> <template> <section style="height: 146px" class="speedDial absolute left-2 top-0 transition-all select-none"> <ImageStateMenu :entityData="entityData" class="h-12" @deleteEntity="deleteEntity" @addTitle="addTitle" @removeTitle="removeTitle" @addText="addText" @removeText="removeText" @openCropImageModal="$emit('openCropImageModal')" <button :style="`background-color: ${themeColorConverted}`" class="settings absolute left-2 top-0 select-none size-10 hover:brightness-75 transition-all cursor-pointer" @click.prevent="isModal = true" > <SettingsIcon color="white" size="25" /> </button> <Modal v-model:isVisible="isModal" theme="black" width="90%" ><template #header><h3 class="w-max mx-auto">Edit paragraph</h3></template> <div class="p-10 flex gap-16 items-center"> <ImageSettingsList v-model:newEntityData="newEntityData" v-model:isTitle="isTitle" v-model:isText="isText" v-model:isEntityWidthFull="isEntityWidthFull" :themeColor="themeColor" :entityIsTitleOptions="entityIsTitleOptions" :entityIsTextOptions="entityIsTextOptions" :entityPositionOptions="entityPositionOptions" :entityTitlePositionOptions="entityTitlePositionOptions" :entityTextPositionOptions="entityTextPositionOptions" :entityParagraphSizeOptions="entityParagraphSizeOptions" :imageScaleOptions="imageScaleOptions" /> <div v-if="entityData?.text || entityData?.title"> <TextFontMenu :entityData="entityData" class="h-12" @changeFontSize="changeFontSize" /> <section :style="`border-color: var(--${themeColor}-200); height: 450px`" class="grow flex flex-col gap-4 p-4 min-h-full border-2 border-slate-100 border-dashed rounded-2xl" > <div :style="`justify-content: ${newEntityData.entity_position};`" class="flex"> <div v-show="isTitle" :style="`border-color: var(--${themeColor}-800); justify-content: ${newEntityData.entity_title_position}; width: ${isEntityWidthFull ? '100%' : '50%'}`" class="flex text-2xl font-bold text-center px-2 py-4 border-2 border-dashed rounded-2xl" > <h3 class="w-2/3 overflow-ellipsis overflow-hidden whitespace-nowrap"> {{ newEntityData.title ?? 'Title' }} </h3> </div> <div v-if="entities.length > 1"> <ImagePositionMenu :entityData="entityData" @editPosition="editPosition" @editTitlePosition="editTitlePosition" @editTextPosition="editTextPosition" @editParagraphWidth="editParagraphWidth" /> </div> <div class="flex"> <div :style="`justify-content: ${newEntityData.entity_position}`" class="flex"> <div v-show="isText" :style="`border-color: var(--${themeColor}-400); width: ${isEntityWidthFull ? '100%' : '50%'};`" class="h-full p-4 pb-2 border-2 border-dashed rounded-2xl overflow-hidden" > <p class="pb-0 overflow-hidden contain-inline-size text"> {{ newEntityData.text ?? 'Text' }} </p> </div> </div> <img class="h-max" :src="newEntityData.imageUrl" alt="" /> </div> </section> <div class="absolute top-4 right-16 z-10 hover:brightness-80 transition-all"> <Button label="Delete" textColor="white" theme="red" textStyle="bold" size="medium"> <template #icon> <TrashIcon color="white" size="25" /> </template> </Button> </div> <div class="absolute top-4 left-4 z-10 hover:brightness-80 transition-all" @click.prevent="saveChanges" > <Button label="Save" textColor="white" :theme="themeColor" textStyle="bold" size="medium"> <template #icon> <SaveIcon color="white" size="25" /> </template> </Button> </div> </div> </Modal> </template> <style scoped></style> <style scoped> .text { --max-lines: v-bind(maxLines); overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: var(--max-lines); } .settings { display: flex; justify-content: center; align-items: center; border-radius: 50%; } </style>
src/components/entities/settings/ParagraphSettings.vue +78 −25 Original line number Diff line number Diff line <script setup lang="ts"> import { convertThemeToColorWhiteDefault, editEntity } from '@/app/helpers'; import { convertThemeToColorWhiteDefault, deleteEntity, editEntity } from '@/app/helpers'; import type { IParagraph } from '@/app/interfaces/entities'; import type { TTheme } from '@/app/interfaces/environment'; import cookies from '@/app/plugins/Cookie'; Loading @@ -9,18 +9,19 @@ interface Props { } const props = defineProps<Props>(); const emit = defineEmits(['saveChanges']); const entityData = props.entityData; let newEntityData = ref({ ...entityData }); const entityData = computed(() => props.entityData); const newEntityData = ref({ ...entityData.value }); watch(entityData, () => (newEntityData.value = entityData.value)); const isModal = ref<boolean>(false); const isModalToDeleteParagraph = ref<boolean>(false); const changeFontSize = (newSize: '16' | '20' | '24' | '40' | '64') => { entityData.font_size = newSize; editEntity({ ...entityData, font_size: newSize }); entityData.value.font_size = newSize; editEntity({ ...entityData.value, font_size: newSize }); }; const themeColor: TTheme = cookies.get('favorite_color'); const themeColorConverted = convertThemeToColorWhiteDefault(themeColor); const isTitle = ref(!!entityData.title); const isEntityWidthFull = ref(entityData.paragraph_size === 'full'); const isTitle = ref(!!entityData.value.title); const isEntityWidthFull = ref(entityData.value.paragraph_size === 'full'); const maxLines = computed(() => { if (isTitle.value) { Loading @@ -29,51 +30,81 @@ const maxLines = computed(() => { return Math.floor(240 / 24); } }); const entityPositionOptions = [ const entityIsTitleOptions = ref([ { label: 'Off', value: false, textStyle: 'bold' }, { label: 'On', value: true, textStyle: 'bold' } ]); const isEntityWidthFullOptions = ref([ { label: 'Half', value: false, textStyle: 'bold' }, { label: 'Full', value: true, textStyle: 'bold' } ]); const entityPositionOptions = ref([ { label: 'left', value: 0 isLabelHidden: true }, { label: 'center', value: 1 isLabelHidden: true }, { label: 'right', value: 2 isLabelHidden: true } ]; const entityTitlePositionOptions = [ ]); const entityTitlePositionOptions = ref([ { label: 'left', value: 0 isLabelHidden: true }, { label: 'center', value: 1 isLabelHidden: true }, { label: 'right', value: 2 isLabelHidden: true } ]; ]); const saveChanges = () => { const entityPosition = isEntityWidthFull.value ? 'full' : 'half'; if (entityPosition !== entityData.entity_position) { if (entityPosition !== entityData.value.entity_position) { newEntityData.value.paragraph_size = entityPosition; } if (isTitle.value !== !!entityData.title) { if (isTitle.value !== !!entityData.value.title) { if (isTitle.value) { newEntityData.value.title = 'Title'; } else { newEntityData.value.title = null; } } if (JSON.stringify(entityData) !== JSON.stringify(newEntityData.value)) { console.log('they are different, I will save it. New data:', newEntityData.value); if (JSON.stringify(entityData.value) !== JSON.stringify(newEntityData.value)) { emit('saveChanges', newEntityData.value); } console.log('newEntityData.value :', newEntityData.value); isModal.value = false; }; const toggleConfirmToDeleteParagraph = () => { isModalToDeleteParagraph.value = !isModalToDeleteParagraph.value; }; const deleteParagraph = () => { deleteEntity(entityData.value.entity_uuid); isModalToDeleteParagraph.value = false; isModal.value = false; }; </script> Loading @@ -86,14 +117,33 @@ const saveChanges = () => { > <SettingsIcon color="white" size="25" /> </button> <Modal v-model:isVisible="isModal" theme="black" width="70%" <Modal v-model:isVisible="isModal" theme="black" width="90%" ><template #header><h3 class="w-max mx-auto">Edit paragraph</h3></template> <Modal v-model:isVisible="isModalToDeleteParagraph" theme="black" width="30%" ><p class="font-bold pt-4 mb-4 text-center">Are you sure you want to delete this element?</p> <div class="flex justify-between"> <Button label="Yes, delete" theme="red" textColor="white" textStyle="bold" @click.prevent="deleteParagraph" /> <Button label="Cancel" theme="white" textColor="black" @click.prevent="toggleConfirmToDeleteParagraph" /></div ></Modal> <div class="p-10 flex gap-16 items-center"> <ParagraphSettingsList v-model:newEntityData="newEntityData" v-model:isTitle="isTitle" v-model:isEntityWidthFull="isEntityWidthFull" :themeColor="themeColor" :entityIsTitleOptions="entityIsTitleOptions" :isEntityWidthFullOptions="isEntityWidthFullOptions" :entityPositionOptions="entityPositionOptions" :entityTitlePositionOptions="entityTitlePositionOptions" /> Loading Loading @@ -121,7 +171,10 @@ const saveChanges = () => { </div> </div> </section> <div class="absolute top-4 right-16 z-10 hover:brightness-80 transition-all"> <div class="absolute top-4 right-16 z-10 hover:brightness-80 transition-all" @click.prevent="toggleConfirmToDeleteParagraph" > <Button label="Delete" textColor="white" theme="red" textStyle="bold" size="medium"> <template #icon> <TrashIcon color="white" size="25" /> Loading