미니멀한 비밀번호 관리자 데스크톱 앱: Golang의 Wails 프레임워크 진출(2부)
안녕하세요, 코더 여러분! 이 짧은 시리즈의 첫 번째 부분에서는 Wails 프레임워크로 만든 비밀번호를 저장하고 암호화하는 데스크톱 애플리케이션의 생성 및 작동을 살펴보았습니다. Go 백엔드와 이를 프론트엔드 측에 바인딩하는 방법에 대해서도 설명했습니다.
이번 부분에서는 사용자 인터페이스를 다루겠습니다. 해당 게시물에서 언급했듯이 Wails를 사용하면 Vanilla JS를 포함하여 우리가 좋아하는 웹 프레임워크를 사용하여 GUI를 구축할 수 있습니다. 내가 말했듯이, Wails의 제작자들은 항상 Svelte를 첫 번째 선택으로 언급하기 때문에 Svelte를 선호하는 것 같습니다. Wails CLI(현재 버전)는 Svelte Typescript(wails init -n myproject -t svelte-ts)를 사용하여 프로젝트 생성을 요청하면 Svelte3을 사용하여 스캐폴딩을 생성합니다. 이미 말했듯이 Svelte5(및 새 기능)를 사용하려는 경우 생성을 자동화하는 bash 스크립트가 있습니다(어쨌든 Wails CLI가 설치되어 있어야 합니다). 게다가 인터페이스 디자인에 딱 맞는 조합인 Taildwindcss Daisyui도 추가되었습니다.
사실 저는 Vanilla Js와 Vue로 먼저 작업한 다음 React로 작업했고, 심지어 많은 사람들이 HTMX(제가 좋아하는 ❤️)입니다. 하지만 Svelte는 시작부터 사랑에 빠지게 만들고, 처음으로 사용한 것은 Wails를 실험하면서였다고 말씀드리고 싶습니다(계속 사용할 것을 약속합니다…). 하지만 웹 프레임워크가 편한 만큼 프론트엔드는 그리 쉽지 않다는 점을 백엔드 개발자에게 상기시켜야 합니다 ?!
본론으로 들어가겠습니다.
I - 프론트엔드 구조 살펴보기
웹 프레임워크를 사용해 본 적이 있다면 Wails CLI가 내부적으로 ViteJ를 사용한다는 사실을 금방 알 수 있을 것입니다.
... . ├── index.html ├── package.json ├── package.json.md5 ├── package-lock.json ├── postcss.config.js ├── README.md ├── src │ ├── App.svelte │ ├── assets │ │ ├── fonts │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ └── OFL.txt │ │ └── images │ │ └── logo-universal.png │ ├── lib │ │ ├── BackBtn.svelte │ │ ├── BottomActions.svelte │ │ ├── EditActions.svelte │ │ ├── EntriesList.svelte │ │ ├── Language.svelte │ │ ├── popups │ │ │ ├── alert-icons.ts │ │ │ └── popups.ts │ │ ├── ShowPasswordBtn.svelte │ │ └── TopActions.svelte │ ├── locales │ │ ├── en.json │ │ └── es.json │ ├── main.ts │ ├── pages │ │ ├── About.svelte │ │ ├── AddPassword.svelte │ │ ├── Details.svelte │ │ ├── EditPassword.svelte │ │ ├── Home.svelte │ │ ├── Login.svelte │ │ └── Settings.svelte │ ├── style.css │ └── vite-env.d.ts ├── svelte.config.js ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wailsjs ├── go │ ├── main │ │ ├── App.d.ts │ │ └── App.js │ └── models.ts └── runtime ├── package.json ├── runtime.d.ts └── runtime.js ...
Vite에서 생성된 웹 프레임워크를 사용해 본 적이 있다면 해당 구성 파일에 놀라지 않을 것입니다. 여기서는 이미 말했듯이 나만의 bash 스크립트를 생성하는 Svelte5(Taildwindcss Daisyui 구성 포함)를 사용합니다. 또한 프론트엔드 개발을 용이하게 하는 TypeScript를 사용하므로 구성도 확인할 수 있습니다.
그런데 이 설명에서 중요한 것은 wailsjs 폴더의 내용입니다. Wails가 수행한 편집이 마법을 발휘한 곳이 바로 여기입니다. go 하위 폴더에는 프론트엔드와 상호작용해야 하는 백엔드 부분의 Js/Ts로 "번역된" 메소드가 저장되는 곳입니다. 예를 들어, main/App.js(또는 해당 TypeScript 버전인 main/App.d.ts)에는 앱 구조의 내보낸(공개) 메서드가 모두 있습니다.
... . ├── index.html ├── package.json ├── package.json.md5 ├── package-lock.json ├── postcss.config.js ├── README.md ├── src │ ├── App.svelte │ ├── assets │ │ ├── fonts │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ └── OFL.txt │ │ └── images │ │ └── logo-universal.png │ ├── lib │ │ ├── BackBtn.svelte │ │ ├── BottomActions.svelte │ │ ├── EditActions.svelte │ │ ├── EntriesList.svelte │ │ ├── Language.svelte │ │ ├── popups │ │ │ ├── alert-icons.ts │ │ │ └── popups.ts │ │ ├── ShowPasswordBtn.svelte │ │ └── TopActions.svelte │ ├── locales │ │ ├── en.json │ │ └── es.json │ ├── main.ts │ ├── pages │ │ ├── About.svelte │ │ ├── AddPassword.svelte │ │ ├── Details.svelte │ │ ├── EditPassword.svelte │ │ ├── Home.svelte │ │ ├── Login.svelte │ │ └── Settings.svelte │ ├── style.css │ └── vite-env.d.ts ├── svelte.config.js ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wailsjs ├── go │ ├── main │ │ ├── App.d.ts │ │ └── App.js │ └── models.ts └── runtime ├── package.json ├── runtime.d.ts └── runtime.js ...
모두 약속을 반환합니다. Promise가 반환 유형으로 사용되는 일부 Go 구조를 "래핑"하거나 해당 함수가 인수 유형을 취하는 경우 Go에 해당하는 클래스를 포함하는 모듈(이 경우 TypeScript를 사용하기 때문에 typeds.ts)이 있을 것입니다. 네임스페이스의 구조체와 그 생성자.
또한 런타임 하위 폴더에는 각각 백엔드에서 듣거나 백엔드에서 발생하는 창과 이벤트를 조작할 수 있는 Go 런타임 패키지의 모든 메서드가 포함되어 있습니다.
src 폴더에는 웹 애플리케이션과 마찬가지로 Vite에서 컴파일하여 "frontend/dist"에 저장(최종 실행 파일에 포함)할 파일이 포함되어 있습니다. Tailwindcss를 사용하므로 style.css에는 기본 Tailwind 구성과 사용해야 하는 CSS 클래스가 포함되어 있습니다. 또한 인터페이스에 웹 기술을 적용한 장점으로 하나 이상의 글꼴(폴더 자산/글꼴)을 쉽게 사용하거나 교환할 수 있습니다.
이 개요를 마무리하기 위해 개발 모드(Wails dev)에서 컴파일할 때 핫 리로드를 허용하는 것 외에도 다음에서 변경된 사항(백엔드와 프런트엔드 모두에서)을 관찰할 수 있을 뿐만 아니라 응용 프로그램 창 자체뿐만 아니라 웹 서버가 시작되므로 주소 http://localhost:34115를 통해 웹 브라우저에서도 마찬가지입니다. 이를 통해 선호하는 브라우저 개발 확장을 사용할 수 있습니다. Wails 자신이 우리에게 매우 유용한 개발 도구를 제공한다고 말해야 하지만, 애플리케이션 창(개발 모드에서만)을 마우스 오른쪽 버튼으로 클릭하고 "요소 검사"를 선택하면:
II - 이제… HTML, CSS, JavaScript에 대해 알아볼까요?
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT import {models} from '../models'; export function AddPasswordEntry(arg1:string,arg2:string,arg3:string):Promise<string>; export function CheckMasterPassword(arg1:string):Promise<boolean>; export function DeleteEntry(arg1:string):Promise<void>; export function Drop():Promise<void>; export function GetAllEntries():Promise<Array<models.PasswordEntry>>; export function GetEntryById(arg1:string):Promise<models.PasswordEntry>; export function GetLanguage():Promise<string>; export function GetMasterPassword():Promise<boolean>; export function GetPasswordCount():Promise<number>; export function SaveLanguage(arg1:string):Promise<void>; export function SaveMasterPassword(arg1:string):Promise<string>; export function UpdateEntry(arg1:models.PasswordEntry):Promise<boolean>;
보시다시피 Svelte에 4개의 JavaScript 패키지를 추가했습니다(이미 언급한 Tailwindcss Daisyui 제외).
- svelte-copy, 사용자 이름과 비밀번호를 클립보드에 더 쉽게 복사할 수 있습니다.
- svelte-i18n, i18n 처리용, 즉 사용자가 애플리케이션의 언어를 변경할 수 있도록 허용합니다.
- svelte-spa-router는 Svelte용 소형 라우팅 라이브러리로, 애플리케이션 창에서 보기를 더 쉽게 변경할 수 있습니다. 이 경우에는 다음에서 제공하는 "공식" 라우팅을 사용할 가치가 없기 때문입니다. SvelteKit.
- sweetalert2, 기본적으로 모달/대화 상자를 쉽고 빠르게 만드는 데 사용하세요.
모든 SPA의 진입점은 main.js(또는 main.ts) 파일이므로 다음부터 시작하겠습니다.
... . ├── index.html ├── package.json ├── package.json.md5 ├── package-lock.json ├── postcss.config.js ├── README.md ├── src │ ├── App.svelte │ ├── assets │ │ ├── fonts │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ └── OFL.txt │ │ └── images │ │ └── logo-universal.png │ ├── lib │ │ ├── BackBtn.svelte │ │ ├── BottomActions.svelte │ │ ├── EditActions.svelte │ │ ├── EntriesList.svelte │ │ ├── Language.svelte │ │ ├── popups │ │ │ ├── alert-icons.ts │ │ │ └── popups.ts │ │ ├── ShowPasswordBtn.svelte │ │ └── TopActions.svelte │ ├── locales │ │ ├── en.json │ │ └── es.json │ ├── main.ts │ ├── pages │ │ ├── About.svelte │ │ ├── AddPassword.svelte │ │ ├── Details.svelte │ │ ├── EditPassword.svelte │ │ ├── Home.svelte │ │ ├── Login.svelte │ │ └── Settings.svelte │ ├── style.css │ └── vite-env.d.ts ├── svelte.config.js ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wailsjs ├── go │ ├── main │ │ ├── App.d.ts │ │ └── App.js │ └── models.ts └── runtime ├── package.json ├── runtime.d.ts └── runtime.js ...
Wails CLI로 생성된 스켈레톤에 추가한 사항을 강조했습니다. svelte-i18n 라이브러리에서는 fallback/initial 언어 설정과 동시에 번역이 포함된 JSON 파일을 main.js/ts 파일에 등록해야 합니다(비록 우리는 나중에 사용자가 기본 설정으로 선택한 항목에 따라 조작될 것입니다. 번역이 포함된 JSON 파일의 형식은 다음과 같습니다.
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT import {models} from '../models'; export function AddPasswordEntry(arg1:string,arg2:string,arg3:string):Promise<string>; export function CheckMasterPassword(arg1:string):Promise<boolean>; export function DeleteEntry(arg1:string):Promise<void>; export function Drop():Promise<void>; export function GetAllEntries():Promise<Array<models.PasswordEntry>>; export function GetEntryById(arg1:string):Promise<models.PasswordEntry>; export function GetLanguage():Promise<string>; export function GetMasterPassword():Promise<boolean>; export function GetPasswordCount():Promise<number>; export function SaveLanguage(arg1:string):Promise<void>; export function SaveMasterPassword(arg1:string):Promise<string>; export function UpdateEntry(arg1:models.PasswordEntry):Promise<boolean>;
저는 이 라이브러리의 시스템이 Svelte 애플리케이션 번역을 용이하게 하는 데 쉽고 편리하다고 생각합니다(자세한 내용은 해당 설명서를 참조할 수 있음).
/* package.json */ ... }, "dependencies": { "svelte-copy": "^2.0.0", "svelte-i18n": "^4.0.1", "svelte-spa-router": "^4.0.1", "sweetalert2": "^11.14.5" } ...
JSON 파일을 다른 언어로 번역하는 데 도움이 되는 이와 같은 사이트를 사용할 수도 있습니다. 그러나 문제는 .svelte 파일을 $format으로 채울 때 수동으로 추적해야 한다는 점입니다. 이는 지루하고 오류가 발생하기 쉽습니다. 이 작업을 자동화할 수 있는 방법은 없습니다. 혹시 아시는 분 계시면 알려주시면 감사하겠습니다. 그렇지 않으면 해당 작업을 수행하기 위한 일종의 스크립트를 생각해야 할 것입니다.
모든 Svelte 애플리케이션에서와 마찬가지로 인터페이스 구축의 다음 단계는 App.svelte 파일입니다.
/* main.ts */ import { mount } from 'svelte' import './style.css' import App from './App.svelte' import { addMessages, init } from "svelte-i18n"; // ⇐ ⇐ import en from './locales/en.json'; // ⇐ ⇐ import es from './locales/es.json'; // ⇐ ⇐ addMessages('en', en); // ⇐ ⇐ addMessages('es', es); // ⇐ ⇐ init({ fallbackLocale: 'en', // ⇐ ⇐ initialLocale: 'en', // ⇐ ⇐ }); const app = mount(App, { target: document.getElementById('app')!, }) export default app
여기에서는 애플리케이션을 컴파일할 때 자동으로 생성되고 구조체 앱의 공개 메서드로 선언된 바인딩인 GetMasterPassword를 사용합니다(이 시리즈의 첫 번째 부분 참조). 이 함수는 데이터베이스를 쿼리하고, 마스터 비밀번호가 등록되어 있는 경우 사용자가 이미 등록된 것으로 간주하고(부울 값을 래핑하는 약속을 반환함) 나머지 비밀번호에 액세스할 수 있도록 해당 비밀번호를 입력하도록 요청합니다. 보기의. 데이터베이스에 마스터 비밀번호가 없는 경우 사용자는 "신규"로 간주되며 처음으로 애플리케이션에 진입하기 위해 자신의 비밀번호를 생성하라는 요청을 받습니다.
마지막으로 Login.svelte 구성 요소를 마운트할 때 나머지 애플리케이션에 중요한 작업을 수행합니다. svelte-i18n 라이브러리는 우리가 이미 본 것처럼 초기 언어 코드를 선언하도록 강제하지만 Login.svelte를 마운트할 때 데이터베이스에 (GetLanguage 바인딩을 사용하여) 저장된 언어 코드가 있는지 확인하도록 요청합니다. 데이터베이스가 빈 문자열을 반환하는 경우, 즉 사용자 기본 설정으로 구성된 언어가 없는 경우 svelte-i18n은initialLocale로 구성된 값을 사용합니다. 대신 언어가 구성된 경우 해당 언어가 설정되고(locale.set(result);) "change_titles" 이벤트가 발생하며, 제목 표시줄의 번역된 제목과 앱의 기본 대화 상자가 전달됩니다. 백엔드가 처리할 내용:
/* frontend/src/locales/en.json */ { "language": "Language", "app_title": "Nu-i uita • minimalist password store", "select_directory": "Select the directory where to save the data export", "select_file": "Select the backup file to import", "master_password": "Master Password ?", "generate": "Generate", "insert": "Insert", "login": "Login", ... } /* frontend/src/locales/es.json */ { "language": "Idioma", "app_title": "Nu-i uita • almacén de contraseñas minimalista", "select_directory": "Selecciona el directorio donde guardar los datos exportados", "select_file": "Selecciona el archivo de respaldo que deseas importar", "master_password": "Contraseña Maestra ?", "generate": "Generar", "insert": "Insertar", "login": "Inciar sesión", ... }
로그인 처리 로직은 다음과 같습니다.
... . ├── index.html ├── package.json ├── package.json.md5 ├── package-lock.json ├── postcss.config.js ├── README.md ├── src │ ├── App.svelte │ ├── assets │ │ ├── fonts │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ └── OFL.txt │ │ └── images │ │ └── logo-universal.png │ ├── lib │ │ ├── BackBtn.svelte │ │ ├── BottomActions.svelte │ │ ├── EditActions.svelte │ │ ├── EntriesList.svelte │ │ ├── Language.svelte │ │ ├── popups │ │ │ ├── alert-icons.ts │ │ │ └── popups.ts │ │ ├── ShowPasswordBtn.svelte │ │ └── TopActions.svelte │ ├── locales │ │ ├── en.json │ │ └── es.json │ ├── main.ts │ ├── pages │ │ ├── About.svelte │ │ ├── AddPassword.svelte │ │ ├── Details.svelte │ │ ├── EditPassword.svelte │ │ ├── Home.svelte │ │ ├── Login.svelte │ │ └── Settings.svelte │ ├── style.css │ └── vite-env.d.ts ├── svelte.config.js ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wailsjs ├── go │ ├── main │ │ ├── App.d.ts │ │ └── App.js │ └── models.ts └── runtime ├── package.json ├── runtime.d.ts └── runtime.js ...
간단히 말하면, 사용자가 입력하는 내용을 가져오는 입력에 바인딩된 상태인 newPassword는 먼저 onLogin에서 검사하여 6자 이상이고 모두 ASCII 문자인지 확인합니다. 즉, 이 작은 함수 const isAscii = (str: string): boolean => 에 의해 길이가 1바이트에 불과합니다(그 이유는 이 시리즈의 I부에서 참조). /^[x00-x7F] $/.test(str);. 검사에 실패하면 함수가 반환되어 사용자에게 경고 토스트를 표시합니다. 이후 데이터베이스에 저장된 마스터 비밀번호가 없는 경우(isLogin = false) 사용자 유형이 무엇이든 SaveMasterPassword 기능(Wails에서 생성된 바인딩)에 의해 저장됩니다. Promise가 성공적으로 해결되면(데이터베이스에 저장된 레코드의 Id로 uuid 문자열을 반환) 사용자는 svelte-spa-router에 의해 홈 뷰로 이동됩니다. 라이브러리의 푸시 방법. 반대로, 비밀번호가 비ASCII 문자의 길이 및 부재에 대한 검사를 통과하고 DB에 마스터 비밀번호가 있는 경우(isLogin = true) CheckMasterPassword 함수는 저장된 비밀번호에 대해 비밀번호의 신원을 확인하거나 다음 중 하나를 수행합니다. 사용자를 홈 보기로 이동하거나(Promise가 true로 해결됨) 입력한 비밀번호가 올바르지 않음을 나타내는 토스트가 표시됩니다.
애플리케이션의 중앙 뷰이자 동시에 가장 복잡한 뷰는 홈 뷰입니다. 해당 HTML은 실제로 검색 입력이 있는 상단 버튼 표시줄(TopActions 구성 요소), 하단 버튼 표시줄(BottomActions 구성 요소) 및 저장된 비밀번호 항목의 총 수 또는 이들 목록이 다음을 사용하여 표시되는 중앙 영역의 3가지 구성 요소로 세분화됩니다. 스크롤 가능한 창(EntriesList 구성 요소):
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT import {models} from '../models'; export function AddPasswordEntry(arg1:string,arg2:string,arg3:string):Promise<string>; export function CheckMasterPassword(arg1:string):Promise<boolean>; export function DeleteEntry(arg1:string):Promise<void>; export function Drop():Promise<void>; export function GetAllEntries():Promise<Array<models.PasswordEntry>>; export function GetEntryById(arg1:string):Promise<models.PasswordEntry>; export function GetLanguage():Promise<string>; export function GetMasterPassword():Promise<boolean>; export function GetPasswordCount():Promise<number>; export function SaveLanguage(arg1:string):Promise<void>; export function SaveMasterPassword(arg1:string):Promise<string>; export function UpdateEntry(arg1:models.PasswordEntry):Promise<boolean>;
즉, 검색 상태(searchTerms)를 빈 문자열로 만들어 검색어가 있으면 재설정되어 전체 목록이 표시됩니다. 반면에 상위 구성 요소가 목록을 표시하거나 숨길 수 있도록 showList 상태(TopActions의 props isEntriesList)를 전환합니다.
위 다이어그램에서 볼 수 있듯이 두 하위 구성 요소는 모두 상위의 searchTerms 상태와 동일한 props를 공유합니다. TopActions 구성 요소는 사용자의 입력을 캡처하여 이를 상위 구성 요소 Home에 상태로 전달하고, Home은 이를 하위 구성 요소 EntriesList에 props
로 전달합니다.전체 목록을 표시하거나 사용자가 입력한 검색어로 필터링된 목록을 표시하는 기본 논리는 예상대로 EntriesList 구성 요소에 의해 수행됩니다.
... . ├── index.html ├── package.json ├── package.json.md5 ├── package-lock.json ├── postcss.config.js ├── README.md ├── src │ ├── App.svelte │ ├── assets │ │ ├── fonts │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ └── OFL.txt │ │ └── images │ │ └── logo-universal.png │ ├── lib │ │ ├── BackBtn.svelte │ │ ├── BottomActions.svelte │ │ ├── EditActions.svelte │ │ ├── EntriesList.svelte │ │ ├── Language.svelte │ │ ├── popups │ │ │ ├── alert-icons.ts │ │ │ └── popups.ts │ │ ├── ShowPasswordBtn.svelte │ │ └── TopActions.svelte │ ├── locales │ │ ├── en.json │ │ └── es.json │ ├── main.ts │ ├── pages │ │ ├── About.svelte │ │ ├── AddPassword.svelte │ │ ├── Details.svelte │ │ ├── EditPassword.svelte │ │ ├── Home.svelte │ │ ├── Login.svelte │ │ └── Settings.svelte │ ├── style.css │ └── vite-env.d.ts ├── svelte.config.js ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wailsjs ├── go │ ├── main │ │ ├── App.d.ts │ │ └── App.js │ └── models.ts └── runtime ├── package.json ├── runtime.d.ts └── runtime.js ...
앞서 말했듯이 2개의 props(listCounter 및 search)가 수신되고 상태가 유지됩니다(항목 입력: models.PasswordEntry[] = $state([]);). 사용자의 요청에 따라 구성요소가 마운트되면 백엔드에 저장된 비밀번호 항목의 전체 목록을 요청합니다. 검색어가 없으면 해당 상태에 저장됩니다. 있는 경우 획득한 배열에 대한 간단한 필터링이 수행되고 다음 상태에 저장됩니다.
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT import {models} from '../models'; export function AddPasswordEntry(arg1:string,arg2:string,arg3:string):Promise<string>; export function CheckMasterPassword(arg1:string):Promise<boolean>; export function DeleteEntry(arg1:string):Promise<void>; export function Drop():Promise<void>; export function GetAllEntries():Promise<Array<models.PasswordEntry>>; export function GetEntryById(arg1:string):Promise<models.PasswordEntry>; export function GetLanguage():Promise<string>; export function GetMasterPassword():Promise<boolean>; export function GetPasswordCount():Promise<number>; export function SaveLanguage(arg1:string):Promise<void>; export function SaveMasterPassword(arg1:string):Promise<string>; export function UpdateEntry(arg1:models.PasswordEntry):Promise<boolean>;
표시된 목록에서 사용자는 2가지 작업을 수행할 수 있습니다. 첫 번째는 해당 버튼을 클릭할 때 수행되는 항목의 세부 정보를 표시하는 것입니다. onclick={() => 푸시(`/details/${entry.Id}`)}. 기본적으로 라우팅 라이브러리의 푸시 메소드를 호출하여 사용자를 세부정보 보기로 이동하지만 해당 항목에 해당하는 Id 매개변수를 전달합니다.
사용자가 수행할 수 있는 또 다른 작업은 목록에서 항목을 삭제하는 것입니다. 해당 버튼을 클릭하면 showAlert 기능을 호출하는 확인 팝업이 표시됩니다. 이 함수는 실제로 sweetalert2 라이브러리에 대한 추상화 계층인 showWarning을 호출합니다(sweetalert2 라이브러리를 호출하는 모든 함수는 frontend/src/lib/popups/popups.ts에 있음). 사용자가 삭제 작업을 확인하면 DeleteEntry 바인딩이 호출되고(DB에서 삭제), 반환된 약속이 해결되면 deleteItem이 호출됩니다(항목 상태에 저장된 배열에서 삭제). :
/* package.json */ ... }, "dependencies": { "svelte-copy": "^2.0.0", "svelte-i18n": "^4.0.1", "svelte-spa-router": "^4.0.1", "sweetalert2": "^11.14.5" } ...
홈 보기의 다른 구성 요소(BottomActions)는 훨씬 간단합니다. props를 받지 않으며 사용자를 다양한 보기(설정, 정보 또는 AddPassword)로 리디렉션하는 것으로 제한됩니다.
AddPassword 및 EditPassword 보기는 매우 유사한 논리를 공유하며 로그인 보기와도 유사합니다. 둘 다 사용자가 텍스트 입력에 입력한 내용의 시작과 끝 부분에 공백을 입력하는 것을 허용하지 않으며 암호 길이가 최소 6 ASCII 문자여야 한다는 로그인 보기와 동일한 정책을 따릅니다. 기본적으로 이들을 구별하는 점은 수행해야 하는 작업과 관련된 Wails 생성 링크를 호출한다는 것입니다.
/* main.ts */ import { mount } from 'svelte' import './style.css' import App from './App.svelte' import { addMessages, init } from "svelte-i18n"; // ⇐ ⇐ import en from './locales/en.json'; // ⇐ ⇐ import es from './locales/es.json'; // ⇐ ⇐ addMessages('en', en); // ⇐ ⇐ addMessages('es', es); // ⇐ ⇐ init({ fallbackLocale: 'en', // ⇐ ⇐ initialLocale: 'en', // ⇐ ⇐ }); const app = mount(App, { target: document.getElementById('app')!, }) export default app
다소 복잡한 또 다른 보기는 설정입니다. 여기에는 상위 구성 요소(설정)에서 props 언어 이름을 받는 언어 구성 요소가 있습니다.
/* frontend/src/locales/en.json */ { "language": "Language", "app_title": "Nu-i uita • minimalist password store", "select_directory": "Select the directory where to save the data export", "select_file": "Select the backup file to import", "master_password": "Master Password ?", "generate": "Generate", "insert": "Insert", "login": "Login", ... } /* frontend/src/locales/es.json */ { "language": "Idioma", "app_title": "Nu-i uita • almacén de contraseñas minimalista", "select_directory": "Selecciona el directorio donde guardar los datos exportados", "select_file": "Selecciona el archivo de respaldo que deseas importar", "master_password": "Contraseña Maestra ?", "generate": "Generar", "insert": "Insertar", "login": "Inciar sesión", ... }
이 구성요소의 HTML은 사용자의 언어 선택을 처리하는 단일 select입니다. onchange 이벤트에서는 다음 3가지 작업을 수행하는 함수(handleChange)를 받습니다.
- svelte-i18n 라이브러리를 사용하여 프런트엔드에서 언어를 설정합니다
- Wails 런타임이 애플리케이션 제목 표시줄의 제목과 관련하여 디렉토리 선택 및 파일 선택 대화 상자의 제목을 변경하도록 이벤트("change_titles")를 생성합니다. 이전 작업으로
- 사용자가 선택한 언어를 DB에 저장하여 다음에 애플리케이션을 시작할 때 해당 언어로 구성된 앱이 열리도록 합니다.
설정 보기로 돌아가면 전체 작업은 백엔드와 주고받는 일련의 이벤트에 의해 관리됩니다. 가장 간단한 것은 종료 버튼입니다. 사용자가 이 버튼을 클릭하면 종료 이벤트가 트리거되어 백엔드에서 수신되고 애플리케이션이 닫힙니다(onclick={() => EventsEmit("quit")}). 팁은 이미 설명한 바와 같이 Esc 키(단축키)가 동일한 작업을 수행함을 사용자에게 알려줍니다.
재설정 버튼은 팝업 창을 표시하는 기능을 호출합니다.
... . ├── index.html ├── package.json ├── package.json.md5 ├── package-lock.json ├── postcss.config.js ├── README.md ├── src │ ├── App.svelte │ ├── assets │ │ ├── fonts │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ └── OFL.txt │ │ └── images │ │ └── logo-universal.png │ ├── lib │ │ ├── BackBtn.svelte │ │ ├── BottomActions.svelte │ │ ├── EditActions.svelte │ │ ├── EntriesList.svelte │ │ ├── Language.svelte │ │ ├── popups │ │ │ ├── alert-icons.ts │ │ │ └── popups.ts │ │ ├── ShowPasswordBtn.svelte │ │ └── TopActions.svelte │ ├── locales │ │ ├── en.json │ │ └── es.json │ ├── main.ts │ ├── pages │ │ ├── About.svelte │ │ ├── AddPassword.svelte │ │ ├── Details.svelte │ │ ├── EditPassword.svelte │ │ ├── Home.svelte │ │ ├── Login.svelte │ │ └── Settings.svelte │ ├── style.css │ └── vite-env.d.ts ├── svelte.config.js ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wailsjs ├── go │ ├── main │ │ ├── App.d.ts │ │ └── App.js │ └── models.ts └── runtime ├── package.json ├── runtime.d.ts └── runtime.js ...
사용자가 작업을 수락하면 DB의 모든 컬렉션을 정리하는 Drop 바인딩이 호출되고, 반환된 약속이 해결되면 사용자를 로그인 보기로 보내서 다음을 표시합니다. 작업의 성공을 나타내는 모달입니다.
남은 나머지 두 액션은 서로 비슷하므로 데이터 가져오기를 살펴보겠습니다.
사용자가 해당 버튼을 클릭하면 백엔드에서 수신되는 이벤트가 발생합니다(onclick={() => EventsEmit("import_data")}). 수신되면 사용자가 백업 파일을 선택할 수 있도록 기본 파일 선택 대화 상자가 열립니다. 사용자가 파일을 선택하면 경로(fileLocation)가 포함된 변수에 빈 문자열이 포함되지 않으며 이는 이제 프런트엔드에서 수신되는 백엔드("enter_password")에서 이벤트를 트리거하여 다음을 표시합니다. 내보내기가 수행될 때 사용된 마스터 비밀번호를 묻는 새 팝업 창. 이번에도 프런트엔드는 사용자가 입력한 마스터 비밀번호를 전달하는 또 다른 이벤트("password")를 내보냅니다. 이 새로운 이벤트는 백엔드에서 수신되면 사용자가 선택한 백업 파일에서 DB의 데이터를 읽고 복원하는 작업을 수행하는 Db 패키지의 ImportDump 메서드를 실행합니다. 결과적으로 실행 결과(성공 또는 실패)를 첨부된 데이터로 전달하는 새 이벤트("imported_data")가 생성됩니다. 프런트엔드는 이벤트를 수신하면 다음 두 가지 작업만 수행하면 됩니다.
- 결과가 성공하면 백업 파일에 저장된 언어를 설정하고 작업 성공을 나타내는 모달을 표시합니다
- 어떤 이유로든 가져오기를 수행할 수 없는 경우 오류와 원인을 표시하세요.
이 모든 것은 말로 설명하는 것보다 코드 로직으로 보는 것이 훨씬 쉽습니다. ?:
... . ├── index.html ├── package.json ├── package.json.md5 ├── package-lock.json ├── postcss.config.js ├── README.md ├── src │ ├── App.svelte │ ├── assets │ │ ├── fonts │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ └── OFL.txt │ │ └── images │ │ └── logo-universal.png │ ├── lib │ │ ├── BackBtn.svelte │ │ ├── BottomActions.svelte │ │ ├── EditActions.svelte │ │ ├── EntriesList.svelte │ │ ├── Language.svelte │ │ ├── popups │ │ │ ├── alert-icons.ts │ │ │ └── popups.ts │ │ ├── ShowPasswordBtn.svelte │ │ └── TopActions.svelte │ ├── locales │ │ ├── en.json │ │ └── es.json │ ├── main.ts │ ├── pages │ │ ├── About.svelte │ │ ├── AddPassword.svelte │ │ ├── Details.svelte │ │ ├── EditPassword.svelte │ │ ├── Home.svelte │ │ ├── Login.svelte │ │ └── Settings.svelte │ ├── style.css │ └── vite-env.d.ts ├── svelte.config.js ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wailsjs ├── go │ ├── main │ │ ├── App.d.ts │ │ └── App.js │ └── models.ts └── runtime ├── package.json ├── runtime.d.ts └── runtime.js ...
프런트엔드(EventsOn)에 리스너를 등록하는 Wails 런타임 함수는 호출 시 해당 리스너를 취소하는 함수를 반환한다는 점을 언급할 가치가 있습니다. 구성 요소가 삭제되면 해당 리스너를 취소하는 것이 편리합니다. React와 마찬가지로 onMount 후크는 리스너가 정리 함수를 반환하도록 하여 해당 리스너를 "정리"할 수 있습니다. 이 경우 별도로 저장하는 예방 조치를 취한 EventsOn에서 반환된 모든 함수를 호출합니다. 변수:
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT import {models} from '../models'; export function AddPasswordEntry(arg1:string,arg2:string,arg3:string):Promise<string>; export function CheckMasterPassword(arg1:string):Promise<boolean>; export function DeleteEntry(arg1:string):Promise<void>; export function Drop():Promise<void>; export function GetAllEntries():Promise<Array<models.PasswordEntry>>; export function GetEntryById(arg1:string):Promise<models.PasswordEntry>; export function GetLanguage():Promise<string>; export function GetMasterPassword():Promise<boolean>; export function GetPasswordCount():Promise<number>; export function SaveLanguage(arg1:string):Promise<void>; export function SaveMasterPassword(arg1:string):Promise<string>; export function UpdateEntry(arg1:models.PasswordEntry):Promise<boolean>;
애플리케이션의 프런트엔드 부분에 대한 검토를 마치려면 About 구성 요소에 대해 언급하는 것만 남았습니다. 이는 about에서 일반적인 것처럼 애플리케이션에 대한 정보를 표시하는 것으로 제한되므로 논리가 거의 없습니다. 그러나 우리가 볼 수 있듯이 보기에는 애플리케이션 저장소에 대한 링크가 표시됩니다. 분명히 일반 웹 페이지에서는 앵커 태그()를 사용하여 해당 링크로 이동하게 되지만 데스크탑 애플리케이션에서는 Wails가 런타임에 이에 대한 특정 기능(BrowserOpenURL)을 갖고 있지 않은 경우에는 이런 일이 발생하지 않습니다. :
/* package.json */ ... }, "dependencies": { "svelte-copy": "^2.0.0", "svelte-i18n": "^4.0.1", "svelte-spa-router": "^4.0.1", "sweetalert2": "^11.14.5" } ...
이렇게 하면 바이너리가 build/bin 폴더에 빌드됩니다. 그러나 다른 빌드 옵션을 선택하거나 크로스 컴파일을 수행하려면 Wails CLI 문서를 살펴보는 것이 좋습니다.
이 애플리케이션에 대해서는 이 시리즈의 첫 번째 부분에서 이미 언급한 것 같습니다. Windows 및 Linux용 컴파일에만 중점을 두었습니다. 이러한 작업(테스트로 인해 반복됨)을 편안하게 수행하기 위해 몇 가지 작은 스크립트와 이를 "조정"하는 Makefile을 만들었습니다.
make create-bundles 명령은 Linux 버전용 애플리케이션과 함께 .tar.xz 압축 파일과 실행 파일을 설치하는 '설치 프로그램' 역할을 하는 Makefile, 시작 메뉴 및 해당 애플리케이션 아이콘. Windows 버전의 경우 바이너리는 dist/라는 폴더 내에서 .zip으로 압축됩니다. 그러나 크로스 플랫폼 자동화 빌드를 선호하는 경우 Wails에는 생성된 아티팩트를 업로드(기본 옵션)할 수 있는 Github Actions가 있습니다. 저장소에 저장하세요.
make create-bundles 명령을 실행할 때 사용하면 Wails 명령 wails build -clean -upx(Linux의 경우) 또는 wails build -skipbindings -s -platform windows/amd64 -를 호출합니다. upx(Windows의 경우). -upx 플래그는 컴퓨터에 설치해야 하는 UPX 유틸리티를 사용하여 바이너리를 압축하는 것을 의미합니다. 실행 파일 크기가 작은 비결 중 하나는 이 유틸리티가 수행하는 탁월한 압축 작업 때문입니다.
마지막으로, 빌드 스크립트는 자동으로 현재 저장소 태그를 정보 보기에 추가하고 빌드 후에 해당 값을 기본값(DEV_VERSION)으로 복원합니다.
휴! 2개의 포스팅이 생각보다 길어졌네요! 하지만 여러분이 이 책을 좋아하셨기를 바라며, 무엇보다도 새로운 프로젝트에 대해 생각하는 데 도움이 되기를 바랍니다. 프로그래밍을 배우다 보면 그런 일이…
이 GitHub 저장소에서 모든 애플리케이션 코드를 찾을 수 있다는 점을 기억하세요.
다른 포스팅에서도 뵙겠습니다. 즐거운 코딩 되셨나요?!
위 내용은 미니멀한 비밀번호 관리자 데스크톱 앱: Golang의 Wails 프레임워크 진출(2부)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











