From 31c84254f72ea77b7c0579ee656262452831bbbe Mon Sep 17 00:00:00 2001 From: misitebao Date: Wed, 9 Sep 2020 12:43:35 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:New=20Chinese=20document=20example=20|?= =?UTF-8?q?=20=E6=96=B0=E5=A2=9E=E4=B8=AD=E6=96=87=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vuepress/config.js | 51 +- package-lock.json | 435 +++++++ package.json | 10 +- zh/README.md | 20 + zh/development/README.md | 110 ++ zh/home.md | 235 ++++ zh/project_status.md | 83 ++ zh/quick_start.md | 204 +++ zh/reference/README.md | 665 ++++++++++ zh/releases/README.md | 14 + zh/releases/v0.17.0.md | 40 + zh/releases/v0.18.2.md | 89 ++ zh/releases/v0.18.5.md | 46 + zh/releases/v0.19.0.md | 40 + zh/releases/v0.20.0.md | 32 + zh/tutorials/README.md | 48 + zh/tutorials/quotes.md | 481 +++++++ zh/tutorials/template.md | 217 ++++ zh/tutorials/todo.md | 2649 ++++++++++++++++++++++++++++++++++++++ 19 files changed, 5459 insertions(+), 10 deletions(-) create mode 100644 zh/README.md create mode 100644 zh/development/README.md create mode 100644 zh/home.md create mode 100644 zh/project_status.md create mode 100644 zh/quick_start.md create mode 100644 zh/reference/README.md create mode 100644 zh/releases/README.md create mode 100644 zh/releases/v0.17.0.md create mode 100644 zh/releases/v0.18.2.md create mode 100644 zh/releases/v0.18.5.md create mode 100644 zh/releases/v0.19.0.md create mode 100644 zh/releases/v0.20.0.md create mode 100644 zh/tutorials/README.md create mode 100644 zh/tutorials/quotes.md create mode 100644 zh/tutorials/template.md create mode 100644 zh/tutorials/todo.md diff --git a/.vuepress/config.js b/.vuepress/config.js index 28a0c1d..aef7306 100644 --- a/.vuepress/config.js +++ b/.vuepress/config.js @@ -1,8 +1,7 @@ module.exports = { title: "Wails", - description: - "The lightweight framework for building web-like desktop apps using Go", + description: "The lightweight framework for building web-like desktop apps using Go", markdown: { lineNumbers: true }, @@ -14,10 +13,19 @@ module.exports = { } ] ], - themeConfig: { - sidebar: { - "/": ["home", "quick_start", "/reference/", "/tutorials/", "/development/", "/releases/", "project_status"] + locales: { + '/': { + lang: 'en-US', + title: 'Wails', + description: `The lightweight framework for building web-like desktop apps using Go` }, + '/zh/': { + lang: 'zh-CN', + title: 'Wails', + description: '使用Go构建类似于Web的桌面应用程序的轻量级框架' + } + }, + themeConfig: { repo: "wailsapp/wails", // repoLabel: 'Contribute!', // if your docs are in a different repo from your main project: @@ -30,6 +38,37 @@ module.exports = { editLinks: true, // custom text for edit link. Defaults to "Edit this page" // editLinkText: 'Help us improve this page!', - nav: [{ text: "Wails", link: "/home.html#wails" }, { text: "Quick Start", link: "/quick_start" }, { text: "Tutorials", link: "/tutorials/" }, { text: "Project Status", link: "/project_status" }, { text: "Twitter", link: "https://twitter.com/wailsapp" }, { text: "Slack", link: "https://gophers.slack.com/messages/CJ4P9F7MZ/" }, { text: "Slack Invite", link: "https://invite.slack.golangbridge.org/" }] + locales: { + '/': { + label: 'English', + nav: [ + { text: "Wails", link: "/home.html#wails" }, + { text: "Quick Start", link: "/quick_start" }, + { text: "Tutorials", link: "/tutorials/" }, + { text: "Project Status", link: "/project_status" }, + { text: "Twitter", link: "https://twitter.com/wailsapp" }, + { text: "Slack", link: "https://gophers.slack.com/messages/CJ4P9F7MZ/" }, + { text: "Slack Invite", link: "https://invite.slack.golangbridge.org/" } + ], + sidebar: { + "/": ["home", "quick_start", "/reference/", "/tutorials/", "/development/", "/releases/", "project_status"] + }, + }, + '/zh/': { + label: '简体中文', + nav: [ + { text: "Wails", link: "/zh/home.html#wails" }, + { text: "快速开始", link: "/zh/quick_start" }, + { text: "教程", link: "/zh/tutorials/" }, + { text: "项目状态", link: "/zh/project_status" }, + { text: "Twitter", link: "https://twitter.com/wailsapp" }, + { text: "Slack", link: "https://gophers.slack.com/messages/CJ4P9F7MZ/" }, + { text: "Slack 邀请", link: "https://invite.slack.golangbridge.org/" } + ], + sidebar: { + "/zh/": ["/zh/home", "/zh/quick_start", "/zh/reference/", "/zh/tutorials/", "/zh/development/", "/zh/releases/", "/zh/project_status"] + }, + } + } } }; diff --git a/package-lock.json b/package-lock.json index ca00aa4..06e402e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1243,6 +1243,12 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, + "@zeit/schemas": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz", + "integrity": "sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg==", + "dev": true + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -1350,6 +1356,15 @@ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" }, + "ansi-align": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "dev": true, + "requires": { + "string-width": "^2.0.0" + } + }, "ansi-colors": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", @@ -1405,6 +1420,18 @@ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "arch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.2.tgz", + "integrity": "sha512-NTBIIbAfkJeIletyABbVtdPgeKfDafR+1mZV/AyyfC1UkVkp9iUjV+wwmqtUgphHYajbI86jejBJp5e+jkGTiQ==", + "dev": true + }, + "arg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-2.0.0.tgz", + "integrity": "sha512-XxNTUzKnz1ctK3ZIcI2XUPlD96wbHP2nGqkPKpvk/HNRlPveYrXIVSTk9m3LcqOgDPg3B1nMvdV/K8wZd7PG4w==", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -1734,6 +1761,29 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, + "boxen": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "dev": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + } + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2098,6 +2148,12 @@ } } }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", + "dev": true + }, "clipboard": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", @@ -2109,6 +2165,66 @@ "tiny-emitter": "^2.0.0" } }, + "clipboardy": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-1.2.3.tgz", + "integrity": "sha512-2WNImOvCRe6r63Gk9pShfkwXsVtKCroMAevIbiae021mS850UkWPbevxsBz3tnvjZIEGvlwaqCPsw+4ulzNgJA==", + "dev": true, + "requires": { + "arch": "^2.1.0", + "execa": "^0.8.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz", + "integrity": "sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } + }, "cliui": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", @@ -2462,6 +2578,58 @@ "sha.js": "^2.4.8" } }, + "cross-env": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.2.tgz", + "integrity": "sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.1" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -2710,6 +2878,12 @@ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, "deepmerge": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz", @@ -3396,6 +3570,23 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, + "fast-url-parser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", + "integrity": "sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=", + "dev": true, + "requires": { + "punycode": "^1.3.2" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, "faye-websocket": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", @@ -4590,6 +4781,12 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, "internal-ip": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", @@ -6662,6 +6859,18 @@ } } }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", @@ -6747,6 +6956,25 @@ "unicode-match-property-value-ecmascript": "^1.1.0" } }, + "registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "dev": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "dev": true, + "requires": { + "rc": "^1.0.1" + } + }, "regjsgen": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", @@ -7074,6 +7302,129 @@ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.7.0.tgz", "integrity": "sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==" }, + "serve": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/serve/-/serve-11.3.2.tgz", + "integrity": "sha512-yKWQfI3xbj/f7X1lTBg91fXBP0FqjJ4TEi+ilES5yzH0iKJpN5LjNb1YzIfQg9Rqn4ECUS2SOf2+Kmepogoa5w==", + "dev": true, + "requires": { + "@zeit/schemas": "2.6.0", + "ajv": "6.5.3", + "arg": "2.0.0", + "boxen": "1.3.0", + "chalk": "2.4.1", + "clipboardy": "1.2.3", + "compression": "1.7.3", + "serve-handler": "6.1.3", + "update-check": "1.5.2" + }, + "dependencies": { + "ajv": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.3.tgz", + "integrity": "sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "compression": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", + "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.14", + "debug": "2.6.9", + "on-headers": "~1.0.1", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "serve-handler": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.3.tgz", + "integrity": "sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "fast-url-parser": "1.1.3", + "mime-types": "2.1.18", + "minimatch": "3.0.4", + "path-is-inside": "1.0.2", + "path-to-regexp": "2.2.1", + "range-parser": "1.2.0" + }, + "dependencies": { + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "dev": true + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "requires": { + "mime-db": "~1.33.0" + } + }, + "path-to-regexp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", + "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==", + "dev": true + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + } + } + }, "serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", @@ -7625,6 +7976,12 @@ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, "stylehacks": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", @@ -7737,6 +8094,65 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" }, + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "dev": true, + "requires": { + "execa": "^0.7.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } + }, "terser": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/terser/-/terser-4.0.0.tgz", @@ -8100,6 +8516,16 @@ "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==" }, + "update-check": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.2.tgz", + "integrity": "sha512-1TrmYLuLj/5ZovwUS7fFd1jMH3NnFDN1y1A8dboedIDt7zs/zJMo6TwwlhYKkSeEwzleeiSBV5/3c9ufAQWDaQ==", + "dev": true, + "requires": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0" + } + }, "upper-case": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", @@ -8615,6 +9041,15 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, + "widest-line": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "dev": true, + "requires": { + "string-width": "^2.1.1" + } + }, "worker-farm": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", diff --git a/package.json b/package.json index 629cd90..393f7f1 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,9 @@ "@vuepress/plugin-google-analytics": "^1.0.0-rc.1" }, "scripts": { - "docs:build": "NODE_ENV=production ./node_modules/.bin/vuepress build", + "docs:build": "cross-env NODE_ENV=production ./node_modules/.bin/vuepress build", "docs:dev": "npx vuepress dev", - "test": "echo \"Error: no test specified\" && exit 1" + "serve:test": "serve .vuepress/dist/" }, "repository": { "type": "git", @@ -24,6 +24,8 @@ }, "homepage": "https://github.com/wailsapp/docs#readme", "devDependencies": { - "@vuepress/plugin-google-analytics": "^1.0.0-rc.1" + "@vuepress/plugin-google-analytics": "^1.0.0-rc.1", + "cross-env": "^7.0.2", + "serve": "^11.3.2" } -} +} \ No newline at end of file diff --git a/zh/README.md b/zh/README.md new file mode 100644 index 0000000..ee523b2 --- /dev/null +++ b/zh/README.md @@ -0,0 +1,20 @@ +--- +home: true +heroImage: /media/logo.png +heroText: " " +tagline: 使用Go&Web Technologies构建桌面应用程序的框架 +actionText: 快速开始 → +actionLink: /zh/quick_start +pageClass: homepage-class +actionTextTwo: 学习更多 → +actionLinkTwo: /zh/home +features: + - title: 后端就是Go + details: 像往常一样编写标准的Go代码。 如果您已有Go应用程序,则可以很容易的在其上放置HTML前端。 + - title: 包含任何前端 + details: 使用您喜欢的任何前端技术,然后从Javascript调用Go代码。 + - title: 应用非常棒 + details: Wails工具生成您的项目,同时构建您的前端和后端,并创建一个二进制文件。 支持Mac,Linux和Windows! +footer: MIT Licensed | Copyright © 2019-present Lea Anthony +sidebarDepth: 1 +--- diff --git a/zh/development/README.md b/zh/development/README.md new file mode 100644 index 0000000..6db4f05 --- /dev/null +++ b/zh/development/README.md @@ -0,0 +1,110 @@ +--- +sidebar: on +sidebarDepth: 1 +--- + +# Development + +In this section we cover all aspects of Wails development and contribution guidelines. + +## Background + +Historically, Wails development has been quite ad-hoc. The aim of this proposal is to create a simple workflow for contributing to the project. It is meant as a starting point and any improvements to this process are welcome (please open a ticket!). + +## Issue Driven Development + +If there is something to add to the code, whether a bug or enhancement, a ticket should be opened so that it can be discussed. If the coding goes ahead, a new branch should be created from the `develop` branch with a reference to the ticket ID, eg: + `64 - Support react` + +Commit messages should follow the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0-beta.3/#summary) format: + + * tag[(scope)]: message + +| Tag | Meaning | +| --------------- | -------------------- | +| fix | Bugfix | +| feat | New Feature | +| docs | Documentation update | +| BREAKING CHANGE | API Change | + +Examples: + + * fix: this is a fix for the project as a whole + * fix(cli): this is a fix for the cli + * docs: updated the contributors + +## Branch Workflow + + * Wails uses a gitflow-like approach to development + * Feature/Bugfix branches are created from the `develop` branch + * Once the work is complete, pull requests should be made against the develop branch + * As features are added, the `develop` branch is tagged with pre-release tags + * Releases are made weekly, so at the end of the weekly cycle, the latest features and bugfixes that were made will be merged to master and tagged with the next appropriate version. + +Example: + + * After release v0.14.0, a ticket (#63) is opened requesting react support + * This is worked on and a PR is made back to `develop` + * Once merged, `develop` is tagged with `v0.14.1-pre` + * A ticket (#64) is opened requesting ultralight support + * This is worked on and a PR is made back to `develop` + * Once merged, `develop` is tagged with `v0.14.2-pre` + * We reach the end of our week and merge v0.14.2-pre to master, tagging it as v0.15.0 + * Work continues on the `devel` branch + +
+ +
+ + +## Tooling + +The Wails cli has developer tooling built in, but needs activating. To create a developer version, do the following: + +``` +cd cmd/wails +go install --tags=dev +``` + +This unlocks a `wails dev` command that has subcommands for development. + +### Creating new project templates + +With a developer enabled cli, you can run `wails dev newtemplate` to create a new project template. You will be asked a number of questions regarding your template and as a result, a new directory will be created in `/cmd/templates`. + +Here is an example run: + +``` +Wails v0.14.4-pre - Generating new project template + +? Please enter the name of your template (eg: React/Webpack Basic): Mithril Basic +? Please enter a short description for the template (eg: React with Webpack 4): Mithril with Webpack 3 +? Please enter a long description: Mithril v2.0.0-rc.4 with Webpack 4 +? Please enter the name of the directory the frontend code resides (eg: frontend): frontend +? Please enter the install command (eg: npm install): npm install +? Please enter the build command (eg: npm run build): npm run build +? Please enter the serve command (eg: npm run serve): npm run serve +? Please enter the name of the directory to copy the wails bridge runtime (eg: src): src +? Please enter a directory name for the template: mithril-basic +Created new template 'Mithril Basic' in directory '/Users/lea/Projects/wails/cmd/templates/mithril-basic' +``` +This generates the following `template.json`: + +```json +{ + "name": "Mithril Basic", + "version": "1.0.0", + "shortdescription": "Mithril with Webpack 3", + "description": "Mithril v2.0.0-rc.4 with Webpack 4", + "install": "npm install", + "build": "npm run build", + "author": "Duncan Disorderly ", + "created": "2019-05-20 20:16:30.394489 +1000 AEST m=+159.490635188", + "frontenddir": "frontend", + "serve": "npm run serve", + "bridge": "src", + "wailsdir": "" +} +``` + +*Note: The `wailsdir` key is currently unused but will be used in place of bridge in the [near future](https://github.com/wailsapp/wails/issues/88)* \ No newline at end of file diff --git a/zh/home.md b/zh/home.md new file mode 100644 index 0000000..fa449f0 --- /dev/null +++ b/zh/home.md @@ -0,0 +1,235 @@ +--- +sidebar: on +sidebarDepth: 1 +--- + +# Wails + +> 使用Go&Web Technologies构建桌面应用程序的框架。 + +## 关于 + +向Go程序提供Web界面的传统方法是通过内置的Web服务器。 Wails提供了一种不同的方法:它提供了将Go代码和Web前端包装到单个二进制文件中的能力。通过处理项目创建,编译和捆绑,Wails cli可以使您轻松实现这一目标。您要做的就是发挥创意! + +## 功能 + +* Use standard Go libraries/frameworks for the backend +* Use any frontend technology to build your UI +* Expose Go methods/functions to the frontend via a single bind command +* Uses native rendering engines - no embedded browser +* Shared events system +* Native file dialogs +* Powerful cli tool +* Multiplatform + +## 总览 + +Wails是一个框架,可帮助使用Go and Web Technologies编写桌面应用程序。对于前端,它使用[Webview] [1]库。反过来,它使用平台的本机渲染引擎(当前用于Linux和Mac的Webkit,用于Windows的MSHTML)。前端使用HTML /Javascript /CSS进行编码,后端是纯Go。通过绑定机制,可以将Go代码作为返回Promise的功能公开给前端。该项目编译为单个可执行文件,将所有资产捆绑到其中。在Windows和MacOS上,可以将二进制文件捆绑到特定于平台的程序包中进行分发。 + +首先,让我们完成设置过程。 + +## 概念 + +Wails旨在使Web技术和Go之间的差距尽可能小。前端是[Webview] [1]组件,您可以使用喜欢的任何常见Javascript框架开发前端代码,并与其中的Go代码进行无缝交互。这是通过共享IPC机制完成的。 + +

+
+

+ +### IPC概念 + +IPC机制可在2个运行时中运行-一个运行在Javascript中,另一个运行在Go中。它们都提供了一个简单的界面,从而减轻了开发人员无需直接处理IPC机制的负担。 + +

+
+

+ +运行时共享开发人员可以与之交互的公共组件:绑定和事件。 + +

+
+

+ +### 绑定 + +Wails应用程序提供了一种方法,可让您将Go代码公开(绑定)到前端。使用此方法,可以将任意函数或结构与公开的方法绑定。在启动时,Wails将分析绑定的函数/方法并自动以Javascript提供等效的函数。这使您可以直接从Javascript调用绑定的Go代码。 + +

+
+

+ +JavaScript包装函数处理了调用Go代码的所有复杂性。您只需用Javascript调用该函数并收到一个Promise。 +绑定Go代码的功能处理了绑定的所有复杂性。如果对Go代码的调用成功完成,则结果将传递到resolve函数。如果返回错误,则将其传递给拒绝函数。 + +### 事件 + +威尔提供了一个统一的事件系统,类似于Javascript的本地事件系统。这意味着从Go或Javascript发送的任何事件都可以由任何一方接收。数据可以与任何事件一起传递。这使您可以做一些整洁的事情,例如在Go中运行后台进程并通知任何更新的前端。 + +

+
+

+ +--- +[1]: https://github.com/zserge/webview + +## 步骤 + +首先,运行 `wails setup` 。这会提示您输入名称和电子邮件,并检查是否已安装依赖项。名称和电子邮件仅用于填写项目模板文件,“仅此而已” *。 +依赖关系会有所不同,具体取决于您使用的是Windows,Linux还是OSX。如果缺少依赖项,它将尝试提供有关如何安装依赖项的帮助。 + +设置完成后,就该初始化一个新项目了。 + +## 项目 + +每个Wails应用程序都构建在一个“项目”中。 + +### 初始化一个项目 + +您可以通过运行“wails init”命令来初始化一个新项目。这会问您一些问题: + + + 项目名称(用于填写模板) + + 要编译为的二进制文件的名称 + + 在其中创建项目的目录名称 + +当前,有3种前端模板:Vue/Webpack,Vuetify和React。 + +### 项目解析 + +在本节中,我们将研究组成项目的内容。 我们将专注于Vue/Webpack模板。 + +#### Vue/Webpack 模板 + +Vue/Webpack模板是一个生成Vuejs项目的项目模板。 它具有以下布局: + +
+.
+├── frontend/
+├── go.mod
+├── main.go
+└── project.json
+
+ + * Frontend 前端代码资源 + * go.mod Go依赖配置文件 + * main.go 主要应用程序入口 + * project.json 项目元数据 + +前端目录基本上是一个标准的Vue项目,其布局如下: + +
+.
+├── README.md
+├── babel.config.js
+├── package-lock.json
+├── package.json
+├── src/
+└── vue.config.js
+
+ + * babel.config.js - babel配置文件 + * package[-lock].json - npm配置文件 + * src - 前端源代码目录 + * vue.config.js - Vue 配置 + +src目录具有默认的Vue应用程序,其布局如下: +
+.
+├── App.vue
+├── assets/
+│   ├── css/
+│   │   └── main.css
+│   ├── fonts/
+│   │   ├── LICENSE.txt
+│   │   └── roboto/
+│   │       ├── roboto-v18-latin-regular.eot
+│   │       ├── roboto-v18-latin-regular.svg
+│   │       ├── roboto-v18-latin-regular.ttf
+│   │       ├── roboto-v18-latin-regular.woff
+│   │       └── roboto-v18-latin-regular.woff2
+│   └── images/
+│       └── logo.png
+├── components/
+│   └── HelloWorld.vue
+├── main.js
+└── wailsbridge.js
+
+ + * App.vue - Vue 项目根组件 + * assets - 前端资源目录 + * components - 前端组件目录 + * main.js - 前端项目的入口 + * wailsbridge.js - 前端和Go程序运行的桥梁 + +该项目分为两部分-后端由根目录中的Go文件组成,而前端则位于“frontend”目录中。前端项目是一个(大多数)标准Vue项目,在vue.config.js中具有一些自定义Webpack设置 + +由于Vue/Webpack模板中包含完整的前端项目,因此项目元数据包含有关如何安装和构建它的详细信息。这是Vue/Webpack项目的示例project.json文件: + +``` json +{ + "name": "My Project", + "description": "Enter your project description", + "author": { + "name": "John Doe", + "email": "jd@jd.com" + }, + "version": "0.1.0", + "binaryname": "my-project", + "frontend": { + "dir": "frontend", + "install": "npm install", + "build": "npm run build", + "bridge": "src", + "serve": "npm run serve" + } +} +``` + +如您所见,还有一个附加部分描述了前端项目。这些键具有以下含义: + +| Key | Meaning | +| -------- | ----------------------------------------------------------- | +| dir | 前端项目所在目录 | +| install | 执行以安装前端依赖 | +| build | 执行以生成前端项目的命令。 在此示例中,这将仅运行前端的package.json文件中定义的构建脚本。 | +| bridge | Wails桥接脚本所在的目录 | +| serve | 以“桥接”模式提供服务的命令(稍后会详细介绍)| + +通常情况下,您无需编辑这些值。 提供它们是为了使模板设计人员具有灵活性。 + +### 以服务形式启动项目 + +Wails可以使用您的标准前端工具,但仍然可以调用Go代码。 这是通过以桥接模式“服务”您的后端来完成的。 当您的前端启动时,它将连接到后端,并在后台进行所有绑定。 + +您可以通过在项目目录中运行`wails serve`命令来为后端服务。 + +您现在可以在前端目录中运行`npm run serve',并使用浏览器和Vue devtools插件开发应用程序。 + +### 构建项目 + +一旦可以将项目构建到单个应用程序中,请在项目目录中运行“ wails build”。 默认情况下,项目以“生产模式”构建。 如果您需要在调试模式下运行应用,请使用'-d'标志进行构建。 以调试模式运行意味着: + + - 调试消息被打印到终端 + - 您可以右键单击以在Web视图中检查您的应用程序(MacOS和Linux) + - 二进制文件具有许多标志,以帮助开发应用程序。 传递--help标志以查看选项。 + +## 执行流程 + +Wails应用程序的执行工作流程为: + + + The application window is created + + The Wails Javascript runtime is injected into the frontend + + All functions that were bound in Go are setup in the frontend + + All WailsInit methods are called with the Go Runtime + + The application CSS is injected into the frontend + + Finally, the application Javascript is injected into the frontend + + On shutdown ( Ctrl-C, Kill Window or runtime. Window. Close() ), all WailsShutdown methods are called + + The applications exits cleanly + + + 创建应用程序窗口 + + Wails Javascript运行时已注入前端 + + Go中绑定的所有功能都在前端设置 + + 所有WailsInit方法都通过Go Runtime调用 + + 将应用程序CSS注入前端 + + 最后,将应用程序Javascript注入到前端 + + 在关闭时(Ctrl-C,Kill Window或`runtime.Window.Close()`),将调用所有WailsShutdown方法 + + 应用程序干净退出 diff --git a/zh/project_status.md b/zh/project_status.md new file mode 100644 index 0000000..3ff7736 --- /dev/null +++ b/zh/project_status.md @@ -0,0 +1,83 @@ +--- +sidebar: on +sidebarDepth: 1 +--- + +# Project Status + +**This project is currently in BETA.** + +This means that in general it works, and there are a few things that are keeping it from becoming a v1 release. Any help with these issues would be greatly appreciated: + + - The project has been mostly developed on Mac, with a lesser amount of testing done on Window and Linux. Feedback from users of these platforms will be invaluable. + - The docs have taken a long time to write. They need reviewing by fresh eyes. Whilst I've tried to keep the docs cross-platform, there may be areas that are lacking (screenshots!). + - Wails uses an older version of Webview, which uses a deprecated API for windowing on Mac. Attempts to use the newer releases have [not been successful](https://github.com/zserge/webview/issues/236). Any help on this would be appreciated. + +In building this project, I have followed the principle of: + + * Make it work + * Make it right + * Make it fast + +It is currently at stage 1. The project has been through a fair number of rewrites and refactors and as such, there will be areas that need attention. I'm hoping with the help of the community we can get to at least stage 2. + +## Platform Support + +These are the current tested platforms. If you have Wails working on a platform not specified here, please consider making a PR. + +* MacOS 10.14 +* Windows 10 +* Linux + * RedHat + * Centos + * Ubuntu 18.04, 19.04 + * Pop!_OS 19.04 + * Debian 9, 10 + * Gentoo + * Arch / Manjaro + * Manjaro + * Zorin + +## Template Support + +The current status of template/Platform support is as follows: + +| Template | Mac | Linux | Windows | +| ---------- | ------- | --------- | --------------- | +| Vue Basic | Working | Working | Working | +| Vuetify | Working | Working | Working | +| React | Working | Working | Working | +| Angular | Working | Working | [Not Working](https://github.com/wailsapp/wails/issues/146) | + + +## Roadmap + +The focus at the moment is very much on ironing out bugs and getting to v1.0. After that, there's a few avenues I'd like to explore: + + * Support other UI Frameworks + + Currently, Wails supports Vue, Vuetify, React and Angular projects, however there is no real reason it couldn't support other frameworks. It would be good (and relatively easy) to create Project Templates to support Preact, Svelte, etc. + + * Create more examples + + Even though Wails is only Beta, it's still possible to create some really cool things. As part of the journey, I created an MP3 player to see what was involved, using Go to decode artwork and sending that with the music to the frontend to play. I really look forward to seeing what can be built with this and hope to provide some sort of showcase, along with tutorials. + + * Expanding the rendering targets + + Wails has been designed to handle different targets for rendering your app. Currently it supports Webview and Browser (via a bridge). It would be good to look at other targets, EG: [Ultralight](https://ultralig.ht/). + + * Support for Desktop Elements + + There is currently no support for Menus, Tray, Notifications or any other native desktop elements. Initially, I thought that might be best served by the rendering target but perhaps it would be possible to support at the application layer. + + * Cross-Compilation + + The holy grail! I believe this might be possible via a docker container containing all the tooling. It's definitely something I think should be explored. + + * Wails Project UI + + Vue has an awesome UI for managing projects. It would be great to build something like this for managing Wails projects. + + * VSCode Extension + + It would be great to be able to have auto-complete of your bound methods whilst writing your frontend code. This would be a good solution as well as offering the usual helpers such as project creation, building, etc. diff --git a/zh/quick_start.md b/zh/quick_start.md new file mode 100644 index 0000000..6160ae1 --- /dev/null +++ b/zh/quick_start.md @@ -0,0 +1,204 @@ +--- +sidebar: on +sidebarDepth: 1 +--- + +# Quick Start + +## High Level Overview + +Wails applications consist of 2 parts: + + * A backend written in Go + * A frontend written using standard HTML/JS/CSS + +These are compiled and bundled together using the `wails build` command. This will firstly build the frontend project into Javascript and CSS bundles. It will then build the main Go application, which bundles the 2 frontend asset files as part of the application. This produces a single application. + +### Frontend + +The frontend is a standard frontend project (Vue, React, etc) that gets compiled down to Javascript and CSS bundles. It lives in the `frontend` directory of the project. There is nothing particularly special about the project other than it is configured to bundle the whole project down to 2 bundles: app.js and app.css. + +### Backend + +The backend initially comprises of a single main.go file: + +```go +package main + +import ( + "github.com/leaanthony/mewn" + "github.com/wailsapp/wails" +) + +func basic() string { + return "Hello World!" +} + +func main() { + + js := mewn.String("./frontend/dist/app.js") + css := mewn.String("./frontend/dist/app.css") + + app := wails.CreateApp(&wails.AppConfig{ + Width: 1024, + Height: 768, + Title: "Quotes", + JS: js, + CSS: css, + Colour: "#131313", + }) + app.Bind(basic) + app.Run() +} +``` +Here is a quick explanation of how this works: + +| Lines | Explanation | +| -------- | -------------------------------------------------------------- | +| 1 | The standard package directive. | +| 3-6 | Imports the Wails framework and the Mewn utility package that we will use for handling assets. | +| 8-10 | A standard Go function that returns the string "Hello World!". | +| 12 | The main function declaration. | +| 14 | Reads in the frontend javascript bundle as a string. | +| 15 | Reads in the frontend css bundle as a string. | +| 17-24 | Creates a new Wails application, specifying width, height, title and a colour for the application window. We also specify the Javascript and CSS we wish the application to render - the JS/CSS we previously read in on lines 14 & 15. | +| 25 | Binds our basic function to the application. We can then call this method from Javascript using `window.backend.basic()`. | +| 26 | Runs the application. | + +If you make changes to the frontend or backend, you simply have to run `wails build` to regenerate your application. + + +## Prerequisites + +Wails uses cgo to bind to the native rendering engines so a number of platform dependent libraries are needed as well as an installation of Go. The basic requirements are: + +- Go 1.12 or above +- npm + +Run `go version && npm --version` to verify. + +### MacOS + +Make sure you have the xcode command line tools installed. This can be done by running: + +`xcode-select --install` + +### Linux + +#### Debian/Ubuntu + +`sudo apt install libgtk-3-dev libwebkit2gtk-4.0-dev` + +_Debian: 8, 9, 10_ + +_Ubuntu: 16.04, 18.04, 19.04_ + +_Also succesfully tested on: Zorin 15, Parrot 4.7, Linuxmint 19, Elementary 5, Kali, Neon_ + +#### Arch Linux + +`sudo pacman -S webkit2gtk gtk3` + +_Also succesfully test on: ArcoLinuxB, Manjaro_ + +#### Centos + +`sudo yum install webkitgtk3-devel gtk3-devel` + +_CentOS 6, 7_ + +#### Fedora + +`sudo yum install webkit2gtk3-devel gtk3-devel` + +_Fedora 29, 30_ + +#### VoidLinux & VoidLinux-musl + +`xbps-install gtk+3-devel webkit2gtk-devel` + +#### Gentoo + +`sudo emerge gtk+:3 webkit-gtk` + +::: tip +If you have successfully installed these dependencies on a different flavour of Linux, please consider clicking the "Edit this page" link at the bottom and submit a PR. +::: + +### Windows + +Windows requires gcc and related tooling. The recommended download is from [http://tdm-gcc.tdragon.net/download](http://tdm-gcc.tdragon.net/download). Once this is installed, you are good to go. + + + +## Installation + +### Preparation + +Ensure Go modules are enabled: + +`export GO111MODULE=on` + +and go/bin is in your PATH variable: + +`echo $PATH | grep go/bin` + +### Install + +Installation is as simple as running the following command: + +
+go get github.com/wailsapp/wails/cmd/wails
+
+ +::: tip +Once installed, the `wails update` command may be used for subsequent updates. +::: + +### Setup + +To finish the installation setup your Wails system by running the [setup command](./reference/#setup) `wails setup` and filling your handle and email. + +## Generate a new project + +Generate a new project using the [init command](./reference/#init) `wails init`. + +Select the default options. + +## Build it! + +Change into the project directory `cd my-project` and compile your application using the [build command](./reference/#build) `wails build`. + +If all went well, you should have a compiled program in your local directory. Run it with `./my-project` or double click `myproject.exe` if on windows. + +
+ +
+ +### Serve + +#### `wails serve` + +While developing your apps using wails the preferred method is by the [serve command](./reference/#serve) `wails serve`. + +::: tip +This produces a much **faster** lightweight build in _debug_ mode, excluding `npm` build scripts, saving time when developing the backend and also enabling use of `npm run serve` for partial browser development of frontend! +::: + +#### `npm run serve` + +Change into the frontend directory `cd my-project/frontend` and serve your GUI using `npm run serve`. + + +## Next Steps + +If you would like to start making an app right away, we suggest you explore Wails via our _awesome_ [tutorials](./tutorials/). +If you would prefer to get to know the framework a little better before building anything, we suggest having a look through the +[concepts](./home.html#concepts). +Finally if you are advanced user and would like to get right in to it head over to the [API reference](./reference/#api) & [Cli reference](./reference/#cli) sections. + +::: tip +Come by our [Slack](https://gophers.slack.com/messages/CJ4P9F7MZ) channel. +[_Invite_](https://invite.slack.golangbridge.org) +For support or just to say hi! +::: diff --git a/zh/reference/README.md b/zh/reference/README.md new file mode 100644 index 0000000..c57b354 --- /dev/null +++ b/zh/reference/README.md @@ -0,0 +1,665 @@ +--- +sidebar: on +sidebarDepth: 1 +--- + +# Reference + +## API + +### Binding + +Having just a web frontend means nothing unless you can interact with the system. Wails enables this through 'binding' - making Go code callable from the frontend. There are 2 types of code you can bind to the frontend: Functions and Struct Methods. When they are bound, they may be used in the frontend. + +### Functions + +Binding a function is as easy as this: + +```go{18} +package main + +import ( + "github.com/wailsapp/wails" + "fmt" +) + +func Greet(name string) string { + return fmt.Printf("Hello %s!", name) +} + +func main() { + + app := wails.CreateApp(&wails.AppConfig{ + Width: 1024, + Height: 768, + }) + app.Bind(Greet) + app.Run() +} +``` + +When this is run, a Javascript function called 'Greet' is made available under the global 'backend' object. The function may be invoked by calling `backend.Greet`, EG: `backend.Greet("World")`. The dynamically generated functions return a standard promise. For this simple example, you could therefore print the result as so: `backend.Greet("World").then(console.log)`. + +### Type conversion + +Scalar types are automatically converted into the relevant Go types. Objects are converted to `map[string]interface{}`. If you wish to make those concrete types in Go, we recommend you use Hashicorp's [mapstructure](https://github.com/mitchellh/mapstructure). + +Example: + +Using a default Vue template project, we update `main.go` to include our struct and callback function: + +```go + type MyData struct { + A string + B float64 + C int64 + } + + // We are expecting a javascript object of the form: + // { A: "", B: 0.0, C: 0 } + func basic(data map[string]interface{}) string { + var result MyData + fmt.Printf("data: %#v\n", data) + + err := mapstructure.Decode(data, &result) + if err != nil { + // Do something with the error + } + fmt.Printf("result: %#v\n", result) + return "Hello World!" + } +``` + +In the frontend, we update the `getMessage` method in the `HelloWorld.vue` component to send our object: + +```go + getMessage: function() { + var self = this; + var mytestStruct = { + A: "hello", + B: 1.1, + C: 99 + } + window.backend.basic(mytestStruct).then(result => { + self.message = result; + }); + } +``` + +When you run this, you will get the following output: + +``` +data: map[string]interface {}{"A":"hello", "B":1.1, "C":99} +Result: main.MyData{A:"hello", B:1.1, C:99} +``` + +::: danger +It is recommended that business logic and data structure predominantly preside in the Go portion of your application and updates are sent to the front end using events. Managing state in 2 places leads to a very unhappy life. +::: + +### Structs + +It is possible to bind structs to the frontend in a similar way but we must be clear on what this means: Binding a struct simply means exposing the public methods of the struct to the frontend. Wails does not attempt, or even believe, that binding data to the frontend is a good thing. Wails views the frontend as primarily a view layer with state and business logic normally handled by Go. As such, the structs that you bind to the front end should be viewed as a "wrapper" or an "interface". + +Binding a struct is as easy as: + +robot.go: +```go +package main + +import "fmt" + +type Robot struct { + Name string +} + +func NewRobot() *Robot { + result := &Robot{ + Name: "Robbie", + } + return result +} + +func (t *Robot) Hello(name string) string { + return fmt.Sprintf("Hello %s! My name is %s", name, t.Name) +} + +func (t *Robot) Rename(name string) string { + t.Name = name + return fmt.Sprintf("My name is now '%s'", t.Name) +} + +func (t *Robot) privateMethod(name string) string { + t.Name = name + return fmt.Sprintf("My name is now '%s'", t.Name) +} + +``` +main.go: +```go +package main + +import "github.com/wailsapp/wails" + +func main() { + app := wails.CreateApp(&wails.AppConfig{ + Width: 1024, + Height: 768, + Title: "Binding Structs", + }) + + app.Bind(NewRobot()) + app.Run() +} +``` + +When the Robot struct is bound, it is made available at `backend.Robot` in the frontend. As the robot struct has a public method called `Hello`, then this is available to call at `backend.Robot.Hello`. The same is true for the `Rename` method. The robot struct also has another method called `privateMethod`, but as that is not public, it is not bound. + +Here is a demonstration of how this works by running the app in debug mode and using the inspector: + +
+ +
+ +#### Struct Initialisation + +If your struct has a special initialisation method, Wails will call it at startup. The signature for this method is: +```go + WailsInit(runtime *wails.Runtime) error +``` +This allows you to do some initialisation before the main application is launched. + +```go + type MyStruct struct { + runtime *wails.Runtime + } + + func (s *MyStruct) WailsInit(runtime *wails.Runtime) error { + // Save runtime + s.runtime = runtime + + // Do some other initialisation + + return nil + } + +``` + +If an error is returned, then the application will log the error and shutdown. + +The Runtime Object that is passed to it is the primary means for interacting with the application at runtime. It consists of a number of subsystems which provide access to different parts of the system. This is detailed in the [Wails Runtime](#wails-runtime) section. + +#### Struct Shutdown + +If your struct has a special shutdown method, Wails will call it during application shutdown. The signature for this method is: +```go + WailsShutdown() +``` +This allows you to do clean up any resources when the main application is terminated. + +```go + type MyStruct struct { + runtime *wails.Runtime + } + + func (s *MyStruct) WailsInit(runtime *wails.Runtime) error { + // Save runtime + s.runtime = runtime + + // Allocate some resources... + + return nil + } + + + func (s *MyStruct) WailsShutdown() { + + // De-Allocate some resources... + + } + +``` + + +### Binding Rules + +Any Go function (or method) may be bound, so long as it follows the following rules: + + - The function must return 0 - 2 results. + - If there are 2 return parameters, the last one must be an error type. + - If you return a struct, or struct pointer, the fields you wish to access in the frontend must have Go's standard json struct tags defined. + +If only one value is returned then it will either be available in the resolve or reject part of the promise depending on if it was an error type or not. + +Example 1: + +```go + func (m *MyStruct) MyBoundMethod(name string) string { + return fmt.Sprintf("Hello %s!", name) + } +``` +In Javascript, the call to `MyStruct.MyBoundMethod` will return a promise that will resolve with a string. + +Example 2: + +```go + ... + func (m *MyStruct) AddUser(name string) error { + if m.userExists(name) { + return fmt.Errorf("user '%s' already exists"); + } + m.saveUser(name) + return nil + } + ... +``` +In Javascript, the call to `MyStruct.MyBoundMethod` with a new user name will return a promise that will resolve with no value. A call to `MyStruct.MyBoundMethod` with an existing user name will return a promise that will reject with the error set to "user '$name' already exists". + +It's good practice to return 2 values, a result and an error, as this maps directly to Javascript promises. If you are not returning anything, then perhaps events may be a better fit. + +### Important Detail! + +A very important detail to consider is that all calls to bound Go code are run in their own goroutine. Any bound functions should be authored with this in mind. The reason for this is to ensure that bound code does not block the main event loop in the application, which leads to a frozen UI. + +## Wails Runtime + +Wails comes with a runtime library that may be accessed from Javascript or Go. It has the following subsystems: + + * Events + * Logging + * Window + * Dialog + * Browser + +**NOTE: At this time, the Javascript runtime does not include the Window and Dialog subsystems** + +When binding a struct with the `WailsInit` method, the Go runtime object is presented by the Application. + +For the frontend, the runtime is accessed through the `@wailsapp/runtime` module: + +```javascript +import runtime from '@wailsapp/runtime'; +``` + + +### Events + +The Events subsystem provides a means of listening and emitting events across the application as a whole. This means that you can listen for events emitted in both Javascript and Go, and events that you emit will be received by listeners in both Go and Javascript. + +In the Go runtime, it is accessible via `runtime.Events` and provides 2 methods: `Emit` and `On`. + +#### Emit + +> Emit(eventName string, optionalData ...interface{}) + +The `Emit` method is used to emit named events across the application. + +The first parameter is the name of the event to emit. The second parameter is an optional list of interface{} types, meaning you can pass arbitrary data along with the event. + +Example 1: + +```go +func (m *MyStruct) WailsInit(runtime *wails.Runtime) error { + runtime.Events.Emit("initialised") +} +``` + +Example 2: + +```go +func (m *MyStruct) WailsInit(runtime *wails.Runtime) error { + t := time.Now() + message := fmt.Sprintf("I was initialised at %s", t.String()) + runtime.Events.Emit("initialised", message) +} +``` + +#### On + +> On(eventName string, callback func(optionalData ...interface{})) + +The `On` method is used to listen for events emitted across the application. + +The first parameter is the name of the event to listen for. The second parameter is a function to call when the event is emitted. This function has an optional parameter which will contain any data that was sent with the event. To listen to the 2 events emitted in the [emit](####emit) examples: + +Example with no data: + +```go +func (m *MyStruct) WailsInit(runtime *wails.Runtime) error { + runtime.Events.On("initialised", func(...interface{}) { + fmt.Println("I received the 'initialised' event!") + }) + return nil +} +``` + +Example with data: + +```go +func (m *MyStruct) WailsInit(runtime *wails.Runtime) error { + runtime.Events.On("hello", func(data ...interface{}) { + // You should probably do better error checking + fmt.Printf("I received the 'initialised' event with the message '%s'!\n", data[0]) + }) + return nil +} +``` + +### Log + +The Log subsystem allows you to log messages at various log levels to the application log. + +#### New + +> New(prefix string) + +Creates a new custom Logger with the given prefix. + + +```go +type MyStruct struct { + log *wails.CustomLogger +} + +func (m *MyStruct) WailsInit(runtime *wails.Runtime) error { + m.log = runtime.Log.New("MyStruct") + return nil +} +``` + +Once created, you may use any of the logger's methods: + +##### Standard logging + +Each of these methods take a string (like fmt.Println): + + - Debug + - Info + - Warn + - Error + - Fatal + +```go + m.Log.Info("This is fine") +``` + +##### Formatted logging + +Each of these methods take a string and optional data (like fmt.Printf): + + - Debugf + - Infof + - Warnf + - Errorf + - Fatalf + +```go + feeling := "okay" + m.Log.Info("I'm %s with the events that are currently unfolding", feeling) +``` + +##### Field logging + +Each of these methods take a string and a set of fields: + + - DebugFields + - InfoFields + - WarnFields + - ErrorFields + - FatalFields + + +```go + m.Log.InfoFields("That's okay", wails.Fields{ + "things are going to be": "okay", + }) +``` + +### Dialog + +The Dialog subsystem allows you to activate the Webview's native dialogs. It is accessible via `runtime.Dialog` and has the following methods: + +**NOTE: Opening a Dialog will halt Javascript execution, just like a browser** + +#### SelectFile + +> SelectFile() + +Prompts the user to select a file for opening. Returns the path to the file. + +```go + selectedFile := runtime.Dialog.SelectFile() +``` + +#### SelectDirectory + +> SelectDirectory() + +Prompts the user to select a directory. Returns the path to the directory. + +```go + selectedDirectory := runtime.Dialog.SelectDirectory() +``` + +#### SelectSaveFile + +> SelectSaveFile() + +Prompts the user to select a file for saving. Returns the path to the file. + +```go + selectedFile := runtime.Dialog.SelectSaveFile() +``` + +### Window + +The Window subsystem provides methods to interact with the application's main window. + +#### SetColour + +> SetColour(colour string) error + +Sets the background colour of the window to the colour given to it (string). The colour may be specified in the following formats: + + - RGB "rgb(0, 0, 0)" + - RGBA "rgba(0, 0, 0, 0.8)" + - HEX "#fff" + +```go + runtime.Window.SetColour("#eee") +``` + +#### Fullscreen + +> Fullscreen() + +Attempts to make the application window fullscreen. Will fail if the application was started with the option "Resize: false". + +```go + runtime.Window.Fullscreen() +``` + +#### UnFullscreen + +> UnFullscreen() + +Attempts to revert the window back to its size prior to a Fullscreen call. Will fail if the application was started with the option "Resize: false" + +```go + UnFullscreen() +``` + +#### SetTitle + +> SetTitle(title string) + +Sets the title in the application title bar. + +```go + runtime.Window.SetTitle("We'll need a bigger boat") +``` + +#### Close + +Closes the main window and thus terminates the application. Use with care! + +```go + runtime.Window.Close() +``` + +### Browser + +The browser subsystem provides methods to interact with the system browser. + +#### OpenURL + +> OpenURL(url string) + +Opens the given URL in the system browser. + +```go +runtime.Browser.OpenURL("https://wails.app") +``` +### A Common Pattern + +A common pattern for the Runtime is to simply save it as part of the struct and use it when needed: + +```go +type MyStruct struct { + Runtime *wails.Runtime +} + +func (m *MyStruct) WailsInit(r *wails.Runtime) error { + m.Runtime = r +} +``` + +--- + +[1]: https://github.com/zserge/webview + +## Cli + +Wails comes with a CLI tool that allows you to generate, build and bundle your projects. It deals with the complexity of juggling Go and Javascript environments. + +It has a number of commands: + +### Help + +> wails --help + +This will output the cli help message with all the available commands and flags. + +### Setup + +> wails setup + +The setup command does a number of things - it asks you for your name and email so that it can fill in project templates with your details. It also checks to see if your environment has the dependencies it needs and if not, try to suggest ways on how to install those dependencies. + +Setup is also the default command so it can be invoked by simply running `wails`. + +
+ +
+ +### Init + +> wails init + +The init command builds out a new project based on a template of your choice. We curently support a basic Vue, Vuetify and React templates. The project will be built automatically after initialisation. + +#### Basic Vue + +This template consists of a frontend composed of Vue components, bundled together using Webpack. It makes a simple call to the backend. + +#### Vuetify + +This template consists of a frontend composed of Vuetify components, bundled together using Webpack. It makes a simple call to the backend. + +#### React + +This template consists of a frontend composed of React components, bundled together using Webpack. It makes a simple call to the backend. + +
+ +
+ +### Serve + +> wails serve + +When you run `wails serve`, it will compile up the backend and run it in headless mode. This allows you to develop the frontend using your standard tooling. When you run your app, it will connect to the backend at startup and make all your backend functions available to you. + +We will cover this more in the tutorial. + +
+ +
+ +### Build + +> wails build + +The build command is the Wails equivalent of `go build`, however it does a number of things: + +- Installs frontend dependencies if needed +- Performs a build of the frontend +- Packs the frontend using Webpack +- It downloads any Go dependencies that are required +- It finally compiles and bundles everything into a single binary + +
+ +
+ +### Build Flags + +Here is a list of all available flags: + +| Flag | Description | +| ---- | -------------------------------------------- | +| -f | Force rebuild of frontend dependencies | +| -d | Build application in Debug mode | +| -p | Package application after a successful build | + +The `-p` flag is currently supports OSX and Windows. On OSX, it bundles your binary into a .app file with the default icon. On Windows, it will generate the application resource files and compile it all into a '.exe'. When the `-p` flag is used, the packaging files are left available for editing. Any changes will be picked up by the next build (eg icon). + +### Update + +> wails update + +This command does a check to see if the current version is the latest. If not, it will download and install the latest version. It is possible to also use it to install 'prerelease' versions by using the `-pre` flag. If a specific version is required, then it supports a `-version` flag. + +Example: `wails update -pre` will update the latest prerelease version + +### Issue + +> wails issue + +This command speeds up the process for submitting an issue to the Wails project. When you run the command, you will be asked to answer a couple of questions: + +
+ +
+ +Wails then determines some environmental details such as it's own version, opens a browser and fills in the default issue template. + +
+ +
+ +Please note: you can edit the template as you feel fit before submitting. + +You now have a good basis for your template. Running `wails init` will now give you your template as an option to install. When the project is generated using the template, it will create directories, copy non-template files then copy template files. Template files end in .template and will be treated as standard Go templates in which embedded codes are substituted with values in the [Project Options](https://github.com/wailsapp/wails/blob/master/cmd/project.go#L139-L154). diff --git a/zh/releases/README.md b/zh/releases/README.md new file mode 100644 index 0000000..a7c0e51 --- /dev/null +++ b/zh/releases/README.md @@ -0,0 +1,14 @@ +--- +sidebar: on +sidebarDepth: 1 +--- + +# Releases + +| Version | Release Date | Summary | Release Notes | +| -------- | -------------- | --------------------------------------- | ------------- | +| v0.17.0 | 1st July 2019 | New Angular template, Debian support, tooling updates | [here](./v0.17.0.md) | +| v0.18.2 | 8th Oct 2019 | Refactored runtime, New Event functionality, Windows fixes, more! | [here](./v0.18.2.md) | +| v0.18.5 | 23rd Oct 2019 | Better shutdown behaviour | [here](./v0.18.5.md) | +| v0.19.0 | 5th Nov 2019 | Better Windows support, Migrate command & misc bugfixes | [here](./v0.19.0.md) | +| v0.20.0 | 5th Dec 2019 | Better Windows support: Angular, Console & HiDPI bugfixes | [here](./v0.20.0.mds) | diff --git a/zh/releases/v0.17.0.md b/zh/releases/v0.17.0.md new file mode 100644 index 0000000..6f6c738 --- /dev/null +++ b/zh/releases/v0.17.0.md @@ -0,0 +1,40 @@ +--- +sidebar: on +sidebarDepth: 1 +--- + +# Wails v0.17.0 + +This version brings with it a new template, a new supported Linux distribution and a helpful utility. Unfortunately, Windows support is again proving problematic. If there is anyone out there who would like to help with Windows support, please get in touch! We are looking to fix the Windows issues and do a point release. + +A *huge* thank you to everyone that was involved in this release! + +## Angular 8 Template (Mac & Linux) + +

+
+

+ +After his awesome React template last release, [admin_3.exe](https://github.com/bh90210) has come up with the goods again and delivered an Angular 8 template for the Wails community! Thanks admin_3.exe! 👏🎉 + +**REQUIRES NODE 10.8+** + +## Debian Support + +

+
+

+ +We continue to expand our supported platform list with the introduction of Debian support. Versions 9 and 10 tested so far. Big shout out to [iceleo-com](https://github.com/iceleo-com) for the original request and testing! + +## Distribution Support Request Wizard! + +

+
+

+ +We have added a feature whereby when you try and run `wails init` on an unsupported Linux distribution, it will now prompt you to submit a support request. This will automatically generate a github request with the distribution name, version and discovery method. There is literally nothing more you have to do than click the 'submit' button in the browser window that opens for you. This really helps us to get your distribution supported. + +## Documentation Site Revamp + +The tireless [admin_3.exe](https://github.com/bh90210) has revamped the documentation site. We hope the improved structure and layout will help those who are new to the project get a better head start. \ No newline at end of file diff --git a/zh/releases/v0.18.2.md b/zh/releases/v0.18.2.md new file mode 100644 index 0000000..451ff2a --- /dev/null +++ b/zh/releases/v0.18.2.md @@ -0,0 +1,89 @@ +--- +sidebar: on +sidebarDepth: 1 +--- + +# Wails v0.18.2 + +This version brings new functionality to the Event subsystem. + +A *huge* thank you to everyone that was involved in this release! + +## Even more Linux! + +We've been busy on the Linux front and now have support for: + * Linux Mint + * KDE Neon + * Elementary + * Kali Linux + * Parrot + * Zorin + * Void Linux + * Manjaro Linux (re-enabled) + +Plus better support for existing distributions: + * Fedora, CentOS, Debian, Arch and Gentoo + +## Refactored Runtime + +The Javascript part of the runtime has been refactored to bring in some improvements. It has been assembled as a standard node module that may now be imported by frontend code to get access to the runtime methods. + +```javascript + +import Wails from '@wailsapp/runtime'; + +Wails.Events.On('myevent', () => { + // etc +}) + +``` + +This is going to allow us to improve the runtime with minimal impact on application code. + +### Method deprecation (renaming) +The original, lower cased methods for the JS runtime have been deprecated. The driver for this has been to unify the Go and JS runtime signatures. The new methods are the same as the old ones, just capitalised. + +## Event Updates + +This release sees a huge refactor of the runtime to improve maintainability and testability. With it come a number of new methods which are designed to simplify coordination between the frontend and backend. For the time being, these are only available in the JS runtime. + +### OnMultiple + +> Events.OnMultiple(eventName, callback, maxCallbacks) + +Registers a listener that will call `callback` a maximum of `maxCallbacks` times. After this time, the listener will be removed. + +### Once + +> Events.Once(eventName, callback) + +Registers a listener that will be destroyed after being notified once. Shorthand for `Events.OnMultiple(eventName, callback, 1)`. + +### Heartbeat + +> Events.Heartbeat(eventName, timeInMilliseconds, callback) + +Heartbeat will emit `eventName` every `timeInMilliseconds` until it is acknowledged by `Event.Acknowledged`. When that happens, `callback` is invoked. + +### Acknowledge + +> Events.Acknowledge(eventName) + +Acknowledge will acknowledge the heartbeat event `eventName`. + +## Binding updates + +It's now possible to bind Go methods with zero return types correctly. This will return an `undefined` in the promise callback. + +## Misc Updates + + * `wails issue` now includes npm, gcc and node versions. Thanks [Byron](https://github.com/bh90210)! + * `wails issue` now also relies on `/etc/os-release` in Linux for system information, replacing the requirement for `lsb`. Thanks [Nikolai Zimmermann](https://github.com/Chronophylos)! + * React template now working on Windows + * Angular template still **NOT** working for Windows + +--- + +We will be spending the next month improving documentation and bug fixing. Please let us know what bugs you find in v0.18.2 and we will look at them with the highest priority! + +We hope you enjoy the final v0.* release! See you at v1.0.0! \ No newline at end of file diff --git a/zh/releases/v0.18.5.md b/zh/releases/v0.18.5.md new file mode 100644 index 0000000..a897678 --- /dev/null +++ b/zh/releases/v0.18.5.md @@ -0,0 +1,46 @@ +--- +sidebar: on +sidebarDepth: 1 +--- + +# Wails v0.18.5 + +This version brings better handling of shutdown behaviour. + +A *huge* thank you to everyone that was involved in this release! + +## Wails Shutdown + +It is now possible to create methods on structs that will get called during application shutdown. Any struct methods with the signature `WailsShutdown()` will be called during shutdown. + +Example: +```go + type MyStruct struct { + runtime *wails.Runtime + } + + // This method will be called during application startup + func (s *MyStruct) WailsInit(runtime *wails.Runtime) error { + // Save runtime + s.runtime = runtime + + // Allocate some resources... + + return nil + } + + // This method will be called during application startup + func (s *MyStruct) WailsShutdown() { + + // De-Allocate some resources... + + return nil + } + +``` + +--- + +We will be spending the next month improving documentation and bug fixing. Please let us know what bugs you find and we will look at them with the highest priority! + +See you at v1.0.0! \ No newline at end of file diff --git a/zh/releases/v0.19.0.md b/zh/releases/v0.19.0.md new file mode 100644 index 0000000..7bb48b9 --- /dev/null +++ b/zh/releases/v0.19.0.md @@ -0,0 +1,40 @@ +--- +sidebar: on +sidebarDepth: 1 +--- + +# Wails v0.19.0 + +This version brings better Windows support as well as a number of bugfixes. + +A *huge* thank you to everyone that was involved in this release! + +## Features + +### Migrate command + +There is a new `wails migrate` command that will attempt to auto-migrate your project to be API compatible with the latest version. This is an *experimental* feature, but has worked well for a number of projects. + +### Binary Name + +The generated binary name is now adjusted on build to be more consistent with the host platform. Projects created on linux but compiled on windows will output `.exe` instead of ``. + +## Bug Fixes + +### Spaces in gcc path + +Users who use a gcc distribution that lives on a path with spaces, used to get errors during the build process. This is now fixed. + +### Allow IE11 for serve + +IE11 was not previously supported for the `wails serve` command. + +### NPM/Node Versions + +The `wails issue` command would report the wrong Node/NPM version. + +--- + +We will be spending the next month improving documentation and bug fixing. Please let us know what bugs you find and we will look at them with the highest priority! + +See you at v1.0.0! \ No newline at end of file diff --git a/zh/releases/v0.20.0.md b/zh/releases/v0.20.0.md new file mode 100644 index 0000000..8d82b13 --- /dev/null +++ b/zh/releases/v0.20.0.md @@ -0,0 +1,32 @@ +--- +sidebar: on +sidebarDepth: 1 +--- + +# Wails v0.20.0 + +This version brings a number of bugfixes for better Windows support. + +A *huge* thank you to everyone that was involved in this release! + +## Bug Fixes + +### Angular support for Windows + +The errors with building the Angular template on Windows have been fixed. This is now fully supported. + +### Alpha release of Console for Windows + +When compiling with `wails build -d` on Windows, a rudimentary console is available by pressing `SHIFT-ESC`. It allows you to see console logs/errors and has basic input capabilities. + +If anyone knows how to get firebug lite working in this scenario, please get in touch! + +### HiDPI Fix + +Windows builds previously did not work correctly with HiDPI screens. This is now fixed. + +--- + +Please let us know what bugs you find and we will look at them with the highest priority! + +See you at v1.0.0! \ No newline at end of file diff --git a/zh/tutorials/README.md b/zh/tutorials/README.md new file mode 100644 index 0000000..70c2567 --- /dev/null +++ b/zh/tutorials/README.md @@ -0,0 +1,48 @@ +--- +sidebar: on +sidebarDepth: 1 +--- +# Tutorials + +## Template Overview + +In this tutorial we go through the default Vue template to gain an understanding of how Wails works. It is a simple introduction to some of the core concepts of the framework. + +[Start the tutorial](./template.md) + +## Quote Generator + +Building on the default template, we create a Quotes Generator. This introduces the concepts of binding a struct to your application as well as interacting with the Wails runtime in Go. + +This is recommended for those who are proficient in Javascript and Go and are looking to quickly create an app. It builds on the Template Tutorial so it is recommended to complete that first. + +[Start the tutorial](./quotes.md) + +## Todo + +What's a framework without a Todo app? We revisit the classic app, basing it on the [Todo MVC](http://todomvc.com/examples/vue/) version. + +This is a comprehensive and advanced tutorial that only requires a basic knowledge of Javascript and Go. It covers all aspects of Wails and is recommended for people of all skill levels. + +[Start the tutorial](./todo.md) + +## CPU Usage App + +In Package Main Episode 16, [Alex Pliutau](https://twitter.com/pliutau) builds a CPU Usage app with Wails. He covers struct binding, the events system and how to use 3rd party Javascript packages in your app. It's a really great tutorial! +

+ +
+ +
+ +
+ +The tutorial is also available in text format at Package Main's [episode 16 github repo](https://github.com/plutov/packagemain/tree/master/16-wails-desktop-app) or as a [Medium article](https://medium.com/js-dojo/building-a-desktop-app-in-go-using-wails-b7f5825f986a). + +::: tip +This tutorial uses an older version of the Runtime which has had some slight changes since v0.18.0. Please read the [Reference Guide](../reference/#wails-runtime) for more information. +::: diff --git a/zh/tutorials/quotes.md b/zh/tutorials/quotes.md new file mode 100644 index 0000000..e72a1c5 --- /dev/null +++ b/zh/tutorials/quotes.md @@ -0,0 +1,481 @@ +# Quotes Generator + +In this tutorial, we will be building on the default Vue/Webpack template to create a quotes generator. We shall cover how we can group methods into a struct in Go and how to use the Go runtime object. + +It is assumed you have completed and understood the [template tutorial](./template.md). We will continue from where we left off. + +## Creating the Quotes Struct + +Wails allows you to bind structs to your application. Methods that are exposed, ie those with their first letters capitalised, will be bound to the application. + +We will start by creating a new file: quotes.go. + +```go +package main + +// Quotes is our bound Quote Struct +type Quotes struct { +} + +// NewQuotes creates a new Quotes Struct +func NewQuotes() *Quotes { + return &Quotes{} +} + +// GetQuote returns a quote +func (q *Quotes) GetQuote() string { + return "s/be/in - Mat Ryer" +} +``` + +Our Quotes struct has one method `GetQuote`. When this is bound to the application, Wails will bind it to the frontend as `backend.Quotes`. Because `GetQuote` is an exposed method, it will be bound in the frontend as `backend.Quotes.GetQuote`. + +The NewQuotes method is simply a convention for creating a new instance of a struct. Wails requires that structs are instantiated before binding, so we will use this function in the main app. + +## Binding it to the application + +Now that we have our initial struct, let's bind it to the app: + +```go +package main + +import ( + "github.com/leaanthony/mewn" + "github.com/wailsapp/wails" +) + +func basic() string { + return "Hello World!" +} + +func main() { + + js := mewn.String("./frontend/dist/app.js") + css := mewn.String("./frontend/dist/app.css") + + app := wails.CreateApp(&wails.AppConfig{ + Width: 1024, + Height: 768, + Title: "Quotes", + JS: js, + CSS: css, + Colour: "#131313", + }) + app.Bind(basic) + app.Bind(NewQuotes()) // Add this + app.Run() +} +``` + +Binding our quotes struct is a single line change to our existing main function. We can test this in the front end by serving the project again: + + * Run `wails serve` in the project directory + * When it is ready, run `npm run serve` in the `frontend` directory + + When it is ready, open the browser to the project url and you should see the same familiar screen as in the template tutorial: + +
+ +
+ +If you open up the browser's inspector window and select console, you should be able to access the bound Quotes struct: + +
+ +
+ +Note that the `GetQuote()` method is available to us. This, just like bound functions, returns a Javascript promise. We can run the method and print the output like so: + + +
+ +
+ +Now that we have access to our Quotes struct, let's add more quotes. + +### Better Quotes + +A quote traditionally has a text and an author. Currently we are returning a single string from our `GetQuote` method, but it would be better to return a struct. Let's define it: + +```go +// Quote holds a single quote and the author who said it +type Quote struct { + Text string `json:"text"` + Author string `json:"author"` +} +``` + +We can now update our `GetQuote` method to return a Quote: + +```go +// GetQuote returns a quote +func (q *Quotes) GetQuote() *Quote { + return &Quote{Text: "s/be/in", Author: "Mat Ryer"} +} +``` + +If the previous `wails serve` is still running, press `ctrl-c` to stop it and re-run the serve command. The frontend does not need recompiling and will automatically reconnect to the backend when it becomes available. During that connection time, you will see a screen like this letting you know it is trying to reconnect to the backend: + +
+ +
+ +Once reconnected, open the console again and re-issue the `GetQuote` command. You should see something like the following: + +
+ +
+ +Now that we have the data in a format we can manipulate, we can update our frontend code to use it. + +### Rendering the Quotes + +Let's create a new component for rendering the quotes. It will be based on our HelloWorld component, so make a copy of this file and name it `Quote.vue`. + +The first thing we will do is register our new Quote component in `App.vue`: + +```javascript + + + +``` + +Then we will update the component to get and store our quote: + +```javascript + +``` + +We are simply changing the name of the data element from `message` to `quote`, and changing the name of the function we are calling to retrieve the quote. We store it in the quote variable defined in `data()`. + +Recap: The quote struct looks like this: +```json +{ + text: 'the quote text', + author: 'the quote author' +} +``` + +Next, we'll update the template to use the quote object: + +```javascript + +``` + +Let's look at Line 3. We are using a blockquote element to encapsulate the quote. Within this element, we are using the `v-if` Vue directive. This will conditionally render the element based on the condition given to it. In our case, this is `quote != nil`. We will define `quote` in the component. This will be what stores our quote struct from the backend. The next directive we use is `:cite`. This simply sets an attribute on the blockquote element. In our case, we are setting it to `quote.author`. This references the author field of the quote struct we are getting from the backend. Within the element tags, we are using a template directive. This will output text based on the data within the double braces. In our case this will be `quote.text`. + +In Line 4, we simply update the name of the component's method to call when the button is clicked. + +If we serve the project now, we can see something like this: + +
+ +
+ +If we press the button, we can see the quote! + +
+ +
+ +The styling is terrible. Let's fix that! + +### Styling the component + +We already have some styling in the ` +``` + +When we reload the app now, we should see something like this: + +
+ +
+ +Pressing the button should yield the following: + +
+ +
+ +### Adding more quotes + +Whilst Mat's quote is iconic (and potentially career ending), we are going to add in a few more quotes. What we want is to retrieve a random quote, every time the button is pressed. + +To do this, we are going to add a Quote slice to our Quotes struct in our quotes.go file: + +```go +// Quotes is our bound Quote Struct +type Quotes struct { + quotes []*Quote +} +``` + +We will also create a small function to create and store our quotes: + +```go +// AddQuote creates a Quote object with the given inputs and +// adds it to the Quotes collection +func (q *Quotes) AddQuote(text, author string) { + q.quotes = append(q.quotes, &Quote{Text: text, Author: author}) +} +``` + +Now that we have our helper functions in place, let's populate the quotes. We can do this in the NewQuotes function: + +```go +// NewQuotes creates a new Quotes Struct +func NewQuotes() *Quotes { + result := &Quotes{} + result.AddQuote("Age is an issue of mind over matter. If you don't mind, it doesn't matter", "Mark Twain") + result.AddQuote("Anyone who stops learning is old, whether at twenty or eighty. Anyone who keeps learning stays young. The greatest thing in life is to keep your mind young", "Henry Ford") + result.AddQuote("Wrinkles should merely indicate where smiles have been", "Mark Twain") + result.AddQuote("True terror is to wake up one morning and discover that your high school class is running the country", "Kurt Vonnegut") + result.AddQuote("A diplomat is a man who always remembers a woman's birthday but never remembers her age", "Robert Frost") + result.AddQuote("As I grow older, I pay less attention to what men say. I just watch what they do", "Andrew Carnegie") + result.AddQuote("How incessant and great are the ills with which a prolonged old age is replete", "C. S. Lewis") + result.AddQuote("Old age, believe me, is a good and pleasant thing. It is true you are gently shouldered off the stage, but then you are given such a comfortable front stall as spectator", "Confucius") + result.AddQuote("Old age has deformities enough of its own. It should never add to them the deformity of vice", "Eleanor Roosevelt") + result.AddQuote("Nobody grows old merely by living a number of years. We grow old by deserting our ideals. Years may wrinkle the skin, but to give up enthusiasm wrinkles the soul", "Samuel Ullman") + result.AddQuote("An archaeologist is the best husband a woman can have. The older she gets the more interested he is in her", "Agatha Christie") + result.AddQuote("All diseases run into one, old age", "Ralph Waldo Emerson") + result.AddQuote("Bashfulness is an ornament to youth, but a reproach to old age", "Aristotle") + result.AddQuote("Like everyone else who makes the mistake of getting older, I begin each day with coffee and obituaries", "Bill Cosby") + result.AddQuote("Age appears to be best in four things old wood best to burn, old wine to drink, old friends to trust, and old authors to read", "Francis Bacon") + result.AddQuote("None are so old as those who have outlived enthusiasm", "Henry David Thoreau") + result.AddQuote("Every man over forty is a scoundrel", "George Bernard Shaw") + result.AddQuote("Forty is the old age of youth fifty the youth of old age", "Victor Hugo") + result.AddQuote("You can't help getting older, but you don't have to get old", "George Burns") + result.AddQuote("Alas, after a certain age every man is responsible for his face", "Albert Camus") + result.AddQuote("Youth is when you're allowed to stay up late on New Year's Eve. Middle age is when you're forced to", "Bill Vaughan") + result.AddQuote("Old age is like everything else. To make a success of it, you've got to start young", "Theodore Roosevelt") + result.AddQuote("A comfortable old age is the reward of a well-spent youth. Instead of its bringing sad and melancholy prospects of decay, it would give us hopes of eternal youth in a better world", "Maurice Chevalier") + result.AddQuote("A man growing old becomes a child again", "Sophocles") + result.AddQuote("I will never be an old man. To me, old age is always 15 years older than I am", "Francis Bacon") + result.AddQuote("Age considers youth ventures", "Rabindranath Tagore") + result.AddQuote("s/be/in", "Mat Ryer") + return result +} +``` + +The only thing left to do now is to update our `GetQuote()` method to return a random quote: + +```go +// GetQuote returns a quote +func (q *Quotes) GetQuote() *Quote { + return q.quotes[rand.Intn(len(q.quotes))] +} +``` + +You will also need to ensure that you import `math/rand`, if your IDE hasn't already: + +```go +import "math/rand" +``` + +Run `wails serve` again to recompile and serve the app. + +Now when we press the button, we get a fabulous quote: + +
+ +
+ +Now with a slight tweak to the CSS, we can make this look even better. In the component CSS, let's add a margin to the author: + +```css +blockquote:after { + content: "\2013 \2003" attr(cite); + display: block; + text-align: right; + font-size: 1.15em; + color: #53cdff; + margin: 1em; +} +``` + +Now we have a great looking quotes app: + +
+ +
+ +## Building the app + +Now that we have the app working, we want to build it as a standalone app. We do this by running `wails build`. You should now have a `quotes` executable (or `quotes.exe` if on Windows). + +
+ +
+ +Running this should run the app. On MacOS, it looks like this: + +
+ +
+ +Pressing the button works as expected: + +
+ +
+ +## Packaging the app + +Wails provides the ability to package your application into a platform native format. Packaing is indicated by running `wails build -p`. + +### MacOS + +On MacOS, running `wails build -p` will generate a .app bundle. If we run this in the quotes project directory you should end up with a quotes.app bundle: + +
+ +
+ +If we open this in finder, you will see your application as a standard Mac app: + +
+ +
+ +Double clicking this will launch the app as expected. Minimising the app to the dock will show you that the icon works as expected: + +
+ +
+ +Of course, it's unlikely that you'll want to use the default icon, so Wails makes it easy for you to replace it. Just replace `appicon.png` with your own icon and rebuild. + +There's a cool icon [here](https://www.onlinewebfonts.com/icon/299634) which can be used. I made some changes so that it works better on my dark desktop, renamed the icon to `appicon.png` and rebuilt: + +
+ +
+ +### Windows + +Due to the nature of Windows, a standard build will also package the app with the default icon. If you run `wails build -p` it will leave the build artifacts, including the default icon, so that you can customise the build. Make your changes and run `wails build` again. + +### Linux + +Currently, packing on Linux isn't supported as it could mean many things. There is the potential to support snap packages in the future. + +## Exercises + + * See if you can animate the quotes using something like [animate.css](https://daneden.github.io/animate.css/) + * See if you can pull quotes from the [They Said So](https://quotes.rest/) quotes API from the backend + +## Summary + +Hopefully you now understand how to build and package a basic application using Wails. diff --git a/zh/tutorials/template.md b/zh/tutorials/template.md new file mode 100644 index 0000000..e5931b0 --- /dev/null +++ b/zh/tutorials/template.md @@ -0,0 +1,217 @@ +# Default Vue Template + +In this tutorial, we will be looking at the default Vue template and gain an understanding of how Wails works. Whilst we do touch on Vue in this tutorial, it is assumed that the reader has a certain amount of experience with Vue. If not, we recommend the Vue tutorial [here](https://vuejs.org/v2/guide/). + +## Initialise Project + +Run `wails init` to generate your project. We will call our project 'Quotes'. Accept the defaults for the following 2 questions: + +``` +The name of the project (My Project): Quotes +Project Name: Quotes +The output binary name (quotes): +Output binary Name: quotes +Project directory name (quotes): +Project Directory: quotes +Project 'Quotes' generated in directory 'quotes'! +To compile the project, run 'wails build' in the project directory. +``` + +## Serve Project + +As we will partially be developing this in the browser, we will serve the backend in bridged mode. This means that the Go functions will be available to us in the browser! + +Run `wails serve` in the project directory. + +You should have some output similar to this: + +``` +Quotes - Debug Build +-------------------- +INFO[0000] [App] Starting +INFO[0000] [Events] Starting +INFO[0000] [IPC] Starting +INFO[0000] [Events] Listening +INFO[0000] [Bind] Starting +INFO[0000] [Bind] Binding Go Functions/Methods +INFO[0000] [Bind] Bound Function: main.basic() +INFO[0000] [Headless] Headless mode started. +INFO[0000] [Headless] The Wails bridge will connect automatically. +>>>>> To connect, you will need to run 'npm run serve' in the 'frontend' directory <<<<< +``` + +Note: In the startup messages, we can see that the function main.basic() has been bound: + +``` +INFO[0000] [Bind] Bound Function: main.basic() +``` + +This means we can call it from the frontend, and in this instance, directly from the browser! ( The `main` part is simply the package name it was defined in. This is dropped when bound ). + +To start the frontend, do the following: + +``` +cd frontend +npm run serve +``` + +This starts the standard Vue development server and will give you a URL that the application is being hosted on. It will be something like this: + +``` + App running at: + - Local: http://localhost:8082/ + - Network: http://localhost:8082/ +``` + +Open a browser to that URL and you should see something similar to the following: + +
+ +
+ +If you click the button, you get a message: + +
+ +
+ +## Understanding the App + +*What just happened?* + +When you pressed the button, the frontend code called a backend function and printed the result to the page. + +*How does that work?* + +The sequence of operations are as follows: + + * main.js runs and waits for the runtime to Initialise. As this is running in bridged mode, it waits until a connection to the backend is established + * Once established, it mounts the main app, defined in App.vue, just as you normally would in a Vue app + * App.vue uses a component called "HelloWorld", defined in the components directory and this defines a message and a button. When the button is pressed, a call is made to the backend basic() function and the result is placed in the message + +There is no more to the app than this. Let's look at those steps: + +### Waiting for the Backend Connection + +When running `wails serve`, the connection to the backend is managed by a bridge which is dynamically injected into the runtime library. This library does a lot of things, however we only need to be concerned with one function: Init(). The Init() function accepts a callback, which is invoked when the connection to the backend is established. This is demonstrated clearly in main.js: + +```javascript +import Wails from '@wailsapp/runtime'; + +Wails.Init(() => { + ... + ... + ... +}); +``` + +The bridge sets up the following things: + + * The wails javascript runtime, available at through the `@wailsapp/runtime` module. This provides a number of useful systems that we will cover over the course of the tutorials. For now, we will not use it. + * The bindings to backend code. These are available at `window.backend`. If you have bound the Go function `Hello` to your application, it will be available at `window.backend.Hello`. + +When the bridge has initialised, it will call the given callback. At this point you know that both the bindings and the runtime is available. + +### Mount the main application + +When the callback from Init is invoked, we create and mount the main Vue App Component: + +```javascript +Wails.Init(() => { + new Vue({ + render: h => h(App) + }).$mount("#app"); +}); +``` + +The App component is defined as a [single file component](https://vuejs.org/v2/guide/single-file-components.html) and is entirely defined in App.vue. Single file components define 3 things: + + * HTML Template + * Javascript + * CSS + +The HTML Template part of App looks like this: +```html + +``` +This defines a simple div containing the Wails logo and the HelloWorld component. + +The Javascript part is fairly simple: +```javascript + +``` + +The App component imports the HelloWorld component from HelloWorld.Vue. It also imports the main css file from the assets directory. It then exports its name and what components it uses. + +If we look at the HelloWorld.vue file we see the following HTML Template: + +```html + +``` +You can see that the container comprises of an h1 header and a link tag. The header has the text {{message}} between its tags. This is a binding in the component. If we set this.message in any component method, the h1 header is rendered with that value. The link tag has a `@click` directive which means that when it is clicked, it will call the function `getMessage`. This will all make more sense when we look at the javascript section: + +```javascript + +``` +Here we define 2 parts of the component: + + - data defines the 'local' variables of the component + - methods defines the methods of the component :-) + +By default, message is a space. This means that a space will be rendered in the h1 tags at startup. + +In the methods section, we see there is a method defined called `getMessage`. This is the function that gets called when the link tag is clicked. When it is clicked, we call `window.backend.basic()`. This is a method that we bind in the main go file! It is defined as such in main.go: + +```go +func basic() string { + return "Hello World!" +} +``` + +All bound Go methods are presented in Javascript as functions that return Promises. Because of this, we call `.then()` which gives us the result and we assign it to the message variable. We use `self` as `this` references the callback function, rather that the component. + +## Exercises + + * See if you can get it to print a different message + * Try changing the styling so that the message is a different colour or size + * See if you can work out how to send a name to the backend from Vue, and get it to return you `Hello !` + +## Summary + +Hopefully you now understand the basics of how Wails works and how the default template interacts with it. diff --git a/zh/tutorials/todo.md b/zh/tutorials/todo.md new file mode 100644 index 0000000..66d934c --- /dev/null +++ b/zh/tutorials/todo.md @@ -0,0 +1,2649 @@ +# Todo + +In this tutorial, we will be creating a todo app based on the [Vue MVC Todo App](http://todomvc.com/examples/vue/). We will cover the following topics: + + * Accessing the Wails runtime in both Javascript and Go + * Using native browser file dialogs + * Using the unified events system + * Binding both functions and structs to the application + * Basic usage of Vue + +The source code to the app is available [here](https://github.com/wailsapp/todo). I highly recommend not looking at it unless you really need to. + +## Setup + +Generate a new project using `wails init`. We will call the project 'todos' and accept the default answers: + +```bash +The name of the project (My Project): todos +Project Name: todos +The output binary name (todos): +Output binary Name: todos +Project directory name (todos): +Project Directory: todos +Template: Vue2/Webpack Basic +Project 'todos' built in directory 'todos'! +``` + +Next we will move into the `todos` directory. You should see the following files, including a todos binary: + +```bash +. +├── frontend +├── go.mod +├── go.sum +├── main.go +├── project.json +└── todos +``` + +Now lets build our frontend! + +## Basic Frontend + +We are going to base our frontend on the cool [Todo MVC](http://todomvc.com/examples/vue/) app that the awesome [Evan You](evanyou.me) created. The main code repository can be located [here](https://github.com/tastejs/todomvc/tree/gh-pages/examples/vue). + +We will start by updating the template section of App.vue located in you project directory in frontend/src. If we look at [index.html](https://github.com/tastejs/todomvc/blob/gh-pages/examples/vue/index.html), we can see that the main html part of the application lies between lines 11 and 48: + +```html +
+
+

todos

+ +
+
+ + +
    +
  • +
    + + + +
    + +
  • +
+
+
+ + {{pluralize('item', remaining)}} left + + + +
+
+ +``` + +We'll base our template on this, using the bare minimum to begin with: + + +```html + +``` + +We will also update our script section as we will not need the HelloWorld component: + +```javascript + +``` + +So the whole App.vue should now look like this: + +```html + + + +``` + +The template has 2 sections: the header where the title is displayed (Lines 4-7) and the main todo list (Lines 8-16). + +So let's now start serving the front end so we can see what it looks like. As we haven't written our backend yet, let's temporarily comment out the Wails runtime parts of `frontend/src/main.js`: + +```javascript +import Vue from "vue"; +import App from "./App.vue"; + +Vue.config.productionTip = false; +Vue.config.devtools = true; + +// import Wails from '@wailsapp/runtime'; + +//Wails.Init((() => { + new Vue({ + render: h => h(App) + }).$mount("#app"); +// }); +``` + +Change into the 'frontend' directory and run `npm run serve`. If all goes well, you should see something like this on the console: + +``` + DONE Compiled successfully in 187ms 8:20:54 AM + + + App running at: + - Local: http://localhost:8082/ + - Network: http://localhost:8082/ + +``` + +Open a browser to this location and you should see something similar to this: + +
+ +
+ +It looks underwhelming but it's early days. For it to look like the original, we need to include the original stylesheets. The original template references 2: + +```html + + +``` + +Download the Base CSS from [here](https://raw.githubusercontent.com/tastejs/todomvc/gh-pages/examples/vue/node_modules/todomvc-common/base.css) and the App CSS from [here](https://raw.githubusercontent.com/tastejs/todomvc/gh-pages/examples/vue/node_modules/todomvc-app-css/index.css). + +Copy them to `frontend/src/assets/css` and rename `index.css` to `app.css`. Let's include those in the App.vue file. We can simply import them: + +```javascript {2-3} + +``` + +As Vue is serving the frontend, these changes should now be reflected in the browser. You should see something like this: + +
+ +
+ +That looks much better! You'll notice that whilst the app is responding, it doesn't do much. Next we'll add some code into our component to manage the list. + +## Implementing the list + +We'll store our todo list in an array in the component: + +```javascript {6-10} + +``` + +We'll also add a condition to the html that we should only show the todo items if we have any items. We do this using the [v-show](https://vuejs.org/v2/guide/conditional.html#v-show) directive: + +```html {1} +
+
    +
  • +
    + +
    +
  • +
+
+``` + +If we look at our app now, we will see our original to do item is now not there. This is because the todos array is empty so our list section of the template is now hidden. + +We will define our todo items in a Javascript object. To start with, this will simply be a unique number to identify the the item as well as its title: + +```javascript +{ + id: , + title: +} +``` + +Let's add a test entry into the main list: + +```javascript {3} + data() { + return { + todos: [{id: 0, title: "My test todo item"}] + } + } +``` + +In vue, we can iterate over a list using [v-for](https://vuejs.org/v2/guide/list.html#v-for-on-a-lt-template-gt) and we will use that to display our list items: + +```html {3-7} +
+
    +
  • +
    + +
    +
  • +
+
+``` + +After saving this, you should see the todo item in the list: + +
+ +
+ +You will notice that if you change the item title and save, it will update the title in the browser! + +Next let's add a checkbox to allow us to indicate that we have completed a task. To do this, we will need to update our todo item model with a variable to store this: + +```javascript +{ + id: , + title: , + completed: +} +``` + +Let's add that to our test item and set it to true: + +```javascript {3} + data() { + return { + todos: [{id: 0, title: "My test todo item", completed: true}] + } + } +``` + +The todo-mvc stylesheet has a css class called "completed" that styles completed items. We will use the [class directive](https://vuejs.org/v2/guide/class-and-style.html) to add that styling based on our item's 'completed' property: + +```html {3} +
+
    +
  • +
    + +
    +
  • +
+
+``` + +You should now have a styled item like so: + +
+ +
+ +## Toggling items + +Let's now add a toggle for the todo item so we can set it as completed ourselves. + +We will add an html checkbox to the item and bind it to the item's 'completed' variable using [v-bind](https://vuejs.org/v2/guide/class-and-style.html). The 'toggle' class is part of the todo-mvc css and turns a boring checkbox into a nicely styled toggle: + +```html {5} +
+
    +
  • +
    + + +
    +
  • +
+
+``` +We will also default our test item's completed variable to false: + +```javascript + todos: [{id: 0, title: "My test item", completed: false}] +``` + +The app should now look like this: + +
+ +
+ +and when you select the item: + +
+ +
+ +## Using the Vue Dev Tools + +One of the motivators for Wails to fully support developing in the browser was so that developers could use the amazing array of developer extensions available. Vue has a brilliant extension called [Vue Dev Tools](https://github.com/vuejs/vue-devtools) which allows you to inspect your application from a Vue perspective. By default, Wails enables support for this extension. + +Once you install it, right click on the browser running your app and select "inspect". There should be a "Vue" tab available. Select that and it should show you the page layout from a component perspective. We only have one (App). If you click on it, you will see your component data on the right. We can see the todos array and if we open it up, we can see our test item: + +
+ +
+ +Not only can we see it, we can edit it! Click the pencil/pen icon next to the variable and you will be able to edit the value. Boolean values have a handy toggle icon. + +If we click our toggle button in the page, we can see the value in dev tools toggle. And the reverse is true also! + +Dev Tools is a great way to develop and debug your application. For more information, I highly recommend the awesome Flavio Copes [tutorial](https://flaviocopes.com/vue-devtools/) on it. + +## Adding Todo Items + +It's about time we had a way of adding todo items. We already have an input at the top of the list but it doesn't do anything. Let's add a binding between that and our component data. We will add a new data item called newTodo and it will be a string: + +```javascript {3} + data() { + return { + newTodo: "", + todos: [{id: 0, title: "My test item", completed: false}] + } + } +``` + +We then add the `v-bind` directive to link the input to the newTodo variable: + +```html{3} +
+

todos

+ +
+``` + +If you reload the page, you should now see the newTodo item in dev tools. If you type in the input at the top of the list you will see its value reflected in the data object. + +Now we need a way of saving this value into our todo list. To achieve this, we need to do 2 things: + * create a method on the component that adds the item to the list + * a mechanism to call this method when we press enter on the input + +The way to declare a [method](https://flaviocopes.com/vue-methods/) in a Vue component is to add it to a `methods` object in the component. For us, we will add a method called `addTodo`: + +```javascript {12-25} + +``` + +This will check that we have a new todo item and trim off the spaces if we do. If we don't then have a title (only spaces were input), then we just return and do nothing. If we have a title, then we push a new todo item object into the todos list (component variables are accessible through `this`). We then reset the newTodo variable to a blank string. + +Now that we have a way of adding, let's add the trigger: + +```html {3} +
+

todos

+ +
+``` + +Vue has a handy directive called [v-on](https://vuejs.org/v2/api/#v-on) which allows us to listen for events. We can use it to listen for when the enter key is released by using `v-on:keyup.enter` or the equivalent shorthand `@keyup.enter`. + +Now if we enter text into the input field and press return, the item gets added to our list: + +
+ +
+ +## Removing Todo Items + +Now that we can add items and mark them as complete, we should also add the ability to remove items from the list. We will do this by using a simple button on each item: + +```html {7} +
+
    +
  • +
    + + + +
    +
  • +
+
+``` + +We are using the `v-on` shorthand again but this time instead of listening for a key event, we are listening for the click event emitted when the button is pressed. When the button is pressed, we call `removeTodo()` with our todo. Let's now add that method after `addTodo`: + +```javascript {14-21} + methods: { + addTodo: function() { + var value = this.newTodo && this.newTodo.trim(); + if (!value) { + return; + } + this.todos.push({ + id: this.todos.length, + title: value, + completed: false + }); + this.newTodo = ""; + }, + removeTodo: function(todo) { + var index = this.todos.indexOf(todo); + this.todos.splice(index, 1); + + for(var i=0; i + + + +## Editing a Todo Item + +Editing a todo item will be the most complicated thing we do with Vue in this tutorial. We need to do the following: + + * Listen for a double click on an item + * Show a text input with the item's text + * Listen for enter signifying the edit is complete + * Set the title of the item to the new input + * Hide the input + +Let's start by adding a listener for a double click to an entry: + +```html {6} +
+
    +
  • +
    + + + +
    +
  • +
+
+``` +When we double click on a todo item, it will now call a method called `editTodo`, passing in the todo we clicked on. We want to retain a reference to that item so let's add a data variable for it: + +```javascript {4} + data() { + return { + newTodo: "", + editedTodo: null, + todos: [{id: 0, title: "My test item", completed: false}] + } + }, +``` + +Let's now define `editTodo` function: + +```javascript + editTodo: function(todo) { + this.editedTodo = todo; + }, +``` + +Next, we need to add the input element to the template to show when editing. This needs to be bound to our todo's title. We also give it the 'edit' css class, as this is what is expected by the TodoMVC stylesheet: + +```html {9-13} +
+
    +
  • +
    + + + +
    + +
  • +
+
+``` + +Hiding and showing is part of the TodoMVC stylesheet - we simply need to set the class 'editing' when editing so we can add that too. We use a simple evaluation that when the todo item is the 'editedTodo', then we add the class 'editing': + +```html {3} +
+
    +
  • +
    + + + +
    + +
  • +
+
+``` + +If we look at our app now, we notice a couple of things: + + * When we double click, the text input does appear but it is not focused. + * When we press return, nothing happens. + +Let's address the first point. Vue uses directives in standard html elements to indicate how that element is processed. It also offers the ability to create [custom directives](https://vuejs.org/v2/guide/custom-directive.html) so you can tailor them to your needs. We will define a directive that will focus the element that uses it. Insert this between the 'data' and 'methods' fields in the component's javascript section. + +```javascript {4-10} + todos: [{id: 0, title: "My test item", completed: false}] + } + }, + directives: { + "todo-focus": function(el, binding) { + if (binding.value) { + el.focus(); + } + } + }, + methods: { + addTodo: function() { +``` + +We can now use this in our input element: + +```html {5} + +``` + +Now let's address the issue of completing the edit. We want to listen for when the return key is pressed. +Vue allows us to do this using the `v-on` directive we used earlier. We will use [keyup](https://vuejs.org/v2/guide/events.html#Key-Modifiers) to listen for the release of the return key. + +```html {5} + +``` + +Now, when the return (enter) key is released, the `doneEdit` method will be called with the todo. We will now define this method in the component: + +```javascript {5-14} + editTodo: function(todo) { + this.editedTodo = todo; + }, + + doneEdit: function(todo) { + if (!this.editedTodo) { + return; + } + this.editedTodo = null; + todo.title = todo.title.trim(); + if (!todo.title) { + this.removeTodo(todo); + } + }, + } +``` + +In this function, we are first ensuring we are actually editing an item by checking if the editedTodo variable is set. If it isn't, we just return and ignore it ever happened. If we are legit editing a todo, we set the editedTodo variable to null as we are now not editing. We then set the todo title to be the current value, but with any whitespace at the ends trimmed off. Finally, we check to see if we actually have any text in the title (because a blank string in javascript evaluates to false), we just call our `removeTodo` function. + +If you try the app now, you will see that the item edits correctly, however there's some slight oddities: if you click off the input, it doesn't end the editing. Also, if you try to create another todo, the input flicks over to the edit box. Let's address those issues by also listening to the [blur event](https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event): + +```html {6} + +``` + +That's better! Now it feels right. It would be good if we could also cancel our edits. We know how to listen to keypresses, so let's listen for escape and cancel the current editing session. To do this, we are first going to keep a copy of the item's text when we first start editing: + +```javascript {2} + editTodo: function(todo) { + this.beforeEditCache = todo.title; + this.editedTodo = todo; + }, +``` + +We are using a variable called `beforeEditCache` to store the initial value. Now let's listen to the escape key: + +```html {7} + +``` + +And finally, let's implement the `cancelEdit` method: + +```javascript + cancelEdit: function(todo) { + this.editedTodo = null; + todo.title = this.beforeEditCache; + } +``` + +Now let's try editing an item and hitting `escape`. It works! + +Finally, let's remove our original test item from the todo list: + +```javascript {5} + data() { + return { + newTodo: "", + editedTodo: null, + todos: [] + } + }, +``` + +Our final App.vue file should now look something like this: + +```html + + + +``` + +Now that we have a basic todo app, let's build it into a desktop app by running `wails build` in the project's root directory. Once it has finished building, run the newly built `todos` binary. You should see something like this: + +
+ +
+ +The problem we now have is that every time we start the app, we lose our previous list. Let's look at using Go to persist our list. + +## Persistence using Go + +Currently, our app is completely standalone from the backend. Our main app is simply wrapping the Vue project and displaying it. What we want to do is load our list up from disk when we start our app. To do that, we must be able to bridge our frontend and backend code. We do this using the Wails Bridge. + +### Bridging Frontend and Backend + +To load our list we will want to call a function in Go which loads a file from a default location and if it doesn't exist, it creates it with an empty list. + +To allow the frontend to talk to the backend, Wails provides a bridge. If you run `wails serve` in your project directory, you will see something like this: + +
+ +
+ +This is now serving your backend Go functions and is waiting for the bridge to connect. We commented this out of `main.js` at the start, but now is the time to add it back in: + +```javascript {7,9,13} +import Vue from "vue"; +import App from "./App.vue"; + +Vue.config.productionTip = false; +Vue.config.devtools = true; + +import Wails from "@wailsapp/runtime"; + +Wails.Init(() => { + new Vue({ + render: h => h(App) + }).$mount("#app"); +}); +``` + +Ensure your frontend is running. If not, run `npm run serve` in the frontend directory to start it. Your app will load as normal, but you may have noticed something flash up quickly. This is message to indicate that there is no connection with the backend. Very quickly, it establishes the connection and disappears. You can see this if you press `ctrl-c` in the terminal running `wails serve`. If you now look at your app, you will see something like this: + +
+ +
+ +The bridge will now be attempting to reconnect to the backend. If we run `wails serve` again, the dialog will disappear. This means the reconnection has happened. If you are uncertain, check the developer console which will have some log output from the bridge: + +
+ +
+ +Now that we have established the connection, we can access the backend from the frontend. All bound functions are registered to the global `backend` object. The default template binds a simple function called `basic`. We can call this from the browser by calling `backend.basic()`. It is important to remember that every function on the `backend` object returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises). To quickly test this in the browser we can simply run `backend.basic().then(console.log)`. We get our response from our backend code: + +
+ +
+ +Awesome! We now have an easy means to call our Go code, so let's get cracking with our Save. + +## Saving the Todo List + +Our todo store in the frontend is just an array with a number of javascript objects in it. We can serialise this to JSON, which makes it an easy thing to load and save. We want to save whenever there are any changes to this list, whether it's adding, removing or editing. Vue allows us to [watch properties](https://vuejs.org/v2/guide/computed.html#Watchers) and call a function when it changes. We do this by adding the `watch` property on the component: + +```javascript {4-8} + todos: [] + } + }, + watch: { + todos: function(todos) { + console.log("Todo list: " + JSON.stringify(todos)); + } + }, + methods: { + addTodo: function() { +``` + +Now when we update the list, the list is printed to the console. + +Instead of just logging to the browser console, we will take our first step into using the Wails runtime from Javascript. It is accessible through the [@wailsapp/runtime](https://www.npmjs.com/package/@wailsapp/runtime) package and has some features that help with building your application: + + * Logging + * Events + * Browser + + To use the Runtime, we need to import it: + +```javascript{5} + +``` + +Stop and restart the backend running `wails serve`. You will notice that the loadList function has now been bound: + +```bash {3} +INFO[0000] [Bind] Binding Go Functions/Methods +INFO[0000] [Bind] Bound Function: main.saveList() +INFO[0000] [Bind] Bound Function: main.loadList() +INFO[0000] [Headless] Headless mode started. +INFO[0000] [Headless] The Wails bridge will connect automatically. +``` + +When you reload the page, you will notice the following in your terminal output: + +```bash +INFO[0337] I got this list: Load the list! +``` +Great! Now let's update our loadList function to load the list file and send that back: + +```go +func loadList() (string, error) { + cwd, err := os.Getwd() + if err != nil { + return "", err + } + filename := path.Join(cwd, "mylist.json") + result, err := ioutil.ReadFile(filename) + return string(result), err +} +``` +This is similar to our `saveList` function, but instead reads the file. Our `ioutil.ReadFile` function returns a byte array so the final line converts this into a string before returning it. Any errors are also returned (We will look at error handling) shortly. + +Reserve the backend again and you will notice the following output: + +```bash +INFO[0014] I got this list: [] +``` + +This is what is in our file (even if it is a blank list)! Last thing for us to do is to convert our loaded json into the todo list. We'll do the following updates to our `mounted` function: + +```javascript + mounted() { + window.backend.loadList().then((list) => { + Wails.Log.Info("I got this list: " + list) + this.todos = JSON.parse(list); + }); + } +``` + +Now every time we reload the page, the todo list is loaded! We can also see the list data that was sent from the backend to the frontend on the console. + +If your editor/IDE has automatic file reload, then open the `mylist.json` file and watch how it gets updated when you add or remove items. It's saved in a compact form which is sometimes hard to read so lets format the data saved slightly. We used `JSON.stringify` to turn the todo list into a string and it has an option to "pretty print", so let's change that in `App.vue`: + +```javascript {3} + watch: { + todos: function(todos) { + window.backend.saveList(JSON.stringify(todos, null, 2)); + } + }, +``` +Now our `mylist.json` file will look something like this: + +```json +[ + { + "id": 0, + "title": "I am a todo", + "completed": false + }, +] +``` +One thing you may notice is that when you mark the item as complete, `mylist.json` isn't updated with `"completed": true`. The reason for that is that our watcher in Vue, by default, watches the array size for changes, not the elements within it. Vue offers a way to [customise this behaviour](https://vuejs.org/v2/api/#vm-watch) by providing an object describing how the watcher should work. Passing `deep: true` will indicate we want to watch for object changes within the array: + +```javascript + watch: { + todos: { + handler: function(todos) { + window.backend.saveList(JSON.stringify(todos, null, 2)); + }, + deep: true + } + }, +``` + +Now when we mark an item for selection, the `completed` variable for the item in `mylist.json` is updated accordingly. + +## Error Handling + +Now that we have most of the functionality, let's look at error handling. There are 2 placed that errors can occur: frontend and backend. When errors occur in the frontend, they are logged to the browser console, but when the app is packaged, there is no browser console, so we need to ensure we handle them correctly before packaging. Backend errors are a little easier to deal with, but we'll come to that. + +### Frontend Errors + +To demonstrate an error in the frontend, let's look at this piece of code: + +```javascript + mounted() { + window.backend.loadList().then(list => { + Wails.Log.Info("I got this list: " + list); + this.todos = JSON.parse(list); + }); + } +``` + +The [JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) method with throw an error if the structure of the JSON isn't correct. We don't handle this case so it will error. Let's see what happens if we edit our `mylist.json` directly to contain the following and reload the page: + +``` +I am not a JSON file +``` + +In the browser console, in the `console` tab, you'll see something like this: + +
+ +
+ +The problem is, there's no indication in the app that an error occurred. We need to capture the error if it occurs and show a message to the user. + +The standard way to catch [errors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) in Javascript is to use [try/catch](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch) blocks. We can do that to catch the error and display an error to the user. Initially, let's just print it out to our console: + +```javascript {4-8} + mounted() { + window.backend.loadList().then(list => { + try { + this.todos = JSON.parse(list); + } catch (e) { + Wails.Log.Info("An error was thrown: " + e.message); + } + }); + } +``` + +You should see something like this appear in your console: + +```bash +INFO[3081] An error was thrown: Unexpected token I in JSON at position 0 +``` + +Now that we are capturing the error, let's show a message to the user. We will do this by having a new component data property called 'errorMessage' and whenever that is set, we will show it. Let's first define the property, with a test error message + +```javascript {5} + data() { + return { + newTodo: "", + editedTodo: null, + errorMessage: "Guru Meditation", + todos: [] + }; + }, +``` + +Next, let's create an error box in the template. We want to only show it if `errorMessage` is not an empty string, so we will use the [v-if]() directive. This will only display the element it is attached to if the condition is met. + +Let's add a new div for the error message in our template: + +```html {3} +