Skip to main content

· 11 min read

2017 年開始紀錄自己使用的自動化紀錄工具,今年更新。

為什麼要做個人自動化紀錄?

有人會問,Facebook/Twitter 不就可以記錄大小事了嗎?如果會這麼回答,那就實在太天真了😏 。FB 上也許包含了自己覺得值得分享的事,但生活中還有諸多事情值得記錄,而不適合與大眾分享。 擁有自己的一份完整數位化生活記錄,是我持續的個人目標之一。要達成這個目標,需要藉助一些自動化紀錄工具,好讓整個過程變得自然而不困難。

照片自動化備份 📷

  • 360CAM 所拍的相片一律備份到手機
  • Dropbox, 自動從手機上傳照片
  • Google 相簿,充電時自動從手機備份照片到 Google 雲端
  • NAS (Synnalogy), 透過Cloud Sync從 Dropbox 同步照片。
graph LR cam[360 CAM] User -- take photo --> cam User -- take photo --> Phone cam --> Phone Phone -.-> Dropbox Phone -.-> gphoto[Google Photo] Dropbox -.-> NAS

運動自動化紀錄 🚶‍♂️

  • 記步,睡眠紀錄:小米手環 2
  • 體重:小米體重計
graph LR User -- 量體重 --> 小米體重計 小米體脂計 -.-> 小米運動App

現已不再帶小米手環 2,覺得記錄睡眠與步數,並無法改善健康狀況,意義不大。 同時為了降低多走路所需要的意志力,把每日步數改成更容易達成的 300 步,只要開始走,通常都會超過需要的步數。

Update: 四月開始為了減重需要,又再戴起小米手環 2。並將體重計換成體脂計。因應生活型態的改變,行走目標也改為每天 4000 步。


生活事件自動化紀錄

延續用 IFTTT 做自動生活紀錄這篇的思路,我把看過的書籍、電影,喜歡的 Youtube 影片,貼過的文章,每日完成的事項都記錄到 Google 日曆中,以方便之後回顧。

自動閱讀 / 觀看紀錄 📚

對於書籍與電影,我使用 RSS + IFTTT + Google Calendar 來自動紀錄。 當我在豆瓣上修改狀態,豆瓣的 RSS 也跟著改變,這時 IFTTT 會將 RSS 中的新事項紀錄到 Google 日曆上。 對於 Youtube 上 like 的影片,Facebook 或 Twitter 上新貼的文章,也會透過 IFTTT 紀錄到 Google 日曆上。

graph LR User -- add movie --> Douban User -- post --> Blog Blog -.-> RSS Douban -.-> RSS RSS -.-> IFTTT IFTTT -.-> gcal[Google Calendar]

透過 RSS 轉 IFTTT 紀錄

graph LR User -- like --> Youtube User -- post --> Facebook User -- post --> Twitter Youtube -.-> IFTTT Facebook -.-> IFTTT Twitter -.-> IFTTT IFTTT -.-> gcal[Google Calendar]

直接透過 IFTTT 紀錄

自動紀錄每日完成的事項 📓

這部份是自動紀錄的核心。使用 Todoist + IFTTT + Google Calendar 即可達成。 我在 Google Calendar 上使用一個單獨的日曆 (成功日記) 來紀錄每日完成的事項。

graph LR User -- checked --> Todoist Todoist -.-> IFTTT IFTTT -.-> gcal[Google Calendar]

If task completed in Todoist, Then log into Google Calendar

從 Email 新增待辦事項 ✉️

為了更方便地蒐集待辦事項,我參考這份影片 https://youtu.be/V7Dk7pzjJmM?t=11m30s 來將 Todoist#Inbox 設定為 Email 聯絡人,這樣處理 Email 的過程中也能快速地新增待辦事項。

此外也透過 IFTTT,將設為Reference標籤的信件備份到 Evernote 中。

事實上這兩種設定都很少使用。

紀錄看過或待看的網頁 🌐

我會將待看的文章搜集到 Pocket。

除了瀏覽 Facebook 或 Twitter 上的文章,我也使用 Feedly 訂閱一些自己挑選過的網站。並將 Feedly 設定成當我做標記時,就將本篇文章轉存到 Pocket 稍候閱讀列表,我可以掃過 Feedly 列表,標記感興趣的新聞,稍後再到 Pocket 閱讀。

