diff --git a/README.md b/README.md index b50b311..ea659b4 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,7 @@ -# Vue-boilerplate -*Minimal static Vue project* - - - -### [Tool link](https://klemek.github.io/vue-boilerplate/) - -## Use this template - - - -```bash -git clone git@github.com/klemek/vue-boilerplate.git {PROJECT} -cd {PROJECT} -git remote rename origin template -git remote add origin {PROJECT REMOTE} -# everytime you want to update your fork -git fetch --all -git merge template/master -``` - -> Every task is indicated with a TODO - -1. [ ] Rename app in [README.md](./README.md), [index.html](./index.html) and [package.json](./package.json) -2. [ ] Change app hue and saturation in [style.css](./style.css) -3. [ ] Remove this part and all TODO +# File-Whizz +*Make that file buzzing through air* +### [Tool link](https://klemek.github.io/file-whizz/) ## Tips diff --git a/eslint.config.mjs b/eslint.config.mjs index 406c0ce..3314c6e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -22,11 +22,14 @@ export default [ "no-magic-numbers": "off", "sort-keys": "off", "no-warning-comments": "off", + "no-inline-comments": "off", // TODO remove "no-ternary": "off", "one-var": "off", - "max-statements": ["warn", 50], + "max-statements": ["warn", 200], // TODO remove + "max-lines-per-function": ["warn", 200], // TODO remove "max-params": ["warn", 5], "max-lines": "off", + "no-console": "off", // TODO remove }, }, { diff --git a/index.html b/index.html index 2ccaa63..727ad06 100644 --- a/index.html +++ b/index.html @@ -3,14 +3,11 @@ - File Whizz - - - - + +
-

File Whizz @@ -40,27 +30,31 @@

-

-
- +

- Initialized: {{ peer ? 'OK' : '...'}}
- Opened: {{ localId ? 'OK' : '...'}}
- Connection: {{ connection ? 'OK' : '...'}}
- File data: {{ data ? 'OK' : '...'}} -
-
- -
- + + + +

 klemek - - -  Repository +  Repository - 2025

