티스토리 뷰

Tip and Error

데스크톱 앱 위치, 사이즈 저장 (NW.js)

geonwoopaeng@gmail.com 2024. 1. 26. 23:49

데스크톱 앱 위치, 사이즈 저장

사용 기술

  • NW.js
  • js, html, css

문제

데스크톱 앱 위치, 사이즈 저장 오류

저희 프로그램은 NW.js의 2가지 버전(0.24.4, 0.72.0)을 호환되어야 합니다.
그리고 데스크톱 앱을 종료하고 시작했을 경우 위치와 사이즈를 기억해야 하기 때문에
NW.js event를 이용하여 값을 localStorage를 사용해서 사용했습니다.

그럼 무엇을 고려했어야 했을까요?

  1. window 7, 32bit까지 + 엄청 느린 컴퓨터에서도 동작하는 경우
  2. 로그인 화면과 로그인 후 화면에서 적용되어야 합니다.
  3. NW.js 0.24.4, 0.72.0 2가지 버전을 고려해야 합니다.
  4. NW.js Event 동작을 고려해야 합니다.
  5. 위치가 이상할 때 실행은 되지만 화면에 안 보이는 경우

(NW.js documentation에 버전마다 적용 안 되는 함수들도 있어 직접 테스트 추천합니다.)

급하게 구현한 것을 정리한 내용으로 개선해야 될 상황이 많습니다.

 

해결 과정

우선 코드 결과부터 보여드리면 다음과 같습니다.

try catch와 NW.js event를 버전을 살피며 잘 조합해야 합니다.

[로그인 부분]

function set_screen_size() {
    const local_main_win_width = localStorage.getItem("main_win_width");
    const local_main_win_height = localStorage.getItem("main_win_height");
    const local_main_win_x = localStorage.getItem("main_win_x");
    const local_main_win_y = localStorage.getItem("main_win_y");
    let main_win = gui.Window.get();

    try {
        if (local_main_win_width || local_main_win_height || local_main_win_x || local_main_win_y) 
        { // 화면 크기 변경
            const win_obj = {
                // 1280 x 800 으로 기본설정
                main_win_width: parseInt(g_nvl(localStorage.getItem("main_win_width"),1280)),
                main_win_height: parseInt(g_nvl(localStorage.getItem("main_win_height"),800)),
                main_win_x: parseInt(g_nvl(localStorage.getItem("main_win_x"), 30)),
                main_win_y: parseInt(g_nvl(localStorage.getItem("main_win_y"), 30))
            }

            // 앱 화면의 width, height 적용 부분
            const tmp_main_width = win_obj.main_win_width < 600 ? 600 : win_obj.main_win_width;
            const tmp_main_height = win_obj.main_win_height < 400 ? 400 : win_obj.main_win_height;
            main_win.width = tmp_main_width;
            main_win.height = tmp_main_height;

            // 앱 화면의 위치 적용 부분
            const tmp_check_screen_width = screen.width - tmp_main_width + 15; //앱 화면이 스크린 우측으로 넘어갔는지 확인하기 위한 값
            const tmp_check_screen_height = screen.height - tmp_main_height + 15; //앱 화면이 스크린 아래로 넘어갔는지 확인하기 위한 값

            // y 좌표
            if (win_obj.main_win_y < -15) {
                // 레티나 화면이 스크린 위쪽 화면 넘어갈 경우
                main_win.y = 30;
            } else if (win_obj.main_win_y > tmp_check_screen_height) {
                // 레티나 화면이 스크린 아래쪽 화면 넘어갈 경우
                main_win.y = tmp_check_screen_height - 50;
            } else {
                main_win.y = win_obj.main_win_y;
            }

            // x 좌표
            if (win_obj.main_win_x < -15) {
                // 레티나 화면이 스크린 왼쪽 화면 넘어갈 경우
                main_win.x = 30;
            } else if (win_obj.main_win_x > tmp_check_screen_width) {
                // 레티나 화면이 스크린 오른쪽 화면 넘어갈 경우
                main_win.x = tmp_check_screen_width - 50;
            } else {
                main_win.x = win_obj.main_win_x;
            }
        } else {
            // localstorage에 화면제어 값이 아무것도 없을 경우 (기본값으로 수정)
            main_win.width = 1280; //너비
            main_win.height = 800; //높이
            main_win.x = 30; //left 위치
            main_win.y = 30; //top 위치
        }
    } catch (e) {
        console.error(e);
    }
}