這樣讓我在看到文章連結當下不需急著看完整篇文章,而是在有空閒的時候才閱讀這些文章。

我唯一的待辦事項收件夾是 Todoist,若看到值得閱讀 (紀錄) 的網頁,桌面上我使用瀏覽器的Pocket外掛插件 (Firefox 瀏覽器內建),將待看網頁記錄到 Pocket 中。

若這個網頁非看不可,我會在按下插件時填入一個自訂標籤fox,然後透過 IFTTT,若發現 Pocket 中新增了一筆含fox標籤的網頁,就新建一筆 Todoist 代辦事項。

在手機上就直接使用 Todoist 和 Pocket 等 App 達到一樣的效果。

graph LR Feedly --> Pocket Browser --> addon[Pocket addon + tag] addon --> Pocket pocket -.-> IFTTT IFTTT -.-> Todoist

文章更新時自動提醒 ⏰

有些網站並未提供 RSS 訂閱,手機上我會使用Web Alert來取得網頁更新提醒。

graph LR webalert[Web Alert] --> User User --> Browser

開發工具設定自動備份 ¨

使用 VS Code Settings Sync ,只需剛開始時設定一次,之後可同步各種 VS Code 中的設定與插件。


自動化網站部署 🌐

目前已使用 Github 來放我的個人網站與部落格,透過與 Travis CI 整合,我所修改的任何內容,在幾分鐘之內都會自動部署到網站上。

如何做可參考 Hello Hexo (個人網站自動化部署) 和 Automatically deploy new commit to github pages via Travis CI

graph LR master[Github:master] travis[Travis CI] ghpages[Github:gh-pages] User -- commit --> master master -. auto build .-> travis travis -. auto deploy .-> ghpages

Auto website deploy flow

一些可以直接運作在瀏覽器的專案 (如 BlocklyDuino 和 Saihubot),我會直接將 gh-pages 設為預設分支,所有改動直接 push 到這分支中。這樣一有改動即可在網頁上看到更新成果。


半自動紀錄

半自動工作紀錄 💼

透過翻看 Todoist 或 Google Calendar,我可以輕易地將過去一週達成的事項整理出來,再送 PR 到 Github 上。 也可以說這塊目前只能算半自動化地列出過去事項列表,可以再繼續改進。

定期回顧與整理

我在 Todoist 中增加一個Template項目,裡面放了周檢視 / 月檢視 / 季檢視 / 年檢視樣板,透過重複時間設定,每段時間自動提醒該做檢視了。

撰寫本文的目的之一,也是讓我有回顧我的自動化運作的機會。

照片備份規則

由於 Dropbox 空間有限,會不定期將 Dropbox 上的照片移動到到 NAS 上按年月份分類的photo/資料夾.

我的照片並不算多,但若有出遊的月份通常照片會暴增。所以我的基本備份規則是依年份,並以雙月份命名資料夾,若是當月有重大活動則直接在檔名中標注。 例如 2016 年的照片資料夾裡會有2016_10_11,或是2016_06_london這樣的命名。

在整理照片的時候,每當遇到特別喜歡的,我會另存到 Dropbox 中的一個依年份歸檔的資料夾,例如 2017 年的精彩照片我會另存到 dropbox/spot/2017資料夾中,這樣隨時可以找出來欣賞。

另外每年累積的一些螢幕截圖,也放在當年度的screenshots資料夾裡。

清理 RSS Feed

透過 Feedly 訂閱 RSS Feed 太容易,但是不小心每天收到的新聞量就遠高於自己能吸收的量,這時可以到 https://feedly.com/i/organize/my 把那些失效的連結清掉,並快速檢視一下現在仍在訂閱的網站,是否還對這些主題感興趣。

手動紀錄

為了平衡日常太依靠電子產品的趨向,前年開始就嘗試使用實體筆記本作一些紀錄,2018 一月中開始嘗試養成更頻繁地使用實體筆記本的習慣。在幾經調整後,目前我使用 B5 方格筆記本做基礎,搭配不同的魔擦筆來作筆記。實體筆記本的好處是除了一般的紀錄,還可以隨意畫心智圖,黏照片,貼紙,蓋印章等。參考各種筆記術書籍,我在每本筆記本前幾頁會空出索引區,將筆記本內容索引起來,以便之後查找。

