code:
FL口上作成メモ
以下を参照ください
往来のERBによる口上作成も可能です。エル口上は往来の方式で作成されています。

eraFL用口上XMLテンプレート記述サンプル
https://docs.google.com/spreadsheets/d/1PeRfw8VfhQqUDLYAuE5BY1XNFvcx4Oii2V8OahN6uYI/edit?usp=sharing

使用例:リュミエール口上
https://docs.google.com/spreadsheets/d/1ku4Yt_i-qnrOVyvd3Aa7GKayNJOKctZw7Ex4NtSnfMM/edit?usp=sharing

path: 資料/口上作成メモ.txt


code:
# App artifacts
/_build
/db
/deps
/*.ez

# Generated on crash by the VM
erl_crash.dump

# Generated on crash by NPM
npm-debug.log

# Static artifacts
/assets/node_modules

# Since we are building assets from assets/,
# we ignore priv/static. You may want to comment
# this depending on your deployment strategy.
/priv/static/

# Files matching config/*.secret.exs pattern contain sensitive
# data and you should not commit them into version control.
#
# Alternatively, you may comment the line below and commit the
# secrets files as long as you replace their contents by environment
# variables.
#/config/*.secret.exs
/config/config.exs
/config/dev.exs
/config/prod.exs
/config/prod.secret.exs
/config/test.exs
/uploads

repo_name: bye/ass

path: .gitignore

license: mit


code:
# Qtfile

To start your Phoenix server:

  * Install dependencies with `mix deps.get`
  * Create and migrate your database with `mix ecto.create && mix ecto.migrate`
  * Install Node.js dependencies with `cd assets && npm install`
  * Start Phoenix endpoint with `mix phx.server`

Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.

Ready to run in production? Please [check our deployment guides](http://www.phoenixframework.org/docs/deployment).

## Learn more

  * Official website: http://www.phoenixframework.org/
  * Guides: http://phoenixframework.org/docs/overview
  * Docs: https://hexdocs.pm/phoenix
  * Mailing list: http://groups.google.com/group/phoenix-talk
  * Source: https://github.com/phoenixframework/phoenix


code:
exports.config = {
  // See http://brunch.io/#documentation for docs.
  files: {
    javascripts: {
      // joinTo: "js/app.js"
        joinTo: {
            // "js/uploader.js": "js/uploader.js",
            "js/room-view.js": [
                "js/uploader.js",
                "js/room.js",
                "js/socket.js",
                "js/file-list.js",
                "js/upload.js",
                "js/file.js",
                "js/file-list-view.js",
                "js/upload-view.js",
                "js/file-view.js",
                "js/room-view.js"
            ],
            "js/app.js": /(js\/app\.js|^(?!js))/
        }

      // To use a separate vendor.js bundle, specify two files path
      // http://brunch.io/docs/config#-files-
      // joinTo: {
      //   "js/app.js": /^js/,
      //   "js/vendor.js": /^(?!js)/
      // }
      //
      // To change the order of concatenation of files, explicitly mention here
      // order: {
      //   before: [
      //     "vendor/js/jquery-2.1.1.js",
      //     "vendor/js/bootstrap.min.js"
      //   ]
      // }
    },
    stylesheets: {
        joinTo: "css/app.css"
            /*
            "css/app.css": [
                "css/app.css",
                "css/register.css"
            ]
            */
    },
    templates: {
      joinTo: "js/app.js"
    }
  },

  conventions: {
    // This option sets where we should place non-css and non-js assets in.
    // By default, we set this to "/assets/static". Files in this directory
    // will be copied to `paths.public`, which is "priv/static" by default.
    assets: /^(static)/
  },

  // Phoenix paths configuration
  paths: {
    // Dependencies and current project directories to watch
    watched: ["static", "css", "js", "vendor"],
    // Where to compile files to
    public: "../priv/static"
  },

    // Configure your plugins
  plugins: {
  },

  modules: {
    autoRequire: {
        "js/app.js": ["js/app"]
    }
  },

  npm: {
    enabled: true,
    whitelist: ["phoenix", "phoenix_html", "vue", "fine-uploader"]
  }
};


code:
/* This file is for your main application css. */
:root {
    --base-text: #C7C7C7;
    --blue-text: #3C8FFF;
    --pink-text: #F557FF;
    --green-text: #57FF53;
    --red-text: #FF0000;
    --qtstream: #f1d98d;
    --dark-background: #202127;
    --lighter-dark-background: #32353e;
    --dark-background2:#1A1B20;
    --l-bg:#54555B;
    --lighter-dark-background2: #292b32;
}

@open-sans {
    font-family: "Open Sans";
    src: url("/font/OpenSans-Regular.ttf")
}

* {
    padding: 0px;
    margin: 0px;
}

a {
    /* color: #4458ED; */
    color: #3961F8;
}

