File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
/*
* Copyright (c) FOX EDU CO., LTD ALL RIGHT RESERVED.
* by flashkid
* 25.SEP.2024(WED)
*/
export class colouredPenciles {
constructor(btnInit, objArr, coloursArr, coloursSelect = 1, brushMin = 0.5, brushMax = 10, saveDataURL = '', loadDataURL = '', deleteDataURL = '', dataParam = {}) {
var _a;
this.svgNS = "http://www.w3.org/2000/svg";
this.ctrlCon = null;
this.targetCon = null;
this.targetArr = [];
this.ifrObj = null;
this.ifrDoc = null;
this.ifrContents = null;
this.ifrContentsType = "etc";
this.svg = null;
this.eraseObj = null;
this.previewObj = null;
this.loadedDrawingData = false;
this.drawingGroup = null;
this.maskingGroup = null;
this.drawPathElement = null;
this.eraserPathElement = null;
this.undoStack = [];
this.redoStack = [];
this.currentPath = [];
this.drawing = false;
this.isErasing = false;
this.detectTargetObj = () => {
let tobj, i = 0;
while (!tobj && i <= this.targetArr.length) {
tobj = document.querySelector(`.${this.targetArr[i]}`);
i++;
}
if (tobj) {
this.targetCon = tobj;
this.targetCon.classList.add('painter-target');
}
};
// Function to handle the scroll event
this.onScroll = () => {
var _a;
if (['img', 'iframe', 'etc', 'unknown'].includes(this.ifrContentsType) && ((_a = this.ifrObj) === null || _a === void 0 ? void 0 : _a.contentWindow) && this.targetCon) {
this.ifrObj.contentWindow.scrollTo(this.targetCon.scrollLeft, this.targetCon.scrollTop);
}
else if (this.ifrContents && this.targetCon) {
this.ifrContents.scrollTo(this.targetCon.scrollLeft, this.targetCon.scrollTop);
}
};
// 아이프레임 온로드
this.onIfrLoad = () => {
var _a, _b, _c;
if (this.btnInit.classList.contains('is-active')) {
this.closeDrawing();
this.onResize();
}
const ifrDoc = ((_b = (_a = this.ifrObj) === null || _a === void 0 ? void 0 : _a.contentWindow) === null || _b === void 0 ? void 0 : _b.document) || ((_c = this.ifrObj) === null || _c === void 0 ? void 0 : _c.contentDocument);
if (!ifrDoc)
return;
this.ifrDoc = ifrDoc;
// ifrContents 선언
this.ifrContents = this.ifrDoc.querySelector('#contents2') || this.ifrDoc.querySelector('.viewer .viewer-body .viewer-cont .quiz-viewer .quiz-viewer-body') || null;
if (this.ifrContents.classList.contains('quiz-viewer-body')) {
this.ifrContentsType = 'quiz';
this.setBaseSize({
w: this.ifrContents.querySelector('.quiz-viewer-cont').clientWidth,
h: this.ifrContents.scrollHeight
});
this.setBaseSize({
x: -this.ifrContents.querySelector('.quiz-viewer-cont').clientWidth / 2,
y: 0
});
}
else if (this.ifrContents.id === 'contents2') {
const video = this.ifrContents.querySelector('video');
const img = this.ifrContents.querySelector('img');
const iframe = this.ifrContents.querySelector('iframe');
const youtube = this.ifrContents.querySelector('input#tempCntntsUrl');
const whiteboard = this.ifrContents.querySelector('.whiteboard');
if (video) {
// 비디오 태그가 있을 경우
this.ifrContentsType = 'video';
this.setBaseSize({ x: -video.videoWidth / 2, y: -video.videoHeight / 2, w: video.videoWidth, h: video.videoHeight });
}
else if (img) {
// 이미지 태그가 있을 경우
this.ifrContentsType = 'img';
this.setBaseSize({ x: -img.naturalWidth / 2, y: -img.naturalHeight / 2, w: img.naturalWidth, h: img.naturalHeight });
}
else if (iframe) {
// 아이프레임이 있을 경우
this.ifrContentsType = 'iframe';
this.setBaseSize({ x: -iframe.clientWidth / 2, y: 0, w: iframe.clientWidth, h: iframe.scrollHeight });
}
else if (youtube) {
// 유튜브 프레임이 들어온 경우
this.ifrContentsType = 'youtube';
this.setBaseSize({ x: -960, y: -540, w: 1920, h: 1080 });
}
else if (whiteboard) {
// 화이트보드가 있을 경우
this.ifrContentsType = 'whiteboard';
this.setBaseSize({ x: -whiteboard.clientWidth / 2, y: -whiteboard.clientHeight / 2, w: whiteboard.clientWidth, h: whiteboard.clientHeight });
}
else {
this.ifrContentsType = 'etc';
this.setBaseSize({ w: this.ifrContents.clientWidth, h: this.ifrContents.scrollHeight });
this.setBaseSize({
x: -this.ifrContents.clientWidth / 2,
y: 0
});
}
}
else {
this.ifrContentsType = 'unknown';
this.setBaseSize({ w: this.ifrContents.clientWidth, h: this.ifrContents.scrollHeight });
this.setBaseSize({
x: -this.ifrContents.clientWidth / 2,
y: 0
});
}
};
this.onResize = () => {
if (!this.ifrDoc || !this.svg || !this.ifrContents)
return;
const hasYScroll = this.ifrContents.scrollHeight > this.ifrContents.clientHeight;
if (hasYScroll) {
switch (this.ifrContentsType) {
case 'img':
// this.svg.style.height = `${this.ifrContents.scrollHeight}px`;
// this.setViewBox(this.baseSize);
// break;
case 'quiz':
this.svg.style.height = `${this.ifrContents.scrollHeight}px`;
this.setViewBox(this.baseSize);
break;
case 'iframe':
case 'unknown':
case 'etc':
this.setViewBox({ x: -this.ifrContents.clientWidth / 2, w: this.ifrContents.clientWidth, h: this.ifrContents.scrollHeight });
break;
case 'video':
case 'youtube':
case 'whiteboard':
default:
this.setViewBox(this.baseSize);
break;
}
}
else {
this.svg.style.height = ``;
switch (this.ifrContentsType) {
case 'quiz':
this.svg.style.height = `${this.ifrContents.scrollHeight}px`;
this.setViewBox({ h: this.ifrContents.scrollHeight });
break;
case 'iframe':
case 'unknown':
case 'etc':
this.setViewBox({ x: -this.ifrContents.clientWidth / 2, w: this.ifrContents.clientWidth, h: this.ifrContents.scrollHeight });
break;
case 'img':
case 'video':
case 'youtube':
case 'whiteboard':
default:
this.setViewBox(this.baseSize);
break;
}
}
};
this.setBaseSize = (newBaseSize) => {
this.baseSize = Object.assign(Object.assign({}, this.baseSize), newBaseSize); // Merge with the existing baseSize
};
this.setViewBox = (newSize) => {
const newBaseSize = Object.assign(Object.assign({}, this.baseSize), newSize);
if (!this.svg)
return;
this.svg.setAttribute("viewBox", `${newBaseSize.x} ${newBaseSize.y} ${newBaseSize.w} ${newBaseSize.h}`);
};
this.createDrawingGroup = () => {
var _a;
const drawingGroup = document.createElementNS(this.svgNS, 'g');
(_a = this.svg) === null || _a === void 0 ? void 0 : _a.append(drawingGroup);
this.drawingGroup = drawingGroup;
};
this.createMaskingGroup = () => {
var _a, _b;
const maskingGroup = document.createElementNS(this.svgNS, 'mask');
maskingGroup.id = 'eraser-mask';
(_a = this.svg) === null || _a === void 0 ? void 0 : _a.append(maskingGroup);
this.maskingGroup = maskingGroup;
(_b = this.drawingGroup) === null || _b === void 0 ? void 0 : _b.setAttribute('mask', 'url(#eraser-mask)');
// 흰색 배경 (지우개 영역은 투명)
const maskRect = document.createElementNS(this.svgNS, "rect");
maskRect.setAttribute("fill", "white");
maskRect.setAttribute("x", `${this.baseSize.x * 100}`);
maskRect.setAttribute("y", `${this.baseSize.y * 100}`);
maskRect.setAttribute("width", `${this.baseSize.w * 100}`);
maskRect.setAttribute("height", `${this.baseSize.h * 100}`);
this.maskingGroup.appendChild(maskRect);
};
// 콘트롤 패널 드래그 앤 드롭 기능 추가
// this.enableDrag = (element) => {
// let offsetX = 0;
// let offsetY = 0;
// let isDragging = false;
// const onMouseDown = (e) => {
// var _a;
// e.preventDefault();
// isDragging = true;
// // 현재 요소의 fixed 위치 계산
// const rect = element.getBoundingClientRect();
// const targetConRect = (_a = this.targetCon) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
// if (!targetConRect)
// return;
// // 마우스 포인터와 요소의 **오른쪽** 및 **위쪽** 경계와의 거리 계산
// // offsetX = rect.right - e.clientX;
// // offsetY = e.clientY - rect.top;
// // // 요소의 스타일을 고정 위치에 맞추어 설정
// // element.style.right = `${window.innerWidth - rect.right}px`; // right 기준으로 위치 지정
// // element.style.top = `${rect.top}px`;
// // element.style.left = 'auto'; // left 속성 제거
// // element.style.transform = 'none'; // 기존 translateY 제거
// // element.style.transition = 'none';
// };
// const onMouseMove = (e) => {
// var _a;
// if (!isDragging)
// return;
// e.preventDefault();
// // painter-target 영역의 크기 가져오기
// const containerRect = (_a = this.targetCon) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
// if (!containerRect)
// return;
// // 새로운 위치 계산
// // let right = window.innerWidth - e.clientX - offsetX;
// // let y = e.clientY - offsetY;
// // // 요소의 x, y 위치가 painter-target 영역을 넘지 않도록 제한
// // right = Math.max(0, Math.min(right, window.innerWidth - containerRect.left - element.offsetWidth));
// // y = Math.max(containerRect.top, Math.min(y, containerRect.bottom - element.offsetHeight));
// // // 요소의 위치 업데이트 (오른쪽 기준)
// // element.style.right = `${right}px`;
// // element.style.top = `${y}px`;
// };
// const onMouseUp = () => {
// isDragging = false;
// element.style.transition = '';
// };
// // 이벤트 리스너 등록
// element.addEventListener('mousedown', onMouseDown);
// document.addEventListener('mousemove', onMouseMove);
// document.addEventListener('mouseup', onMouseUp);
// };
this.uiUpdate = () => {
if (!this.targetCon)
return; // targetCon이 null인지 확인
this.btnInit.classList.add('is-active');
this.targetCon.classList.add('is-active');
this.ctrlCon = document.createElement('aside');
this.ctrlCon.classList.add('painterbar');
this.targetCon.append(this.ctrlCon);
// 드래그 기능 활성화
//this.enableDrag(this.ctrlCon);
const minBtn = document.createElement('button');
minBtn.type = "button";
minBtn.classList.add('btn-painterbar-size');
minBtn.textContent = '색연필 최소화';
this.ctrlCon.append(minBtn);
minBtn.onclick = () => {
var _a;
(_a = this.ctrlCon) === null || _a === void 0 ? void 0 : _a.classList.toggle('min'); // null 체크
};
const previewCon = document.createElement('div');
previewCon.classList.add('pencil-ctrl-options', 'pencil-preview');
previewCon.style.width = `${this.sizeRange['max'] * 0.1 + 0.2}rem`;
previewCon.style.height = `${this.sizeRange['max'] * 0.1 + 0.2}rem`;
this.ctrlCon.append(previewCon);
this.previewObj = document.createElement('div');
previewCon.append(this.previewObj);
const sizeCon = document.createElement('div');
sizeCon.classList.add('pencil-ctrl-options', 'painterbar-stroke');
this.ctrlCon.append(sizeCon);
const range = document.createElement('input');
range.type = "range";
range.min = this.sizeRange['min'].toString();
range.max = this.sizeRange['max'].toString();
range.step = "0.1";
range.setAttribute("orient", "vertical");
range.value = this.size.toString();
sizeCon.append(range);
// range input 이벤트에서 드래그 앤 드롭과 충돌 방지
range.addEventListener('mousedown', (e) => e.stopPropagation());
range.addEventListener('mousemove', (e) => e.stopPropagation());
range.addEventListener('mouseup', (e) => e.stopPropagation());
range.oninput = () => this.changeSize(Number(range.value)); // 값 변환
const fncCon = document.createElement('div');
fncCon.classList.add('pencil-ctrl-options', 'painterbar-fnc');
this.ctrlCon.append(fncCon);
const undo = document.createElement('button');
undo.type = "button";
undo.classList.add('undo');
undo.textContent = "undo";
fncCon.append(undo);
undo.addEventListener('click', this.undo);
const redo = document.createElement('button');
redo.type = "button";
redo.classList.add('redo');
redo.textContent = "redo";
fncCon.append(redo);
redo.addEventListener('click', this.redo);
this.eraseObj = document.createElement('input');
this.eraseObj.type = "checkbox";
this.eraseObj.id = "colouredPencils_eraser";
this.eraseObj.classList.add('clear');
this.eraseObj.checked = this.isErasing;
fncCon.append(this.eraseObj);
const eraserLB = document.createElement('label');
eraserLB.setAttribute("for", this.eraseObj.id);
eraserLB.textContent = "지우개";
fncCon.append(eraserLB);
this.eraseObj.onchange = () => {
this.switchEraser();
};
const reset = document.createElement('button');
reset.type = 'button';
reset.classList.add('reset');
reset.textContent = 'reset';
fncCon.append(reset);
reset.onclick = () => this.reset();
// const save = document.createElement('button');
// save.type = 'button';
// save.classList.add('save');
// save.textContent = 'save';
// fncCon.append(save);
// save.onclick = () => this.save();
const colorDiv = document.createElement('div');
colorDiv.classList.add('pencil-ctrl-options', 'painterbar-color');
colorDiv.setAttribute('data-opt', this.coloursArr[this.coloursSelect]);
this.ctrlCon.append(colorDiv);
this.coloursArr.forEach((el, i) => {
const radio = document.createElement('input');
radio.type = "radio";
radio.name = "colouredPencils_colour";
radio.id = "colouredPencils_colour_" + i;
radio.value = i.toString();
if (i === this.coloursSelect)
radio.checked = true;
colorDiv.append(radio);
const label = document.createElement('label');
label.setAttribute("for", radio.id);
label.textContent = el;
label.style.backgroundColor = el;
if (el === '#ffffff')
label.classList.add('white');
colorDiv.append(label);
radio.onchange = () => {
this.changeColour(i);
};
});
const close = document.createElement('button');
close.type = "button";
close.classList.add('btn-viewer-painter-close');
close.textContent = "색연필 닫기";
this.ctrlCon.append(close);
close.onclick = () => {
this.save();
this.closeDrawing();
};
this.updatePreview();
};
this.undo = () => {
if (this.undoStack.length === 0)
return;
const paths = this.undoStack.pop();
if (!paths)
return;
Array.isArray(paths) ? paths.forEach(path => path.remove()) : {};
this.redoStack.push(paths);
};
this.redo = () => {
if (this.redoStack.length === 0)
return;
const paths = this.redoStack.pop();
if (!paths)
return;
// Check if drawingGroup and maskingGroup are not null
if (!this.drawingGroup || !this.maskingGroup)
return;
Array.isArray(paths)
? paths.forEach(path => {
if (path.dataset.use === 'draw') {
this.drawingGroup.append(path); // Add null check with "!" to ensure not null
}
else {
this.maskingGroup.append(path); // Add null check
}
})
: {};
this.undoStack.push(paths);
};
this.switchEraser = () => {
this.isErasing = !this.isErasing;
if (this.eraseObj) {
this.eraseObj.checked = this.isErasing;
}
this.updatePreview();
};
this.reset = () => {
this.undoStack = [];
this.redoStack = [];
if (this.maskingGroup) {
const maskPaths = this.maskingGroup.querySelectorAll('path');
maskPaths.forEach(path => path.remove());
}
if (this.drawingGroup) {
const drawPaths = this.drawingGroup.querySelectorAll('path');
drawPaths.forEach(path => path.remove());
}
};
this.save = () => {
if (!this.drawingGroup)
return;
const paths = this.drawingGroup.querySelectorAll('path');
const drawingData = [];
paths.forEach(path => {
const pathData = [
path.getAttribute('stroke'),
parseFloat(path.getAttribute('stroke-width') || '0'),
path.getAttribute('d'),
];
drawingData.push(pathData);
});
if (drawingData.length === 0) {
if (this.loadedDrawingData) {
// 그린 데이터가 없는데 로드된 데이터가 있으면 삭제
this.deleteDrawingData();
}
return;
}
if (this.maskingGroup) {
const maskPaths = this.maskingGroup.querySelectorAll('path');
const maskData = [];
maskPaths.forEach(path => {
const maskPathData = [
path.getAttribute('stroke'),
parseFloat(path.getAttribute('stroke-width') || '0'),
path.getAttribute('d'),
];
maskData.push(maskPathData);
});
this.saveDrawingData({ drawingData, maskData });
}
};
this.changeSize = (size) => {
this.size = Number(size); // Ensure the size is converted to a number
this.updatePreview();
};
this.changeColour = (key) => {
this.coloursSelect = key;
if (this.isErasing) {
this.switchEraser();
}
this.updatePreview();
};
this.updatePreview = () => {
if (this.previewObj) {
this.previewObj.style.width = `${this.size * 0.1}rem`;
this.previewObj.style.height = `${this.size * 0.1}rem`;
if (this.coloursArr[this.coloursSelect] === '#ffffff') {
this.previewObj.classList.add('white');
}
else {
this.previewObj.classList.remove('white');
}
if (this.isErasing) {
this.previewObj.classList.add('erase');
}
else {
this.previewObj.classList.remove('erase');
this.previewObj.style.backgroundColor = `${this.coloursArr[this.coloursSelect]}`;
}
}
};
this.closeDrawing = () => {
var _a;
if (this.ctrlCon) {
this.ctrlCon.remove();
this.ctrlCon = null;
}
if (this.drawingGroup)
this.drawingGroup = null;
if (this.maskingGroup)
this.maskingGroup = null;
if (this.svg && this.targetCon) {
this.targetCon.removeChild(this.svg);
this.svg = null;
}
this.isErasing = false;
this.btnInit.classList.remove('is-active');
(_a = this.targetCon) === null || _a === void 0 ? void 0 : _a.classList.remove('is-active');
// resize 이벤트 리스너 제거
window.removeEventListener('resize', this.onResize);
// beforeunload 이벤트 리스너 제거
// window.removeEventListener('beforeunload', this.beforeUnloadHandler);
};
this.createDrawEvents = () => {
if (!this.svg)
return;
this.svg.addEventListener("mousedown", this.pressEventHandler);
this.svg.addEventListener("mousemove", this.dragEventHandler);
this.svg.addEventListener("mouseup", this.releaseEventHandler);
this.svg.addEventListener("mouseout", this.cancelEventHandler);
this.svg.addEventListener("touchstart", this.pressEventHandler);
this.svg.addEventListener("touchmove", this.dragEventHandler);
this.svg.addEventListener("touchend", this.releaseEventHandler);
this.svg.addEventListener("touchcancel", this.cancelEventHandler);
};
// line 대신 path 사용으로 메모리 사용량 최적화
this.releaseEventHandler = () => {
this.drawing = false;
if (this.drawPathElement) {
this.currentPath.push(this.drawPathElement);
}
if (this.eraserPathElement) {
this.currentPath.push(this.eraserPathElement);
}
if (this.currentPath.length > 0) {
this.undoStack.push(this.currentPath);
this.redoStack = [];
}
this.drawPathElement = null; // 현재 그리던 path 초기화
this.eraserPathElement = null; // 현재 그리던 path 초기화
this.currentPath = [];
// 드로잉이 끝나면 다시 기본 동작을 허용
this.preventPageInteractions(true);
// 드로잉이 끝나면 다시 painterbar의 pointer-events 활성
this.disablePointerEvents(this.ctrlCon, false);
};
this.cancelEventHandler = () => {
this.releaseEventHandler();
};
this.pressEventHandler = (e) => {
var _a;
this.drawing = true;
const newPoint = this.getMousePosition(e);
// 드로잉 중에는 페이지에서 텍스트 선택 및 드래그가 되지 않도록 차단
this.preventPageInteractions(false);
// 드로잉 중에는 painterbar의 pointer-events 비활성
this.disablePointerEvents(this.ctrlCon, true);
// 드로잉 또는 지우기 동작 처리
const strokeColor = this.isErasing ? 'black' : this.coloursArr[this.coloursSelect];
this.startDrawing(strokeColor, this.size, this.isErasing, newPoint);
// 마스킹 그룹에 path 가 있을 경우, 마스킹 그룹에도 path 추가
if (!this.isErasing && ((_a = this.maskingGroup) === null || _a === void 0 ? void 0 : _a.querySelector('path'))) {
this.startDrawing('white', this.size, true, newPoint);
}
};
this.dragEventHandler = (e) => {
if (!this.drawing)
return;
const newPoint = this.getMousePosition(e);
// 새로운 path 요소 그리기
if (this.drawPathElement) {
this.drawPath(`L ${newPoint.x} ${newPoint.y}`);
}
if (this.eraserPathElement) {
this.drawPath(`L ${newPoint.x} ${newPoint.y}`, true);
}
e.preventDefault();
};
// 새로운 path를 생성하고 시작 지점을 그리는 함수
this.startDrawing = (strokeColor, strokeWidth, eraser, point) => {
this.createNewPath(strokeColor, strokeWidth, eraser);
this.drawPath(`M ${point.x} ${point.y} L ${point.x + 0.01} ${point.y}`, eraser);
};
this.createNewPath = (strokeColor, strokeWidth, eraser = false) => {
var _a, _b;
// 새로운 path 요소 생성
const pathElement = document.createElementNS(this.svgNS, 'path');
pathElement.setAttribute('fill', 'none');
pathElement.setAttribute('stroke', strokeColor);
pathElement.setAttribute('stroke-width', `${strokeWidth}`);
pathElement.setAttribute('stroke-linecap', 'round');
pathElement.setAttribute('d', '');
if (!eraser) {
pathElement.setAttribute('data-use', 'draw');
(_a = this.drawingGroup) === null || _a === void 0 ? void 0 : _a.append(pathElement);
this.drawPathElement = pathElement;
}
else {
(_b = this.maskingGroup) === null || _b === void 0 ? void 0 : _b.append(pathElement);
this.eraserPathElement = pathElement;
}
};
this.drawPath = (d, eraser = false) => {
const pathElement = eraser ? this.eraserPathElement : this.drawPathElement;
// 새로운 path 요소 그리기
const od = pathElement.getAttribute('d');
pathElement.setAttribute('d', `${od} ${d}`);
};
this.getMousePosition = (e) => {
var _a;
if (!this.svg)
return new DOMPoint(0, 0); // Add a fallback value in case svg is null
const point = this.svg.createSVGPoint();
point.x = e.changedTouches ? e.changedTouches[0].clientX : e.clientX;
point.y = e.changedTouches ? e.changedTouches[0].clientY : e.clientY;
return point.matrixTransform((_a = this.svg.getScreenCTM()) === null || _a === void 0 ? void 0 : _a.inverse()); // Convert screen coordinates to SVG coordinates
};
// 페이지의 기본 선택 및 드래그 동작을 활성/비활성화하는 함수
this.preventPageInteractions = (state) => {
if (state) {
document.onselectstart = null; // 텍스트 선택 차단 해제
document.ondragstart = null; // 드래그 차단 해제
}
else {
document.onselectstart = () => false; // 텍스트 선택 차단
document.ondragstart = () => false; // 드래그 차단
}
};
// pointer-events를 활성/비활성화하는 함수
this.disablePointerEvents = (target, state) => {
if (!target)
return;
target.style.pointerEvents = state ? 'none' : '';
};
// 데이터 가져오기
this.loadDrawingData = () => {
if (this.loadDataUrl === '') {
// 세션스토리지에서 데이터 가져오기
const userKey = this.dataParam.userKey;
const contentsKey = this.dataParam.contentsKey;
if (userKey && contentsKey) {
const storedData = sessionStorage.getItem(`drawingData-${userKey}-${contentsKey}`);
if (storedData) {
const parsedData = JSON.parse(storedData);
const { drawingData, maskData } = parsedData;
// 드로잉 데이터를 복원
if (drawingData && this.drawingGroup) {
drawingData.forEach((line) => {
this.createNewPath(line[0], line[1]);
this.drawPath(line[2]);
});
}
// 마스크 데이터를 복원
if (maskData && this.maskingGroup) {
maskData.forEach((line) => {
this.createNewPath(line[0], line[1], true);
this.drawPath(line[2], true);
});
}
this.loadedDrawingData = true;
console.log('Data loaded from session storage');
}
else {
this.loadedDrawingData = false;
console.log('No data found in session storage');
}
}
else {
console.error('userKey or contentsKey is missing in dataParam');
}
return;
}
// fetch(this.loadDataUrl, {
// method: 'GET',
// headers: {
// 'Content-Type': 'application/json',
// }
// })
fetch(this.loadDataUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(this.dataParam),
})
.then(response => response.json())
.then(data => {
const { drawingData, maskData } = data;
if (!drawingData || drawingData && drawingData.length === 0) {
this.loadedDrawingData = false;
return;
}
if (drawingData && this.drawingGroup) {
drawingData.forEach((line) => {
this.createNewPath(line[0], line[1]);
this.drawPath(line[2]);
});
}
if (maskData && this.maskingGroup) {
maskData.forEach((line) => {
this.createNewPath(line[0], line[1], true);
this.drawPath(line[2], true);
});
}
this.loadedDrawingData = true;
})
.catch(error => console.error('Error loading drawing data:', error));
};
// 데이터 저장
this.saveDrawingData = (data) => {
const params = Object.assign(Object.assign({}, this.dataParam), data);
console.log('save data :', params);
console.log(this.dataParam);
// saveDataUrl이 비어 있으면 세션스토리지에 저장
if (this.saveDataUrl === '') {
if ('userKey' in params && 'contentsKey' in params) {
sessionStorage.setItem(`drawingData-${params['userKey']}-${params['contentsKey']}`, JSON.stringify(params));
console.log('Data saved to session storage');
}
else {
console.error('userKey or contentsKey is missing in params');
}
return;
}
fetch(this.saveDataUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
}).then(response => this.reset())
.catch(error => console.error('Error saving drawing data:', error));
};
// 저장된 데이터 삭제
this.deleteDrawingData = () => {
const params = Object.assign(Object.assign({}, this.dataParam));
// deleteDataUrl이 비어 있으면 세션스토리지를 확인하여 삭제
if (this.deleteDataUrl === '') {
if ('userKey' in params && 'contentsKey' in params) {
sessionStorage.removeItem(`drawingData-${params['userKey']}-${params['contentsKey']}`);
console.log('Data deleted to session storage');
}
else {
console.error('userKey or contentsKey is missing in params');
}
return;
}
fetch(this.deleteDataUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(this.dataParam),
})
.then(response => {
// 먼저 text로 받아서 확인
return response.text().then(text => {
console.log("서버 응답 내용:\n", text);
try {
return JSON.parse(text);
} catch (err) {
console.error("⚠ JSON 파싱 실패: ", err);
throw new Error("서버에서 JSON이 아닌 HTML 응답을 받음");
}
});
})
.then(data => {
console.log("정상적으로 파싱된 JSON:", data);
})
.catch(error => {
console.error("Fetch 처리 중 에러:", error);
});
};
// 창 닫기 이벤트에서 save() 호출
this.beforeUnloadHandler = (event) => {
this.save();
event.preventDefault();
// // 경고 메시지 표시 (선택 사항)
// const confirmationMessage = '색연필로 작성한 내용이 저장되지 않을 수 있습니다.';
// event.returnValue = confirmationMessage;
// return confirmationMessage;
};
this.btnInit = btnInit;
this.targetArr = objArr;
this.coloursArr = coloursArr.map(c => c.toLowerCase());
this.coloursSelect = coloursSelect - 1;
this.baseSize = { x: -800, y: -450, w: 1600, h: 900 };
brushMax = brushMax > 60 ? 60 : brushMax;
this.sizeRange = {
min: brushMin,
max: brushMax
};
this.size = Math.round((brushMin + brushMax) / 3);
this.saveDataUrl = saveDataURL;
this.loadDataUrl = loadDataURL;
this.deleteDataUrl = deleteDataURL;
this.dataParam = dataParam;
this.btnInit.onclick = () => {
var _a, _b;
if (this.btnInit.classList.contains('is-active')) {
this.save();
this.closeDrawing();
}
else {
this.detectTargetObj();
const svg = document.createElementNS(this.svgNS, 'svg');
this.svg = svg;
this.svg.id = 'painterSvg';
this.svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
(_a = this.targetCon) === null || _a === void 0 ? void 0 : _a.append(this.svg);
this.createDrawingGroup(); // 드로잉 그룹 생성
this.createMaskingGroup(); // 마스크 생성
this.uiUpdate();
this.createDrawEvents();
this.loadDrawingData();
this.onResize();
// Listen for scroll event
(_b = this.targetCon) === null || _b === void 0 ? void 0 : _b.addEventListener('scroll', this.onScroll);
}
};
const ifrObj = document.querySelector('.iframe-area iframe');
this.ifrObj = ifrObj;
(_a = this.ifrObj) === null || _a === void 0 ? void 0 : _a.addEventListener('load', this.onIfrLoad);
window.addEventListener('resize', this.onResize);
// 창을 닫거나 새로고침할 때 save() 호출
// window.addEventListener('beforeunload', this.beforeUnloadHandler);
//window.onbeforeunload = this.beforeUnloadHandler;
document.addEventListener("visibilitychange", (event) => {
if (document.visibilityState === 'hidden') {
this.beforeUnloadHandler(event); // 사용자가 페이지를 떠나면 저장
}
});
}
// Getter for dataParam
getDataParam() {
return this.dataParam;
}
// Setter for dataParam
setDataParam(newDataParam) {
this.dataParam = Object.assign(Object.assign({}, this.dataParam), newDataParam); // Merge with the existing dataParam
}
saveDrawing() {
this.save();
}
}
// Create the instance and attach it to the window object
export function initColouredPenciles() {
window.addEventListener('DOMContentLoaded', () => {
const btnInit = document.querySelector('.btn-painter-toggle'); // 색연필 활성 버튼
if (!btnInit) {
// console.error('.btn-painter-toggle 요소를 찾을 수 없습니다.');
return;
}
const objArr = ['viewer-painter']; // 색연필 콘테이너 클래스명 - 여러개 지정 가능, 앞에 위치한 클래스명의 엘리먼트 우선
const colorArray = ["#222222", "#fd4418", "#ffce00", "#00d56a", "#2f77ff", "#ffffff"]; // 색상 팔레트 6색
const defaultColor = 5; // 기본 선 굵기
const brushMin = 0.5, brushMax = 43; // 최소, 최대 선 굵기
const saveDataUrl = '/classroom/insertClassLessonWrtng.json'; // save api url
const loadDataURL = '/classroom/selectClassLessonWrtng.json'; // load api url
const deleteDataURL = '/classroom/deleteClassLessonWrtng.json'; // delete api url
// const loadDataURL = 'lineData.json'; // test load api url
/** dataParam은 외부에서 설정, 사용 환경에 따라 key, value 자유 설정 가능 */
const dataParam = {};
// 인스턴스 생성 후 전역 window 객체에 저장
window.cpInstance = new colouredPenciles(btnInit, objArr, colorArray, defaultColor, brushMin, brushMax, saveDataUrl, loadDataURL, deleteDataURL, dataParam);
});
}
initColouredPenciles();