<!DOCTYPE html>
<html>
    <meta charset="utf-8">
    <head>
        <style>
            table
            {
                border-collapse: collapse;
            }
            
            table, th, td
            {
                border: 1px solid #aaa;
                padding: 0.5em;
                margin-top: 0.5em;
                margin-bottom: 0.5em;
            }
            
            .error
            {
                color: red;
            }
            
            .section
            {
                text-align: center;
                font-weight: bold;
            }
        </style>
    </head>
    <body>
        <table>
            <tr>
                <td colspan="2" class="section">
                    <a href="http://opengamestudio.org/lfsa">Local file system access</a>
                </td>
            </tr>
            <tr>
                <td>Path:</td>
                <td id="lfsaPath">Updating...</td>
            </tr>
            <tr>
                <td colspan="2" class="section">
                    Configuration
                    <button type="button" onclick="window.pskovTool.saveCfg()">Save</button>
                </td>
            </tr>
            <tr>
                <td>Input directory:</td>
                <td><input id="input"></input>
            </tr>
            <tr>
                <td>News item template:</td>
                <td><input id="item"></input>
            </tr>
            <tr>
                <td>News preview template:</td>
                <td><input id="preview"></input>
            </tr>
            <tr>
                <td>Index template:</td>
                <td><input id="index"></input>
            </tr>
            <tr>
                <td>Output directory:</td>
                <td><input id="output"></input>
            </tr>
            <tr>
                <td colspan="2" class="section">Log</td>
            </tr>
            <tr>
                <td id="log" colspan="2"></td>
            </tr>
        </table>
        <script type="text/javascript">
            <!-- LFSA.js -->
            // LFSA class to communicate with local-file-system-access instance.
            function LFSA()
            {
                this.host = "http://127.0.0.1";
                this.port = 8000;
            }
            LFSA.prototype.get = function(url, successCallback, failureCallback)
            {
                var req = new XMLHttpRequest();
                req.addEventListener(
                    "load",
                    function()
                    {
                        //console.log("get.successCallback.responseText: '" + this.responseText + "'");
                        successCallback(this.responseText);
                    }
                );
                req.addEventListener(
                    "error",
                    function()
                    {
                        failureCallback();
                    }
                );
                req.addEventListener(
                    "abort",
                    function()
                    {
                        failureCallback();
                    }
                );
            
                const prefix = this.host + ":" + this.port;
                const addr = prefix + url;
                //console.log("GET addr: '" + addr + "'");
                req.open("GET", addr);
                req.send();
            }
            LFSA.prototype.requestPath = function(successCallback, failureCallback)
            {
                this.get("/path", successCallback, failureCallback);
            }
            LFSA.prototype.post = function(url, data, successCallback, failureCallback)
            {
                var req = new XMLHttpRequest();
                req.addEventListener(
                    "load",
                    function()
                    {
                        console.log("POST.successCallback.responseText: '" + this.responseText + "'");
                        successCallback(this.responseText);
                    }
                );
                req.addEventListener(
                    "error",
                    function()
                    {
                        failureCallback();
                    }
                );
                req.addEventListener(
                    "abort",
                    function()
                    {
                        failureCallback();
                    }
                );
            
                const prefix = this.host + ":" + this.port;
                const addr = prefix + url;
                console.log("POST addr: '" + addr + "'");
                req.open("POST", addr);
                req.send(data);
            }
            LFSA.prototype.requestFileList = function(dir, successCallback, failureCallback)
            {
                this.post(
                    "/list",
                    dir,
                    function(response)
                    {
                        var json = JSON.parse(response);
                        successCallback(json);
                    },
                    failureCallback
                );
            }
            LFSA.prototype.requestFile = function(path, successCallback, failureCallback)
            {
                this.post(
                    "/read",
                    path,
                    successCallback,
                    failureCallback
                );
            }
            LFSA.prototype.saveFile = function(path, contents, successCallback, failureCallback)
            {
                var data = {};
                data["path"] = path;
                data["contents"] = btoa(contents);
                var datastr = JSON.stringify(data);
            
                this.post(
                    "/write",
                    datastr,
                    successCallback,
                    failureCallback
                );
            }

            <!-- Reusable -->
            function fileExists(fileName, fileList)
            {
                for (var id in fileList)
                {
                    var file = fileList[id];
                    if (file.path === fileName)
                    {
                        return true;
                    }
                }
                return false;
            }
            // Topic: Create GUID / UUID in JavaScript?
            // Source: https://stackoverflow.com/a/2117523
            function generateUUID()
            {
                return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
                    /[xy]/g,
                    function(c)
                    {
                        var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
                        return v.toString(16);
                    }
                );
            }
            function LOG(message)
            {
                var log = document.getElementById("log");
                var now = new Date();
                log.innerHTML += now.toISOString() + " " + message + "<br>";
            }
            function parseINI(contents)
            {
                var cfg = { };
                var lines = contents.split(/\r\n|\r|\n/);
                for (var id in lines)
                {
                    var line = lines[id];
                    var keyAndValue = line.split(/=/);
                    if (keyAndValue.length == 2)
                    {
                        var key = keyAndValue[0].trim();
                        var value = keyAndValue[1].trim();
                        cfg[key] = value;
                    }
                }
            
                return cfg;
            }
            // ReporterSubscription class.
            
            function ReporterSubscription(id, callback, reporter)
            {
                this.id = id;
                this.callback = callback;
                this.reporter = reporter;
            }
            
            // Reporter class.
            
            function Reporter(name)
            {
                this.name =
                    (typeof name !== "undefined") ? 
                    name :
                    "";
                this.subscriptions = [];
            }
            
            Reporter.prototype.report = function()
            {
                for (var id in this.subscriptions)
                {
                    var subscription = this.subscriptions[id];
                    subscription.callback();
                }
            }
            
            Reporter.prototype.subscribe = function(callback)
            {
                var id = generateUUID();
                var subscription = new ReporterSubscription(id, callback, this);
                this.subscriptions.push(subscription);
                return subscription;
            }

            <!-- Tool.js -->
            // Tool class.
            function Tool()
            {
                this.lfsa = new LFSA();
                this.cfgExists = false;
                this.cfgExistsChanged = new Reporter();
                this.cfg = {};
                this.cfgChanged = new Reporter();
                this.cfgSaved = new Reporter();
            }
            // Tool's startup sequence.
            Tool.prototype.run = function()
            {
                var self = this;
                this.refreshLFSAPath();
                this.checkCfgExistence();
                this.cfgExistsChanged.subscribe(function(){
                    LOG("cfgExistsChanged. cfgExists: '" + self.cfgExists + "'");
                });
                this.cfgExistsChanged.subscribe(function(){
                    self.readCfg();
                });
                this.cfgChanged.subscribe(function(){
                    LOG("cfgChanged. cfg: '" + self.cfg + "'");
                });
                this.cfgChanged.subscribe(function(){
                    self.displayCfg();
                });
                this.cfgSaved.subscribe(function(){
                    self.readCfg();
                });
            }
            Tool.prototype.refreshLFSAPath = function()
            {
                var lfsaPath = document.getElementById("lfsaPath");
            
                var success = function(response)
                {
                    lfsaPath.innerHTML = response;
                }
                var failure = function()
                {
                    lfsaPath.innerHTML = "<span class='error'>Error</span>";
                }
            
                this.lfsa.requestPath(success, failure);
            }
            Tool.prototype.checkCfgExistence = function()
            {
                var self = this;
            
                var success = function(fileList)
                {
                    var cfgExists = fileExists("cfg", fileList);
                    if (!cfgExists)
                    {
                        LOG("ERROR `cfg` file does not exist");
                        return;
                    }
            
                    // Report.
                    self.cfgExists = true;
                    self.cfgExistsChanged.report();
                }
                var failure = function()
                {
                    LOG("ERROR Could not check if `cfg` exists");
                }
            
                this.lfsa.requestFileList("", success, failure);
            }
            Tool.prototype.readCfg = function()
            {
                var self = this;
            
                var success = function(contents)
                {
                    self.cfg = parseINI(contents);
                    self.cfgChanged.report();
                }
                var failure = function()
                {
                    LOG("ERROR Could not read `cfg` file");
                }
            
                this.lfsa.requestFile("cfg", success, failure);
            }
            Tool.prototype.displayCfg = function()
            {
                var input = document.getElementById("input");
                input.value = this.cfg["input"];
                var item = document.getElementById("item");
                item.value = this.cfg["item"];
                var preview = document.getElementById("preview");
                preview.value = this.cfg["preview"];
                var index = document.getElementById("index");
                index.value = this.cfg["index"];
                var output = document.getElementById("output");
                output.value = this.cfg["output"];
            }
            Tool.prototype.saveCfg = function()
            {
                // Compose cfg file to save.
                var cfgFile = "";
                var input = document.getElementById("input");
                cfgFile += "input = " + input.value + "\n";
                var item = document.getElementById("item");
                cfgFile += "item = " + item.value + "\n";
                var preview = document.getElementById("preview");
                cfgFile += "preview = " + preview.value + "\n";
                var index = document.getElementById("index");
                cfgFile += "index = " + index.value + "\n";
                var output = document.getElementById("output");
                cfgFile += "output = " + output.value + "\n";
            
                // Save file.
                var self = this;
            
                var success = function()
                {
                    self.cfgSaved.report();
                }
                var failure = function()
                {
                    LOG("ERROR Could not save `cfg` file");
                }
            
                this.lfsa.saveFile("cfg", cfgFile, success, failure);
            }

            <!-- Startup -->
            window.pskovTool = new Tool();
            window.pskovTool.run()
        </script>
    </body>
</html>