code:
// Brunch automatically concatenates all files in your
// watched paths. Those paths can be configured at
// config.paths.watched in "brunch-config.js".
//
// However, those files will only be executed if
// explicitly imported. The only exception are files
// in vendor, which are never wrapped in imports and
// therefore are always executed.

// Import dependencies
//
// If you no longer want to use a dependency, remember
// to also remove its path from "config.paths.watched".
import * as phoenix from "phoenix_html";

// Import local files
//
// Local files can be imported directly using relative
// paths "./socket" or full ones "web/static/js/socket".

// import socket from "./socket"


code:
"use strict";

function sleep(time) {
    return new Promise(resolve => setTimeout(resolve, time));
}

export default {
    name: "async-button",
    template: "#async-button-template",
    props: ["action", "defaultIcon"],
    data() {
        return {
            failCount: 0,
            status: "ready"
        };
    },
    methods: {
        async click() {
            if (this.status == "ready") {
                this.status = "waiting";
                const result = await this.action();
                if (result.success) {
                    this.status = "succeeded";
                } else {
                    this.status = "failed";
                    this.failCount += 1;
                    await sleep((2 ** this.failCount) * 1000);
                    this.status = "ready";
                }
            }
        }
    }
};

repo_name: Loves/qtfile

license: mit


code:
"use strict";

import file from "./file-view";
import upload from "./upload-view";

export default function(room) {
    return {
        name: "fileList",
        template: "#file-list-template",
        props: ["role", "filter"],
        data() {
            return room.fileList;
        },
        components: {
            file: file(room),
            upload: upload(room)
        },
        computed: {
            filteredFiles() {
                return this.files.filter((f) => {
                    return f.filename.toUpperCase().search(this.filter.toUpperCase()) >= 0;
                });
            },
            filesLength() {
                return this.files.length;
            },
            filteredFilesLength() {
                return this.filteredFiles.length;
            }
        }
    };
};


code:
"use strict";

import Upload from "./upload";
import File from "./file";

export default class FileList {
    constructor(room_id) {
        this.uploads = [];
        this.files = [];
        this.status = 0;
        this.room_id = room_id;
    }

    async wakeUploader() {
        if (this.status) {
            return;
        }

        this.status += 1;

        try {
            await this.performUploads();
        } finally {
            // todo: catch
            this.status -= 1;
        }
    }

    async performUploads() {
        while (this.uploads.length) {
            await this.upload(this.uploads[0]);
            this.uploads.shift();
        }
    }

    upload(upload) {
        return new Promise((resolve, reject) => {
            const req = new XMLHttpRequest();
            var query = "room_id=" + this.room_id + "&filename=" + upload.file.name;

            if (upload.file.type) {
                query += "&content_type=" + upload.file.type;
            }

            req.open("POST", "/api/upload?" + query, true);
            req.setRequestHeader("Content-Type", "application/octet-stream");

            req.upload.addEventListener("progress", (ev) => {
                if (ev.lengthComputable) {
                    upload.uploaded = ev.loaded;
                }
            });

            req.addEventListener("load", (ev) => {
                resolve(req.response);
            });

            req.addEventListener("error", (ev) => {
                reject(req.response);
            });

            req.addEventListener("abort", (ev) => {
                resolve("aborted xhr");
            });

            req.send(upload.file);
        });
    }

    async addUpload(id, name) {
        const upload = new Upload(id, name);
        this.uploads.push(upload);
        await this.wakeUploader();
    }

    static search(idKey, items, id, cont) {
        for (let i = 0; i < items.length; i++) {
            if (items[i][idKey] == id) {
                return cont(items[i], i);
            }
        }
        return null;
    }

    searchUploads(id, cont) {
        return FileList.search("id", this.uploads, id, cont);
    }

    searchFiles(id, cont) {
        return FileList.search("uuid", this.files, id, cont);
    }

    setFileDeletionFailed(uuids) {
        for (let file of this.files.reverse()) {
            if (uuids[uuids.length - 1] == file.uuid) {
                file.deleteStatus = "failed";
                uuids.pop();
            }
        }
    }

    setFileDeletionResults(uuids_original, results_original) {
        let uuids = uuids_original.slice(0);
        let results = results_original.slice(0);
        for (let file of this.files.reverse()) {
            if (uuids[uuids.length - 1] == file.uuid) {
                if (results[results.length - 1] == "ok") {
                    file.deleteStatus = "succeeded";
                } else {
                    file.deleteStatus = "failed";
                }
                results.pop();
                uuids.pop();
            }
        }
    }

    getDeletedFiles() {
        const result = [];
        for (let file of this.files) {
            if (file.marked) {
                file.deleteStatus = "waiting";
                result.push(file.uuid);
            }
        }
        return result;
    }

    progressUpload(id, uploaded, total) {
        this.searchUploads(id, (upload, i) => {
            upload.uploaded = uploaded;
            upload.total = total;
        });
    }

    completeUpload(id) {
        this.searchUploads(id, (upload, i) => {
            this.uploads.splice(i, 1);
        });
    }