a:visited {
    color: #9660FA;
}

html {
    height: 100%;
}

body {
    font-family: Open Sans;
    font-size: 16px;
    color: white;
    height: 100%;
    overflow: hidden;
    background: var(--lighter-dark-background2);
}

.container {
    height: 100%;
}

main {
    height: 100%;
}

.body-smaller {
    font-size: 14px;
}

.content-container {
    text-align: center;
    padding: 20px;
}

.nav-container {
    display: flex;
    width: 100%;
    border-color: var(--lighter-dark-background);
    border-bottom: 2px solid #555555;
    background-color: var(--lighter-dark-background);
}

.navigation {
    flex: 1;
    width: 100%;
    position: relative;
    font-size: 25px;
    line-height: 100px;
}

.header-icon {
    text-align: center;
    padding: 15px;
    /* background-color: #2d2d2d; */
    background-color: var(--lighter-dark-background);
    /* width: 100%; */
}

#header-image {
    max-width: 40px;
}

.qtstream {
    padding-left: 10px;
    color: var(--qtstream);
    font-weight: bold;
}

.nav-text {
    color: var(--base-text);
    font-weight: normal;
}

.user-profile {
    text-align: center;
    width: 100%;
}

.huge-text {
    font-size: 24px;
    text-align: center;
    padding: 20px;
}

/*
.huge-text a:link {
    color: var(--blue-text);
}

.stream-url-container {
    text-align: center;
    width: 100%;
}

.stream-url-box {
    display: inline-block;
    margin: auto;
    background-color: #2d2d2d;
    padding: 20px;
    border-radius: 5px;
    border: 2px solid #555555;
}

.stream-url {
    color: var(--pink-text);
    font-weight: bold;
}

.live-url {
    color: var(--red-text);
}

.key {
    color: var(--blue-text);
}
*/

hr {
    border-top: 1px solid #626262;
    margin-top: 10px;
    margin-bottom: 5px;
}

button, .delete.button {
    color: red;
    font-size: 14px;
    display: inline-block;
    background-color: #3c3c3c;
    border: 2px solid #5e5e5e;
    border-radius: 2px;
    padding: 5px;
    margin-top: 10px;
    margin-right: 10px;
    width: -moz-fit-content;
    width: -webkit-fit-content;
}

#file-dropzone {
    width: 100%;
    background-color: #2d2d2d;
    border-top: 2px solid #555555;
    margin-top: 10px;
    height: 100%;
    display: inline;
}

#file-list-container {
    display: block;
    max-height: 100%;
    overflow:auto;
}

#file-list {
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;
}

.file-container {
    width: 100%;
    padding-top: 3px;
    padding-bottom: 4px;
    background-color: var(--lighter-dark-background);
    display: inline-block;
}

.file-odd {
    background-color: var(--dark-background);
}

.file-link {
    margin-left: 7px;
    text-decoration: none;
    vertical-align: middle;
    height: 100%;
}

.file-uploader {
    background: #232126;
    border-style: solid;
    border-color: #58525b;
    border-width: 1px;
    border-radius: 2px;
    padding-left: 2px;
    padding-right: 2px;
    font-size: 11px;
    top: 50%;
    margin-left:5px;
    vertical-align: middle;
}

.mod {

}

.v-cloak {
    visibility: hidden;
    display: inherit;
}

.hidden {
    visibility: hidden;
}

#room {
    display: inherit;
}

.form-group {
    display: inline;
}

#dim-screen {
    position: fixed;
    padding: 0;
    margin: 0;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(255, 255, 255, 0.5);
    display: flex;
}

#loading-message {
    margin: auto;
    font-size: 36px;
    text-align: center;
}

.register-form {
    background: var(--lighter-dark-background);
    margin-left: 10%;
    margin-right: 10%;
    margin-top: 5%;
    padding-bottom: 15px;
    padding-top: 8px;
    border: 1px solid rgb(20,20,20);
    border-radius: 5px;
}

.register-field input, .login-field input, span.info {
    position: relative;
    padding-top: 15px;
    padding-left: 10px;
}

.login-field input {
    margin-left: -8px;
}

.register-field input, .login-field input {
    background: var(--lighter-dark-background);
    border-style: none;
    border-bottom-style: solid;
    border-color: var(--lighter-dark-background);
    transition: ease-in-out 0.2s;
    width: 40%;
    max-width: 300px;
    color: rgb(140,143,148);
    font-size: 18px;
}

.register-field input:focus, .login-field input:focus {
    border-color: #4e588d;
    background: #373743;
    color: #c7c9db;
    transition: ease-in-out 0.2s;
}

.form-group button {
    margin-left: 10%;
}

.register-form h1 {
    padding-left: 10px;
}