// 앱 사이즈, 위치 값 저장하는 함수
function control_screen_size() {
    const node_number = Number(process.versions['node-webkit'].replaceAll(".","")); // 노드웹킷 버전 가져오기
    const node_version = node_number >= 720 ? true : false; // 노드 버전

    try {
        if (node_version === true) {
            // 0.72.0의 경우 진행 프로세스.
            main_win.on('resize', (width, height) => {
                try {
                    localStorage.setItem("main_win_width", width);
                    localStorage.setItem("main_win_height", height);
                } catch (e) {
                    console.error(e);
                }
            });
            main_win.on("move", (x,y) => {
                try {
                    localStorage.setItem("main_win_x", x);
                    localStorage.setItem("main_win_y", y);
                    localStorage.setItem("main_win_width", main_win.width);
                    localStorage.setItem("main_win_height", main_win.height);
                } catch (e) {
                    console.error(e);
                }
            });
        } else {
            //0.24.4의 경우 진행 프로세스.
            main_win.on('resize', (width, height) => {
                try {
                    main_win.window.localStorage.setItem("main_win_x", main_win.x);
                    main_win.window.localStorage.setItem("main_win_y", main_win.y);
                    main_win.window.localStorage.setItem("main_win_width", width);
                    main_win.window.localStorage.setItem("main_win_height", height);
                } catch (e) {
                    console.error(e);
                }
            });
            main_win.on("move", (x,y) => {
                try {
                    main_win.window.localStorage.setItem("main_win_x", x);
                    main_win.window.localStorage.setItem("main_win_y", y);
                } catch (e) {
                    console.error(e);
                }
            });
        }
    } catch (e) {
        console.error(e);
    }
}

[로그인 후 부분]

function control_screen_size() {
    const node_number = Number(process.versions['node-webkit'].replaceAll(".","")); // 노드웹킷 버전 가져오기
    const node_version = node_number >= 720 ? true : false;
    let main_win = nw.Window.get();

    if (node_version == true) {
        // 0.72.0의 경우만 진행하는 프로세스.
        try {
            main_win.on('resize', (width, height) => {
                try {
                    localStorage.setItem("main_win_width", width);
                    localStorage.setItem("main_win_height", height);
                } catch (e) {
                    console.error(e);
                }
            });
            main_win.on("move", (x,y) => {
                try {
                    localStorage.setItem("main_win_x", x);
                    localStorage.setItem("main_win_y", y);
                    localStorage.setItem("main_win_width", main_win.width);
                    localStorage.setItem("main_win_height", main_win.height);
                } catch (e) {
                    console.error(e);
                }
            });
        } catch (e) {
            console.error(e);
        }
    }
}

 

1. window 7, 32bit 까지 + 엄청 느린 컴퓨터에서도 동작하는 경우

개발을 하면서 작은 기업이다 보니 사용자들을 엄청 가까이에서 접할 수 있는 기회가 많았습니다.
사용자들을 원격, 전화등을 해보니 로그인도 잘 못하시는 분, 설치를 못하시는 분, 프로그램을 어떻게 사용하시지? 할 정도의 컴퓨터를 가지고 계신 분 다양한 분들이 계셨습니다.

그런데 공통으로 '편안하게 사용하고 싶다'라는 생각을 가지고 계셨습니다.
그래서 꼭 해당 기능을 빠르게 구현해야 했습니다.

또한 브라우저를 킬때도 엄청 느린 컴퓨터를 가지고 계신 분들이 많았습니다.
이런 상황에서 잘 동작하지 않으면 안 되기 때문에
Slow 3G로 테스트를 하고 회사 소유의 노후화된 컴퓨터로 테스트를 진행하게 되었습니다.

사용자들이 모두 쉽고 편리하게 사용하게 만들고 싶었습니다.

그래서 사용자들이 많이 사용하는 3가지 상황을 정리해 보니 다음과 같았습니다.

  1. 앱 사이즈를 조절할 때
  2. 앱을 이동시킬 때
  3. 앱 최대화를 했을 때

 

2. 로그인 화면과 로그인 후 화면에서 적용되어야 합니다.

기존 실행 과정이 다음과 같습니다.
update -> login -> value setting -> login after

그래서 최소 2가지 화면에서 수시로 변경이 가능하기 때문에 필요한 부분에서만
NW.js event를 적용해야 했습니다. (너무 값을 남발하지 않기 위해)

login과 login after(header) 부분에서 값을 적용하게 되었습니다.

 

3. NW.js 0.24.4, 0.72.0 2가지 버전을 고려해야 합니다.

1번에서 login과 login after(header) 부분에서 event를 적용하였으나
event 적용 범위와 방법이 조금씩 달랐습니다.

0.24.4인 경우 login에서만 event를 적용해도 전체에 event가 적용이 되지만
0.72.0인 경우 login, login after(header) 2곳에서 event를 적용해야 했습니다.

즉, 0.72.0 버전의 event 범위 < 0.24.4의 event 범위임을 확인할 수 있었습니다.

그래서 login after(header) 쪽에서는 process.versions['node-webkit']을 통해 version을 체크하여
0.72.0일 때만 event가 동작할 수 있게 적용하였습니다.

 

4. NW.js Event 동작을 고려해야 합니다.

맨 위에서 정리한 3가지 상황에 맞는 event를 정리하니 다음과 같았습니다.

그런데 0.24.4, 0.72.0에서 각각 event 적용되는 것이 달랐습니다.
둘 다 resizemove 이벤트를 사용하였지만 x, y, width, height 값이 적용되는 부분이 달랐습니다.

0.24.4 같은 경우
resize 에서 x, y, width, height가 되었고
move 에서는 x, y가 적용되었습니다.

0.72.0
resize 에서 x, y가 되었고
move 에서는 x, y, width, height가 적용되었습니다.