diff --git a/main.js b/main.js index 4f5a69c..4c9de7c 100644 --- a/main.js +++ b/main.js @@ -10,9 +10,14 @@ const utils = { }, }); }, + bufferCopy(src, startSrc, dst, startDst, size) { + const viewSrc = new Uint8Array(src, startSrc, size); + const dstSrc = new Uint8Array(dst, startDst, size); + dstSrc.set(viewSrc); + }, }; -const MAX_CHUNK_SIZE = 1024 * 1024; // 1024 KB +const MAX_CHUNK_SIZE = 10 * 1024; // 10 KB const app = createApp({ data() { @@ -22,13 +27,37 @@ const app = createApp({ remoteId: null, connection: null, // TODO multiple connections data: null, // TODO multiple file - fileStream: null, + buffer: null, fileName: null, fileSize: null, t0: new Date(), + received: [], + downloading: false, }; }, - computed: {}, + computed: { + canConnect() { + return this.peer !== null && this.localId !== null; + }, + isConnected() { + return this.connection !== null; + }, + readyToDownload() { + return this.buffer !== null && !this.downloading; + }, + isServer() { + return this.data !== null; + }, + isClient() { + return this.isConnected && !this.isServer; + }, + downloadProgress() { + return this.received.length * MAX_CHUNK_SIZE; + }, + downloadTotal() { + return this.fileSize ?? 0; + }, + }, watch: {}, updated() { utils.updateIcons(); @@ -44,13 +73,6 @@ const app = createApp({ showApp() { document.getElementById("app").setAttribute("style", ""); }, - connect() { - if (this.remoteId) { - this.initConnection( - this.peer.connect(this.remoteId, { reliable: true }), - ); - } - }, initPeer() { this.peer = new Peer({ debug: 3, @@ -82,6 +104,7 @@ const app = createApp({ this.connection.on("close", this.connClose); this.connection.on("error", this.connError); this.connection.on("data", this.connData); + this.serverInfo(); }); }, peerOpen(id) { @@ -96,43 +119,114 @@ const app = createApp({ peerClose() { console.log("peerClose"); this.peer = null; - // setTimeout(this.initPeer); }, peerDisconnected() { console.log("peerDisconnected"); - // this.peer.reconnect(); + this.peer.reconnect(); }, peerError(err) { console.log("peerError", err); // TODO handle error + throw err; + }, + createStream() { + this.buffer = new ArrayBuffer(this.fileSize); + }, + serverInfo() { + this.connection.send({ + type: "server-info", + fileName: this.data ? this.fileName : null, + fileSize: this.data ? this.fileSize : null, + }); + }, + serverSendData(index) { + const to = Math.min(this.fileSize, index + MAX_CHUNK_SIZE); + this.connection.send({ + type: "server-chunk", + index, + bytes: this.data.slice(index, to), + }); + }, + serverDone() { + this.connection.send({ + type: "server-done", + }); + }, + clientStartTransfer() { + this.downloading = true; + this.connection.send({ + type: "client-start-transfer", + }); + }, + clientSeek() { + const indexes = []; + for (let index = 0; index < this.fileSize; index += MAX_CHUNK_SIZE) { + if (!this.received.includes(index)) { + indexes.push(index); + } + } + if (indexes.length) { + this.connection.send({ + type: "client-seek", + indexes, + }); + return true; + } + return false; + }, + clientDone() { + this.connection.send({ + type: "client-done", + }); + const blob = new Blob([this.buffer], { + type: "application/octet-stream", + }); + const link = document.createElement("a"); + link.href = URL.createObjectURL(blob); + link.download = this.fileName; + link.click(); }, connData(data) { console.log("connData"); console.log(data.type); switch (data.type) { - case "start": - this.t0 = new Date(); + case "server-info": this.fileName = data.fileName; this.fileSize = data.fileSize; - this.fileStream = streamSaver - .createWriteStream(this.fileName, { - size: this.fileSize, - }) - .getWriter(); - break; - case "chunk": - if (this.fileStream) { - this.fileStream.write(new Uint8Array(data.bytes)); + if (this.fileName !== null) { + this.createStream(); } break; - case "end": - if (this.fileStream) { - this.fileStream.close(); + case "server-chunk": + utils.bufferCopy( + data.bytes, + 0, + this.buffer, + data.index, + data.bytes.length, + ); + this.received.push(data.index); + break; + case "server-done": + if (!this.clientSeek()) { + this.clientDone(); } - console.log(new Date() - this.t0); - console.log(this.fileSize / (new Date() - this.t0)); + break; + case "client-start-transfer": + for (let index = 0; index < this.fileSize; index += MAX_CHUNK_SIZE) { + this.serverSendData(index); + } + this.serverDone(); + break; + case "client-seek": + data.indexes.forEach(this.serverSendData); + this.serverDone(); + break; + case "client-done": + this.connection.close(); break; default: + console.error("Invalid data type"); break; } }, @@ -144,8 +238,16 @@ const app = createApp({ connError(err) { console.log("connError", err); // TODO handle error + throw err; }, - fileChange(event) { + onRemoteIdChange() { + if (this.remoteId) { + this.initConnection( + this.peer.connect(this.remoteId, { reliable: true }), + ); + } + }, + onFileChange(event) { console.log(event.target.files[0]); const file = event.target.files[0]; // TODO multiple files if (!file) { @@ -157,44 +259,15 @@ const app = createApp({ const reader = new FileReader(); reader.onload = () => { this.data = reader.result; + if (this.isConnected) { + this.serverInfo(); + } }; - reader.onerror = (err) => { - console.error(err); - }; - reader.onloadstart = () => { - console.log("reading"); - }; - reader.onloadend = () => { - console.log("read"); + reader.onerror = () => { + // TODO handle file reading error }; reader.readAsArrayBuffer(file); // TODO check ArrayBuffer.prototype.maxByteLength }, - start() { - this.connection.send({ - type: "start", - fileName: this.fileName, - fileSize: this.fileSize, - }); - console.log("start"); - for ( - let index = 0; - index < this.data.byteLength; - index += MAX_CHUNK_SIZE - ) { - console.log("chunk"); - this.connection.send({ - type: "chunk", - bytes: this.data.slice( - index * MAX_CHUNK_SIZE, - Math.min(this.data.byteLength, (index + 1) * MAX_CHUNK_SIZE), - ), - }); - } - console.log("end"); - this.connection.send({ - type: "end", - }); - }, initApp() { this.initPeer(); }, diff --git a/style.css b/style.css index 1f25cc9..05a6453 100644 --- a/style.css +++ b/style.css @@ -85,7 +85,7 @@ CUSTOM STYLE :root { /* https://materialui.co/colors/ */ - /* TODO: 2. change hue and saturation */ + --hue-primary: 65.52; --sat-primary: 20%; --background: hsl(var(--hue-primary), var(--sat-primary), 96.08%); @@ -213,6 +213,7 @@ h6 .lucide { opacity: 50%; } -input { +input, +progress { width: 100%; }