暫時還沒想好(也還沒需求)該如何數位化。

參考資料

· 5 min read

先記錄下來手邊使用的工具,才有機會從繁雜中歸納出簡單的使用規則。

20111年時紀錄了一次當時的個人工具箱,2016 年開始再次紀錄自己手邊工具箱的改變2 3 4🤹。 今年初工作上有了變化,也將家中的環境包含進配置列表中。

主要裝備

  • 💻 Dell XPS 13 (8550), Windows 10, i7 4GHz x4, 8GB RAM, 13.3" LCD,作業系統:Elementary OS + Windows
  • 📱 One Plus 3, Android, 6GB RAM, 5.5" AMOLED
  • 🎧 Sony WI-1000 降噪耳機

  • 🖥️ 24" LCD
  • ⌨️ 羅技 K375s 鍵盤
  • 🖱 羅技 M720 滑鼠
  • 📦 Synology NAS: 電影,影集,照片,資料備份
  • 💡 Yeelight LED 燈泡
  • 備用 💻 Macbook Air 13", 2012 年版
  • 備用 📺 Chromecast 一代
  • 備用 🔈 Echodot
  • 備用 Respberry Pi 3
  • 備用 📙 Kindle Voyage

和去年相比的改變:

主力用💻 Dell XPS 13",家中添購了一台🖥️ 24" LCD 接筆電,搭配 3 轉 1 HDMI Hub,接🎮Nintendo Switch, Chromecast, Respberry Pi,可滿足影音相關需求。HDMI 設備通電時會自動切換到對應的設備。

graph LR LCD -- DVI --- Notebook LCD -- HDMI --- hub{HDMI Hub} hub --- ns[Nintendo Switch] hub --- Chromecast hub --- rpi[Respberry Pi]

上次記錄前即開始使用的⌨️ 羅技 K375s 鍵盤與🖱羅技 M720 滑鼠,兩者特點都是同時支援使用 USB 或藍牙控制設備,讓我可以用一套鍵鼠控制多種設備。

🕸️ 網站工具

設計工具

開發工具

  • 編輯器:VS Code
    • Blank Line at the End
    • Diff Tool
    • EditorConfig for VSCode
    • Go
    • Guides: more guide line
    • GitLens: more git info
    • Python
    • Settings Sync
    • solidity
    • Trailing Spaces
  • 版本控制:git
  • 套件管理:apt (Linux), nvm, yarn, Chocolatey(windows)

若需要在 Windows 上開發,能用 Chocolatey 處理的就用 Chocolatey 處理。

娛樂工具

  • 🎮Nintendo Switch
  • 📺 Chromecast (少用)
  • 🎮Steam /vlc (少用)

因為攜帶便利,現在主要只玩 NS 上的遊戲。Steam 上看到喜歡的遊戲,也盡量等 NS 上出了再買。

📱 Android 手機應用

  • 瀏覽器:Firefox for Android
  • 網頁更新通知:Web Alert
  • 閱讀:Feedly, Readmoo
  • 稍候閱讀:Pocket
  • 信箱:Inbox, Gmail
  • 地圖:Google 地圖
  • 記事:Keep
  • 內建相機,內建時鐘
  • 相簿:內建相簿,Google 相簿,Dropbox
  • 社交:Facebook, LINE, Twitter
  • 待辦事項:Todoist
  • 密碼管理:Bitwarden
  • 記帳: Toshl
  • 線上影片:Youtube
  • 有聲書:Audible
  • 音樂:豆瓣 FM
  • 健康:小米運動,Pokemon Go, 7Min workout

工具

  • 2 步驟認證:Duo Mobile
  • 旅遊:Google 翻譯,TripAdvisor
  • 影片播放:MX Player, DS video
  • 記錄看書狀況:Readmoo 分享書
  • 線上學習:Udemy, EggHead.io
  • IFTTT
  • 💳街口支付

Reference

· 8 min read

因為智能合約一旦部署就難以修改,因此合約的安全性極其重要,要避免合約中出現一些基礎錯誤,除了透過第三方驗證外,完整地單元測試 (unit test) 也是必需的。

目前最成熟的智能合約單元測試方式,還是透過Truffle開發框架來達成。有趣的是 Truffle 主要使用 Javascript 來撰寫智能合約的單元測試(也可以用 solidity 來寫)。