Go Language는 효율적이고 확장 가능한 시스템을 구축하는 데 잘 작동합니다. 장점은 다음과 같습니다. 1. 고성능 : 기계 코드로 컴파일, 빠른 달리기 속도; 2. 동시 프로그래밍 : 고어 라틴 및 채널을 통한 멀티 태스킹 단순화; 3. 단순성 : 간결한 구문, 학습 및 유지 보수 비용 절감; 4. 크로스 플랫폼 : 크로스 플랫폼 컴파일, 쉬운 배포를 지원합니다.

Golang은 동시성에서 C보다 낫고 C는 원시 속도에서 Golang보다 낫습니다. 1) Golang은 Goroutine 및 Channel을 통해 효율적인 동시성을 달성하며, 이는 많은 동시 작업을 처리하는 데 적합합니다. 2) C 컴파일러 최적화 및 표준 라이브러리를 통해 하드웨어에 가까운 고성능을 제공하며 극도의 최적화가 필요한 애플리케이션에 적합합니다.

Golang과 C는 각각 공연 경쟁에서 고유 한 장점을 가지고 있습니다. 1) Golang은 높은 동시성과 빠른 발전에 적합하며 2) C는 더 높은 성능과 세밀한 제어를 제공합니다. 선택은 프로젝트 요구 사항 및 팀 기술 스택을 기반으로해야합니다.

