Commit 93fc3356 authored by jan.koester's avatar jan.koester
Browse files

import status bar

parent eef66309
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -351,6 +351,7 @@
            </div>
            <div id="import-html-candidates"></div>
        </div>
        <p id="import-html-progress-status" class="set-status" style="display:none"></p>
        <div class="dialog-actions">
            <button id="btn-import-html-back" data-i18n="I18N_BACK" style="display:none">Zur&uuml;ck</button>
            <button id="btn-import-html-next" data-i18n="I18N_NEXT">Weiter</button>
+11 −0
Original line number Diff line number Diff line
@@ -555,6 +555,11 @@ var EditorApi = (function() {
            return request('POST', '/api/document/parse-html', data);
        },

        // Response is either {status: "ok", images_imported, images_failed, ...}
        // (nothing to fetch, or media_import wasn't requested) or
        // {status: "accepted", job_id, images_total, ...} when there were
        // external images to re-host -- see getImportHtmlStatus for polling
        // that job to completion.
        importHtml: function(html, mode, parentUuid, sections, mediaImport, baseUrl, extraCss) {
            var data = { html: html };
            if (mode) data.mode = mode;
@@ -566,6 +571,12 @@ var EditorApi = (function() {
            return request('POST', '/api/document/import-html', data);
        },

        // Polls an import-html media-import job. Response is
        // {status: "running"|"done", images_total, images_imported, images_failed}.
        getImportHtmlStatus: function(jobId) {
            return request('POST', '/api/document/import-html-status', { job_id: jobId });
        },

        exportHtml: function() {
            return request('GET', '/api/document/export-html');
        },
+72 −6
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
    var importHtmlPendingSource = ''; // raw HTML staged in the import dialog, once past step 1
    var importHtmlPendingCandidates = []; // last parse-html result for the block picker
    var importHtmlPendingBaseUrl = ''; // set when importHtmlPendingSource was fetched from a URL, see loadImportHtmlFromUrl
    var importHtmlPollTimer = null; // pending setTimeout for pollImportHtmlJob, cleared on cancel/dialog close
    var saveDialogMode = 'save';
    var projectsCache = [];
    var currentProjectId = '';
@@ -757,12 +758,16 @@
        return { conn_id: connId, album_id: albumId };
    }

    // step is 1, 2, or 'progress' -- the last one is shown while a
    // background image-import job (see pollImportHtmlJob) is still running:
    // just the progress line and the Cancel button, everything else hidden.
    function showImportHtmlStep(step) {
        document.getElementById('import-html-step1').style.display = step === 1 ? '' : 'none';
        document.getElementById('import-html-step2').style.display = step === 2 ? '' : 'none';
        document.getElementById('btn-import-html-back').style.display = step === 2 ? '' : 'none';
        document.getElementById('btn-import-html-next').style.display = step === 1 ? '' : 'none';
        document.getElementById('btn-import-html-confirm').style.display = step === 2 ? '' : 'none';
        document.getElementById('import-html-progress-status').style.display = step === 'progress' ? '' : 'none';
    }

    // Parses html for the block picker; if parsing finds nothing to choose
@@ -804,6 +809,44 @@
        });
    }

    // Polls a media-import job started by handleImportHtml's async path (see
    // EditorApi.getImportHtmlStatus) until it's done, updating the dialog's
    // progress line in place. cssJsParts carries the css/js fetch stats from
    // the initial (already-final) import-html response so the closing alert
    // can report everything together, same as the synchronous path always
    // has.
    function pollImportHtmlJob(jobId, cssJsParts) {
        var progressEl = document.getElementById('import-html-progress-status');
        progressEl.style.display = '';
        EditorApi.getImportHtmlStatus(jobId).then(function(status) {
            progressEl.textContent = I18n.t('I18N_IMPORT_HTML_IMAGES_PROGRESS',
                'Bilder werden importiert: {done} von {total}...')
                .replace('{done}', status.images_imported + status.images_failed)
                .replace('{total}', status.images_total);

            if (status.status !== 'done') {
                importHtmlPollTimer = setTimeout(function() {
                    pollImportHtmlJob(jobId, cssJsParts);
                }, 1000);
                return;
            }

            importHtmlPollTimer = null;
            progressEl.style.display = 'none';
            document.getElementById('import-html-dialog').close();
            var parts = cssJsParts.slice();
            if (status.images_imported) parts.push(status.images_imported + ' Bild(er) in MediaDB importiert');
            if (status.images_failed) parts.push(status.images_failed + ' Bild(er) fehlgeschlagen');
            if (parts.length) alert(parts.join(', ') + '.');
        }).catch(function() {
            // Transient network hiccup polling the status endpoint -- the
            // job itself keeps running server-side regardless, just retry.
            importHtmlPollTimer = setTimeout(function() {
                pollImportHtmlJob(jobId, cssJsParts);
            }, 1000);
        });
    }

    function renderImportHtmlCandidates(nodes) {
        var container = document.getElementById('import-html-candidates');
        container.innerHTML = '';
@@ -1116,17 +1159,32 @@
            }
            var mediaImport = getImportHtmlMediaImportOptions();
            EditorApi.importHtml(html, null, null, sections, mediaImport, importHtmlPendingBaseUrl, getImportHtmlExtraCss()).then(function(resp) {
                document.getElementById('import-html-dialog').close();
                var cssJsParts = [];
                if (resp.css_fetched) cssJsParts.push(resp.css_fetched + ' externes Stylesheet geladen');
                if (resp.css_failed) cssJsParts.push(resp.css_failed + ' externes Stylesheet fehlgeschlagen');
                if (resp.js_fetched) cssJsParts.push(resp.js_fetched + ' externes Skript geladen');
                if (resp.js_failed) cssJsParts.push(resp.js_failed + ' externes Skript fehlgeschlagen');

                // The widget tree itself is already built either way -- only
                // externally-hosted images may still be mid-fetch/upload in
                // the background (see handleImportHtml/ImportImageJob).
                // Refresh and let the user carry on editing immediately;
                // images just keep their original (external, directly
                // renderable) src until the job swaps in the re-hosted one.
                Canvas.clearSelection(); DocumentTree.clearSelection();
                PropertiesPanel.clear();
                refreshDocument();
                var parts = [];

                if (resp.status === 'accepted') {
                    showImportHtmlStep('progress');
                    pollImportHtmlJob(resp.job_id, cssJsParts);
                    return;
                }

                document.getElementById('import-html-dialog').close();
                var parts = cssJsParts.slice();
                if (resp.images_imported) parts.push(resp.images_imported + ' Bild(er) in MediaDB importiert');
                if (resp.images_failed) parts.push(resp.images_failed + ' Bild(er) fehlgeschlagen');
                if (resp.css_fetched) parts.push(resp.css_fetched + ' externes Stylesheet geladen');
                if (resp.css_failed) parts.push(resp.css_failed + ' externes Stylesheet fehlgeschlagen');
                if (resp.js_fetched) parts.push(resp.js_fetched + ' externes Skript geladen');
                if (resp.js_failed) parts.push(resp.js_failed + ' externes Skript fehlgeschlagen');
                if (parts.length) alert(parts.join(', ') + '.');
            }).catch(function(err) {
                alert(I18n.t('I18N_IMPORT_FAILED') + ': ' + (err.error || ''));
@@ -1134,6 +1192,14 @@
        });

        document.getElementById('btn-import-html-cancel').addEventListener('click', function() {
            // The background image-import job (if any) isn't tied to this
            // dialog -- it keeps running server-side and its results land in
            // doc.root whenever it finishes, so leaving it be here is fine;
            // this just stops polling/updating this now-closed dialog.
            if (importHtmlPollTimer) {
                clearTimeout(importHtmlPollTimer);
                importHtmlPollTimer = null;
            }
            document.getElementById('import-html-dialog').close();
        });

