Skip to content
Snippets Groups Projects
Commit 8ef9ed9f authored by Дмитрий Малюгин's avatar Дмитрий Малюгин :clock4:
Browse files

image settings in progress

parent 2dd967a9
No related branches found
No related tags found
1 merge request!14Refactor entities settings
Showing
with 887 additions and 192 deletions
......@@ -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']
......@@ -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']
......@@ -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']
......
......@@ -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);
};
......
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;
}
<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,
......@@ -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 };
......@@ -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')"
/>
<div v-if="entityData?.text || entityData?.title">
<TextFontMenu :entityData="entityData" class="h-12" @changeFontSize="changeFontSize" />
</div>
<div v-if="entities.length > 1">
<ImagePositionMenu
:entityData="entityData"
@editPosition="editPosition"
@editTitlePosition="editTitlePosition"
@editTextPosition="editTextPosition"
@editParagraphWidth="editParagraphWidth"
<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"
/>
<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>
<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>
</section>
</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>
<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';
......@@ -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) {
......@@ -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>
......@@ -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"
/>
......@@ -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" />
......
<script setup lang="ts">
import type { TTheme } from '@/app/interfaces/environment';
import { useVModels } from '@vueuse/core';
import type { IImage } from '@/app/interfaces/entities';
import ToggleButton from '@/shared/ui/ToggleButton.vue';
import type { ISliderOption, IToggleButtonItem } from '@/app/interfaces/ui';
interface Props {
newEntityData: IImage;
isTitle: boolean;
isText: boolean;
isEntityWidthFull: boolean;
themeColor: TTheme;
entityIsTitleOptions: IToggleButtonItem[];
entityIsTextOptions: IToggleButtonItem[];
entityPositionOptions: IToggleButtonItem[];
entityTitlePositionOptions: IToggleButtonItem[];
entityParagraphSizeOptions: IToggleButtonItem[];
entityTextPositionOptions: IToggleButtonItem[];
imageScaleOptions: ISliderOption[];
}
const props = defineProps<Props>();
const emit = defineEmits([
'update:newEntityData',
'update:isTitle',
'update:isText',
'update:isEntityWidthFull'
]);
const { newEntityData, isTitle, isText, isEntityWidthFull } = useVModels(props, emit);
</script>
<template>
<div>
<div class="flex flex-col items-center" style="min-width: 30%; min-height: 100px">
<p class="py-2">Image size</p>
<Slider
v-model:value="newEntityData.image_scale"
width="300px"
size="small"
max="7"
:options="imageScaleOptions"
:isSmooth="true"
backgroundColor="white"
:theme="themeColor"
/>
</div>
<ul class="flex gap-2 h-full" style="min-width: 35%">
<li class="flex flex-col items-center justify-between gap-4" style="min-width: 150px">
<div>
<p class="py-2 text-center">Title</p>
<ToggleButton
v-model:value="isTitle"
:theme="themeColor"
:options="entityIsTitleOptions"
rounded="true"
:border="themeColor"
:activeBGColor="themeColor"
/>
</div>
<div style="height: 108px" class="flex gap-8 items-center justify-between col-span-2">
<Transition name="fading">
<div v-show="isTitle" class="flex flex-col items-center">
<p class="py-2 text-center">Title position</p>
<ToggleButton
v-model:value="newEntityData.entity_title_position"
:theme="themeColor"
:options="entityTitlePositionOptions"
rounded="true"
:border="themeColor"
:activeBGColor="themeColor"
>
<template #1Icon><AlignLeftIcon /></template>
<template #2Icon><AlignCenterIcon /></template>
<template #3Icon><AlignRightIcon /></template
></ToggleButton>
</div>
</Transition>
</div>
<div style="height: 108px" class="flex gap-8 items-center justify-between col-span-2">
<Transition name="fading">
<div v-show="isText" class="flex flex-col items-center">
<p class="py-2">Text position</p>
<ToggleButton
v-model:value="newEntityData.text_position"
:theme="themeColor"
:options="entityTextPositionOptions"
rounded="true"
:border="themeColor"
:activeBGColor="themeColor"
/>
</div>
</Transition>
</div>
</li>
<li class="flex flex-col items-center justify-between gap-4" style="min-width: 150px">
<div>
<p class="py-2 text-center">Text</p>
<div class="flex items-center">
<ToggleButton
v-model:value="isText"
:theme="themeColor"
:options="entityIsTextOptions"
rounded="true"
:border="themeColor"
:activeBGColor="themeColor"
/>
</div>
</div>
<div style="height: 108px" class="flex gap-8 items-center justify-between">
<div class="flex flex-col items-center">
<p class="py-2 text-center">Block position</p>
<ToggleButton
v-model:value="newEntityData.entity_position"
:theme="themeColor"
:options="entityPositionOptions"
rounded="true"
:border="themeColor"
:activeBGColor="themeColor"
><template #1Icon><AlignLeftIcon /></template>
<template #2Icon><AlignCenterIcon /></template>
<template #3Icon><AlignRightIcon /></template
></ToggleButton>
</div>
</div>
<div style="height: 108px" class="flex gap-8 items-center justify-between">
<Transition name="fading">
<div v-show="isText" class="flex flex-col items-center">
<p class="py-2">Text width</p>
<ToggleButton
v-model:value="newEntityData.paragraph_size"
:theme="themeColor"
:options="entityParagraphSizeOptions"
rounded="true"
:border="themeColor"
:activeBGColor="themeColor"
/>
</div>
</Transition>
</div>
</li>
</ul>
</div>
</template>
<style scoped></style>
......@@ -2,20 +2,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';
interface Props {
newEntityData: IParagraph;
isTitle: boolean;
isEntityWidthFull: boolean;
themeColor: TTheme;
entityPositionOptions: {
label: string;
value: number;
}[];
entityTitlePositionOptions: {
label: string;
value: number;
}[];
entityIsTitleOptions: IToggleButtonItem[];
isEntityWidthFullOptions: IToggleButtonItem[];
entityPositionOptions: IToggleButtonItem[];
entityTitlePositionOptions: IToggleButtonItem[];
}
const props = defineProps<Props>();
const emit = defineEmits(['update:newEntityData', 'update:isTitle', 'update:isEntityWidthFull']);
......@@ -23,66 +21,72 @@ const { newEntityData, isTitle, isEntityWidthFull } = useVModels(props, emit);
</script>
<template>
<ul class="flex gap-8 h-full" style="min-width: 35%">
<li class="flex flex-col items-center gap-4" style="min-width: 150px">
<ul class="flex gap-2 h-full" style="min-width: 35%">
<li class="flex flex-col items-center gap-4" style="min-width: 150px; min-height: 108px">
<div>
<p class="py-2 text-center">Title</p>
<div class="flex items-center">
Off
<ToggleSwitch v-model:isActive="isTitle" class="mx-2" :theme="themeColor" />
On
<ToggleButton
v-model:value="isTitle"
:theme="themeColor"
:options="entityIsTitleOptions"
rounded="true"
:border="themeColor"
:activeBGColor="themeColor"
/>
</div>
</div>
<div style="height: 108px" class="flex gap-8 items-center justify-between col-span-2">
<div class="flex items-center justify-between">
<Transition name="fading">
<div v-show="isTitle" class="flex flex-col items-center">
<p class="py-2 text-center">Title position</p>
<Slider
<ToggleButton
v-model:value="newEntityData.entity_title_position"
:theme="themeColor"
width="150"
size="small"
isSmooth="true"
backgroundColor="white"
min="0"
max="2"
step="1"
:options="entityTitlePositionOptions"
/>
rounded="true"
size="small"
:border="themeColor"
:activeBGColor="themeColor"
>
<template #1Icon><AlignLeftIcon /></template>
<template #2Icon><AlignCenterIcon /></template>
<template #3Icon><AlignRightIcon /></template>
</ToggleButton>
</div>
</Transition>
</div>
</li>
<li class="flex flex-col items-center gap-4" style="min-width: 150px">
<li class="flex flex-col items-center gap-4" style="min-width: 150px; min-height: 108px">
<div>
<p class="py-2 text-center">Paragraph width</p>
<div class="flex items-center">
Half
<ToggleSwitch
v-model:isActive="isEntityWidthFull"
class="mx-2"
<div class="flex flex-col items-center">
<p class="py-2 text-center">Paragraph width</p>
<ToggleButton
v-model:value="isEntityWidthFull"
:theme="themeColor"
:negativeTheme="themeColor"
:options="isEntityWidthFullOptions"
rounded="true"
:border="themeColor"
:activeBGColor="themeColor"
/>
Full
</div>
</div>
<div style="height: 108px" class="flex gap-8 items-center justify-between col-span-2">
<div class="flex gap-8 items-center justify-between">
<Transition name="fading">
<div v-show="!isEntityWidthFull" class="flex flex-col items-center">
<p class="py-2">Paragraph position</p>
<Slider
<ToggleButton
v-model:value="newEntityData.entity_position"
:theme="themeColor"
width="150"
size="small"
isSmooth="true"
backgroundColor="white"
min="0"
max="2"
step="1"
:options="entityPositionOptions"
/>
rounded="true"
size="small"
:border="themeColor"
:activeBGColor="themeColor"
><template #1Icon><AlignLeftIcon /></template>
<template #2Icon><AlignCenterIcon /></template>
<template #3Icon><AlignRightIcon /></template>
</ToggleButton>
</div>
</Transition>
</div>
......
<script setup lang="ts">
import { useVModel, useWindowSize } from '@vueuse/core';
import { useWindowSize } from '@vueuse/core';
import type { IImage } from '@/app/interfaces/entities';
import { editEntity } from '@/app/helpers';
import { cropImage } from '@/app/helpers/images';
import type { IEntity } from '@/app/interfaces/environment';
import { useDataStore } from '@/app/stores/data';
interface Props {
entityData: IImage;
isEditMode: boolean;
}
const props = defineProps<Props>();
const emit = defineEmits(['update:entityData']);
const entityData = useVModel(props, 'entityData', emit);
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 isModalCropImage = ref<boolean>(false);
const { width: windowWidth } = useWindowSize();
......@@ -27,6 +35,10 @@ const editTitle = () => {
const editText = () => {
editEntity({ ...entityData.value, text: entityData.value.text });
};
const saveChanges = (newState: IImage) => {
editEntity(newState);
entityData.value = newState;
};
const saveImage = async (newUrl: string, newWidth: number, newHeight: number) => {
entityData.value.imageUrl = newUrl;
entityData.value.image_width = newWidth;
......@@ -47,7 +59,6 @@ const openCropImageModal = () => (isModalCropImage.value = true);
<template>
<section
ref="container"
:class="[
'entityContainer relative flex px-16 transition-all',
{
......@@ -87,9 +98,9 @@ const openCropImageModal = () => (isModalCropImage.value = true);
style="min-height: 100px; max-height: 700px"
class="object-contain order-1"
/>
<div class="speedDialSize absolute left-0 top-0 transition-all select-none">
<ImageSizeMenu v-if="isEditMode" :entityData="entityData" @scaleImage="scaleImage" />
</div>
<!-- <div class="speedDialSize absolute left-0 top-0 transition-all select-none">-->
<!-- <ImageSizeMenu v-if="isEditMode" :entityData="entityData" @scaleImage="scaleImage" />-->
<!-- </div>-->
</div>
<div
v-if="entityData.text_position"
......@@ -113,12 +124,13 @@ const openCropImageModal = () => (isModalCropImage.value = true);
/>
</div>
</div>
<ImageMenu
v-if="isEditMode"
v-model:entityData="entityData"
@openCropImageModal="openCropImageModal"
<ImageSettings v-if="isEditMode" :entityData="entityData" @saveChanges="saveChanges" />
<EntityPositionSettings
v-if="isEditMode && entitiesLength > 1"
:entityUuid="entityData.entity_uuid"
:entityIndex="entityIndex"
:entitiesLength="entitiesLength"
/>
<EntityPositionSettings :entityUuid="entityData.entity_uuid" />
</div>
</section>
</template>
......
<script setup lang="ts">
import { useElementSize, useTextareaAutosize } from '@vueuse/core';
import { useTextareaAutosize } from '@vueuse/core';
import type { IParagraph } from '@/app/interfaces/entities';
import { editEntity } from '@/app/helpers';
import { useDataStore } from '@/app/stores/data';
......
......@@ -554,40 +554,6 @@ video {
--tw-contain-style: ;
}
.container {
width: 100%;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
}
.pointer-events-none {
pointer-events: none;
}
......@@ -716,6 +682,11 @@ video {
margin-bottom: 1rem;
}
.mx-4 {
margin-left: 1rem;
margin-right: 1rem;
}
.-mb-2 {
margin-bottom: -0.5rem;
}
......@@ -825,6 +796,11 @@ video {
height: 100%;
}
.h-max {
height: -moz-max-content;
height: max-content;
}
.min-h-full {
min-height: 100%;
}
......@@ -985,6 +961,10 @@ video {
gap: 2rem;
}
.gap-0 {
gap: 0px;
}
.overflow-auto {
overflow: auto;
}
......
<script setup lang="ts">
interface Props {
color?: string;
size?: string | number;
}
defineProps<Props>();
</script>
<template>
<svg
:width="size ?? '25px'"
:height="size ?? '25px'"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 6H21M3 14H21M17 10H7M17 18H7"
:stroke="color ?? '#000000'"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
<style scoped></style>
<script setup lang="ts">
interface Props {
color?: string;
size?: string | number;
}
defineProps<Props>();
</script>
<template>
<svg
:width="size ?? '25px'"
:height="size ?? '25px'"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 10H16M3 14H21M3 18H16M3 6H21"
:stroke="color ?? '#000000'"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
<style scoped></style>
<script setup lang="ts">
interface Props {
color?: string;
size?: string | number;
}
defineProps<Props>();
</script>
<template>
<svg
:width="size ?? '25px'"
:height="size ?? '25px'"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8 10H21M3 14H21M8 18H21M3 6H21"
:stroke="color ?? '#000000'"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
<style scoped></style>
......@@ -59,14 +59,14 @@ interface Props {
const props = defineProps<Props>();
const themeColor = computed(() => convertThemeToColorWhiteDefault(props.theme));
const textColor = computed(() => convertThemeToColorBlackDefault(props.textColor));
const borderColor = computed(() => convertThemeToColorBlackDefault(props.border));
const borderColor = computed(() =>
props.border ? convertThemeToColorBlackDefault(props.border) : ''
);
const textSize = computed(() => {
if (!props?.size) return '16px';
if (!props?.size || props.size === 'medium') return '16px';
switch (props.size) {
case 'small':
return '12px';
case 'medium':
return '16px';
case 'large':
return '20px';
case 'extraLarge':
......@@ -74,12 +74,10 @@ const textSize = computed(() => {
}
});
const buttonPadding = computed(() => {
if (!props?.size) return '0.75rem 0.5rem';
if (!props?.size || props.size === 'medium') return '0.75rem 0.5rem';
switch (props.size) {
case 'small':
return '0.5rem 0.375rem';
case 'medium':
return '0.75rem 0.5rem';
case 'large':
return '1.2rem 0.8rem';
case 'extraLarge':
......@@ -93,10 +91,11 @@ const buttonPadding = computed(() => {
:class="[
'button',
{
'flex-column': iconPos === 'top' || iconPos === 'bottom'
'flex-column': iconPos === 'top' || iconPos === 'bottom',
border: borderColor
}
]"
:style="`padding: ${buttonPadding}; border: 1px solid ${borderColor}`"
:style="`padding: ${buttonPadding}`"
>
<span :style="`background-color: ${themeColor}`" class="background"></span>
<span
......@@ -154,7 +153,7 @@ const buttonPadding = computed(() => {
flex-direction: column;
}
.order-1 {
order: 1;
order: -1;
}
.bold {
font-weight: bold;
......@@ -162,4 +161,7 @@ const buttonPadding = computed(() => {
.italic {
font-style: italic;
}
.border {
border: 2px solid v-bind(borderColor);
}
</style>
......@@ -17,6 +17,7 @@ interface Props {
options?: {
label: string;
value: number;
color?: string;
}[];
}
const props = defineProps<Props>();
......@@ -75,7 +76,7 @@ const themeBackground = computed(() => convertThemeToColorBlackDefault(props.bac
<div v-if="options?.length">
<ul
class="marksList"
:style="`width: ${width ?? 200}px; margin-bottom: 5px; font-size: 10px`"
:style="`width: ${width ?? 200}px; margin-bottom: 5px; font-size: 10px; padding: 0 15px`"
>
<li v-for="option of options" :key="option">|</li>
</ul>
......@@ -89,7 +90,11 @@ const themeBackground = computed(() => convertThemeToColorBlackDefault(props.bac
]"
>
<template v-for="option of options" :key="option.value">
<option :value="option.value" :label="option.label"></option>
<option
:value="option.value"
:label="option.label"
:style="`color: ${option.color ?? 'white'}`"
></option>
</template>
</datalist>
</div>
......@@ -98,7 +103,7 @@ const themeBackground = computed(() => convertThemeToColorBlackDefault(props.bac
<style scoped>
.slideContainer {
width: 100%;
width: v-bind(width);
}
.slider {
-webkit-appearance: none;
......
<script setup lang="ts">
import { computed } from 'vue';
import { convertThemeToColorBlackDefault, convertThemeToColorWhiteDefault } from '@/app/helpers';
import { useVModel } from '@vueuse/core';
type TTheme =
| 'white'
| 'slate'
| 'blue'
| 'sky'
| 'teal'
| 'lime'
| 'green'
| 'yellow'
| 'orange'
| 'pink'
| 'fuchsia'
| 'purple'
| 'indigo'
| 'rose'
| 'red'
| 'black';
interface Props {
value: any;
options: {
label: string;
value?: any;
textColor?: TTheme;
backgroundColor?: TTheme;
isLabelHidden?: boolean;
iconPos?: string;
textStyle?: 'bold' | 'italic';
}[];
border?: TTheme;
size?: 'small' | 'medium' | 'large' | 'extraLarge';
theme?: TTheme;
rounded?: any;
activeBGColor?: TTheme;
}
const props = defineProps<Props>();
const emit = defineEmits(['update:value']);
const value = useVModel(props, 'value', emit);
const activeBGColor = computed(() =>
props.activeBGColor ? convertThemeToColorBlackDefault(props.activeBGColor) : ''
);
const borderColor = computed(() =>
props.border ? convertThemeToColorBlackDefault(props.border) : ''
);
const textSize = computed(() => {
if (!props?.size || props.size === 'medium') return '16px';
switch (props.size) {
case 'small':
return '12px';
case 'large':
return '20px';
case 'extraLarge':
return '24px';
}
});
const buttonPadding = computed(() => {
if (!props?.size || props.size === 'medium') return '0.75rem 0.5rem';
switch (props.size) {
case 'small':
return '0.5rem 0.375rem';
case 'large':
return '1.2rem 0.8rem';
case 'extraLarge':
return '1.8rem 1.2rem';
}
});
const buttonHeight = computed(() => {
if (!props?.size || props.size === 'medium') return '40px';
switch (props.size) {
case 'small':
return '24px';
case 'large':
return '68px';
case 'extraLarge':
return '114px';
}
});
</script>
<template>
<div
:class="[
'buttonGroup',
{
'rounded-full': props.rounded,
border: borderColor
}
]"
>
<button
v-for="(item, index) of options"
:key="item.label"
:class="[
'button',
{
'flex-column': item.iconPos === 'top' || item.iconPos === 'bottom'
}
]"
:style="`padding: ${buttonPadding}`"
@click.prevent="value = item?.value ?? item.label"
>
<span
:style="`background-color: ${activeBGColor && (value === item.value || value === item.label) ? activeBGColor : convertThemeToColorWhiteDefault(item.backgroundColor)}`"
:class="[
'background',
{
'rounded-left': index === 0,
'rounded-left-full': index === 0 && props.rounded,
'rounded-right': index === options.length - 1,
'rounded-right-full': index === options.length - 1 && props.rounded
}
]"
></span>
<span
v-if="!item.isLabelHidden"
:style="`color: ${convertThemeToColorBlackDefault(item.textColor)}; font-size: ${textSize}`"
:class="[
'text',
{
bold: item.textStyle === 'bold',
italic: item.textStyle === 'italic'
}
]"
>{{ item.label ?? 'Button' }}</span
>
<slot
:class="[
'icon',
{
'order-1': item.iconPos === 'left' || item.iconPos === 'top'
}
]"
:name="`${index + 1}Icon`"
/>
</button>
</div>
</template>
<style scoped>
.buttonGroup {
display: flex;
border-radius: 0.5rem;
position: relative;
}
.button {
position: relative;
display: flex;
gap: 8px;
align-items: center;
user-select: none;
}
.button:hover .background {
filter: brightness(90%);
}
.background {
width: 100%;
height: 100%;
position: absolute;
z-index: -2;
top: 0;
left: 0;
transition: filter 0.2s ease-in-out;
}
.icon {
display: flex;
align-items: center;
justify-content: center;
}
.flex-column {
flex-direction: column;
}
.order-1 {
order: -1;
}
.bold {
font-weight: bold;
}
.italic {
font-style: italic;
}
.border {
border: 2px solid v-bind(borderColor);
}
.rounded-left {
border-radius: 0.5rem 0 0 0.5rem;
}
.rounded-left-full {
border-radius: v-bind(buttonHeight) 0 0 v-bind(buttonHeight);
}
.rounded-right {
border-radius: 0 0.5rem 0.5rem 0;
}
.rounded-right-full {
border-radius: 0 v-bind(buttonHeight) v-bind(buttonHeight) 0;
}
.rounded-full {
border-radius: v-bind(buttonHeight);
}
</style>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment