diff --git a/package.json b/package.json index 09eb325106801b80d0a4eae7a90fadbda11e52ce..2870c72f7d559c49627160dd1d93f5223f823259 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "dev": "vite", + "dev": "vite && npx tailwindcss -i ./src/input.css -o ./src/output.css --watch", "build": "run-p type-check \"build-only {@}\" --", "preview": "vite preview", "build-only": "vite build", diff --git a/src/app/components.d.ts b/src/app/components.d.ts index 8d24d3dfa47e2fa3c35a9149c78f8ec47a578f5a..1128b7dafbe76045943ec66e6138aaa3b3d2c4d4 100644 --- a/src/app/components.d.ts +++ b/src/app/components.d.ts @@ -23,6 +23,7 @@ declare module 'vue' { Divider: typeof import('./../shared/ui/Divider.vue')['default'] DividerItem: typeof import('./../modules/entities/DividerItem.vue')['default'] DividerSettings: typeof import('./../components/entities/settings/DividerSettings.vue')['default'] + DividerSettingsList: typeof import('./../components/entities/settings/lists/DividerSettingsList.vue')['default'] DottedIcon: typeof import('./../shared/icons/DottedIcon.vue')['default'] Drawer: typeof import('./../shared/ui/Drawer.vue')['default'] EntitiesList: typeof import('./../modules/entities/EntitiesList.vue')['default'] diff --git a/src/app/helpers/images.ts b/src/app/helpers/images.ts index 2f4eceda5d33f65aed85a109990347e12878bf89..4aa5cd235b6908b0769d45dfe3e0766e411e247c 100644 --- a/src/app/helpers/images.ts +++ b/src/app/helpers/images.ts @@ -3,7 +3,7 @@ import { useFilesWebsocketStore } from '@/app/stores/filesWebsocket'; import type { IImage } from '@/app/interfaces/entities'; import { useWebsocketStore } from '@/app/stores/websocket'; import { useInterfaceStore } from '@/app/stores/interface'; -import { imageScaleOptions } from '@/components/entities/settings/lists/options'; +import { imageScaleOptions } from '@/components/entities/settings/lists/constants/options'; export const setDefaultPageBackground = () => { const interfaceStore = useInterfaceStore(); @@ -25,6 +25,9 @@ export const addUrlsToImageEntities = (entities: IEntity[]) => { filesBuffer[index] = new Blob([filesBuffer[index].data], { type: 'image/jpeg' }); entity.image_url = URL.createObjectURL(filesBuffer[index]); index += 1; + filesBuffer[index] = new Blob([filesBuffer[index].data], { type: 'image/jpeg' }); + entity.image_url_initial = URL.createObjectURL(filesBuffer[index]); + index += 1; } return entity; }); @@ -43,13 +46,28 @@ export const checkIsImage = (entity: IEntity) => { return entityToReturn; }; -export const cropImage = async (newUrl: string, entity: IImage) => { +export const calcImageWidth = (fileWidth: number, windowWidth: number) => { + let imageWidth = Math.ceil((fileWidth / (windowWidth - 128)) * 100); + if (imageWidth > 100) { + imageWidth = 100; + } + if (imageWidth < 5) { + imageWidth = 5; + } + return imageWidth; +}; + +export const sendCropImage = async (newUrl: string, entity: IImage) => { const filesWebsocketStore = useFilesWebsocketStore(); filesWebsocketStore.saveImageUrl(newUrl); const websocketStore = useWebsocketStore(); const response = await fetch(newUrl); const blob = await response.blob(); const buffer = await blob.arrayBuffer(); + const dataSetCropNow = { + event: 'setCropNow' + }; + websocketStore.sendData(dataSetCropNow); filesWebsocketStore.sendData(buffer); const data = { event: 'cropImage', @@ -67,7 +85,7 @@ export const getImageScalesToRemove = ( let scale = entity.image_scale; if (scale[0] === 'x') scale = scale.slice(1); const initialImageWidth = Math.ceil(+entity.image_width / +scale); - console.log(initialImageWidth); + const initialImageHeight = +entity.file_height_initial; if (initialImageWidth <= 20) { valuesToRemove.push('x0.25'); if (initialImageWidth <= 10) { @@ -83,30 +101,42 @@ export const getImageScalesToRemove = ( if ( initialImageWidth > 80 || (initialImageWidth > 60 && isText) || - (!isEntityWidthFull && isText && initialImageWidth > 40) + (!isEntityWidthFull && isText && initialImageWidth > 40) || + initialImageHeight * 1.25 > 1000 ) { valuesToRemove.push('x1.25'); - if ( - initialImageWidth > 66 || - (initialImageWidth > 50 && isText) || - (!isEntityWidthFull && isText && initialImageWidth > 33) - ) { - valuesToRemove.push('x1.5'); - if ( - initialImageWidth > 57 || - (initialImageWidth > 42 && isText) || - (!isEntityWidthFull && isText && initialImageWidth > 28) - ) { - valuesToRemove.push('x1.75'); - if ( - initialImageWidth > 50 || - (initialImageWidth > 37 && isText) || - (!isEntityWidthFull && isText && initialImageWidth > 25) - ) { - valuesToRemove.push('x2'); - } - } - } + } + if ( + initialImageWidth > 66 || + (initialImageWidth > 50 && isText) || + (!isEntityWidthFull && isText && initialImageWidth > 33) || + initialImageHeight * 1.5 > 1000 + ) { + valuesToRemove.push('x1.5'); + } + if ( + initialImageWidth > 57 || + (initialImageWidth > 42 && isText) || + (!isEntityWidthFull && isText && initialImageWidth > 28) || + initialImageHeight * 1.75 > 1000 + ) { + valuesToRemove.push('x1.75'); + } + if ( + initialImageWidth > 57 || + (initialImageWidth > 42 && isText) || + (!isEntityWidthFull && isText && initialImageWidth > 28) || + initialImageHeight * 1.75 > 1000 + ) { + valuesToRemove.push('x1.75'); + } + if ( + initialImageWidth > 50 || + (initialImageWidth > 37 && isText) || + (!isEntityWidthFull && isText && initialImageWidth > 25) || + initialImageHeight * 2 > 1000 + ) { + valuesToRemove.push('x2'); } return valuesToRemove; }; diff --git a/src/app/helpers/index.ts b/src/app/helpers/index.ts index 92df535cb2387cdf1144062bffff1ab5cf3515ca..db01ef4efe55d3af677aa884d23af716a2cf7a66 100644 --- a/src/app/helpers/index.ts +++ b/src/app/helpers/index.ts @@ -51,7 +51,7 @@ export const editEntity = (newState: IEntity) => { websocketStore.sendData(data); }; -export const returnOriginalImageSize = (newState: IEntity) => { +export const sendReturnOriginalSize = (newState: IEntity) => { const websocketStore = useWebsocketStore(); const data = { event: 'returnOriginalImageSize', diff --git a/src/app/interfaces/entities.ts b/src/app/interfaces/entities.ts index d2793221d98d45920c356a62a23e660e702dc5dd..96afa4f83134389fd900164e806a2b83db9f54ec 100644 --- a/src/app/interfaces/entities.ts +++ b/src/app/interfaces/entities.ts @@ -52,9 +52,10 @@ export interface IImage extends IEntity { font_size?: '16' | '20' | '24' | '40' | '64' | null; paragraph_size?: 'full' | 'half' | null; text_position?: 'left' | 'right' | null; + image_url_initial: string; + image_width_initial: number; file_width_initial: number; file_height_initial: number; - image_url_initial: string; image_url: string; image_width: number; file_width: number; diff --git a/src/app/interfaces/environment.ts b/src/app/interfaces/environment.ts index 5f85a646585abe85647e839a1a6fbcdf74562f2e..08052ba4d66f8642bc45b736c052f4208fd514b0 100644 --- a/src/app/interfaces/environment.ts +++ b/src/app/interfaces/environment.ts @@ -25,9 +25,10 @@ export interface IEntity { paragraph_size?: string | null; text_position?: string | null; image_buffer?: string; + image_url_initial?: string; + image_width_initial?: number; file_width_initial?: number; file_height_initial?: number; - image_url_initial?: string; image_url?: string; image_width?: number; file_width?: number; diff --git a/src/app/interfaces/ui.ts b/src/app/interfaces/ui.ts index 4e294a91bb158284f2febcd7c0f6d23d8fd283ce..e224bf26fb03442678db44a0eb69157d870e78c5 100644 --- a/src/app/interfaces/ui.ts +++ b/src/app/interfaces/ui.ts @@ -11,6 +11,6 @@ export interface IToggleButtonOption { } export interface ISliderOption { label: string; - value: number; + value: string | number; color?: string; } diff --git a/src/app/stores/websocket.ts b/src/app/stores/websocket.ts index 067c6472ca6de1f6fd57454d457a93878dbdd519..a67488edc35d888c3564ab2059fb7c06f85c3cbe 100644 --- a/src/app/stores/websocket.ts +++ b/src/app/stores/websocket.ts @@ -31,7 +31,6 @@ export const useWebsocketStore = defineStore('websocketStore', () => { socket.value = new WebSocket('ws://localhost:5000'); socket.value.onopen = async () => { const userUuid = cookies.get('user_uuid'); - // console.log('userUuid', userUuid); if (userUuid) { const getUserData = { event: 'getUser', @@ -163,7 +162,6 @@ export const useWebsocketStore = defineStore('websocketStore', () => { originalUrl.value = entity.image_url; // entity.file_width = entity.file_width_initial; // entity.file_height = entity.file_height_initial; - console.log('entity.image_url ', entity.image_url); editEntity(entity); return entity; }); @@ -207,7 +205,7 @@ export const useWebsocketStore = defineStore('websocketStore', () => { watch([filesBufferLength, entities], () => { if ( (entities.value.length && - filesBufferLength.value === imageEntitiesCount.value && + filesBufferLength.value === imageEntitiesCount.value * 2 && imageEntitiesCount.value) || (isInitialAddUrlsToImageEntitiesFinished.value && filesBufferLength.value) ) { diff --git a/src/components/CreateEntityMenu.vue b/src/components/CreateEntityMenu.vue index 201447e5d944af6a20f0651bce94c5cbf8eb0894..66a54f558a639eb7d23dcd61558bcc76d1f043ab 100644 --- a/src/components/CreateEntityMenu.vue +++ b/src/components/CreateEntityMenu.vue @@ -4,6 +4,7 @@ import { useAuthorizationStore } from '@/app/stores/authorization'; import { useFilesWebsocketStore } from '@/app/stores/filesWebsocket'; import { useDataStore } from '@/app/stores/data'; import cookies from '@/app/plugins/Cookie'; +import { calcImageWidth } from '@/app/helpers/images'; const emit = defineEmits(['createEntity']); @@ -31,21 +32,13 @@ const addImage = async (files: FileList) => { const blob = await response.blob(); const buffer = await blob.arrayBuffer(); const { width: windowWidth } = useWindowSize(); - const maxHeight = 600; + const maxHeight = 1000; const initWidth = image.width; if (image.height > maxHeight) { const coefficient = maxHeight / image.height; image.width *= coefficient; } - let imageWidth = Math.ceil((image.width / (windowWidth.value - 128)) * 100); - if (imageWidth > 100) { - imageWidth = 100; - } - if (imageWidth < 5) { - imageWidth = 5; - } - console.log(`image.width: ${image.width}, - image.height: ${image.height}`); + const imageWidth = calcImageWidth(image.width, windowWidth.value); emit('createEntity', { entity_type: 'image', entity_order: entitiesCount.value + 1, @@ -56,6 +49,7 @@ const addImage = async (files: FileList) => { text_position: 'right', paragraph_size: 'full', image_width: imageWidth, + image_width_initial: imageWidth, file_width: initWidth, file_height: image.height, file_width_initial: initWidth, diff --git a/src/components/entities/settings/DividerSettings.vue b/src/components/entities/settings/DividerSettings.vue index 18e6ea2e41c372502e5735851c8d003d3cd7a319..2dcd8aea1d8360fafce71b10ee50f74900e48995 100644 --- a/src/components/entities/settings/DividerSettings.vue +++ b/src/components/entities/settings/DividerSettings.vue @@ -1,151 +1,100 @@ <script setup lang="ts"> -import { changeEntitiesOrder, deleteEntity, editEntity } from '@/app/helpers'; +import { convertThemeToColorWhiteDefault, deleteEntity } from '@/app/helpers'; import type { IDivider } from '@/app/interfaces/entities'; -import { useVModel } from '@vueuse/core'; -import type { IEntity } from '@/app/interfaces/environment'; -import { useDataStore } from '@/app/stores/data'; +import type { TTheme } from '@/app/interfaces/environment'; +import cookies from '@/app/plugins/Cookie'; interface Props { entityData: IDivider; } const props = defineProps<Props>(); -const emit = defineEmits(['update:entityData']); -const entityData = useVModel(props, 'entityData', emit); +const emit = defineEmits(['saveChanges']); -const dataStore = useDataStore(); -const entities = computed(() => dataStore.entities); -const entityIndex = computed(() => - entities.value.findIndex((entity: IEntity) => entity.entity_uuid === props.entityData.entity_uuid) -); +const prevEntityData = computed(() => props.entityData); +const newEntityData = ref({ ...props.entityData }); +const isModal = ref<boolean>(false); +const isModalToDeleteDivider = ref<boolean>(false); -const changeHeight = (height: number) => { - entityData.value.divider_height = height; - editEntity({ ...entityData.value, divider_height: height }); -}; -const changeType = (type: 'solid' | 'dashed' | 'dotted') => { - entityData.value.divider_type = type; - editEntity({ ...entityData.value, divider_type: type }); +const themeColor: TTheme = cookies.get('favorite_color'); +const themeColorConverted = convertThemeToColorWhiteDefault(themeColor); + +const openSettings = () => { + isModal.value = true; + newEntityData.value = { ...prevEntityData.value }; }; -const speedDialSettings = computed(() => { - const state = []; - if (props.entityData?.divider_height !== 0) { - const solid = { - label: 'Solid', - command: () => changeType('solid') - }; - const dashed = { - label: 'Dashed', - command: () => changeType('dashed') - }; - const dotted = { - label: 'Dotted', - command: () => changeType('dotted') - }; - if (props.entityData?.divider_type === 'solid') { - state.push(dashed, dotted); - } else if (props.entityData?.divider_type === 'dashed') { - state.push(solid, dotted); - } else { - state.push(solid, dashed); - } - } - const clear = { - label: 'Clear', - command: () => changeHeight(0) - }; - const smallHeight = { - label: 'Small', - command: () => changeHeight(1) - }; - const mediumHeight = { - label: 'Medium', - command: () => changeHeight(2) - }; - const largeHeight = { - label: 'Large', - command: () => changeHeight(4) - }; - const extraLargeHeight = { - label: 'Extra large', - command: () => changeHeight(8) - }; - if (props.entityData?.divider_height === 0) { - state.push(smallHeight, mediumHeight, largeHeight, extraLargeHeight); - } else if (props.entityData?.divider_height === 1) { - state.push(clear, mediumHeight, largeHeight, extraLargeHeight); - } else if (props.entityData?.divider_height === 2) { - state.push(clear, smallHeight, largeHeight, extraLargeHeight); - } else if (props.entityData?.divider_height === 4) { - state.push(clear, smallHeight, mediumHeight, extraLargeHeight); - } else { - state.push(clear, smallHeight, mediumHeight, largeHeight); - } - if (entityIndex.value !== 0) { - state.push({ - label: 'Up', - icon: 'pi pi-arrow-up', - command: () => changeEntitiesOrder(props.entityData.entity_uuid, 'up') - }); +const saveChanges = () => { + if (JSON.stringify(prevEntityData.value) !== JSON.stringify(newEntityData.value)) { + emit('saveChanges', newEntityData.value); } - if (entityIndex.value !== entities.value.length - 1) { - state.push({ - label: 'Down', - icon: 'pi pi-arrow-down', - command: () => changeEntitiesOrder(props.entityData.entity_uuid, 'down') - }); - } - state.push({ - label: 'Delete', - icon: 'pi pi-trash', - command: () => deleteEntity(props.entityData.entity_uuid) - }); - return state; -}); + isModal.value = false; +}; +const toggleConfirmToDeleteDivider = () => { + isModalToDeleteDivider.value = !isModalToDeleteDivider.value; +}; +const deleteDivider = () => { + deleteEntity(prevEntityData.value.entity_uuid); + isModalToDeleteDivider.value = false; + isModal.value = false; +}; </script> <template> - <div class="speedDial absolute left-2 top-0 transition-all select-none"> - <div class="relative z-40"> - <SpeedDial :model="speedDialSettings" direction="right" pt:root:class="speedDialRoot size-12"> - <template #button="{ toggleCallback }"> - <button - class="border p-6 size-10 rounded-full bg-blue-500 flex items-center justify-center" - @click="toggleCallback" - > - <i class="pi pi-cog"></i> - </button> - </template> - <template #item="{ item, toggleCallback }"> - <div - :class="[ - 'flex flex-col bg-black bg-opacity-80 items-center justify-between -translate-8 gap-2 p-2 border rounded border-surface-200 dark:border-surface-700 w-20 cursor-pointer', - { - 'text-red-400 font-semibold': item?.icon?.includes('trash'), - 'bg-violet-500': item.label === 'Extra large', - 'bg-red-500': item.label === 'Large', - 'bg-yellow-500': item.label === 'Medium', - 'bg-green-500': item.label === 'Small', - 'bg-blue-500': item.label === 'Clear' - } - ]" - @click="toggleCallback" - > - <span :class="item?.icon" /> - <span v-show="item.label === 'Solid'"><SolidIcon /></span> - <span v-show="item.label === 'Dashed'"> - <DashedIcon /> - </span> - <span v-show="item.label === 'Dotted'"> - <DottedIcon /> - </span> - <span class="text-center"> - {{ item.label }} - </span> - </div> - </template> - </SpeedDial> + <button + :style="`background-color: ${themeColorConverted}`" + class="settings absolute left-2 top-0 transition-all select-none size-10 flex justify-center items-center rounded-full hover:brightness-75 cursor-pointer" + @click.prevent="openSettings" + > + <SettingsIcon color="white" size="25" /> + </button> + <Modal v-model:isVisible="isModal" theme="black" width="90%" + ><template #header><h3 class="w-max mx-auto">Edit divider</h3></template> + <Modal v-model:isVisible="isModalToDeleteDivider" theme="black" width="30%" + ><p class="font-bold pt-4 mb-4 text-center">Are you sure you want to delete this divider?</p> + <div class="flex justify-between"> + <Button + label="Yes, delete" + theme="red" + textColor="white" + textStyle="bold" + @click.prevent="deleteDivider" + /> + <Button + label="Cancel" + theme="white" + textColor="black" + @click.prevent="toggleConfirmToDeleteDivider" + /></div + ></Modal> + <div class="p-10 pt-4"> + <DividerSettingsList v-model:newEntityData="newEntityData" :themeColor="themeColor" /> + <section + :style="`border-color: var(--${themeColor}-200); height: 32px`" + class="mt-8 flex flex-col justify-center gap-4 p-8 min-h-full border-2 border-slate-100 border-dashed rounded-2xl" + > + <Divider :type="newEntityData.divider_type" :height="newEntityData.divider_height" /> + </section> + <div + class="absolute top-4 right-16 z-10 hover:brightness-80 transition-all" + @click.prevent="toggleConfirmToDeleteDivider" + > + <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> - </div> + </Modal> </template> <style scoped></style> diff --git a/src/components/entities/settings/ImageSettings.vue b/src/components/entities/settings/ImageSettings.vue index 6f646f5d64612a05c4022de3b674dfeeaf67bbc5..0618c7c80ef33e974c11c8b854059078ca3d5a12 100644 --- a/src/components/entities/settings/ImageSettings.vue +++ b/src/components/entities/settings/ImageSettings.vue @@ -3,30 +3,29 @@ import type { IImage } from '@/app/interfaces/entities'; import { convertThemeToColorWhiteDefault, deleteEntity, editEntity } from '@/app/helpers'; import type { TTheme } from '@/app/interfaces/environment'; import cookies from '@/app/plugins/Cookie'; -import { cropImage } from '@/app/helpers/images'; +import { sendCropImage } from '@/app/helpers/images'; interface Props { entityData: IImage; } const props = defineProps<Props>(); const emit = defineEmits(['saveChanges', 'returnOriginalSize']); -const entityData = computed(() => props.entityData); -const prevEntityData = { ...entityData.value }; -const newEntityData = ref({ ...entityData.value }); -watch(entityData, () => (newEntityData.value = entityData.value)); +const prevEntityData = computed(() => props.entityData); +const newEntityData = ref({ ...prevEntityData.value }); const isModal = ref<boolean>(false); const isModalCropImage = ref<boolean>(false); const changeFontSize = (newSize: '16' | '20' | '24' | '40' | '64') => { - entityData.value.font_size = newSize; - editEntity({ ...entityData.value, font_size: newSize }); + prevEntityData.value.font_size = newSize; + editEntity({ ...prevEntityData.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 isTitle = ref(!!prevEntityData.value.title); +const isText = ref(!!prevEntityData.value.text); +const isEntityWidthFull = ref(prevEntityData.value.paragraph_size === 'full'); const isModalToDeleteImage = ref<boolean>(false); + const textContainerWidth = computed(() => { if (!isEntityWidthFull.value) return (100 - newEntityData.value.image_width) / 2; return 100 - newEntityData.value.image_width; @@ -38,31 +37,44 @@ const maxLines = computed(() => { return 0; } }); -const saveChanges = () => { - const entityPosition = isEntityWidthFull.value ? 'full' : 'half'; - if (entityPosition !== prevEntityData.paragraph_size) { - newEntityData.value.paragraph_size = entityPosition; +const openSettings = () => { + isModal.value = true; + newEntityData.value = { ...prevEntityData.value }; +}; +const saveChanges = async () => { + const paragraphSize = isEntityWidthFull.value ? 'full' : 'half'; + if (paragraphSize !== prevEntityData.value.paragraph_size) { + newEntityData.value.paragraph_size = paragraphSize; } - if (isTitle.value !== !!prevEntityData.title) { + if (isTitle.value !== !!prevEntityData.value.title) { if (isTitle.value) { newEntityData.value.title = 'Title'; } else { newEntityData.value.title = null; } } - if (isText.value !== !!prevEntityData.text) { + if (isText.value !== !!prevEntityData.value.text) { if (isText.value) { newEntityData.value.text = 'Text'; } else { newEntityData.value.text = null; } } - if (JSON.stringify(prevEntityData) !== JSON.stringify(newEntityData.value)) { + if (newEntityData.value.image_url !== prevEntityData.value.image_url) { + await sendCropImage(newEntityData.value.image_url, newEntityData.value); + } + if ( + newEntityData.value.image_url === newEntityData.value.image_url_initial && + prevEntityData.value.image_url !== prevEntityData.value.image_url_initial + ) { + emit('returnOriginalSize'); + } + if (JSON.stringify(prevEntityData.value) !== JSON.stringify(newEntityData.value)) { emit('saveChanges', newEntityData.value); } isModal.value = false; }; -const saveImage = async ( +const cropImage = async ( newUrl: string, newWidth: number, newFileWidth: number, @@ -72,22 +84,22 @@ const saveImage = async ( newEntityData.value.image_width = newWidth; newEntityData.value.file_width = newFileWidth; newEntityData.value.file_height = newFileHeight; - await cropImage(newUrl, newEntityData.value); isModalCropImage.value = false; }; const toggleConfirmDeleteImageModal = () => { isModalToDeleteImage.value = !isModalToDeleteImage.value; }; const returnOriginalSize = () => { - const newState = entityData.value; + const newState = { ...newEntityData.value }; newState.image_url = newState.image_url_initial; newState.file_width = newState.file_width_initial; newState.file_height = newState.file_height_initial; - newEntityData.value = newState; - emit('returnOriginalSize'); + newState.image_width = newState.image_width_initial; + newState.image_scale = 'x1'; + newEntityData.value = { ...newState }; }; const deleteImage = () => { - deleteEntity(entityData.value.entity_uuid); + deleteEntity(prevEntityData.value.entity_uuid); isModalToDeleteImage.value = false; isModal.value = false; }; @@ -98,7 +110,7 @@ const openCropImageModal = () => (isModalCropImage.value = true); <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" + @click.prevent="openSettings" > <SettingsIcon color="white" size="25" /> </button> @@ -107,7 +119,7 @@ const openCropImageModal = () => (isModalCropImage.value = true); <CropImageModal v-model:isVisible="isModalCropImage" v-model:imageInfo="newEntityData" - @saveImage="saveImage" + @cropImage="cropImage" /> <ConfirmDeleteEntityModal v-model:isModalToDeleteEntity="isModalToDeleteImage" @@ -123,13 +135,20 @@ const openCropImageModal = () => (isModalCropImage.value = true); :themeColor="themeColor" /> <section - :style="`border-color: var(--${themeColor}-200); align-items: ${newEntityData.entity_position === 'right' ? 'end' : newEntityData.entity_position === 'left' ? 'start' : newEntityData.entity_position}`" + :style="`border-color: var(--${themeColor}-200); align-items: ${ + newEntityData.entity_position === 'right' + ? 'end' + : newEntityData.entity_position === 'left' + ? 'start' + : newEntityData.entity_position + }`" class="flex grow flex-col gap-4 p-4 w-full min-h-full border-2 border-slate-100 border-dashed rounded-2xl" > <div v-show="isTitle" :style="`border-color: var(--${themeColor}-800); - justify-content: ${newEntityData.entity_title_position}; width: ${isEntityWidthFull ? '100%' : '50%'}; font-size: ${newEntityData.font_size / 2 + 5}px`" + justify-content: ${newEntityData.entity_title_position}; width: ${isEntityWidthFull ? '100%' : '50%'}; + font-size: ${newEntityData.font_size / 2 + 5}px`" class="flex text-2xl font-bold text-center px-2 py-4 border-2 border-dashed rounded-2xl" > <h3 class="w-max overflow-ellipsis overflow-hidden whitespace-nowrap"> @@ -137,7 +156,13 @@ const openCropImageModal = () => (isModalCropImage.value = true); </h3> </div> <div - :style="`gap: 32px; justify-content: ${newEntityData.entity_position === 'right' ? 'end' : newEntityData.entity_position === 'left' ? 'start' : 'center'};`" + :style="`gap: 32px; justify-content: ${ + newEntityData.entity_position === 'right' + ? 'end' + : newEntityData.entity_position === 'left' + ? 'start' + : 'center' + };`" class="flex w-full" > <div @@ -152,7 +177,7 @@ const openCropImageModal = () => (isModalCropImage.value = true); <img :src="newEntityData?.image_url" :alt="`Image ${newEntityData?.title}` || 'Image'" - style="max-height: 350px" + style="max-height: 600px" class="object-contain order-1" /> </div> diff --git a/src/components/entities/settings/ParagraphSettings.vue b/src/components/entities/settings/ParagraphSettings.vue index c969552ed9616e86acc22198be53c118796708cf..c837790105afd35d1c15e2ad552cfb2805e16df9 100644 --- a/src/components/entities/settings/ParagraphSettings.vue +++ b/src/components/entities/settings/ParagraphSettings.vue @@ -9,20 +9,25 @@ interface Props { } const props = defineProps<Props>(); const emit = defineEmits(['saveChanges']); -const entityData = computed(() => props.entityData); -const prevEntityData = { ...entityData.value }; -const newEntityData = ref({ ...entityData.value }); + +const prevEntityData = computed(() => props.entityData); +const newEntityData = ref({ ...props.entityData }); const isModal = ref<boolean>(false); const isModalToDeleteParagraph = ref<boolean>(false); + const changeFontSize = (newSize: '16' | '20' | '24' | '40' | '64') => { - entityData.value.font_size = newSize; - editEntity({ ...entityData.value, font_size: newSize }); + prevEntityData.value.font_size = newSize; + editEntity({ ...prevEntityData.value, font_size: newSize }); }; const themeColor: TTheme = cookies.get('favorite_color'); const themeColorConverted = convertThemeToColorWhiteDefault(themeColor); const isTitle = ref(!!newEntityData.value.title); const isEntityWidthFull = ref(newEntityData.value.paragraph_size === 'full'); +const openSettings = () => { + isModal.value = true; + newEntityData.value = { ...prevEntityData.value }; +}; const maxLines = computed(() => { if (isTitle.value) { return Math.floor(168 / 24); @@ -30,71 +35,19 @@ const maxLines = computed(() => { return Math.floor(240 / 24); } }); -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', - 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 saveChanges = () => { - const entityPosition = isEntityWidthFull.value ? 'full' : 'half'; - if (entityPosition !== prevEntityData.entity_position) { - newEntityData.value.paragraph_size = entityPosition; + const paragraphSize = isEntityWidthFull.value ? 'full' : 'half'; + if (paragraphSize !== prevEntityData.value.paragraph_size) { + newEntityData.value.paragraph_size = paragraphSize; } - if (isTitle.value !== !!prevEntityData.title) { + if (isTitle.value !== !!prevEntityData.value.title) { if (isTitle.value) { newEntityData.value.title = 'Title'; } else { newEntityData.value.title = null; } } - if (JSON.stringify(prevEntityData) !== JSON.stringify(newEntityData.value)) { + if (JSON.stringify(prevEntityData.value) !== JSON.stringify(newEntityData.value)) { emit('saveChanges', newEntityData.value); } isModal.value = false; @@ -103,7 +56,7 @@ const toggleConfirmToDeleteParagraph = () => { isModalToDeleteParagraph.value = !isModalToDeleteParagraph.value; }; const deleteParagraph = () => { - deleteEntity(entityData.value.entity_uuid); + deleteEntity(prevEntityData.value.entity_uuid); isModalToDeleteParagraph.value = false; isModal.value = false; }; @@ -113,7 +66,7 @@ const deleteParagraph = () => { <button :style="`background-color: ${themeColorConverted}`" class="settings absolute left-2 top-0 transition-all select-none size-10 flex justify-center items-center rounded-full hover:brightness-75 cursor-pointer" - @click.prevent="isModal = true" + @click.prevent="openSettings" > <SettingsIcon color="white" size="25" /> </button> @@ -142,10 +95,6 @@ const deleteParagraph = () => { v-model:isTitle="isTitle" v-model:isEntityWidthFull="isEntityWidthFull" :themeColor="themeColor" - :entityIsTitleOptions="entityIsTitleOptions" - :isEntityWidthFullOptions="isEntityWidthFullOptions" - :entityPositionOptions="entityPositionOptions" - :entityTitlePositionOptions="entityTitlePositionOptions" /> <section :style="`border-color: var(--${themeColor}-200); height: 320px`" diff --git a/src/components/entities/settings/lists/DividerSettingsList.vue b/src/components/entities/settings/lists/DividerSettingsList.vue new file mode 100644 index 0000000000000000000000000000000000000000..9a9d710cb80c9f69ec5686e919374df2a382c63f --- /dev/null +++ b/src/components/entities/settings/lists/DividerSettingsList.vue @@ -0,0 +1,51 @@ +<script setup lang="ts"> +import type { TTheme } from '@/app/interfaces/environment'; +import { useVModel } from '@vueuse/core'; +import type { IDivider } from '@/app/interfaces/entities'; +import ToggleButton from '@/shared/ui/ToggleButton.vue'; +import { + entityHeightOptions, + entityTypeOptions +} from '@/components/entities/settings/lists/constants/options'; + +interface Props { + newEntityData: IDivider; + themeColor: TTheme; +} +const props = defineProps<Props>(); +const emit = defineEmits(['update:newEntityData', 'update:isTitle', 'update:isEntityWidthFull']); +const newEntityData = useVModel(props, 'newEntityData', emit); +</script> + +<template> + <ul class="flex justify-center gap-8 h-full"> + <li class="flex flex-col items-center" style="min-width: 150px; min-height: 100px"> + <p class="py-2 text-center">Type</p> + <div class="flex items-center"> + <ToggleButton + v-model:value="newEntityData.divider_type" + :theme="themeColor" + :options="entityTypeOptions" + rounded="true" + :border="themeColor" + :activeBGColor="themeColor" + /> + </div> + </li> + <li class="flex flex-col items-center" style="min-width: 150px; min-height: 100px"> + <p class="py-2 text-center">Height</p> + <div class="flex items-center"> + <ToggleButton + v-model:value="newEntityData.divider_height" + :theme="themeColor" + :options="entityHeightOptions" + rounded="true" + :border="themeColor" + :activeBGColor="themeColor" + /> + </div> + </li> + </ul> +</template> + +<style scoped></style> diff --git a/src/components/entities/settings/lists/ImageSettingsList.vue b/src/components/entities/settings/lists/ImageSettingsList.vue index 4d6ed8e055229c7b9286165c90d35350894f0b5a..fab770e02613543cf9f1a87b19171e63924fb104 100644 --- a/src/components/entities/settings/lists/ImageSettingsList.vue +++ b/src/components/entities/settings/lists/ImageSettingsList.vue @@ -11,7 +11,7 @@ import { entityTitlePositionOptions, entityTextPositionOptions, isEntityWidthFullOptions -} from './options'; +} from '@/components/entities/settings/lists/constants/options'; import { filterImageScaleOptions, scaleImage } from '@/app/helpers/images'; interface Props { newEntityData: IImage; diff --git a/src/components/entities/settings/lists/ParagraphSettingsList.vue b/src/components/entities/settings/lists/ParagraphSettingsList.vue index 51188843087859daf2ab02df4f798f6df12867f6..62b1d64c9e68910659d31ef8a61609ca02a0c214 100644 --- a/src/components/entities/settings/lists/ParagraphSettingsList.vue +++ b/src/components/entities/settings/lists/ParagraphSettingsList.vue @@ -3,17 +3,18 @@ import type { TTheme } from '@/app/interfaces/environment'; import { useVModels } from '@vueuse/core'; import type { IParagraph } from '@/app/interfaces/entities'; import ToggleButton from '@/shared/ui/ToggleButton.vue'; -import type { IToggleButtonItem } from '@/app/interfaces/ui'; +import { + entityIsTitleOptions, + entityPositionOptions, + entityTitlePositionOptions, + isEntityWidthFullOptions +} from '@/components/entities/settings/lists/constants/options'; interface Props { newEntityData: IParagraph; isTitle: boolean; isEntityWidthFull: boolean; themeColor: TTheme; - entityIsTitleOptions: IToggleButtonItem[]; - isEntityWidthFullOptions: IToggleButtonItem[]; - entityPositionOptions: IToggleButtonItem[]; - entityTitlePositionOptions: IToggleButtonItem[]; } const props = defineProps<Props>(); const emit = defineEmits(['update:newEntityData', 'update:isTitle', 'update:isEntityWidthFull']); diff --git a/src/components/entities/settings/lists/options.ts b/src/components/entities/settings/lists/constants/options.ts similarity index 74% rename from src/components/entities/settings/lists/options.ts rename to src/components/entities/settings/lists/constants/options.ts index df95f941edb8995ef58816eff2147db1961ebaf1..635102bc114d939ef820c61543591f39d8616d61 100644 --- a/src/components/entities/settings/lists/options.ts +++ b/src/components/entities/settings/lists/constants/options.ts @@ -118,6 +118,50 @@ const isEntityWidthFullOptions = ref<IToggleButtonOption[]>([ textStyle: 'bold' } ]); +const entityHeightOptions = ref<IToggleButtonOption[]>([ + { + label: 'Clear', + value: 0, + textStyle: 'bold' + }, + { + label: 'Small', + value: 1, + textStyle: 'bold' + }, + { + label: 'Medium', + value: 2, + textStyle: 'bold' + }, + { + label: 'Large', + value: 4, + textStyle: 'bold' + }, + { + label: 'Extra large', + value: 8, + textStyle: 'bold' + } +]); +const entityTypeOptions = ref<IToggleButtonOption[]>([ + { + label: 'Solid', + value: 'solid', + textStyle: 'bold' + }, + { + label: 'Dashed', + value: 'dashed', + textStyle: 'bold' + }, + { + label: 'Dotted', + value: 'dotted', + textStyle: 'bold' + } +]); export { imageScaleOptions, entityIsTitleOptions, @@ -125,5 +169,7 @@ export { entityPositionOptions, entityTitlePositionOptions, entityTextPositionOptions, - isEntityWidthFullOptions + isEntityWidthFullOptions, + entityHeightOptions, + entityTypeOptions }; diff --git a/src/components/entities/share/EntityTitle.vue b/src/components/entities/share/EntityTitle.vue index 3f6cdd4dd963bb7e718dcb7aa694890479f900af..2553166d431682bb23398b9f777f46b4844e26aa 100644 --- a/src/components/entities/share/EntityTitle.vue +++ b/src/components/entities/share/EntityTitle.vue @@ -29,7 +29,7 @@ const title = useVModel(props, 'title', emit); } ]" size="1" - @change="$emit('editTitle')" + @input="$emit('editTitle')" /> </template> diff --git a/src/modules/CropImageModal.vue b/src/modules/CropImageModal.vue index 31e0be0c0f7bb7fc65b3cc9cbdd3af00726f3c62..c627cffb37724e5da4ae7c28730ac890c471c6e2 100644 --- a/src/modules/CropImageModal.vue +++ b/src/modules/CropImageModal.vue @@ -11,7 +11,7 @@ interface Props { imageInfo: IImageMainInfo | IImage; } const props = defineProps<Props>(); -const emit = defineEmits(['update:isVisible', 'saveImage']); +const emit = defineEmits(['update:isVisible', 'cropImage']); const { isVisible, imageInfo } = useVModels(props, emit); const imageInstance = new Image(); @@ -41,10 +41,6 @@ watch( imageInstance.onload = () => { const imageWidth = (imageInfo.value.image_width / 100) * (windowWidth.value - 128); const imageHeight = (imageWidth / imageInfo.value.file_width) * imageInfo.value.file_height; - console.log(`file width: ${imageInfo.value.file_width}, - file height: ${imageInfo.value.file_height}`); - console.log(`width: ${imageWidth}, - height: ${imageHeight}`); imageInstance.src = imageInfo.value.image_url; if (imageWidth < (imageHeight * windowWidth.value) / windowHeight.value) { imageInstance.onload = () => { @@ -88,7 +84,7 @@ const onCropperChange = async ({ canvas }) => { }; const submitForm = () => { emit( - 'saveImage', + 'cropImage', finalImageUrl.value, Math.ceil((imageInstance.width / windowWidth.value) * 100), imageInstance.width, diff --git a/src/modules/entities/DividerItem.vue b/src/modules/entities/DividerItem.vue index 9ca6c0fcae51bbac8187c12ec040f792db965217..23c2264c85ddc36bd69cf62944954a7c1d6de337 100644 --- a/src/modules/entities/DividerItem.vue +++ b/src/modules/entities/DividerItem.vue @@ -1,18 +1,39 @@ <script setup lang="ts"> import type { IDivider } from '@/app/interfaces/entities'; +import { editEntity } from '@/app/helpers'; +import { useDataStore } from '@/app/stores/data'; +import type { IEntity } from '@/app/interfaces/environment'; interface Props { entityData: IDivider; isEditMode: boolean; } -defineProps<Props>(); +const props = defineProps<Props>(); +const entityData = ref(props.entityData); + +const dataStore = useDataStore(); +const entities = computed(() => dataStore.entities); +const entityIndex = computed(() => + entities.value.findIndex((entity: IEntity) => entity.entity_uuid === props.entityData.entity_uuid) +); +const entitiesLength = computed(() => entities.value.length); + +const saveChanges = (newState: IDivider) => { + editEntity(newState); + entityData.value = newState; +}; </script> <template> <section class="entityContainer relative px-16 py-6"> <Divider :type="entityData.divider_type" :height="entityData.divider_height" /> - <DividerSettings v-if="isEditMode" :entityData="entityData" /> - <EntityPositionSettings :entityUuid="entityData.entity_uuid" /> + <DividerSettings v-if="isEditMode" :entityData="entityData" @saveChanges="saveChanges" /> + <EntityPositionSettings + v-if="isEditMode && entitiesLength > 1" + :entityUuid="entityData.entity_uuid" + :entityIndex="entityIndex" + :entitiesLength="entitiesLength" + /> </section> </template> diff --git a/src/modules/entities/ImageItem.vue b/src/modules/entities/ImageItem.vue index 1713958cb6dba063f683713f0803f12f222d7cff..b98375bee9c86ab2a57d8eb4de8789083fcc91c9 100644 --- a/src/modules/entities/ImageItem.vue +++ b/src/modules/entities/ImageItem.vue @@ -1,7 +1,7 @@ <script setup lang="ts"> import type { IImage } from '@/app/interfaces/entities'; -import { editEntity, returnOriginalImageSize } from '@/app/helpers'; -import { cropImage } from '@/app/helpers/images'; +import { editEntity, sendReturnOriginalSize } from '@/app/helpers'; +import { sendCropImage } from '@/app/helpers/images'; import type { IEntity } from '@/app/interfaces/environment'; import { useDataStore } from '@/app/stores/data'; import { useVModel } from '@vueuse/core'; @@ -21,18 +21,26 @@ const entityIndex = computed(() => ); const entitiesLength = computed(() => entities.value.length); -const isModalCropImage = ref<boolean>(false); - const textContainerWidth = computed(() => { if (entityData.value?.paragraph_size === 'half') return (100 - entityData.value.image_width) / 2; return 100 - entityData.value.image_width; }); +let titleTimeout; +let textTimeout; const editTitle = () => { - editEntity({ ...entityData.value, title: entityData.value.title }); + clearTimeout(titleTimeout); + titleTimeout = setTimeout( + () => editEntity({ ...entityData.value, title: entityData.value.title }), + 600 + ); }; const editText = () => { - editEntity({ ...entityData.value, text: entityData.value.text }); + clearTimeout(textTimeout); + textTimeout = setTimeout( + () => editEntity({ ...entityData.value, text: entityData.value.text }), + 600 + ); }; const saveChanges = (newState: IImage) => { editEntity(newState); @@ -43,15 +51,9 @@ const returnOriginalSize = () => { newState.file_width = newState.file_width_initial; newState.file_height = newState.file_height_initial; entityData.value = newState; - returnOriginalImageSize(newState); + sendReturnOriginalSize(newState); // entityData.value.image_url = newState.image_url_initial; }; -const saveImage = async (newUrl: string, newWidth: number) => { - entityData.value.image_url = newUrl; - entityData.value.image_width = newWidth; - await cropImage(newUrl, entityData.value); - isModalCropImage.value = false; -}; </script> <template> @@ -65,12 +67,14 @@ const saveImage = async (newUrl: string, newWidth: number) => { } ]" > - <CropImageModal - v-model:isVisible="isModalCropImage" - v-model:imageInfo="entityData" - @saveImage="saveImage" - /> - <div class="flex flex-col"> + <div + :class="[ + { + 'w-1/2': entityData.paragraph_size === 'half', + 'w-full': entityData.paragraph_size === 'full' + } + ]" + > <EntityTitle v-model:title="entityData.title" :entityData="entityData" @@ -80,7 +84,7 @@ const saveImage = async (newUrl: string, newWidth: number) => { <div style="gap: 32px" :class="[ - 'flex', + 'flex py-2', { 'justify-start': entityData.entity_position === 'left', 'justify-center': entityData.entity_position === 'center', @@ -100,7 +104,7 @@ const saveImage = async (newUrl: string, newWidth: number) => { <img :src="entityData?.image_url" :alt="`Image ${entityData?.title}` || 'Image'" - style="min-height: 100px; max-height: 700px" + style="min-height: 100px; max-height: 1000px" class="object-contain order-1" /> </div> @@ -122,7 +126,7 @@ const saveImage = async (newUrl: string, newWidth: number) => { rows="7" :style="`font-size: ${entityData.font_size}px`" spellcheck="false" - @change="editText" + @input="editText" /> </div> </div> diff --git a/src/modules/entities/ParagraphItem.vue b/src/modules/entities/ParagraphItem.vue index fc61f3af746409da604482a8c7e679cb5d68f9dc..50a8799157f9fe0725dfe48540100f5f6476dec8 100644 --- a/src/modules/entities/ParagraphItem.vue +++ b/src/modules/entities/ParagraphItem.vue @@ -19,17 +19,28 @@ const entityIndex = computed(() => ); const entitiesLength = computed(() => entities.value.length); +let titleTimeout; +let textTimeout; const editTitle = () => { - editEntity({ ...entityData.value, title: entityData.value.title }); + clearTimeout(titleTimeout); + titleTimeout = setTimeout( + () => editEntity({ ...entityData.value, title: entityData.value.title }), + 600 + ); }; const editText = () => { editEntity({ ...entityData.value, text: entityData.value.text }); }; +const { textarea, triggerResize } = useTextareaAutosize({ styleProp: 'minHeight' }); +const editTextAndTriggerResize = () => { + triggerResize(); + clearTimeout(textTimeout); + textTimeout = setTimeout(() => editText(), 600); +}; const saveChanges = (newState: IParagraph) => { editEntity(newState); entityData.value = newState; }; -const { textarea, triggerResize } = useTextareaAutosize({ styleProp: 'minHeight' }); </script> <template> @@ -64,15 +75,14 @@ const { textarea, triggerResize } = useTextareaAutosize({ styleProp: 'minHeight' :class="[ 'w-full indent-5 leading-normal resize-none outline-0', { - 'pointer-events-none': !isEditMode + 'pointer-events-none': !`isEditMode` } ]" :style="`font-size: ${entityData.font_size}px;`" placeholder="Enter text..." rows="1" spellcheck="false" - @change="editText" - @input="triggerResize" + @input="editTextAndTriggerResize" /> </div> <ParagraphSettings v-if="isEditMode" :entityData="entityData" @saveChanges="saveChanges" /> diff --git a/src/output.css b/src/output.css index bd7b43be6cc9f423bf206be85719c2a6b6ccb1ed..14d1ade4cc00cc461892df092d14f707e1d0f390 100644 --- a/src/output.css +++ b/src/output.css @@ -642,10 +642,6 @@ video { z-index: 20; } -.z-40 { - z-index: 40; -} - .z-50 { z-index: 50; } @@ -729,6 +725,14 @@ video { margin-top: 1rem; } +.mt-16 { + margin-top: 4rem; +} + +.mt-8 { + margin-top: 2rem; +} + .block { display: block; } @@ -802,10 +806,6 @@ video { width: 66.666667%; } -.w-20 { - width: 5rem; -} - .w-48 { width: 12rem; } @@ -1041,11 +1041,6 @@ video { background-color: rgb(0 0 0 / var(--tw-bg-opacity)); } -.bg-blue-500 { - --tw-bg-opacity: 1; - background-color: rgb(59 130 246 / var(--tw-bg-opacity)); -} - .bg-blue-600 { --tw-bg-opacity: 1; background-color: rgb(37 99 235 / var(--tw-bg-opacity)); @@ -1066,39 +1061,15 @@ video { background-color: rgb(17 24 39 / var(--tw-bg-opacity)); } -.bg-green-500 { - --tw-bg-opacity: 1; - background-color: rgb(34 197 94 / var(--tw-bg-opacity)); -} - -.bg-red-500 { - --tw-bg-opacity: 1; - background-color: rgb(239 68 68 / var(--tw-bg-opacity)); -} - -.bg-violet-500 { - --tw-bg-opacity: 1; - background-color: rgb(139 92 246 / var(--tw-bg-opacity)); -} - .bg-white { --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity)); } -.bg-yellow-500 { - --tw-bg-opacity: 1; - background-color: rgb(234 179 8 / var(--tw-bg-opacity)); -} - .bg-opacity-70 { --tw-bg-opacity: 0.7; } -.bg-opacity-80 { - --tw-bg-opacity: 0.8; -} - .object-contain { -o-object-fit: contain; object-fit: contain; @@ -1120,8 +1091,8 @@ video { padding: 1rem; } -.p-6 { - padding: 1.5rem; +.p-8 { + padding: 2rem; } .px-16 { @@ -1194,6 +1165,10 @@ video { padding-right: 0.5rem; } +.pt-16 { + padding-top: 4rem; +} + .pt-4 { padding-top: 1rem; } @@ -1264,11 +1239,6 @@ video { color: rgb(156 163 175 / var(--tw-text-opacity)); } -.text-red-400 { - --tw-text-opacity: 1; - color: rgb(248 113 113 / var(--tw-text-opacity)); -} - .text-white { --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity)); diff --git a/src/pages/[uuid]/SheetPage.vue b/src/pages/[uuid]/SheetPage.vue index 08773d6bbef9d59d2a773d57727d37fd8d7419fe..a91309e75a7b1e440450a3fddee4d7e9d33478b9 100644 --- a/src/pages/[uuid]/SheetPage.vue +++ b/src/pages/[uuid]/SheetPage.vue @@ -7,7 +7,8 @@ import type { IEntity } from '@/app/interfaces/environment'; import type { IImageMainInfo } from '@/app/interfaces'; import { createEntity, fetchForEntities } from '@/app/helpers'; import cookies from '@/app/plugins/Cookie'; -import { setDefaultPageBackground } from '@/app/helpers/images'; +import { calcImageWidth, setDefaultPageBackground } from '@/app/helpers/images'; +import { useWindowSize } from '@vueuse/core'; const dataStore = useDataStore(); const interfaceStore = useInterfaceStore(); @@ -30,11 +31,12 @@ const backgroundImageInfo = ref<IImageMainInfo>({ image_width: 0, image_height: 0 }); +const { width: windowWidth } = useWindowSize(); onMounted(() => { const onKeydown = (event) => { - if (event.key === 'Alt') isEditMode.value = !isEditMode.value; - if (event.key === 'Escape') isMenuVisible.value = !isMenuVisible.value; + if (event.ctrlKey && event.altKey) isEditMode.value = !isEditMode.value; + if (event.ctrlKey && event.shiftKey) isMenuVisible.value = !isMenuVisible.value; }; document.addEventListener('keydown', onKeydown); const getPageBackgroundData = { @@ -64,9 +66,11 @@ const uploadFile = ($event: Event) => { const url = URL.createObjectURL(file); image.src = url; image.onload = function () { + const imageWidth = calcImageWidth(image.width, windowWidth.value); backgroundImageInfo.value.image_url = url; - backgroundImageInfo.value.image_width = image.width; - backgroundImageInfo.value.image_height = image.height; + backgroundImageInfo.value.image_width = imageWidth; + backgroundImageInfo.value.file_width = image.width; + backgroundImageInfo.value.file_height = image.height; isModalUploadFile.value = true; }; } @@ -92,7 +96,7 @@ const openMenu = () => (isMenuVisible.value = true); <CropImageModal v-model:isVisible="isModalUploadFile" v-model:imageInfo="backgroundImageInfo" - @saveImage="saveImage" + @cropImage="saveImage" /> <main id="pageContainer"