Golang은 성능과 확장 성 측면에서 Python보다 낫습니다. 1) Golang의 컴파일 유형 특성과 효율적인 동시성 모델은 높은 동시성 시나리오에서 잘 수행합니다. 2) 해석 된 언어로서 파이썬은 천천히 실행되지만 Cython과 같은 도구를 통해 성능을 최적화 할 수 있습니다.

C는 하드웨어 리소스 및 고성능 최적화가 직접 제어되는 시나리오에 더 적합하지만 Golang은 빠른 개발 및 높은 동시성 처리가 필요한 시나리오에 더 적합합니다. 1.C의 장점은 게임 개발과 같은 고성능 요구에 적합한 하드웨어 특성 및 높은 최적화 기능에 가깝습니다. 2. Golang의 장점은 간결한 구문 및 자연 동시성 지원에 있으며, 이는 동시성 서비스 개발에 적합합니다.

Golang과 Python은 각각 고유 한 장점이 있습니다. Golang은 고성능 및 동시 프로그래밍에 적합하지만 Python은 데이터 과학 및 웹 개발에 적합합니다. Golang은 동시성 모델과 효율적인 성능으로 유명하며 Python은 간결한 구문 및 풍부한 라이브러리 생태계로 유명합니다.

goimpactsdevelopmentpositively throughlyspeed, 효율성 및 단순성.

Golang과 C의 성능 차이는 주로 메모리 관리, 컴파일 최적화 및 런타임 효율에 반영됩니다. 1) Golang의 쓰레기 수집 메커니즘은 편리하지만 성능에 영향을 줄 수 있습니다. 2) C의 수동 메모리 관리 및 컴파일러 최적화는 재귀 컴퓨팅에서 더 효율적입니다.