加入測試

接續上一篇建立的HelloToken合約,在test/目錄下加入test_hello_token.js測試檔案(如果覺得這份程式碼不易理解,可跳過這節,後面會介紹更簡潔的測試方法,到時再回來對照著看)。

var HelloToken = artifacts.require('HelloToken');

const INITIAL_SUPPLY = 100000;
let _totalSupply;

contract('HelloToken', function(accounts) {
it('should met initial supply', function() {
var contract;
HelloToken.deployed().then((instance) => {
contract = instance;
return contract.totalSupply.call();
}).then((totalSupply) => {
_totalSupply = totalSupply;
assert.equal(totalSupply.toNumber(), INITIAL_SUPPLY);
return contract.balanceOf(accounts[0]);
}).then((senderBalance) => {
assert.equal(_totalSupply.toNumber(), senderBalance.toNumber());
});
});
});

運行truffle test可看到測試通過的結果。

Contract: HelloToken
✓ should met initial supply

1 passing (11ms)

講解

var HelloToken = artifacts.require('HelloToken');

artifacts.require的用法和在migrations/中的用法相同,可以直接引入對應的智能合約。

contract('HelloToken', function(accounts) {
it('should met initial supply', function() {
});
});

Truffle 是使用 Javascript 開發中常見的Mocha測試框架和Chai斷言庫來做單元測試。差別只是把 Mocha test 中的 describe換成contract。根據官方文件1contact執行前會自動重新部署到 testrpc (或測試網路) 上,所以智能合約會是剛部署好乾淨的狀態。

此外,contract也會帶入accounts變數,裡面儲存了 testrpc 或其他你運行的測試網路所提供的帳號,開發者可以直接使用這些帳號來測試合約。

第一個測試是來測部署合約後預設的代幣數目是否正確。

var contract;
HelloToken.deployed().then((instance) => {
contract = instance;
return contract.totalSupply.call();
}).then((totalSupply) => {
...
});

這邊內容和在truffle console中輸入的測試內容雷同,使用Promise來確定每個非同步的操作都在上一個操作完成後才繼續執行。

上一個操作可以透過 return 語句回傳下個操作需要的參數。例如這邊then裡面傳入的totalSupply參數,是來自上一行return contract.totalSupply.call()的結果。

assert.equal(totalSupply.toNumber(), INITIAL_SUPPLY);
...
assert.equal(_totalSupply.toNumber(), senderBalance.toNumber());

這邊我們透過assert.equal語句驗證了HelloToken合約中的初始代幣總額與INITIAL_SUPPLY參數的值相符,且與合約部署者 (accounts[0]) 帳戶中擁有的總額相符。

使用 async/await 簡化測試

要理解這樣的 promise chain 需要一些練習。但其實上面的測試用例中,我們只想做好最後的兩個 assert 驗證。有沒有比較直覺的測試方法呢?

有的!2017 下半年,Javascript 語言支援了async/await語句[2](只要安裝 Node 7.6 版以上即可使用),可以用更直覺的方式撰寫非同步的程式碼。

智能合約測試剛好也使用大量的非同步程式碼。使用async/await語句改寫後的智能合約測試程式碼如下:

var HelloToken = artifacts.require('HelloToken');

const INITIAL_SUPPLY = 100000;

contract('HelloToken', function(accounts) {
it('should met initial supply', async function() {
let contract = await HelloToken.deployed();
let totalSupply = await contract.totalSupply.call();
let senderBalance = await contract.balanceOf(accounts[0]);
assert.equal(totalSupply.toNumber(), INITIAL_SUPPLY);
assert.equal(totalSupply.toNumber(), senderBalance.toNumbe());
});
});

運行truffle test可看到測試通過的結果。

Contract: HelloToken
✓ should met initial supply

1 passing (11ms)

講解

it('should met initial supply', async function() {
});

要在程式碼中使用 async/await,需要在函式前加入async宣告,這樣解譯器才會解析函式中的await語法。

let contract = await HelloToken.deployed();
let totalSupply = await contract.totalSupply.call();
let senderBalance = await contract.balanceOf(accounts[0]);

透過在非同步的操作前加上await宣告,這三行程式會依照順序,等待第一行 await 語句執行完,取得contract變數後,再依序執行第二行語句。第二行語句執行完,取得totalSupply變數後,再繼續執行第三行語句以取得senderBalance變數。

