From 4f7d21fdefdc4303f910847d4795cce4d6724698 Mon Sep 17 00:00:00 2001 From: ckx6954 Date: Sat, 21 Dec 2024 19:13:34 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9C=A0=ED=8B=B8=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 14 ++++ package.json | 2 + src/assets/css/app-calendar.css | 105 ++++++++++++++++++++++++- src/common/axios-interceptor.js | 1 + src/common/convertImageToWebp.js | 129 +++++++++++++++++++++++++++++++ src/common/utils.js | 94 ++++++++++++++++++++++ 6 files changed, 344 insertions(+), 1 deletion(-) create mode 100644 src/common/convertImageToWebp.js diff --git a/package-lock.json b/package-lock.json index c2c4262..b68528b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,9 +20,11 @@ "dayjs": "^1.11.13", "flatpickr": "^4.6.13", "front": "file:", + "heic2any": "^0.0.4", "pinia": "^2.2.6", "pinia-plugin-persist": "^1.0.0", "quill": "^2.0.3", + "upload-images-converter": "^2.0.2", "vue": "^3.5.13", "vue-flatpickr-component": "^11.0.5", "vue-router": "^4.4.5" @@ -2977,6 +2979,12 @@ "node": ">= 0.4" } }, + "node_modules/heic2any": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/heic2any/-/heic2any-0.0.4.tgz", + "integrity": "sha512-3lLnZiDELfabVH87htnRolZ2iehX9zwpRyGNz22GKXIu0fznlblf0/ftppXKNqS26dqFSeqfIBhAmAj/uSp0cA==", + "license": "MIT" + }, "node_modules/hookable": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", @@ -4247,6 +4255,12 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/upload-images-converter": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upload-images-converter/-/upload-images-converter-2.0.2.tgz", + "integrity": "sha512-cltVwZ+RU70Qx8heZrSj8OpQhR9mFHZ6CXudosKkFlM91Iiznl8Oluw/x+9DTnSKkvipvkf1oCvyVw5UPIpZ1Q==", + "license": "MIT" + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index f452a1b..bba201d 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,11 @@ "dayjs": "^1.11.13", "flatpickr": "^4.6.13", "front": "file:", + "heic2any": "^0.0.4", "pinia": "^2.2.6", "pinia-plugin-persist": "^1.0.0", "quill": "^2.0.3", + "upload-images-converter": "^2.0.2", "vue": "^3.5.13", "vue-flatpickr-component": "^11.0.5", "vue-router": "^4.4.5" diff --git a/src/assets/css/app-calendar.css b/src/assets/css/app-calendar.css index fd733bb..48ee9d4 100644 --- a/src/assets/css/app-calendar.css +++ b/src/assets/css/app-calendar.css @@ -1 +1,104 @@ -.app-calendar-wrapper{position:relative;border-radius:.375rem}.app-calendar-wrapper .app-calendar-sidebar{position:absolute;overflow:hidden;flex-grow:0;flex-basis:18.75rem;left:calc(-18.75rem - 1.2rem);height:100%;width:18.75rem;transition:all .2s;z-index:4}.app-calendar-wrapper .app-calendar-sidebar.show{left:0}.app-calendar-wrapper .app-calendar-sidebar .flatpickr-calendar{box-shadow:none}.app-calendar-wrapper .app-calendar-sidebar .flatpickr-calendar .flatpickr-month,.app-calendar-wrapper .app-calendar-sidebar .flatpickr-calendar .flatpickr-weekday,.app-calendar-wrapper .app-calendar-sidebar .flatpickr-calendar .flatpickr-weekdays{background:rgba(0,0,0,0)}.app-calendar-wrapper .app-calendar-sidebar .flatpickr-calendar .flatpickr-days{border:0}.app-calendar-wrapper .app-calendar-sidebar .flatpickr-calendar:focus{outline:0}.app-calendar-wrapper .app-calendar-content{position:relative}.app-calendar-wrapper .fc-toolbar h2{font-size:1.5rem;line-height:2.375rem}@media(max-width: 767.98px){.app-calendar-wrapper .fc-toolbar h2{font-size:1rem}}.app-calendar-wrapper .fc-toolbar-chunk{overflow:auto}.app-calendar-wrapper table.fc-scrollgrid{border-left:0;border-right:0}.app-calendar-wrapper table.fc-scrollgrid th,.app-calendar-wrapper table.fc-scrollgrid td{border-right:0}.app-calendar-wrapper .fc-timeGridDay-view table.fc-scrollgrid tbody tr:not(.fc-scrollgrid-section:first-of-type) td,.app-calendar-wrapper .fc-timeGridWeek-view table.fc-scrollgrid tbody tr:not(.fc-scrollgrid-section:first-of-type) td{border-bottom:0}.app-calendar-wrapper .fc-dayGridMonth-view table.fc-scrollgrid td{border-bottom:0 !important}.app-calendar-wrapper .fc-header-toolbar{margin-bottom:1.5rem !important}.app-calendar-wrapper .fc-view-container{margin:0 -1.6rem}.app-calendar-wrapper .event-sidebar .ql-editor{min-height:5rem}.app-calendar-wrapper .event-sidebar .select2 .select2-selection__choice{display:flex}.app-calendar-wrapper .event-sidebar .select2 .select2-selection__choice .avatar{display:none}@media(min-width: 992px){.app-calendar-wrapper .app-calendar-sidebar{position:static;height:auto;background-color:rgba(0,0,0,0) !important}.app-calendar-wrapper .app-calendar-sidebar .flatpickr-days{background-color:rgba(0,0,0,0)}}[dir=rtl] .app-calendar-wrapper .fc .fc-toolbar .fc-sidebarToggle-button{order:1}[dir=rtl] .app-calendar-wrapper .app-calendar-sidebar{left:auto;right:calc(-18.75rem - 1.2rem)}[dir=rtl] .app-calendar-wrapper .app-calendar-sidebar.show{left:auto;right:0}.light-style .app-calendar-wrapper .app-calendar-sidebar{background-color:#fff}.dark-style .app-calendar-wrapper .app-calendar-sidebar{background-color:#2b2c40} +.app-calendar-wrapper { + position: relative; + border-radius: 0.375rem; +} +.app-calendar-wrapper .app-calendar-sidebar { + position: absolute; + overflow: hidden; + flex-grow: 0; + flex-basis: 18.75rem; + left: calc(-18.75rem - 1.2rem); + height: 100%; + width: 18.75rem; + transition: all 0.2s; + z-index: 4; +} +.app-calendar-wrapper .app-calendar-sidebar.show { + left: 0; +} +.app-calendar-wrapper .app-calendar-sidebar .flatpickr-calendar { + box-shadow: none; +} +.app-calendar-wrapper .app-calendar-sidebar .flatpickr-calendar .flatpickr-month, +.app-calendar-wrapper .app-calendar-sidebar .flatpickr-calendar .flatpickr-weekday, +.app-calendar-wrapper .app-calendar-sidebar .flatpickr-calendar .flatpickr-weekdays { + background: rgba(0, 0, 0, 0); +} +.app-calendar-wrapper .app-calendar-sidebar .flatpickr-calendar .flatpickr-days { + border: 0; +} +.app-calendar-wrapper .app-calendar-sidebar .flatpickr-calendar:focus { + outline: 0; +} +.app-calendar-wrapper .app-calendar-content { + position: relative; +} +.app-calendar-wrapper .fc-toolbar h2 { + font-size: 1.5rem; + line-height: 2.375rem; +} +@media (max-width: 767.98px) { + .app-calendar-wrapper .fc-toolbar h2 { + font-size: 1rem; + } +} +.app-calendar-wrapper .fc-toolbar-chunk { + overflow: auto; +} +.app-calendar-wrapper table.fc-scrollgrid { + border-left: 0; + border-right: 0; +} +.app-calendar-wrapper table.fc-scrollgrid th, +.app-calendar-wrapper table.fc-scrollgrid td { + border-right: 0; +} +.app-calendar-wrapper .fc-timeGridDay-view table.fc-scrollgrid tbody tr:not(.fc-scrollgrid-section:first-of-type) td, +.app-calendar-wrapper .fc-timeGridWeek-view table.fc-scrollgrid tbody tr:not(.fc-scrollgrid-section:first-of-type) td { + border-bottom: 0; +} +.app-calendar-wrapper .fc-dayGridMonth-view table.fc-scrollgrid td { + border-bottom: 0 !important; +} +.app-calendar-wrapper .fc-header-toolbar { + margin-bottom: 1.5rem !important; +} +.app-calendar-wrapper .fc-view-container { + margin: 0 -1.6rem; +} +.app-calendar-wrapper .event-sidebar .ql-editor { + min-height: 5rem; +} +.app-calendar-wrapper .event-sidebar .select2 .select2-selection__choice { + display: flex; +} +.app-calendar-wrapper .event-sidebar .select2 .select2-selection__choice .avatar { + display: none; +} +@media (min-width: 992px) { + .app-calendar-wrapper .app-calendar-sidebar { + position: static; + height: auto; + background-color: rgba(0, 0, 0, 0) !important; + } + .app-calendar-wrapper .app-calendar-sidebar .flatpickr-days { + background-color: rgba(0, 0, 0, 0); + } +} +[dir='rtl'] .app-calendar-wrapper .fc .fc-toolbar .fc-sidebarToggle-button { + order: 1; +} +[dir='rtl'] .app-calendar-wrapper .app-calendar-sidebar { + left: auto; + right: calc(-18.75rem - 1.2rem); +} +[dir='rtl'] .app-calendar-wrapper .app-calendar-sidebar.show { + left: auto; + right: 0; +} +.light-style .app-calendar-wrapper .app-calendar-sidebar { + background-color: #fff; +} +.dark-style .app-calendar-wrapper .app-calendar-sidebar { + background-color: #2b2c40; +} diff --git a/src/common/axios-interceptor.js b/src/common/axios-interceptor.js index c286882..6af0940 100644 --- a/src/common/axios-interceptor.js +++ b/src/common/axios-interceptor.js @@ -4,6 +4,7 @@ import router from "@/router/index"; const $api = axios.create({ baseURL: import.meta.env.VITE_API_URL, timeout: 300000, + withCredentials : true }) /** diff --git a/src/common/convertImageToWebp.js b/src/common/convertImageToWebp.js new file mode 100644 index 0000000..947c5ba --- /dev/null +++ b/src/common/convertImageToWebp.js @@ -0,0 +1,129 @@ +/** + image to webp + @사용법 + const webpFile = await convertImageToWebp(this.attach); + attach = webpFile[0]; +*/ +import heic2any from 'heic2any'; +import { imageConverter } from 'upload-images-converter'; + +const calculateAspectRatioSize = (originWidth, originHeight, maxWidthOrHeight) => { + const maxSize = Math.max(originWidth, originHeight); + + if (maxSize <= maxWidthOrHeight) { + return { width: originWidth, height: originHeight }; + } + + if (originWidth === maxSize) { + const width = maxWidthOrHeight; + const height = Number(((originHeight * maxWidthOrHeight) / originWidth).toFixed(1)); + + return { width, height }; + } + + const width = Number(((originWidth * maxWidthOrHeight) / originHeight).toFixed(1)); + const height = maxWidthOrHeight; + + return { width, height }; +}; + +const getImageSize = (imageFile) => { + return new Promise((resolve, reject) => { + if (!imageFile) { + resolve({ originWidth: 100, originHeight: 100 }); // Default size + return; + } + + const img = new Image(); + const reader = new FileReader(); + + img.onload = () => { + const originWidth = img.naturalWidth; + const originHeight = img.naturalHeight; + + resolve({ originWidth, originHeight }); + }; + + reader.onload = () => { + img.src = reader.result?.toString() ?? ''; + }; + + reader.readAsDataURL(imageFile); + + img.onerror = error => { + reject(error); + }; + + reader.onerror = error => { + reject(error); + }; + }); +}; + +const convertImageToWebp = async (imageFile) => { + if (typeof imageFile === 'string' || imageFile.name.split('.')[1].toLowerCase() === 'webp') { + return imageFile; + } + + if (!imageFile || !(imageFile instanceof Blob)) { + const placeholderBlob = new Blob([], { type: 'image/webp' }); + const placeholderFile = new File([placeholderBlob], `${Date.now().toString()}.webp`); + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(placeholderFile); + return dataTransfer.files; + } + + const fileExtension = imageFile.name.split('.').pop().toLowerCase(); + + if (fileExtension === 'heic') { + try { + const webpBlob = await heic2any({ + blob: imageFile, + toType: 'image/webp', + quality: 0.8, + }); + + const { originWidth, originHeight } = await getImageSize(webpBlob); + const maxWidthOrHeight = 1200; + + const { width, height } = calculateAspectRatioSize(originWidth, originHeight, maxWidthOrHeight); + + const compressedBlob = await imageConverter({ files: [webpBlob], width, height }); + + const outputWebpFile = new File([compressedBlob[0]], `${Date.now().toString()}.webp`); + + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(outputWebpFile); + + return dataTransfer.files; + } catch (error) { + console.error('HEIC 변환 오류:', error); + throw error; + } finally { + // 로딩있으면 여기서 끝 + } + } else { + try { + const { originWidth, originHeight } = await getImageSize(imageFile); + const maxWidthOrHeight = 1200; + + const { width, height } = calculateAspectRatioSize(originWidth, originHeight, maxWidthOrHeight); + + const compressedBlob = await imageConverter({ files: [imageFile], width, height }); + + const outputWebpFile = new File([compressedBlob[0]], `${Date.now().toString()}.webp`); + + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(outputWebpFile); + + return dataTransfer.files; + } catch (error) { + console.error('HEIC 변환 오류:', error); + throw error; + } finally { + // 로딩있으면 여기서 끝 + } + } +}; + +export { convertImageToWebp }; diff --git a/src/common/utils.js b/src/common/utils.js index 1dd59e1..4b6fbc3 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -5,3 +5,97 @@ export const isEmpty = (value) => { return true; return false; } + +export const isPC = () => { + const varUA = navigator.userAgent.toLowerCase(); + const isMobile = /iphone|ipad|ipod|android|blackberry|iemobile|opera mini/i.test(varUA); + return !isMobile; +} + +export const returnMimeTypes = (extension) => { + const mimeTypes = { + // Archive + '7z': 'application/x-7z-compressed', + 'gz': 'application/gzip', + 'tar': 'application/x-tar', + 'zip': 'application/zip', + + // Audio + 'aac': 'audio/aac', + 'mp3': 'audio/mpeg', + 'ogg': 'audio/ogg', + 'opus': 'audio/opus', + 'wav': 'audio/wav', + 'webm': 'audio/webm', + + // Image + 'bmp': 'image/bmp', + 'gif': 'image/gif', + 'ico': 'image/x-icon', + 'jpeg': 'image/jpeg', + 'jpg': 'image/jpeg', + 'png': 'image/png', + 'svg': 'image/svg+xml', + 'tiff': 'image/tiff', + 'webp': 'image/webp', + + // Video + 'avi': 'video/x-msvideo', + 'mp4': 'video/mp4', + 'mpeg': 'video/mpeg', + 'ogg': 'video/ogg', + 'webm': 'video/webm', + 'mov': 'video/quicktime', + 'wmv': 'video/x-ms-wmv', + + // Document + 'doc': 'application/msword', + 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'epub': 'application/epub+zip', + 'html': 'text/html', + 'htm': 'text/html', + 'odt': 'application/vnd.oasis.opendocument.text', + 'pdf': 'application/pdf', + 'ppt': 'application/vnd.ms-powerpoint', + 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'rtf': 'application/rtf', + 'txt': 'text/plain', + 'xls': 'application/vnd.ms-excel', + 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xml': 'application/xml', + 'json': 'application/json', + 'csv': 'text/csv', + + // Code / Script + 'css': 'text/css', + 'js': 'text/javascript', + 'json': 'application/json', + 'php': 'application/x-httpd-php', + 'xml': 'application/xml', + + // Font + 'woff': 'font/woff', + 'woff2': 'font/woff2', + 'ttf': 'font/ttf', + 'otf': 'font/otf', + + // Web + 'jsonld': 'application/ld+json', + 'webmanifest': 'application/manifest+json', + + // Others + 'bin': 'application/octet-stream', + 'exe': 'application/octet-stream', + 'apk': 'application/vnd.android.package-archive', + 'dll': 'application/x-msdownload', + 'iso': 'application/x-iso9660-image', + 'msi': 'application/x-msi', + 'torrent': 'application/x-bittorrent', + + // Unknown + 'default': 'application/octet-stream' + } + + const ext = extension.trim().toLowerCase(); + return mimeTypes[ext] || mimeTypes['default']; +};