button, .delete.button, .button {
    position: relative;
    background: #202531;
    border-radius: 5px;
    border: 1px solid #0e1320;
    display: inline-block;
    cursor: pointer;
    color: #e8e8e8;
    font-family: Arial;
    font-size: 15px;
    font-weight: bold;
    padding: 11px 23px;
    text-decoration: none;
    transition: ease-in-out 0.1s;
}
button:hover {
    background-color: #222834;
    transition: ease-in-out 0.1s;
}

.delete.button {
    padding: 0px;
}

.file-container.file-even {
    background: var(--lighter-dark-background);
}

.file-container.file-odd {
    background: #202227
}

.file-expiration {
    font-size: 10px;
}

.nav-text {
    text-decoration: none
}

.register-form h1 {
    padding-left: 10px;
}

.header-icon, .red {
    background: var(--lighter-dark-background);
}


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".
require("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";

const file = require("./file-view");
const upload = require("./upload-view");

module.exports = (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.search(this.filter) >= 0;
                });
            }
        }
    };
};


code:
"use strict";

const Upload  = require("./upload");
const File  = require ("./file");

class FileList {
    constructor() {
        this.uploads = [];
        this.files = [];
    }

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

    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);
    }

    getDeletedFiles() {
        const result = [];
        for (let file of this.files) {
            if (file.markedForDeletion) {
                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);
    }
}

module.exports = FileList;


code:
"use strict";

module.exports = (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"],
        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 % 2;
            },
            isEven() {
                return (this.index + 1) % 2;
            },
            formattedExpirationDate() {
                return (new Date(this.expiration_date)).toLocaleString();
            }
        }
    };
};


code:
"use strict";

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

    constructor() {
        this.markedForDeletion = false;
    }
}

module.exports = File;


code:
"use strict";

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

// A room with a vue
module.exports = new Vue({
    name: "room",
    data: room,
    el: "#room",
    components: {
        fileList: fileListView(room)
    },
    methods: {
        async deleteFiles() {
            try {
                const files = room.fileList.getDeletedFiles();
                const results = await room.push("delete", {files: files})
                // todo: handle result
                console.log("files deleted: " + results.results)
            } catch (e) {
                console.log("failed to delete files: " + e)
            }
        }
    },
    computed: {
        mod() {
            return this.role == "mod" || this.role == "admin";
        }
    }
});


code:
//import "js/uploader.js";
const FileList = require("./file-list");
const qq = require("fine-uploader/lib/core");
const dnd = require("fine-uploader/lib/dnd");

class Room {
    constructor(socket) {
        this.room_id = window.config.room_id;
        this.fileList = new FileList();
        const self = this;
        Room.createChannel(socket, this.room_id, this).then(channel => {
            self.channel = channel;
        });
        this.uploader = Room.createUploader(this.fileList, this.room_id);
        this.dnD = Room.createDnD(this.uploader);
        this.role = "user";
        this.filter = "";
    }

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

    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.join()
                .receive("ok", resp => {
                    resolve(channel);
                })
                .receive("error", reject)
            ;
        });
    }

    static createUploader(fileList, room_id) {
        const uploader = new qq.FineUploaderBasic({
            request: {
                endpoint: "/api/upload",
                inputName: "file"
            },

            retry: {
                enableAuto: true
            },

            button: document.getElementById("upload-button"),

            callbacks: {
                onSubmitted: function(id, name) {
                    fileList.addUpload(id, name);
                    return true;
                },

                onProgress: function(id, name, uploaded, total) {
                    fileList.progressUpload(id, uploaded, total);
                },

                onComplete: function(id, name, response, xhr) {
                    fileList.completeUpload(id);
                },

                onSubmit: function(id, name) {
                    this.setParams({
                        "room_id": room_id,
                        "mime_type": uploader.getFile(id).type
                    });
                }
            },

            maxConnections: 1
        });

        return uploader;
    }

    static createDnD(uploader) {
        const dragAndDrop = new dnd.DragAndDrop({
            dropZoneElements: [document.getElementById("file-dropzone")],

            callbacks: {
                processingDroppedFiles: function() {
                    //TODO: display some sort of a "processing" or spinner graphic
                },
                processingDroppedFilesComplete: function(files, dropTarget) {
                    //TODO: hide spinner/processing graphic

                    uploader.addFiles(files); //this submits the dropped files to Fine Uploader
                }
            }
        });

        return dragAndDrop;
    }
}

module.exports = Room;


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":
Socket = require("phoenix").Socket;

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:

module.exports = socket;


code:
"use strict";

module.exports = (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.total) * 100);
            },
            domId() {
                return "upload-" + this.id;
            },
            isOdd() {
                return this.index % 2;
            },
            isEven() {
                return (this.index + 1) % 2;
            }
        }
    };
};


code:
"use strict";

class Upload {
    constructor(id, name) {
        this.id = id;
        this.name = name;
        this.uploaded = 0;
        this.total = 1;
    }
};

module.exports = Upload;