後面兩個 assert 語句則與 promise 撰寫時完全一樣。這樣改寫後,程式碼的可讀性大大地提昇了!

加入轉帳測試

再透過async/await語句試著加入轉帳測試:

  it('should have right balance after transfer', async function() {
const AMOUNT = 123;
let contract = await HelloToken.deployed();
// check init balance
let account0Balance = await contract.balanceOf(accounts[0]);
let account1Balance = await contract.balanceOf(accounts[1]);
assert.equal(account0Balance.toNumber(), INITIAL_SUPPLY);
assert.equal(account1Balance.toNumber(), 0);
// check balance after transferred
await contract.transfer(accounts[1], AMOUNT);
account0Balance = await contract.balanceOf(accounts[0]);
account1Balance = await contract.balanceOf(accounts[1]);
assert.equal(account0Balance.toNumber(), INITIAL_SUPPLY - AMOUNT);
assert.equal(account1Balance.toNumber(), AMOUNT);
});

運行truffle test可看到測試通過的結果。

Contract: HelloToken
✓ should met initial supply
✓ should have right balance after transfer (92ms)

2 passing (151ms)

講解

let account0Balance = await contract.balanceOf(accounts[0]);
let account1Balance = await contract.balanceOf(accounts[1]);
assert.equal(account0Balance.toNumber(), INITIAL_SUPPLY);
assert.equal(account1Balance.toNumber(), 0);

範例的前半部測試帳號0帳號1中的代幣餘額。帳號0即部署代幣的帳號,因此擁有所有的HelloToken代幣,而帳號1中則沒有HelloToken代幣。

await contract.transfer(accounts[1], AMOUNT);

接著呼叫合約的transfer方法將一些代幣轉入帳號1。注意這些都是非同步的操作(送出傳輸命令後,要先等待區塊鍊確認),因此需要使用await語句。

account0Balance = await contract.balanceOf(accounts[0]);
account1Balance = await contract.balanceOf(accounts[1]);
assert.equal(account0Balance.toNumber(), INITIAL_SUPPLY - AMOUNT);
assert.equal(account1Balance.toNumber(), AMOUNT);

範例的後半部再次測試帳號0帳號1中的代幣餘額。結果符合轉帳後兩個帳戶的預期代幣數額。

結語

async/await語句相當適合拿來寫非同步的程式,這特性太適合用來寫智能合約的測試了。因為async/await這語法太新,所以大部分的參考資料都還在用Promise來撰寫。我建議當你看到相關的智能合約測試時,可以用 async/await 改寫看看,會有很不一樣的感受。

參考資料

[1] Writing Tests in Javascript http://truffleframework.com/docs/getting_started/javascript-tests [2] 6 Reasons Why JavaScript’s Async/Await Blows Promises Away (Tutorial)https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9

· 6 min read

今天已把前陣子買到的 Ledget Nano S 硬體錢包在 Windows 10/Elementary OS (/Ubuntu 16.04) 上設定好,正式開始使用。和預期一樣,雖然在 Linux 上剛開始設定時需要多做一些步驟,但是一旦設定好後,使用過程和在其他平台上並沒有區別。

硬體錢包

過去透過 NAS,外接硬碟等方式來保護自己的相片,作品等「數位資產」,以防哪天檔案遺失或外流。現在要守護的範圍更要擴及「加密代幣」,即保護自己的「數位財產」。

graph LR 作品 --> NAS 相片 --> NAS 相片 --> 線上備份 NAS --> 數位資產 線上備份 --> 數位資產 加密代幣 --> 硬體錢包 硬體錢包 --> 數位資產

MyEtherWallet 網站推薦任何擁有超過「2 周的薪資」數位財產的人,使用「硬體錢包」來保管自己的「加密代幣」,以避免可能的財產損失。

目前 Ledget Nano S 和 TREZOR 是兩款較多人使用的硬體錢包。硬體錢包的安全性從產生錢包帳號開始。硬體錢包帳號的私鑰一直保存在硬體設備中,只要保存好「recovery phase」(Mnemonic Seed),不會發生使用線上交易所時帳號或帳戶中的代幣可能被盜的風險。只有在發送代幣時需要解鎖錢包帳號。解鎖錢包帳號時,輸入 PIN 碼解鎖的過程,也是透過硬體錢包上的按鍵完成,從而避免了所使用的電腦可能已被入侵者安裝鍵盤側錄軟體而造成的財產上的風險。