+302 −143

File changed.

Preview size limit exceeded, changes collapsed.

+18 −1
Original line number Diff line number Diff line
@@ -285,11 +285,17 @@ namespace webedit {
        // HTML import/export handlers
        void handleParseHtml(libhttppp::HttpRequest &curreq, const std::string &sessionid);
        void handleImportHtml(libhttppp::HttpRequest &curreq, const std::string &sessionid);
        // Polls the background image-import job handleImportHtml starts when
        // the request includes media_import and there are external images to
        // fetch/upload -- see ImportImageJob's own doc comment (webedit_api.cpp)
        // for why that work happens off the request thread instead of
        // synchronously.
        void handleImportHtmlStatus(libhttppp::HttpRequest &curreq, const std::string &sessionid);
        void handleExportHtml(libhttppp::HttpRequest &curreq, const std::string &sessionid);
        void handleImportCss(libhttppp::HttpRequest &curreq, const std::string &sessionid);

        // Helpers
        void sendJson(libhttppp::HttpRequest &curreq, json_object *obj, const std::string &state = "200 OK");
        void sendJson(libhttppp::HttpRequest &curreq, json_object *obj, int status = 200);
        void sendJsonError(libhttppp::HttpRequest &curreq, int status, const std::string &msg);
        void ensureSessionCookie(libhttppp::HttpRequest &curreq, libhttppp::HttpResponse &resp);
        std::string getRequestBody(libhttppp::HttpRequest &curreq);
@@ -391,6 +397,17 @@ namespace webedit {

        std::mutex _docsMutex;
        std::map<std::string, std::unique_ptr<DocumentState>> _documents;

        // Background image-import job tracking (see ImportImageJob in
        // webedit_api.cpp) -- held by shared_ptr, not value, since a
        // std::mutex member makes the struct itself non-movable/non-copyable
        // and the background thread that updates one needs to keep it alive
        // independently of whatever handleImportHtmlStatus poll calls come
        // and go around it. Entries are never erased (same convention as
        // _documents above) -- a finished job is just a few ints, not worth
        // the bookkeeping.
        std::mutex _importJobsMtx;
        std::map<std::string, std::shared_ptr<struct ImportImageJob>> _importJobs;
    };

}
Loading