    removeFile(uuid) {
        this.searchFiles(uuid, (file, i) => {
            this.files.splice(i, 1);
        });
    }

    addFile(data) {
        const file = File.create(data);
        this.files.unshift(file);
    }
}


code:
"use strict";

import asyncButton from "./async-button-view";

export default function(room) {
    return {
        data() {
            const x = room.fileList.searchFiles(this.uuid, (file, i) => {
                return file;
            });
            return x;
        },
        name: "file",
        template: "#file-template",
        props: ["role", "uuid", "index", "filesLength"],
        computed: {
            domId() {
                return "file-" + this.uuid;
            },
            link() {
                return `/get/${this.uuid}/${this.filename}`;
            },
            mod() {
                return this.role == "mod" || this.role == "admin";
            },
            isOdd() {
                return (this.index + this.filesLength) % 2;
            },
            isEven() {
                return (this.index + this.filesLength + 1) % 2;
            },
            formattedExpirationDate() {
                return (new Date(this.expiration_date)).toLocaleString();
            },
            shrunken_ip() {
                return this.ip_address.substring(0, 22);
            }
        },
        methods: {
            async deleteMe() {
                this.deleteStatus = "waiting";
                const files = [this.uuid];
                let result = await room.deleteFiles(files);
                result.success = result.results.length == 1 && result.results[0] == "ok";
                return result;
            },
            async banMe() {
                const date = new Date(
                    new Date().setFullYear(new Date().getFullYear() + 10)
                );
                const ban = {
                    file_bans: [
                        {
                            hash: this.hash
                        }
                    ],
                    reason: "quick ban",
                    end: Math.round(date.getTime() / 1000)
                };
                const result = await room.ban(ban);
                return result;
            },

            async banUploader() {
                const date = new Date(
                    new Date().setHours(new Date().getHours() + 1)
                );

                const ban = {
                    user_bans: [
                        {
                            bannee_id: this.uploader_id,
                            hell: false,
                            ip_bans: [
                                {
                                    ip_address: this.ip_address
                                }
                            ]
                        }
                    ],
                    reason: "quick ban",
                    end: Math.round(date.getTime() / 1000)
                };
                const result = await room.ban(ban);
                return result;
            }
        },

        components: {
            asyncButton: asyncButton
        }
    };
};


code:
"use strict";

export default class File {
    static create(data) {
        const file = new File();
        return Object.assign(file, data);
    }

    constructor() {
        this.marked = false;
        this.deleteStatus = "ready";
        this.banStatus = "ready";
    }
}


code:
"use strict";

export default function(room) {
    return {
        name: "presence",
        template: "#presence-template",
        props: ["role", "presenceHidden"],
        data() {
            return room.presence;
        },
        methods: {
            togglePresence() {
                this.$emit("toggle-presence");
            }
        },
        computed: {
            hidden() {
                return this.presenceHidden;
            }
        }
    };
};


code:
"use strict";

import {Presence as PhoenixPresence} from "phoenix";

export default class Presence {
    constructor() {
        this.presences = {};
    }

    syncState(state) {
        this.presences = PhoenixPresence.syncState(this.presences, state);
    }

    diffState(diff) {
        this.presences = PhoenixPresence.syncDiff(this.presences, diff);
    }
}


code:
"use strict";

import socket from "./socket";
import Room from "./room";
import Vue from "vue/dist/vue.common.js";
import fileListView from "./file-list-view";
import presenceView from "./presence-view";
const room = new Room(socket);

// A room with a vue
export default new Vue({
    name: "room",
    data: room,
    el: "#room",
    components: {
        fileList: fileListView(room),
        presence: presenceView(room)
    },
    mounted() {
        room.initialiseUploader();
    },
    methods: {
        async deleteFiles() {
            const files = room.fileList.getDeletedFiles();
            await room.deleteFiles(files);
        },
        togglePresence() {
            this.presenceSize = (this.presenceSize + 1) % 4;
        }
    },
    computed: {
        mod() {
            return this.role == "mod" || this.role == "admin";
        },
        presenceBig() {
            return this.presenceSize == 3;
        },
        presenceMedium() {
            return this.presenceSize == 2;
        },
        presenceSmall() {
            return this.presenceSize == 1;
        },
        presenceHidden() {
            return this.presenceSize == 0;
        }
    }
});


code:
//import "js/uploader.js";
import FileList from "./file-list";
import Presence from "./presence";

export default class Room {
    constructor(socket) {
        this.room_id = window.config.room_id;
        this.fileList = new FileList(this.room_id);
        this.presence = new Presence();
        const self = this;
        Room.createChannel(socket, this.room_id, this).then(channel => {
            self.channel = channel;
        });
        this.role = "user";
        this.filter = "";
        this.topid = 0;
        this.presenceSize = 0;
    }

    push(method, data) {
        return new Promise((resolve, reject) => {
            this.channel.push(method, data)
                .receive("ok", resolve)
                .receive("error", reject)
            ;
        });
    }

