개요
레시피 상세보기 기능과 이를 표시할 모달 창(Modal Window)을 구현한다. DB 내에 저장된 레시피 압축 파일 Buffer를 임시 파일로 작성하고, 해당 파일을 압축 해제하여 모달 창에 이미지와 텍스트 파일을 표시한다.
검색 기능 및 결과 화면 정의
- 검색어를 입력하고, 컬럼을 선택해 제출하면 검색 결과 목록을 표시한다.
- 레시피는 한 페이지에 10개 표시하며, 페이지 버튼 또한 10개씩 표시하고 하단 부에 위치시킨다.
- 검색 결과 목록의 각 레시피는 이미지, title, ingredients, 다운로드 버튼, 상세 보기 버튼을 포함한다. (id, instrunctions 제외)
- ingredients 컬럼 값의 개수(배열 크기)가 3개 이상일 때는 값 목록을 접고 펼치도록 한다. (추가)
검색 결과 화면의 마지막 기능인 레시피 상세보기 기능을 구현한다. 결과 리스트에 레시피를 모두 표시 하기에는 텍스트의 양이 많으므로, View 버튼 클릭 시 새로운 창을 띄워 그 안에 표시하기로 했다. 데이터는 다운로드 기능에서 사용했던 레시피 압축 파일을 사용한다. Buffer 그대로 사용하여 다운로드한 것과 달리 이미지, 텍스트를 화면에 직접 표시해야하므로 압축 해제가 필요하다.
압축 파일 쿼리문
//rcp_sqlite_db.js
...
selectRcpZipFileById(id) {
if (this.db == null)
return '';
const result = this.db.prepare(
`
SELECT id, title, image_name, recipe_zip_file
FROM tb_recipe
WHERE id = ${id}
`
).all();
return result;
}
...
다운로드 기능에서 사용한 쿼리문을 그대로 사용한다. image_name 컬럼의 값 또한 이 기능에서 사용하게 된다.
상세 보기 기능 구현
//forge.config.js
...
{
name: 'rcp_modal',
html: './src/renderer/rcp_modal/index.html',
js: './src/renderer/rcp_modal/renderer.js',
preload: {
js: './src/preload.js'
}
},
...
forge.config.js: 모달 창의 페이지가 추가되었으므로, forge 설정 파일에 페이지로 사용할 html 파일 등을 매핑해준다.
//main.js
...
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
},
});
const modalWindow = new BrowserWindow({
modal: true,
parent: mainWindow,
width: 700,
height: 560,
minWidth: 700,
minHeight: 560,
show: false,
opacity: 0,
hasShadow: false,
webPreferences: {
preload: RCP_MODAL_PRELOAD_WEBPACK_ENTRY,
},
});
...
main.js: ModalWindow 인스턴스를 생성한다. show 값은 false로 설정하여 우선 보이지 않게 둔다.preload 매핑란에는 main window와 같은 것을 사용하지 않고 forge 설정 파일에서 매핑한 이름을 토대로 ‘RCP_MODAL_PRELOAD_WEBPACK_ENTRY’ 라는 전역 변수 값을 사용한다. forge.config.js에서 설정한 페이지 이름이 rcp_modal 이었기 때문에 ‘RCP_MODAL’ 이라는 문자열 뒤에 ‘_PRELOAD_WEBPACK_ENTRY’를 추가하면 된다.
//preload.js
...
req_rcpViewByDecompress: (id) => ipcRenderer.send("req_rcpViewByDecompress", id),
resp_rcpViewByDecompress: (viewResult) => ipcRenderer.on("resp_rcpViewByDecompress", viewResult),
...
preload.js: renderer에서 사용할 ipc 함수를 window 전역 변수에 설정한다.
//rcp_ipc.js
...
addRcpViewByDecompress() {
ipcMain.on('req_rcpViewByDecompress', (event, id) => {
let zipFileResult = this.db.selectRcpZipFileById(id)[0];
let rcpDecompress = new RcpDecompress();
rcpDecompress.decompressRecipeZipFile(zipFileResult, this.viewRcpDetail, this.modalWindow);
});
}
viewRcpDetail(title, rcpImageSrc, rcpString, modalWindow) {
// Center the modalWindow in the mainWindow
const parentBounds = (modalWindow.getParentWindow()).getBounds();
const x = parentBounds.x + Math.floor((parentBounds.width - 700) / 2);
const y = parentBounds.y + Math.floor((parentBounds.height - 560) / 2);
modalWindow.setPosition(x, y);
modalWindow.setOpacity(0);
modalWindow.show();
// fade in effect
let opacity = 0;
const interval = setInterval(() => {
opacity += 0.25;
modalWindow.setOpacity(opacity);
if (opacity >= 1) {
clearInterval(interval);
}
}, 25);
let viewResult = {
title: title,
rcpImageSrc: rcpImageSrc,
rcpString: rcpString.toString()
};
modalWindow.webContents.postMessage('resp_rcpViewByDecompress', viewResult);
return;
}
...
rcp_ipc.js:
- addRcpViewByDecompress: 다운로드 관련 IPC 함수와 같은 쿼리문을 호출하여 결과값을 얻는다. 해당 결과값은 title, image_name, recipe_ziop_file(버퍼) 값을 가지고 있고, 이 값들을 압축 해제 관련 함수에 인자로 전달한다.
- viewRcpDetail: 압축 해제 함수에 콜백 함수로 전달된다. 모달 창의 위치를 메인 창의 가운데로 하게끔 조절하고, 표시할 데이터들을 modal의 renderer로 전달한다.
//rcp_decompress.js
import { app, nativeImage } from 'electron';
const path = require('path');
const fs = require('fs');
const extract = require('extract-zip');
export default class RcpDecompress {
constructor() {
}
/**
* A functions that decompress a compressed file into a target path.
* @param {*} compressedFilePath
* @param {*} outputDirectoryPath
* @param {*} onComplete
*/
extractZip(compressedFilePath, outputDirectoryPath, onComplete) {
extract(compressedFilePath, { dir: outputDirectoryPath })
.then(() => {
console.log('Finished decompressing the file');
onComplete();
})
.catch((err) => {
console.error('Error decompressing a compressed file:', err);
});;
}
/**
* Aunctions that write the compressed file buffer data to a file and call the decompress function
* @param {*} zipFileResult
* @param {*} viewRcpDetail
* @param {*} modalWindow
* @returns
*/
decompressRecipeZipFile(zipFileResult, viewRcpDetail, modalWindow) {
// zip file reuslt
let title = zipFileResult.title;
let imageName = zipFileResult.image_name;
let zipBuffer = zipFileResult.recipe_zip_file;
// temp file path
const tempPath = app.getPath('temp');
let zipFilePath = path.join(tempPath, title + '.zip');
let txtFilePath = path.join(tempPath, title + '_recipe.txt');
let jpgFilePath = path.join(tempPath, imageName + '.jpg');
// create zip file from buffer
fs.writeFileSync(zipFilePath, zipBuffer);
this.extractZip(zipFilePath, tempPath, () => {
let rcpString = fs.readFileSync(txtFilePath);
let rcpImageSrc = nativeImage.createFromPath(jpgFilePath).toDataURL();
viewRcpDetail(title, rcpImageSrc, rcpString, modalWindow);
// delete temp file
fs.unlink(zipFilePath, (err) => {
});
fs.unlink(txtFilePath, (err) => {
});
fs.unlink(jpgFilePath, (err) => {
});
});
return true;
}
}
rcp_decompress.js: 압축 파일 관련 임시 경로를 생성하고, 해당 경로에 파일을 작성(write)하고 압축을 해제한다. 해제한 경로의 레시피 텍스트 파일과 이미지 파일을 콜백 함수로 전달한다.
//rcp_modal/renderer.js
...
/**
* A listener that receives decompressed recipe data (Image, All data)
* and displays it in a modal window
*/
window.apis.resp_rcpViewByDecompress((event, viewResult)=>{
$('textarea').css('height', '0px'); // init
$('#title').html(viewResult.title+" recipe");
$('#rcp_image').attr('src', viewResult.rcpImageSrc);
$('#rcp_string').val(viewResult.rcpString);
const textarea = document.querySelector('textarea')
const textHeight = textarea.scrollHeight
textarea.style.height = textHeight + 'px'
})
...
renderer.js: modal 창의 renderer로서, 압축 해제된 텍스트와 이미지의 데이터를 받아 모달 창에 표시한다.
결과 화면
'토이 프로젝트 > 레시피 일렉트론 앱 (완)' 카테고리의 다른 글
[Electron] 12.멀티스레딩 구현 with worker(1) - 레시피 일렉트론 앱 (0) | 2024.02.11 |
---|---|
[Electron] 11.타이틀바 추가(feat. Modal window 재사용 이슈) - 레시피 일렉트론 앱 (0) | 2024.02.04 |
[Electron] 9.검색 기능 구현(3): 다운로드 기능(Download buffer) - 레시피 일렉트론 앱 (0) | 2024.02.02 |
[Electron] 8.검색 기능 구현(2): 이미지 설정(Buffer to Data URL) - 레시피 일렉트론 앱 (0) | 2024.02.01 |
[Electron] 7.검색 기능 구현(1): 검색 결과 및 페이징 - 레시피 일렉트론 앱 (1) | 2024.01.28 |