mirror of
https://github.com/jackyzha0/quartz.git
synced 2026-03-21 21:45:42 -05:00
ll
This commit is contained in:
parent
59b5807601
commit
dec3e85fe2
25
content/00-Overview/Home.md
Normal file
25
content/00-Overview/Home.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# ⚡ Electron.js Crash Course
|
||||||
|
|
||||||
|
**University:**
|
||||||
|
**Semester:**
|
||||||
|
**Students:** ~__ | Background: Basic HTML/CSS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Sessions
|
||||||
|
|
||||||
|
| # | Title | Status |
|
||||||
|
| --- | ----------------------------------------------- | ------- |
|
||||||
|
| 1 | [[Session 1 - How Electron Works]] | ✅ Ready |
|
||||||
|
| 2 | [[Session 2 - JavaScript Bootcamp]] | ✅ Ready |
|
||||||
|
| 3 | [[Session 3 - Windows Menus & Native Features]] | ✅ Ready |
|
||||||
|
| 4 | [[Session 4 - IPC Communication]] | ✅ Ready |
|
||||||
|
| 5 | [[Session 5 - App Design & WebSockets Intro]] | ✅ Ready |
|
||||||
|
| 6 | [[Session 6 - WebSocket Server]] | ✅ Ready |
|
||||||
|
| 7 | [[Session 7 - Connecting UI to WebSocket]] | ✅ Ready |
|
||||||
|
| 8 | [[Session 8 - Packaging & Distribution]] | ✅ Ready |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏁 Final Project
|
||||||
|
[[Final Project - LAN Chat App]]
|
||||||
126
content/01-Sessions/Session 1 - How Electron Works.md
Normal file
126
content/01-Sessions/Session 1 - How Electron Works.md
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
---
|
||||||
|
tags: [electron, setup, architecture]
|
||||||
|
session: 1
|
||||||
|
duration: 2 hours
|
||||||
|
status: ready
|
||||||
|
---
|
||||||
|
|
||||||
|
# Session 1 — The Desktop Web: How Electron Works
|
||||||
|
|
||||||
|
## 🎯 Objectives
|
||||||
|
- Understand what Electron is and why it exists
|
||||||
|
- Set up the development environment
|
||||||
|
- Run a working Electron app
|
||||||
|
- Understand the Main vs Renderer process model
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧠 Concepts Covered
|
||||||
|
|
||||||
|
### What is Electron?
|
||||||
|
Electron lets you build desktop apps using HTML, CSS, and JavaScript.
|
||||||
|
It bundles two things together:
|
||||||
|
- **Chromium** → renders your UI (like a built-in browser)
|
||||||
|
- **Node.js** → gives you access to the filesystem, OS, and more
|
||||||
|
|
||||||
|
> Real apps built with Electron: VS Code, Slack, Discord, Figma, Notion
|
||||||
|
|
||||||
|
### Desktop vs Web Apps
|
||||||
|
| Web App | Desktop App |
|
||||||
|
|---------|-------------|
|
||||||
|
| Runs in browser | Runs natively on OS |
|
||||||
|
| No file system access | Full file system access |
|
||||||
|
| URL-based | Has its own window/icon |
|
||||||
|
| Auto-updated | Needs packaging/installer |
|
||||||
|
|
||||||
|
### The Two-Process Model ⚠️ (Most Important Concept)
|
||||||
|
```
|
||||||
|
┌─────────────────────────────┐
|
||||||
|
│ MAIN PROCESS │ ← Node.js world
|
||||||
|
│ - Creates windows │ File system, OS APIs
|
||||||
|
│ - App lifecycle │
|
||||||
|
│ - Background logic │
|
||||||
|
└────────────┬────────────────┘
|
||||||
|
│ IPC (messages)
|
||||||
|
┌────────────▼────────────────┐
|
||||||
|
│ RENDERER PROCESS │ ← Browser world
|
||||||
|
│ - Displays your HTML/CSS │ DOM, UI events
|
||||||
|
│ - One per window │
|
||||||
|
└─────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key rule:** Renderer cannot directly access Node.js. They communicate via IPC (covered in Session 4).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🖥 Talking Points
|
||||||
|
|
||||||
|
1. Open VS Code and show the folder structure of a blank Electron app
|
||||||
|
2. Walk through `package.json` — point out `"main": "main.js"`
|
||||||
|
3. Open `main.js` — explain `app.whenReady()` and `BrowserWindow`
|
||||||
|
4. Open `index.html` — show it's just normal HTML
|
||||||
|
5. Run `npm start` — a window appears!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 Live Demo
|
||||||
|
```js
|
||||||
|
// main.js — bare minimum Electron app
|
||||||
|
const { app, BrowserWindow } = require('electron')
|
||||||
|
|
||||||
|
function createWindow() {
|
||||||
|
const win = new BrowserWindow({
|
||||||
|
width: 800,
|
||||||
|
height: 600
|
||||||
|
})
|
||||||
|
win.loadFile('index.html')
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(createWindow)
|
||||||
|
```
|
||||||
|
```html
|
||||||
|
<!-- index.html -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title>My First App</title></head>
|
||||||
|
<body>
|
||||||
|
<h1>Hello Electron! 👋</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠 Hands-on Exercise (45 min)
|
||||||
|
|
||||||
|
**Goal:** Get a window running and customize it
|
||||||
|
|
||||||
|
1. Install Node.js from nodejs.org
|
||||||
|
2. Run in terminal:
|
||||||
|
```bash
|
||||||
|
mkdir my-electron-app
|
||||||
|
cd my-electron-app
|
||||||
|
npm init -y
|
||||||
|
npm install --save-dev electron
|
||||||
|
```
|
||||||
|
3. Create `main.js` and `index.html` from the demo above
|
||||||
|
4. Add to `package.json`: `"start": "electron ."`
|
||||||
|
5. Run `npm start`
|
||||||
|
|
||||||
|
**Challenges (pick one or more):**
|
||||||
|
- Change the window title
|
||||||
|
- Change the window size to 400x400
|
||||||
|
- Add your name in big text to `index.html`
|
||||||
|
- Set `resizable: false` on the window
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Resources
|
||||||
|
- [Electron Docs](https://electronjs.org/docs)
|
||||||
|
- [Node.js Download](https://nodejs.org)
|
||||||
|
- [[Session 2 - JavaScript Bootcamp]] ← next session
|
||||||
|
|
||||||
|
## ✅ Checklist
|
||||||
|
- [ ] Node.js installed on lab machines
|
||||||
|
- [ ] Starter repo shared with students
|
||||||
|
- [ ] Demo code tested on Windows + Mac
|
||||||
140
content/01-Sessions/Session 2 - JavaScript Bootcamp.md
Normal file
140
content/01-Sessions/Session 2 - JavaScript Bootcamp.md
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
---
|
||||||
|
tags: [javascript, fundamentals, dom]
|
||||||
|
session: 2
|
||||||
|
duration: 2 hours
|
||||||
|
status: ready
|
||||||
|
---
|
||||||
|
|
||||||
|
# Session 2 — JavaScript Crash Bootcamp
|
||||||
|
|
||||||
|
## 🎯 Objectives
|
||||||
|
- Write and understand modern JavaScript
|
||||||
|
- Manipulate the DOM
|
||||||
|
- Use Node.js modules with `require`
|
||||||
|
- Install and use an npm package
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧠 Concepts Covered
|
||||||
|
|
||||||
|
### Variables & Functions
|
||||||
|
```js
|
||||||
|
// Prefer const and let over var
|
||||||
|
const name = "Alice"
|
||||||
|
let count = 0
|
||||||
|
|
||||||
|
// Arrow function
|
||||||
|
const greet = (name) => {
|
||||||
|
return `Hello, ${name}!`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Short form
|
||||||
|
const double = (n) => n * 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### DOM Manipulation
|
||||||
|
```js
|
||||||
|
// Select an element
|
||||||
|
const btn = document.getElementById('myButton')
|
||||||
|
const title = document.querySelector('h1')
|
||||||
|
|
||||||
|
// Change content
|
||||||
|
title.textContent = "New Title"
|
||||||
|
title.style.color = "blue"
|
||||||
|
|
||||||
|
// Listen for events
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
console.log('Button clicked!')
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Node.js Modules
|
||||||
|
```js
|
||||||
|
// Built-in Node module
|
||||||
|
const path = require('path')
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
// Read a file
|
||||||
|
const content = fs.readFileSync('notes.txt', 'utf-8')
|
||||||
|
console.log(content)
|
||||||
|
```
|
||||||
|
|
||||||
|
### npm Packages
|
||||||
|
```bash
|
||||||
|
npm install moment # installs a package
|
||||||
|
```
|
||||||
|
```js
|
||||||
|
const moment = require('moment')
|
||||||
|
console.log(moment().format('MMMM Do YYYY'))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Async Basics
|
||||||
|
```js
|
||||||
|
// Things that take time use callbacks or async/await
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log("This runs after 2 seconds")
|
||||||
|
}, 2000)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🖥 Talking Points
|
||||||
|
|
||||||
|
1. Show the browser DevTools console — students can try JS right there
|
||||||
|
2. Demo DOM changes live in the browser
|
||||||
|
3. Show `require` in Node.js vs `import` (keep it simple, use require)
|
||||||
|
4. Explain why `const` is preferred
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠 Hands-on Exercise (50 min)
|
||||||
|
|
||||||
|
**Build a click counter app, then load it in Electron**
|
||||||
|
|
||||||
|
Part 1 — Build in browser first:
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body { font-family: sans-serif; text-align: center; padding: 40px; }
|
||||||
|
button { font-size: 24px; padding: 10px 30px; cursor: pointer; }
|
||||||
|
#count { font-size: 72px; color: #333; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Click Counter</h1>
|
||||||
|
<div id="count">0</div>
|
||||||
|
<button id="btn">Click me!</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let clicks = 0
|
||||||
|
const countEl = document.getElementById('count')
|
||||||
|
const btn = document.getElementById('btn')
|
||||||
|
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
clicks++
|
||||||
|
countEl.textContent = clicks
|
||||||
|
if (clicks >= 10) countEl.style.color = 'red'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
Part 2 — Load it in Electron:
|
||||||
|
- Drop this `index.html` into their Electron project from Session 1
|
||||||
|
- Run `npm start` — it's now a desktop app!
|
||||||
|
|
||||||
|
**Challenges:**
|
||||||
|
- Add a reset button
|
||||||
|
- Show a congratulations message at 20 clicks
|
||||||
|
- Change the background color based on click count
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Resources
|
||||||
|
- [MDN JavaScript Guide](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide)
|
||||||
|
- [[Session 1 - How Electron Works]] ← previous
|
||||||
|
- [[Session 3 - Windows Menus & Native Features]] ← next
|
||||||
|
-
|
||||||
@ -0,0 +1,112 @@
|
|||||||
|
---
|
||||||
|
tags: [electron, BrowserWindow, menus, dialogs, filesystem]
|
||||||
|
session: 3
|
||||||
|
duration: 2 hours
|
||||||
|
status: ready
|
||||||
|
---
|
||||||
|
|
||||||
|
# Session 3 — Windows, Menus & Native Features
|
||||||
|
|
||||||
|
## 🎯 Objectives
|
||||||
|
- Configure BrowserWindow with different options
|
||||||
|
- Build a native application menu
|
||||||
|
- Open and save files using system dialogs
|
||||||
|
- Read and write files with Node.js `fs`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧠 Concepts Covered
|
||||||
|
|
||||||
|
### BrowserWindow Options
|
||||||
|
```js
|
||||||
|
const win = new BrowserWindow({
|
||||||
|
width: 1000,
|
||||||
|
height: 700,
|
||||||
|
minWidth: 400,
|
||||||
|
title: "My App",
|
||||||
|
backgroundColor: '#1e1e1e',
|
||||||
|
frame: true, // set false for frameless window
|
||||||
|
resizable: true,
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(__dirname, 'preload.js')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### App Lifecycle Events
|
||||||
|
```js
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') app.quit()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Native Menus
|
||||||
|
```js
|
||||||
|
const { Menu, MenuItem } = require('electron')
|
||||||
|
|
||||||
|
const menu = Menu.buildFromTemplate([
|
||||||
|
{
|
||||||
|
label: 'File',
|
||||||
|
submenu: [
|
||||||
|
{ label: 'Open', accelerator: 'CmdOrCtrl+O', click: openFile },
|
||||||
|
{ label: 'Save', accelerator: 'CmdOrCtrl+S', click: saveFile },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'quit' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ role: 'editMenu' },
|
||||||
|
{ role: 'viewMenu' }
|
||||||
|
])
|
||||||
|
|
||||||
|
Menu.setApplicationMenu(menu)
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Dialogs & fs
|
||||||
|
```js
|
||||||
|
const { dialog } = require('electron')
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
async function openFile() {
|
||||||
|
const result = await dialog.showOpenDialog({
|
||||||
|
filters: [{ name: 'Text Files', extensions: ['txt', 'md'] }]
|
||||||
|
})
|
||||||
|
if (!result.canceled) {
|
||||||
|
const content = fs.readFileSync(result.filePaths[0], 'utf-8')
|
||||||
|
// send content to renderer via IPC (next session!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠 Hands-on Exercise — Markdown Notepad (60 min)
|
||||||
|
|
||||||
|
Build a simple text editor:
|
||||||
|
|
||||||
|
**Features to implement:**
|
||||||
|
1. A `<textarea>` that fills the window
|
||||||
|
2. File → Open loads a `.txt` file into the textarea
|
||||||
|
3. File → Save writes the textarea content back to disk
|
||||||
|
4. Title bar shows the filename
|
||||||
|
|
||||||
|
**Starter structure:**
|
||||||
|
```
|
||||||
|
notepad/
|
||||||
|
├── main.js
|
||||||
|
├── preload.js
|
||||||
|
├── index.html
|
||||||
|
└── package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠️ Note: Full IPC wiring comes in Session 4 — for now, students can use `remote` module or handle everything in main.js as a simplified demo
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Resources
|
||||||
|
- [BrowserWindow Docs](https://www.electronjs.org/docs/latest/api/browser-window)
|
||||||
|
- [dialog Docs](https://www.electronjs.org/docs/latest/api/dialog)
|
||||||
|
- [[Session 4 - IPC Communication]] ← next (this is where it all clicks)
|
||||||
101
content/01-Sessions/Session 4 - IPC Communication.md
Normal file
101
content/01-Sessions/Session 4 - IPC Communication.md
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
---
|
||||||
|
tags: [electron, ipc, preload, contextBridge, security]
|
||||||
|
session: 4
|
||||||
|
duration: 2 hours
|
||||||
|
status: ready
|
||||||
|
---
|
||||||
|
|
||||||
|
# Session 4 — IPC: Making the Two Processes Talk
|
||||||
|
|
||||||
|
## 🎯 Objectives
|
||||||
|
- Understand why IPC exists and why it's necessary
|
||||||
|
- Send messages from Renderer → Main and back
|
||||||
|
- Use `preload.js` and `contextBridge` safely
|
||||||
|
- Wire a full two-way communication flow
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧠 Concepts Covered
|
||||||
|
|
||||||
|
### Why IPC?
|
||||||
|
The Renderer runs in a sandboxed browser context.
|
||||||
|
It cannot directly call `fs.readFile()` or access Node.js.
|
||||||
|
IPC is the secure message-passing bridge.
|
||||||
|
```
|
||||||
|
Renderer → ipcRenderer.send('open-file') →
|
||||||
|
Main → ipcMain.on('open-file', handler) →
|
||||||
|
Main → win.webContents.send('file-content', data) →
|
||||||
|
Renderer → ipcRenderer.on('file-content', handler)
|
||||||
|
```
|
||||||
|
|
||||||
|
### The Secure Way: preload.js + contextBridge
|
||||||
|
```js
|
||||||
|
// preload.js — runs in a special context with BOTH Node and DOM access
|
||||||
|
const { contextBridge, ipcRenderer } = require('electron')
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
|
openFile: () => ipcRenderer.invoke('open-file'),
|
||||||
|
saveFile: (content) => ipcRenderer.invoke('save-file', content),
|
||||||
|
onFileLoaded: (callback) => ipcRenderer.on('file-loaded', callback)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
```js
|
||||||
|
// main.js — handles the requests
|
||||||
|
const { ipcMain, dialog } = require('electron')
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
ipcMain.handle('open-file', async () => {
|
||||||
|
const result = await dialog.showOpenDialog({ ... })
|
||||||
|
if (!result.canceled) {
|
||||||
|
return fs.readFileSync(result.filePaths[0], 'utf-8')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('save-file', async (event, content) => {
|
||||||
|
const result = await dialog.showSaveDialog({})
|
||||||
|
if (!result.canceled) {
|
||||||
|
fs.writeFileSync(result.filePath, content)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
```js
|
||||||
|
// renderer (index.html script) — uses the exposed API
|
||||||
|
document.getElementById('open-btn').addEventListener('click', async () => {
|
||||||
|
const content = await window.electronAPI.openFile()
|
||||||
|
document.getElementById('editor').value = content
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### invoke vs send
|
||||||
|
| `ipcRenderer.send` | `ipcRenderer.invoke` |
|
||||||
|
|--------------------|----------------------|
|
||||||
|
| Fire and forget | Returns a Promise |
|
||||||
|
| One-way | Two-way (request/response) |
|
||||||
|
| Use for events | Use for data fetching |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠 Hands-on Exercise — Wire the Notepad (60 min)
|
||||||
|
|
||||||
|
Complete the Notepad from Session 3 using proper IPC:
|
||||||
|
|
||||||
|
1. Set up `preload.js` with `contextBridge`
|
||||||
|
2. Add `webPreferences: { preload: ... }` to BrowserWindow
|
||||||
|
3. Expose `openFile` and `saveFile` via `electronAPI`
|
||||||
|
4. Wire up Open and Save buttons in the renderer
|
||||||
|
5. **Bonus:** Add a word count display that updates as you type
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Common Mistakes
|
||||||
|
- Forgetting to set `preload` in `webPreferences`
|
||||||
|
- Using `nodeIntegration: true` (insecure, avoid it)
|
||||||
|
- Trying to `require('fs')` in renderer directly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Resources
|
||||||
|
- [IPC Docs](https://www.electronjs.org/docs/latest/tutorial/ipc)
|
||||||
|
- [contextBridge Docs](https://www.electronjs.org/docs/latest/api/context-bridge)
|
||||||
|
- [[Session 5 - App Design & WebSockets Intro]] ← next
|
||||||
120
content/01-Sessions/Session 5 - App Design & WebSockets Intro.md
Normal file
120
content/01-Sessions/Session 5 - App Design & WebSockets Intro.md
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
---
|
||||||
|
tags: [websockets, planning, design, project]
|
||||||
|
session: 5
|
||||||
|
duration: 2 hours
|
||||||
|
status: ready
|
||||||
|
---
|
||||||
|
|
||||||
|
# Session 5 — What Are We Building? + WebSockets Intro
|
||||||
|
|
||||||
|
## 🎯 Objectives
|
||||||
|
- Decide on the final project as a class
|
||||||
|
- Understand what WebSockets are and when to use them
|
||||||
|
- Sketch the app UI and define features
|
||||||
|
- Set up the project repo
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧠 Part 1 — Choosing the App (30 min)
|
||||||
|
|
||||||
|
### What makes a good Electron + WebSocket project?
|
||||||
|
- Real-time updates (needs WebSockets)
|
||||||
|
- Benefits from being a desktop app (not just a website)
|
||||||
|
- Simple enough to build in 3 sessions
|
||||||
|
- Visual and satisfying to demo
|
||||||
|
|
||||||
|
### Proposed Options
|
||||||
|
|
||||||
|
| App Idea | WebSocket Use | Complexity |
|
||||||
|
|----------|---------------|------------|
|
||||||
|
| 🗨 LAN Chat App | Send/receive messages in real-time | ⭐⭐ |
|
||||||
|
| 📝 Collaborative Notes | Multiple users edit the same note | ⭐⭐⭐ |
|
||||||
|
| 📊 Live System Dashboard | Show CPU/memory data live | ⭐⭐ |
|
||||||
|
| 🎮 Multiplayer Quiz Game | Questions pushed to all clients | ⭐⭐⭐ |
|
||||||
|
|
||||||
|
> 💡 **Recommended:** LAN Chat App — no internet needed, instantly testable in class, clearly demonstrates WebSocket concepts
|
||||||
|
|
||||||
|
### Class Vote → Decision: _______________
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧠 Part 2 — WebSockets Explained (30 min)
|
||||||
|
|
||||||
|
### HTTP vs WebSocket
|
||||||
|
```
|
||||||
|
HTTP (request/response):
|
||||||
|
Client → "Give me the messages" → Server
|
||||||
|
Server → "Here are 3 messages" → Client
|
||||||
|
(connection closes)
|
||||||
|
|
||||||
|
WebSocket (persistent):
|
||||||
|
Client ←→ Server (connection stays open)
|
||||||
|
Server can push data anytime!
|
||||||
|
```
|
||||||
|
|
||||||
|
### When to use WebSockets vs HTTP
|
||||||
|
| Use HTTP | Use WebSockets |
|
||||||
|
|----------|----------------|
|
||||||
|
| Load a page | Live chat |
|
||||||
|
| Submit a form | Real-time notifications |
|
||||||
|
| Fetch data once | Multiplayer games |
|
||||||
|
| REST API calls | Live dashboards |
|
||||||
|
|
||||||
|
### WebSocket Lifecycle
|
||||||
|
```
|
||||||
|
1. Client sends HTTP upgrade request
|
||||||
|
2. Server agrees → connection upgrades to WS
|
||||||
|
3. Both sides can now send messages freely
|
||||||
|
4. Either side can close the connection
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠 Part 3 — Design the App (60 min)
|
||||||
|
|
||||||
|
**For LAN Chat App:**
|
||||||
|
|
||||||
|
Sketch on paper / whiteboard first:
|
||||||
|
- What does the UI look like?
|
||||||
|
- What data does each message contain?
|
||||||
|
- What happens when someone connects / disconnects?
|
||||||
|
|
||||||
|
**Message structure:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "message",
|
||||||
|
"username": "Alice",
|
||||||
|
"text": "Hello everyone!",
|
||||||
|
"timestamp": "14:32"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Set up the project:**
|
||||||
|
```bash
|
||||||
|
mkdir lan-chat-app
|
||||||
|
cd lan-chat-app
|
||||||
|
npm init -y
|
||||||
|
npm install --save-dev electron
|
||||||
|
npm install ws
|
||||||
|
```
|
||||||
|
|
||||||
|
**File structure to set up today:**
|
||||||
|
```
|
||||||
|
lan-chat-app/
|
||||||
|
├── main.js
|
||||||
|
├── preload.js
|
||||||
|
├── package.json
|
||||||
|
└── renderer/
|
||||||
|
├── index.html
|
||||||
|
├── style.css
|
||||||
|
└── app.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Build the static HTML/CSS shell of the chat UI (no functionality yet)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Resources
|
||||||
|
- [WebSocket MDN Docs](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
|
||||||
|
- [ws npm package](https://www.npmjs.com/package/ws)
|
||||||
|
- [[Session 6 - WebSocket Server]] ← next
|
||||||
139
content/01-Sessions/Session 6 - WebSocket Server.md
Normal file
139
content/01-Sessions/Session 6 - WebSocket Server.md
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
---
|
||||||
|
tags: [websockets, server, nodejs, ws]
|
||||||
|
session: 6
|
||||||
|
duration: 2 hours
|
||||||
|
status: ready
|
||||||
|
---
|
||||||
|
|
||||||
|
# Session 6 — Building the WebSocket Server
|
||||||
|
|
||||||
|
## 🎯 Objectives
|
||||||
|
- Set up a `ws` WebSocket server inside Electron's Main process
|
||||||
|
- Handle client connections and disconnections
|
||||||
|
- Receive and broadcast messages to all clients
|
||||||
|
- Test with two laptops communicating in real-time
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧠 Concepts Covered
|
||||||
|
|
||||||
|
### The `ws` Package
|
||||||
|
```bash
|
||||||
|
npm install ws
|
||||||
|
```
|
||||||
|
|
||||||
|
### Basic WebSocket Server
|
||||||
|
```js
|
||||||
|
const { WebSocketServer } = require('ws')
|
||||||
|
|
||||||
|
const wss = new WebSocketServer({ port: 3000 })
|
||||||
|
|
||||||
|
wss.on('connection', (socket) => {
|
||||||
|
console.log('A client connected!')
|
||||||
|
|
||||||
|
socket.on('message', (data) => {
|
||||||
|
const message = JSON.parse(data)
|
||||||
|
console.log('Received:', message)
|
||||||
|
|
||||||
|
// Broadcast to ALL connected clients
|
||||||
|
wss.clients.forEach((client) => {
|
||||||
|
if (client.readyState === client.OPEN) {
|
||||||
|
client.send(JSON.stringify(message))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('close', () => {
|
||||||
|
console.log('A client disconnected')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('WebSocket server running on ws://localhost:3000')
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integrating Into Electron's Main Process
|
||||||
|
```js
|
||||||
|
// main.js
|
||||||
|
const { app, BrowserWindow } = require('electron')
|
||||||
|
const { WebSocketServer } = require('ws')
|
||||||
|
|
||||||
|
let wss
|
||||||
|
|
||||||
|
function startServer() {
|
||||||
|
wss = new WebSocketServer({ port: 3000 })
|
||||||
|
|
||||||
|
wss.on('connection', (socket) => {
|
||||||
|
socket.on('message', (data) => {
|
||||||
|
// Broadcast to all
|
||||||
|
wss.clients.forEach((client) => {
|
||||||
|
if (client.readyState === 1) {
|
||||||
|
client.send(data.toString())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
startServer()
|
||||||
|
createWindow()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Connecting From Another Machine (LAN)
|
||||||
|
```js
|
||||||
|
// On another laptop, connect to the server's IP
|
||||||
|
const ws = new WebSocket('ws://192.168.1.X:3000')
|
||||||
|
// Replace X with the server machine's local IP
|
||||||
|
```
|
||||||
|
|
||||||
|
> Find your IP: `ipconfig` (Windows) or `ifconfig` (Mac/Linux)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠 Hands-on Exercise (70 min)
|
||||||
|
|
||||||
|
**Goal: Two laptops, one chat**
|
||||||
|
|
||||||
|
1. Student A sets up the WebSocket server in `main.js`
|
||||||
|
2. Student B opens a plain HTML file and connects to Student A's IP
|
||||||
|
3. Student B types a message → it appears on Student A's console
|
||||||
|
4. Add broadcasting so it goes back to all clients
|
||||||
|
|
||||||
|
**Test script for Student B (plain HTML):**
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<input id="msg" placeholder="Type a message">
|
||||||
|
<button onclick="send()">Send</button>
|
||||||
|
<div id="log"></div>
|
||||||
|
<script>
|
||||||
|
const ws = new WebSocket('ws://REPLACE_WITH_IP:3000')
|
||||||
|
|
||||||
|
ws.onmessage = (event) => {
|
||||||
|
const log = document.getElementById('log')
|
||||||
|
log.innerHTML += `<p>${event.data}</p>`
|
||||||
|
}
|
||||||
|
|
||||||
|
function send() {
|
||||||
|
const text = document.getElementById('msg').value
|
||||||
|
ws.send(JSON.stringify({ username: 'Student B', text }))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Common Issues
|
||||||
|
- Firewall blocking port 3000 → disable Windows Defender firewall temporarily
|
||||||
|
- Wrong IP address → double check with `ipconfig`
|
||||||
|
- Port already in use → change to `3001`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Resources
|
||||||
|
- [ws Documentation](https://github.com/websockets/ws)
|
||||||
|
- [[Session 7 - Connecting UI to WebSocket]] ← next
|
||||||
124
content/01-Sessions/Session 7 - Connecting UI to WebSocket.md
Normal file
124
content/01-Sessions/Session 7 - Connecting UI to WebSocket.md
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
---
|
||||||
|
tags: [websockets, ipc, renderer, UI, final-project]
|
||||||
|
session: 7
|
||||||
|
duration: 2 hours
|
||||||
|
status: ready
|
||||||
|
---
|
||||||
|
|
||||||
|
# Session 7 — Connecting the UI to the WebSocket
|
||||||
|
|
||||||
|
## 🎯 Objectives
|
||||||
|
- Connect the Renderer UI to the WebSocket via IPC
|
||||||
|
- Display incoming messages dynamically in the DOM
|
||||||
|
- Send messages from the input field
|
||||||
|
- Handle connection states and polish the UX
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧠 Architecture Recap
|
||||||
|
```
|
||||||
|
[Renderer UI]
|
||||||
|
↕ ipcRenderer (via preload.js)
|
||||||
|
[Main Process]
|
||||||
|
↕ WebSocket
|
||||||
|
[Other Clients]
|
||||||
|
```
|
||||||
|
|
||||||
|
The Renderer doesn't touch WebSocket directly — it talks to Main via IPC, and Main relays to/from the WebSocket server.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧠 Concepts Covered
|
||||||
|
|
||||||
|
### Forwarding WebSocket Messages via IPC
|
||||||
|
```js
|
||||||
|
// main.js — when a WS message arrives, forward to renderer
|
||||||
|
wss.on('connection', (socket) => {
|
||||||
|
socket.on('message', (data) => {
|
||||||
|
// Broadcast to all WS clients
|
||||||
|
wss.clients.forEach(client => {
|
||||||
|
if (client.readyState === 1) client.send(data.toString())
|
||||||
|
})
|
||||||
|
// Also send to our own renderer window
|
||||||
|
mainWindow.webContents.send('new-message', JSON.parse(data.toString()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exposing the API via preload.js
|
||||||
|
```js
|
||||||
|
// preload.js
|
||||||
|
const { contextBridge, ipcRenderer } = require('electron')
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('chatAPI', {
|
||||||
|
sendMessage: (message) => ipcRenderer.send('send-message', message),
|
||||||
|
onMessage: (callback) => ipcRenderer.on('new-message', (_, msg) => callback(msg))
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Renderer: Sending & Displaying Messages
|
||||||
|
```js
|
||||||
|
// renderer/app.js
|
||||||
|
const form = document.getElementById('message-form')
|
||||||
|
const input = document.getElementById('message-input')
|
||||||
|
const chatBox = document.getElementById('chat-box')
|
||||||
|
|
||||||
|
// Send a message
|
||||||
|
form.addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
const text = input.value.trim()
|
||||||
|
if (!text) return
|
||||||
|
|
||||||
|
window.chatAPI.sendMessage({
|
||||||
|
username: localStorage.getItem('username') || 'Anonymous',
|
||||||
|
text,
|
||||||
|
timestamp: new Date().toLocaleTimeString()
|
||||||
|
})
|
||||||
|
input.value = ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// Receive messages
|
||||||
|
window.chatAPI.onMessage((msg) => {
|
||||||
|
const div = document.createElement('div')
|
||||||
|
div.className = 'message'
|
||||||
|
div.innerHTML = `
|
||||||
|
<span class="username">${msg.username}</span>
|
||||||
|
<span class="text">${msg.text}</span>
|
||||||
|
<span class="time">${msg.timestamp}</span>
|
||||||
|
`
|
||||||
|
chatBox.appendChild(div)
|
||||||
|
chatBox.scrollTop = chatBox.scrollHeight
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Connection State UI
|
||||||
|
```js
|
||||||
|
// Show "connecting..." / "connected" / "disconnected" states
|
||||||
|
ipcRenderer.on('ws-status', (_, status) => {
|
||||||
|
document.getElementById('status').textContent = status
|
||||||
|
document.getElementById('status').className = status
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠 Hands-on Exercise — Complete the App (80 min)
|
||||||
|
|
||||||
|
1. Wire up `preload.js` with `chatAPI`
|
||||||
|
2. Implement the message send handler
|
||||||
|
3. Implement the message receive + DOM append
|
||||||
|
4. Style the chat bubbles in `style.css`
|
||||||
|
5. Add username prompt on app start (`prompt()` or a simple input screen)
|
||||||
|
6. Test across 3+ laptops on the same network
|
||||||
|
|
||||||
|
**Bonus challenges:**
|
||||||
|
- Show "User X joined" when someone connects
|
||||||
|
- Different color bubbles for your messages vs others
|
||||||
|
- Sound notification on new message
|
||||||
|
- Timestamp on each message
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Resources
|
||||||
|
- [[Session 6 - WebSocket Server]] ← previous
|
||||||
|
- [[Session 8 - Packaging & Distribution]] ← next
|
||||||
115
content/01-Sessions/Session 8 - Packaging & Distribution.md
Normal file
115
content/01-Sessions/Session 8 - Packaging & Distribution.md
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
---
|
||||||
|
tags: [electron, packaging, electron-builder, distribution]
|
||||||
|
session: 8
|
||||||
|
duration: 2 hours
|
||||||
|
status: ready
|
||||||
|
---
|
||||||
|
|
||||||
|
# Session 8 — Packaging, Distribution & Wrap-up
|
||||||
|
|
||||||
|
## 🎯 Objectives
|
||||||
|
- Package the app into a native installer
|
||||||
|
- Create `.exe` (Windows), `.dmg` (Mac), `.AppImage` (Linux)
|
||||||
|
- Understand what comes after (updates, crash reporting)
|
||||||
|
- Demo day — run each other's packaged apps
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧠 Concepts Covered
|
||||||
|
|
||||||
|
### electron-builder
|
||||||
|
```bash
|
||||||
|
npm install --save-dev electron-builder
|
||||||
|
```
|
||||||
|
|
||||||
|
Add to `package.json`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"start": "electron .",
|
||||||
|
"build": "electron-builder"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"appId": "com.yourname.lanchat",
|
||||||
|
"productName": "LAN Chat",
|
||||||
|
"directories": {
|
||||||
|
"output": "dist"
|
||||||
|
},
|
||||||
|
"win": {
|
||||||
|
"target": "nsis"
|
||||||
|
},
|
||||||
|
"mac": {
|
||||||
|
"target": "dmg"
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"target": "AppImage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Output appears in `/dist` folder.
|
||||||
|
|
||||||
|
### App Icons
|
||||||
|
- Create a 512x512 `.png` icon
|
||||||
|
- Save as `build/icon.png` (electron-builder picks it up automatically)
|
||||||
|
- Tools: [icon.kitchen](https://icon.kitchen) or Figma
|
||||||
|
|
||||||
|
### What's Inside the Package?
|
||||||
|
```
|
||||||
|
dist/
|
||||||
|
├── win-unpacked/ ← the app folder
|
||||||
|
├── LAN Chat Setup.exe ← installer
|
||||||
|
└── builder-effective-config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧠 What's Next After This Course?
|
||||||
|
|
||||||
|
### Electron Alternatives
|
||||||
|
| Tool | Language | Why Use It |
|
||||||
|
|------|----------|------------|
|
||||||
|
| **Tauri** | Rust + Web | Much smaller bundle size |
|
||||||
|
| **NW.js** | Web | Older, similar to Electron |
|
||||||
|
| **Flutter** | Dart | Cross-platform inc. mobile |
|
||||||
|
|
||||||
|
### Electron Features to Explore
|
||||||
|
- **Auto-updater** — push updates silently to users
|
||||||
|
- **Tray icon** — app lives in system tray
|
||||||
|
- **Notifications** — native OS notifications
|
||||||
|
- **Deep links** — open app from a URL
|
||||||
|
- **Crash reporter** — collect error reports
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠 Demo Day (60 min)
|
||||||
|
|
||||||
|
**Each student / pair:**
|
||||||
|
1. Runs `npm run build`
|
||||||
|
2. Shares the installer via USB or local network share
|
||||||
|
3. Another student installs it on a fresh machine
|
||||||
|
4. Everyone's chat app connects to one room
|
||||||
|
|
||||||
|
**Presentation (2 min each):**
|
||||||
|
- What feature are you most proud of?
|
||||||
|
- What was the hardest bug you fixed?
|
||||||
|
- What would you add with more time?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Course Wrap-up Checklist
|
||||||
|
- [ ] All students have a working packaged app
|
||||||
|
- [ ] Share the vault / notes repo with students
|
||||||
|
- [ ] Collect feedback form
|
||||||
|
- [ ] Share "what to learn next" resources
|
||||||
|
|
||||||
|
## 📦 Final Resources
|
||||||
|
- [electron-builder Docs](https://www.electron.build)
|
||||||
|
- [Awesome Electron](https://github.com/sindresorhus/awesome-electron)
|
||||||
|
- [[Session 1 - How Electron Works]] ← back to the beginning
|
||||||
6
content/index.md
Normal file
6
content/index.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
title: Welcome to Quartz
|
||||||
|
---
|
||||||
|
|
||||||
|
This is a blank Quartz installation.
|
||||||
|
See the [documentation](https://quartz.jzhao.xyz) for how to get started.
|
||||||
Loading…
Reference in New Issue
Block a user