이럴 때 3가지 조건을 조금 만족할 수 있었습니다.

추가로 try catch를 적용시켜 줘야 했습니다.

왜냐?

event가 지속적으로 동작을 하기 때문에 스크립트가 죽으면 안 되었습니다.
그리고 런타임에러에서만 동작을 하기 때문에 확인이 용이했고 동기적으로 동작을 하여 nwjs event 내부에서 적용해야 했습니다.

 

5. 위치가 이상할 때 실행은 되지만 화면에 안 보이는 경우

해당 부분은 모니터 밖으로 프로그램이 위치했을 경우 생겼던 문제였습니다.

그래서 4가지 방향으로 프로그램이 모니터 밖으로 넘어가면 해당 위치에서 프로그램 전체를 보여주게 위치, 사이즈를 적용시켜야 했습니다.

그래서 다음과 같은 코드가 탄생하였습니다.
주석을 많이 달아놔서 쉽게 확인하실 수 있습니다.

function set_screen_size() {
    const local_main_win_width = localStorage.getItem("main_win_width");
    const local_main_win_height = localStorage.getItem("main_win_height");
    const local_main_win_x = localStorage.getItem("main_win_x");
    const local_main_win_y = localStorage.getItem("main_win_y");
    let main_win = gui.Window.get();

    try {
        if (local_main_win_width || local_main_win_height || local_main_win_x || local_main_win_y) 
        { // 화면 크기 변경
            const win_obj = {
                // 1280 x 800 으로 기본설정
                main_win_width: parseInt(g_nvl(localStorage.getItem("main_win_width"),1280)),
                main_win_height: parseInt(g_nvl(localStorage.getItem("main_win_height"),800)),
                main_win_x: parseInt(g_nvl(localStorage.getItem("main_win_x"), 30)),
                main_win_y: parseInt(g_nvl(localStorage.getItem("main_win_y"), 30))
            }

            // 앱 화면의 width, height 적용 부분
            const tmp_main_width = win_obj.main_win_width < 600 ? 600 : win_obj.main_win_width;
            const tmp_main_height = win_obj.main_win_height < 400 ? 400 : win_obj.main_win_height;
            main_win.width = tmp_main_width;
            main_win.height = tmp_main_height;

            // 앱 화면의 위치 적용 부분
            const tmp_check_screen_width = screen.width - tmp_main_width + 15; //앱 화면이 스크린 우측으로 넘어갔는지 확인하기 위한 값
            const tmp_check_screen_height = screen.height - tmp_main_height + 15; //앱 화면이 스크린 아래로 넘어갔는지 확인하기 위한 값

            // y 좌표
            if (win_obj.main_win_y < -15) {
                // 레티나 화면이 스크린 위쪽 화면 넘어갈 경우
                main_win.y = 30;
            } else if (win_obj.main_win_y > tmp_check_screen_height) {
                // 레티나 화면이 스크린 아래쪽 화면 넘어갈 경우
                main_win.y = tmp_check_screen_height - 50;
            } else {
                main_win.y = win_obj.main_win_y;
            }

            // x 좌표
            if (win_obj.main_win_x < -15) {
                // 레티나 화면이 스크린 왼쪽 화면 넘어갈 경우
                main_win.x = 30;
            } else if (win_obj.main_win_x > tmp_check_screen_width) {
                // 레티나 화면이 스크린 오른쪽 화면 넘어갈 경우
                main_win.x = tmp_check_screen_width - 50;
            } else {
                main_win.x = win_obj.main_win_x;
            }
        } else {
            // localstorage에 화면제어 값이 아무것도 없을 경우 (기본값으로 수정)
            main_win.width = 1280; //너비
            main_win.height = 800; //높이
            main_win.x = 30; //left 위치
            main_win.y = 30; //top 위치
        }
    } catch (e) {
        console.error(e);
    }
}

 

6. 추가 해결 방안 (생각)

  • X 버튼을 눌렀을 때 값을 적용해 보기
  • 마지막 동작에서 1 ~ 2초가 머물렀을 때 값 적용

제가 하는 서비스에 추가적인 서비스가 많이 붙어 조금 안정화가 되면 바로 진행해 볼 것입니다.

 

마치며

현재 프로그램이 많이 옛날 기술을 사용하고 10년간 여러 사람들이 거쳐갔습니다.
또한 하루 안에 해결을 해야 하는 문제이기도 했습니다. ㅜ
그래서 정보를 찾아보기 힘든 경우도 많고 같은 코드도 여러 군데 사용되어 있고 특히,
저의 급한 마음...

문제를 해결했지만 더 좋은 해결방안이 있어 고치려고 합니다.

이번에 어쩌다 대표 프로그램을 혼자 맡게 되어 많이 힘들지만
프로그램의 속도 개선, 설치 프로그램, upgrade 등등 전반적인 내용을 경험하고 있습니다.

그리고 유행 기술인 React, Typescript를 사용하지 않아 커리어에 고민이 되지만
열심히 하다 보면 좋은 기회를 얻을 수 있을 것이라고 믿습니다.

긴 글 읽어주셔서 감사합니다.

반응형
공지사항
최근에 올라온 글