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 @@
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%;
}