設定流程

1. 設定 Ledget Nano S 硬體

在 Ledget Nano S 上透過按壓左右兩顆硬體按鈕,設定 4~8 字的 PIN Code 與 24 個單字的「recovery phase」,並用紙筆等實體方式記錄下來(千萬別用拍照的...)。完成後機器會隨機挑幾個次序測試,而你需要選擇對應的單字來確認安全性。如果以後機器壞了或遺失了,仍然可以透過這 24 個單字的「recovery phase」來取回帳號。

2. 在電腦上安裝 Chrome 或 Chromium

因為目前 Ledger Nano S 使用 Chrome App 技術來提供桌面應用程式,Chrome 也是唯一支援WebUSB API的瀏覽器...Google 近期公告 Chrome App 即將被 Progressive Web App 取代,我們拭目以待 Ledger 公司將拿出什麼方案來應對。

3. 安裝 Ledger Manager

前往https://www.ledgerwallet.com/apps/manager安裝 Ledger Manager。

4. 設定 USB 連線

這時開啟 Ledger Manager,將 Ledget Nano S 連線到電腦並解鎖,Ledger Manager 無法找到對應的設備。

這時可以參考What if Ledger Wallet is not recognized on Linux?在命令行環境下輸入以下命令:

wget -q -O - https://www.ledgerwallet.com/support/add_udev_rules.sh | sudo bash

執行後重新將 Ledget Nano S 連線到電腦並解鎖,可以看到 Ledger Manager 開始更新資料。

4. 安裝 Ethereum App

Ledger Manager 與 Ledget Nano S 連線後,除了可以更新韌體之外,也能安裝不同的「加密代幣」App 進 Ledget Nano S。

這邊選擇安裝 Ethereum App。

安裝完成後,在 Ledget Nano S 上可以看到多出一個Ethereum的圖示。

點選進入Ethereum,確認Browser Support選項為No (Ethereum> Settings > Browser Support > No),這樣稍後安裝的 Ledger Wallet Ethereum 才能辨識到 Ledget Nano S。

5. 安裝 Ledger Wallet Ethereum

參考How to install and use Ethereum and Ethereum Classic? 前往https://www.ledgerwallet.com/apps/ethereum下載 Ledger Wallet Ethereum App。

安裝好後重新將 Ledget Nano S 連線到電腦並解鎖,可以看到相關操作界面。

使用 MyEtherWallet 取代 Ledger Wallet Ethereum

若不喜歡使用 App,還可透過 MyEtherWallet 來存取。

參考Moving from MyEtherWallet to LedgerHow to use MyEtherWallet with Ledger 這兩篇設定,將Setting中的Contract DataBrowser support選項都設成Yes

Ethereum > Settings > Contract Data > Yes
Ethereum > Settings > Browser Support > Yes

透過 Chrome 瀏覽器,在 MyEtherWallet 中看到How would you like to access your wallet選項時,選擇Ledger Wallet並在硬體上解鎖即可。

參考資料

· 2 min read

隨著 Firefox 56,57 版的推出,我參與製作的 Firefox Onboarding 功能也正式和大眾見面了。

Firefox 56

在 Firefox 56 版中當新使用者開啟瀏覽器時,會看到一個可愛的狐狸頭。 Imgur

點進去可以看到一些功能導覽頁面。 Imgur

點擊導覽頁面右下方的按鈕的話,會聚焦到瀏覽器對應的功能區塊上,使用者可以快速嘗試這些功能。 Imgur

我們也加入了 Web assessibility,使用者可以只用鍵盤來瀏覽整個使用者導覽頁面。

Firefox Quantum (57)

經過使用者研究 (User research),在 Firefox Quantum (57) 版上我們針對 Onboarding 的體驗又做了不小的修改。

Imgur

這次的更新除了主視覺與一些互動元素都變得不一樣之外,也加了個小彩蛋:新使用者 (全新的 profile) 和從過去版本升級的使用者,所看到的功能導覽項目是不盡相同的喔。

Imgur

有興趣進一步了解我們怎麼製作 Onboarding Tour,可參考Onboarding 文件