Loading src/app/assets/main.css +31 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,15 @@ --orange-700: #c2410c; --orange-800: #9a3412; --orange-900: #7c2d12; --amber-100: #fef3c7; --amber-200: #fde68a; --amber-300: #fcd34d; --amber-400: #fbbf24; --amber-500: #f59e0b; --amber-600: #d97706; --amber-700: #b45309; --amber-800: #92400e; --amber-900: #78350f; --yellow-100: #fef9c3; --yellow-200: #fef08a; --yellow-300: #fde047; Loading Loading @@ -283,3 +292,25 @@ input[type=file]::-webkit-file-upload-button { transform: scale(1); } } .v-enter-active, .v-leave-active { transition: all 0.5s ease; } .fading-enter-active, .fading-leave-active { transition: opacity 0.3s ease-in-out; } .fading-enter-active { animation: fading-in 0.5s; } .fading-leave-active { animation: fading-in 0.5s reverse; } @keyframes fading-in { 0% { opacity: 0; } 100% { opacity: 1; } } src/app/components.d.ts +3 −0 Original line number Diff line number Diff line Loading @@ -46,9 +46,12 @@ declare module 'vue' { PageHeader: typeof import('./../modules/PageHeader.vue')['default'] PageMenuButton: typeof import('./../components/PageMenuButton.vue')['default'] ParagraphIcon: typeof import('./../shared/icons/ParagraphIcon.vue')['default'] ParagraphItem: typeof import('./../modules/entities/ParagraphItem.vue')['default'] ParagraphSettings: typeof import('./../components/entities/settings/ParagraphSettings.vue')['default'] PlusIcon: typeof import('./../shared/icons/PlusIcon.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] SaveIcon: typeof import('./../shared/icons/SaveIcon.vue')['default'] SettingsIcon: typeof import('./../shared/icons/SettingsIcon.vue')['default'] SheetPage: typeof import('./../pages/[uuid]/SheetPage.vue')['default'] SidebarMenuContent: typeof import('./../modules/SidebarMenuContent.vue')['default'] Loading src/app/helpers/index.ts +45 −0 Original line number Diff line number Diff line Loading @@ -100,6 +100,8 @@ export const convertThemeToColorWhiteDefault = (theme: string | undefined) => { return '#22c55e'; case 'yellow': return '#eab308'; case 'amber': return '#f59e0b'; case 'orange': return '#f97316'; case 'pink': Loading @@ -120,6 +122,47 @@ export const convertThemeToColorWhiteDefault = (theme: string | undefined) => { return '#ffffff'; }; export const convert800ThemeToColorGrayDefault = (theme: string | undefined) => { if (!theme) return '#9294a1'; switch (theme) { case 'white': return '#ffffff'; case 'slate': return '#1e293b'; case 'blue': return '#1e40af'; case 'sky': return '#075985'; case 'teal': return '#115e59'; case 'lime': return '#3f6212'; case 'green': return '#166534'; case 'yellow': return '#854d0e'; case 'amber': return '#92400e'; case 'orange': return '#9a3412'; case 'pink': return '#9d174d'; case 'fuchsia': return '#86198f'; case 'purple': return '#6b21a8'; case 'indigo': return '#3730a3'; case 'rose': return '#9f1239'; case 'red': return '#991b1b'; case 'black': return '#000000'; } return '#9294a1'; }; export const convertThemeToColorBlackDefault = (theme: string | undefined) => { if (!theme) return '#000000'; switch (theme) { Loading @@ -139,6 +182,8 @@ export const convertThemeToColorBlackDefault = (theme: string | undefined) => { return '#22c55e'; case 'yellow': return '#eab308'; case 'amber': return '#f59e0b'; case 'orange': return '#f97316'; case 'pink': Loading src/app/interfaces/environment.ts +1 −4 Original line number Diff line number Diff line Loading @@ -38,12 +38,10 @@ export interface IEntity { } export type TTheme = | 'white' | 'slate' | 'blue' | 'sky' | 'teal' | 'lime' | 'green' | 'yellow' | 'orange' Loading @@ -52,5 +50,4 @@ export type TTheme = | 'purple' | 'indigo' | 'rose' | 'red' | 'black'; | 'red'; src/components/entities/settings/ParagraphSettings.vue 0 → 100644 +207 −0 Original line number Diff line number Diff line <script setup lang="ts"> import { convertThemeToColorWhiteDefault, editEntity } from '@/app/helpers'; import type { IParagraph } from '@/app/interfaces/entities'; import type { TTheme } from '@/app/interfaces/environment'; import cookies from '@/app/plugins/Cookie'; interface Props { entityData: IParagraph; } const props = defineProps<Props>(); const emit = defineEmits(['saveChanges']); const entityData = props.entityData; let newEntityData = ref({ ...entityData }); const isModal = ref<boolean>(false); const changeFontSize = (newSize: '16' | '20' | '24' | '40' | '64') => { entityData.font_size = newSize; editEntity({ ...entityData, 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 maxLines = computed(() => { if (isTitle.value) { return Math.floor(168 / 24); } else { return Math.floor(240 / 24); } }); const entityPositionOptions = [ { label: 'left', value: 0 }, { label: 'center', value: 1 }, { label: 'right', value: 2 } ]; const entityTitlePositionOptions = [ { label: 'left', value: 0 }, { label: 'center', value: 1 }, { label: 'right', value: 2 } ]; const saveChanges = () => { const entityPosition = isEntityWidthFull.value ? 'full' : 'half'; if (entityPosition !== entityData.entity_position) { newEntityData.value.paragraph_size = entityPosition; } if (isTitle.value !== !!entityData.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); emit('saveChanges', newEntityData.value); } console.log('newEntityData.value :', newEntityData.value); isModal.value = false; }; </script> <template> <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" > <SettingsIcon color="white" size="25" /> </button> <Modal v-model:isVisible="isModal" theme="black" width="70%" ><template #header><h3 class="w-max mx-auto">Edit paragraph</h3></template> <div class="p-10 flex gap-16 items-center"> <ul class="flex gap-8 h-full" style="min-width: 35%"> <li class="flex flex-col items-center gap-4" style="min-width: 150px"> <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 </div> </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> <Slider 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" /> </div> </Transition> </div> </li> <li class="flex flex-col items-center gap-4" style="min-width: 150px"> <div> <p class="py-2 text-center">Paragraph width</p> <div class="flex items-center"> Half <ToggleSwitch v-model:isActive="isEntityWidthFull" class="mx-2" :theme="themeColor" :negativeTheme="themeColor" /> Full </div> </div> <div style="height: 108px" class="flex gap-8 items-center justify-between col-span-2"> <Transition name="fading"> <div v-show="!isEntityWidthFull" class="flex flex-col items-center"> <p class="py-2">Paragraph position</p> <Slider v-model:value="newEntityData.entity_position" :theme="themeColor" width="150" size="small" isSmooth="true" backgroundColor="white" min="0" max="2" step="1" :options="entityPositionOptions" /> </div> </Transition> </div> </li> </ul> <section :style="`border-color: var(--${themeColor}-200); height: 320px`" class="grow flex flex-col gap-4 p-4 min-h-full border-2 border-slate-100 border-dashed rounded-2xl" > <h3 v-show="isTitle" :style="`border-color: var(--${themeColor}-800); text-align: ${newEntityData.entity_title_position}`" class="text-2xl font-bold text-center px-2 py-4 border-2 border-dashed rounded-2xl" > {{ newEntityData.title ?? 'Title' }} </h3> <div :style="`justify-content: ${newEntityData.entity_position}`" class="grow flex"> <div :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 }}</p> </div> </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> .text { --max-lines: v-bind(maxLines); overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: var(--max-lines); } </style> Loading
src/app/assets/main.css +31 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,15 @@ --orange-700: #c2410c; --orange-800: #9a3412; --orange-900: #7c2d12; --amber-100: #fef3c7; --amber-200: #fde68a; --amber-300: #fcd34d; --amber-400: #fbbf24; --amber-500: #f59e0b; --amber-600: #d97706; --amber-700: #b45309; --amber-800: #92400e; --amber-900: #78350f; --yellow-100: #fef9c3; --yellow-200: #fef08a; --yellow-300: #fde047; Loading Loading @@ -283,3 +292,25 @@ input[type=file]::-webkit-file-upload-button { transform: scale(1); } } .v-enter-active, .v-leave-active { transition: all 0.5s ease; } .fading-enter-active, .fading-leave-active { transition: opacity 0.3s ease-in-out; } .fading-enter-active { animation: fading-in 0.5s; } .fading-leave-active { animation: fading-in 0.5s reverse; } @keyframes fading-in { 0% { opacity: 0; } 100% { opacity: 1; } }
src/app/components.d.ts +3 −0 Original line number Diff line number Diff line Loading @@ -46,9 +46,12 @@ declare module 'vue' { PageHeader: typeof import('./../modules/PageHeader.vue')['default'] PageMenuButton: typeof import('./../components/PageMenuButton.vue')['default'] ParagraphIcon: typeof import('./../shared/icons/ParagraphIcon.vue')['default'] ParagraphItem: typeof import('./../modules/entities/ParagraphItem.vue')['default'] ParagraphSettings: typeof import('./../components/entities/settings/ParagraphSettings.vue')['default'] PlusIcon: typeof import('./../shared/icons/PlusIcon.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] SaveIcon: typeof import('./../shared/icons/SaveIcon.vue')['default'] SettingsIcon: typeof import('./../shared/icons/SettingsIcon.vue')['default'] SheetPage: typeof import('./../pages/[uuid]/SheetPage.vue')['default'] SidebarMenuContent: typeof import('./../modules/SidebarMenuContent.vue')['default'] Loading
src/app/helpers/index.ts +45 −0 Original line number Diff line number Diff line Loading @@ -100,6 +100,8 @@ export const convertThemeToColorWhiteDefault = (theme: string | undefined) => { return '#22c55e'; case 'yellow': return '#eab308'; case 'amber': return '#f59e0b'; case 'orange': return '#f97316'; case 'pink': Loading @@ -120,6 +122,47 @@ export const convertThemeToColorWhiteDefault = (theme: string | undefined) => { return '#ffffff'; }; export const convert800ThemeToColorGrayDefault = (theme: string | undefined) => { if (!theme) return '#9294a1'; switch (theme) { case 'white': return '#ffffff'; case 'slate': return '#1e293b'; case 'blue': return '#1e40af'; case 'sky': return '#075985'; case 'teal': return '#115e59'; case 'lime': return '#3f6212'; case 'green': return '#166534'; case 'yellow': return '#854d0e'; case 'amber': return '#92400e'; case 'orange': return '#9a3412'; case 'pink': return '#9d174d'; case 'fuchsia': return '#86198f'; case 'purple': return '#6b21a8'; case 'indigo': return '#3730a3'; case 'rose': return '#9f1239'; case 'red': return '#991b1b'; case 'black': return '#000000'; } return '#9294a1'; }; export const convertThemeToColorBlackDefault = (theme: string | undefined) => { if (!theme) return '#000000'; switch (theme) { Loading @@ -139,6 +182,8 @@ export const convertThemeToColorBlackDefault = (theme: string | undefined) => { return '#22c55e'; case 'yellow': return '#eab308'; case 'amber': return '#f59e0b'; case 'orange': return '#f97316'; case 'pink': Loading
src/app/interfaces/environment.ts +1 −4 Original line number Diff line number Diff line Loading @@ -38,12 +38,10 @@ export interface IEntity { } export type TTheme = | 'white' | 'slate' | 'blue' | 'sky' | 'teal' | 'lime' | 'green' | 'yellow' | 'orange' Loading @@ -52,5 +50,4 @@ export type TTheme = | 'purple' | 'indigo' | 'rose' | 'red' | 'black'; | 'red';
src/components/entities/settings/ParagraphSettings.vue 0 → 100644 +207 −0 Original line number Diff line number Diff line <script setup lang="ts"> import { convertThemeToColorWhiteDefault, editEntity } from '@/app/helpers'; import type { IParagraph } from '@/app/interfaces/entities'; import type { TTheme } from '@/app/interfaces/environment'; import cookies from '@/app/plugins/Cookie'; interface Props { entityData: IParagraph; } const props = defineProps<Props>(); const emit = defineEmits(['saveChanges']); const entityData = props.entityData; let newEntityData = ref({ ...entityData }); const isModal = ref<boolean>(false); const changeFontSize = (newSize: '16' | '20' | '24' | '40' | '64') => { entityData.font_size = newSize; editEntity({ ...entityData, 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 maxLines = computed(() => { if (isTitle.value) { return Math.floor(168 / 24); } else { return Math.floor(240 / 24); } }); const entityPositionOptions = [ { label: 'left', value: 0 }, { label: 'center', value: 1 }, { label: 'right', value: 2 } ]; const entityTitlePositionOptions = [ { label: 'left', value: 0 }, { label: 'center', value: 1 }, { label: 'right', value: 2 } ]; const saveChanges = () => { const entityPosition = isEntityWidthFull.value ? 'full' : 'half'; if (entityPosition !== entityData.entity_position) { newEntityData.value.paragraph_size = entityPosition; } if (isTitle.value !== !!entityData.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); emit('saveChanges', newEntityData.value); } console.log('newEntityData.value :', newEntityData.value); isModal.value = false; }; </script> <template> <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" > <SettingsIcon color="white" size="25" /> </button> <Modal v-model:isVisible="isModal" theme="black" width="70%" ><template #header><h3 class="w-max mx-auto">Edit paragraph</h3></template> <div class="p-10 flex gap-16 items-center"> <ul class="flex gap-8 h-full" style="min-width: 35%"> <li class="flex flex-col items-center gap-4" style="min-width: 150px"> <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 </div> </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> <Slider 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" /> </div> </Transition> </div> </li> <li class="flex flex-col items-center gap-4" style="min-width: 150px"> <div> <p class="py-2 text-center">Paragraph width</p> <div class="flex items-center"> Half <ToggleSwitch v-model:isActive="isEntityWidthFull" class="mx-2" :theme="themeColor" :negativeTheme="themeColor" /> Full </div> </div> <div style="height: 108px" class="flex gap-8 items-center justify-between col-span-2"> <Transition name="fading"> <div v-show="!isEntityWidthFull" class="flex flex-col items-center"> <p class="py-2">Paragraph position</p> <Slider v-model:value="newEntityData.entity_position" :theme="themeColor" width="150" size="small" isSmooth="true" backgroundColor="white" min="0" max="2" step="1" :options="entityPositionOptions" /> </div> </Transition> </div> </li> </ul> <section :style="`border-color: var(--${themeColor}-200); height: 320px`" class="grow flex flex-col gap-4 p-4 min-h-full border-2 border-slate-100 border-dashed rounded-2xl" > <h3 v-show="isTitle" :style="`border-color: var(--${themeColor}-800); text-align: ${newEntityData.entity_title_position}`" class="text-2xl font-bold text-center px-2 py-4 border-2 border-dashed rounded-2xl" > {{ newEntityData.title ?? 'Title' }} </h3> <div :style="`justify-content: ${newEntityData.entity_position}`" class="grow flex"> <div :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 }}</p> </div> </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> .text { --max-lines: v-bind(maxLines); overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: var(--max-lines); } </style>