    async deleteFiles(files) {
        try {
            const results = await this.push("delete", {files: files})
            this.fileList.setFileDeletionResults(files, results.results);
            console.log("files deleted: " + results.results);
            return results;
        } catch (e) {
            this.fileList.setFileDeletionFailed(files);
            console.log("failed to delete files: " + e);
            throw e;
        }
   }

    async ban(data) {
        try {
            const result = await this.push("ban", data)
            return result;
        } catch (e) {
            console.log("failed to ban: " + e);
            throw e;
        }
   }

    initialiseUploader() {
        const uploadButton = document.getElementById("upload-button");
        const self = this;
        uploadButton.addEventListener("change", function() {
            const files = this.files;
            for (const file of files) {
                self.fileList.addUpload(self.topid, file);
                self.topid += 1;
            }
            this.value = "";
        });

        window.addEventListener("drop", (ev) => {
            ev.preventDefault();
            ev.stopPropagation();
            return false;
        });

        window.addEventListener("dragover", (ev) => {
            ev.preventDefault();
            ev.stopPropagation();
            return false;
        });

        const dropzone = document.getElementById("file-dropzone");
        dropzone.addEventListener("drop", (ev) => {
            ev.preventDefault();
            ev.stopPropagation();
            const dt = ev.dataTransfer;
            if (dt.items) {
                for (let item of dt.items) {
                    if (item.kind == "file") {
                        this.fileList.addUpload(this.topid, item.getAsFile());
                        this.topid += 1;
                    }
                }
            } else {
                // version that works in palememe
                for (let item of dt.files) {
                    this.fileList.addUpload(this.topid, item);
                    this.topid += 1;
                }
            }
            return false;
        }, true);
    }

    static createChannel(socket, room_id, self) {
        return new Promise((resolve, reject) => {
            const channel = socket.channel("room:" + room_id, {});

            channel.on("files", payload => {
                payload.body.forEach(file => {
                    self.fileList.addFile(file);
                });
            });

            channel.on("role", payload => {
                self.role = payload.body;
            });

            channel.on("deleted", payload => {
                self.fileList.removeFile(payload.body);
            });

            channel.on("presence_state", payload => {
                self.presence.syncState(payload);
            });

            channel.on("presence_diff", payload => {
                self.presence.diffState(payload);
            });

            channel.join()
                .receive("ok", resp => {
                    resolve(channel);
                })
                .receive("error", reject)
            ;
        });
    }
}


code:
// NOTE: The contents of this file will only be executed if
// you uncomment its entry in "assets/js/app.js".

// To use Phoenix channels, the first step is to import Socket
// and connect at the socket path in "lib/web/endpoint.ex":
import {Socket} from "phoenix";

let channel_token = document.querySelector("#channel_token").content;
let socket = new Socket("/socket", {params: {token: channel_token}});

// When you connect, you'll often need to authenticate the client.
// For example, imagine you have an authentication plug, `MyAuth`,
// which authenticates the session and assigns a `:current_user`.
// If the current user exists you can assign the user's token in
// the connection for use in the layout.
//
// In your "lib/web/router.ex":
//
//     pipeline :browser do
//       ...
//       plug MyAuth
//       plug :put_user_token
//     end
//
//     defp put_user_token(conn, _) do
//       if current_user = conn.assigns[:current_user] do
//         token = Phoenix.Token.sign(conn, "user socket", current_user.id)
//         assign(conn, :user_token, token)
//       else
//         conn
//       end
//     end
//
// Now you need to pass this token to JavaScript. You can do so
// inside a script tag in "lib/web/templates/layout/app.html.eex":
//
//     <script>window.userToken = "<%= assigns[:user_token] %>";</script>
//
// You will need to verify the user token in the "connect/2" function
// in "lib/web/channels/user_socket.ex":
//
//     def connect(%{"token" => token}, socket) do
//       # max_age: 1209600 is equivalent to two weeks in seconds
//       case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do
//         {:ok, user_id} ->
//           {:ok, assign(socket, :user, user_id)}
//         {:error, reason} ->
//           :error
//       end
//     end
//
// Finally, pass the token on connect as below. Or remove it
// from connect if you don't care about authentication.

socket.connect();

// Now that you are connected, you can join channels with a topic:

export default socket;


code:
"use strict";

import asyncButton from "./async-button-view";

export default function(room) {
    return {
        name: "upload",
        template: "#upload-template",
        props: ["role", "index", "id"],
        data() {
            return room.fileList.searchUploads(this.id, (upload, i) => {
                return upload;
            });
        },
        computed: {
            progress() {
                return Math.round((this.uploaded / this.file.size) * 100);
            },
            domId() {
                return "upload-" + this.id;
            },
            isOdd() {
                return this.index % 2;
            },
            isEven() {
                return (this.index + 1) % 2;
            }
        },
        components: {
            asyncButton: asyncButton
        }
    };
};


code:
"use strict";

export default class Upload {
    constructor(id, file) {
        this.id = id;
        this.file = file;
        this.uploaded = 0;
    }
};