bundled/include/LibreOfficeKit/LibreOfficeKitEnums.h | 2 kit/ChildSession.cpp | 35 ++++++++++ kit/ChildSession.hpp | 1 loleaflet/css/partsPreviewControl.css | 6 + loleaflet/src/control/Control.PartsPreview.js | 61 +++++++++++++++++++ loleaflet/src/layer/tile/TileLayer.js | 2 wsd/ClientSession.cpp | 22 ++++++ wsd/protocol.txt | 11 +++ 8 files changed, 135 insertions(+), 5 deletions(-)
New commits: commit 1c93fd7ead50ff16994cf90936602891c0bbc8d8 Author: Ashod Nakashian <[email protected]> AuthorDate: Mon Sep 17 07:17:31 2018 -0400 Commit: Andras Timar <[email protected]> CommitDate: Fri Oct 18 22:29:18 2019 +0200 wsd: leaflet: support reordering of slides via drag-and-drop Change-Id: I1b471ba07dd0a1016a0759de729171ae968262cb Reviewed-on: https://gerrit.libreoffice.org/69635 Reviewed-by: Ashod Nakashian <[email protected]> Tested-by: Ashod Nakashian <[email protected]> (cherry picked from commit 021d67f1430ff2a554b65c65d7aee752fed0b77f) Reviewed-on: https://gerrit.libreoffice.org/80578 Reviewed-by: Andras Timar <[email protected]> Tested-by: Andras Timar <[email protected]> diff --git a/bundled/include/LibreOfficeKit/LibreOfficeKitEnums.h b/bundled/include/LibreOfficeKit/LibreOfficeKitEnums.h index 4f9d668c4..a686cdef4 100644 --- a/bundled/include/LibreOfficeKit/LibreOfficeKitEnums.h +++ b/bundled/include/LibreOfficeKit/LibreOfficeKitEnums.h @@ -564,7 +564,7 @@ typedef enum * "type" tells the type of the window the action is associated with * - "dialog" - window is a dialog * - "child" - window is a floating window (combo boxes, etc.) - * - "panel" - window is a docked panel (i.e. in the sidebar) + * - "deck" - window is a docked/floating deck (i.e. the sidebar) * * "action" can take following values: * - "created" - window is created in the backend, client can render it now diff --git a/kit/ChildSession.cpp b/kit/ChildSession.cpp index 5bec45b48..1e4a81953 100644 --- a/kit/ChildSession.cpp +++ b/kit/ChildSession.cpp @@ -227,6 +227,10 @@ bool ChildSession::_handleInput(const char *buffer, int length) { return selectClientPart(buffer, length, tokens); } + else if (tokens[0] == "moveselectedclientparts") + { + return moveSelectedClientParts(buffer, length, tokens); + } else if (tokens[0] == "setpage") { return setPage(buffer, length, tokens); @@ -1823,6 +1827,37 @@ bool ChildSession::selectClientPart(const char* /*buffer*/, int /*length*/, cons return true; } +bool ChildSession::moveSelectedClientParts(const char* /*buffer*/, int /*length*/, const std::vector<std::string>& tokens) +{ + int nPosition; + if (tokens.size() < 2 || + !getTokenInteger(tokens[1], "position", nPosition)) + { + sendTextFrame("error: cmd=moveselectedclientparts kind=invalid"); + return false; + } + + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); + + getLOKitDocument()->setView(_viewId); + + if (getLOKitDocument()->getDocumentType() != LOK_DOCTYPE_TEXT) + { + getLOKitDocument()->moveSelectedParts(nPosition, false); // Move, don't duplicate. + + // Get the status to recreate the previews and correctly order parts. + const std::string status = LOKitHelper::documentStatus(getLOKitDocument()->get()); + if (!status.empty()) + return sendTextFrame("statusupdate: " + status); + } + else + { + LOG_WRN("ChildSession::moveSelectedClientParts[" << getName() << "]: error moving parts on text documents."); + } + + return true; // Non-fatal to fail. +} + bool ChildSession::setPage(const char* /*buffer*/, int /*length*/, const std::vector<std::string>& tokens) { int page; diff --git a/kit/ChildSession.hpp b/kit/ChildSession.hpp index ed272f225..2f33e8c63 100644 --- a/kit/ChildSession.hpp +++ b/kit/ChildSession.hpp @@ -263,6 +263,7 @@ private: bool saveAs(const char* buffer, int length, const std::vector<std::string>& tokens); bool setClientPart(const char* buffer, int length, const std::vector<std::string>& tokens); bool selectClientPart(const char* buffer, int length, const std::vector<std::string>& tokens); + bool moveSelectedClientParts(const char* buffer, int length, const std::vector<std::string>& tokens); bool setPage(const char* buffer, int length, const std::vector<std::string>& tokens); bool sendWindowCommand(const char* buffer, int length, const std::vector<std::string>& tokens); bool signDocumentContent(const char* buffer, int length, const std::vector<std::string>& tokens); diff --git a/loleaflet/css/partsPreviewControl.css b/loleaflet/css/partsPreviewControl.css index 438b137d3..66e47c9bb 100644 --- a/loleaflet/css/partsPreviewControl.css +++ b/loleaflet/css/partsPreviewControl.css @@ -20,7 +20,7 @@ .preview-img { /* In draw docs, the width of previews are small, but we want a min of 180px to align it with document's left edge */ - min-width: 180px; + min-width: 180px; vertical-align: middle; max-width: 184px; cursor: pointer; @@ -30,3 +30,7 @@ .preview-img-selected { border-color: #000000; } + +.preview-img-dropsite { + border-bottom: 2px solid red; +} diff --git a/loleaflet/src/control/Control.PartsPreview.js b/loleaflet/src/control/Control.PartsPreview.js index dc978f39f..78834a6d8 100644 --- a/loleaflet/src/control/Control.PartsPreview.js +++ b/loleaflet/src/control/Control.PartsPreview.js @@ -105,6 +105,7 @@ L.Control.PartsPreview = L.Control.extend({ _createPreview: function (i, hashCode, bottomBound) { var frame = L.DomUtil.create('div', 'preview-frame', this._scrollContainer); + this._addDnDHandlers(frame); L.DomUtil.create('span', 'preview-helper', frame); var imgClassName = 'preview-img'; @@ -259,6 +260,7 @@ L.Control.PartsPreview = L.Control.extend({ for (it = 0; it < parts; it++) { if (this._previewTiles[it].hash !== e.partNames[it]) { this._previewTiles[it].hash = e.partNames[it]; + this._map.getPreview(it, it, 180, 180, {autoUpdate: this.options.autoUpdate}); } } } @@ -326,7 +328,66 @@ L.Control.PartsPreview = L.Control.extend({ } } } + }, + + _addDnDHandlers: function (elem) { + if (elem) { + elem.setAttribute('draggable', true); + elem.addEventListener('dragstart', this._handleDragStart, false); + elem.addEventListener('dragenter', this._handleDragEnter, false) + elem.addEventListener('dragover', this._handleDragOver, false); + elem.addEventListener('dragleave', this._handleDragLeave, false); + elem.addEventListener('drop', this._handleDrop, false); + elem.addEventListener('dragend', this._handleDragEnd, false); + elem.partsPreview = this; + } + }, + + _handleDragStart: function (e) { + // By default we move when dragging, but can + // support duplication with ctrl in the future. + e.dataTransfer.effectAllowed = 'move'; + }, + + _handleDragOver: function (e) { + if (e.preventDefault) { + e.preventDefault(); + } + + // By default we move when dragging, but can + // support duplication with ctrl in the future. + e.dataTransfer.dropEffect = 'move'; + + this.classList.add('preview-img-dropsite'); + return false; + }, + + _handleDragEnter: function () { + }, + + _handleDragLeave: function () { + this.classList.remove('preview-img-dropsite'); + }, + + _handleDrop: function (e) { + if (e.stopPropagation) { + e.stopPropagation(); + } + + var part = $('#slide-sorter .mCSB_container .preview-frame').index(e.target.parentNode); + if (part !== null) { + var partId = parseInt(part); + this.partsPreview._map._socket.sendMessage('moveselectedclientparts position=' + partId); + } + + this.classList.remove('preview-img-dropsite'); + return false; + }, + + _handleDragEnd: function () { + this.classList.remove('preview-img-dropsite'); } + }); L.control.partsPreview = function (options) { diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index 6a94f9495..19cfb391c 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -164,6 +164,7 @@ bool ClientSession::_handleInput(const char *buffer, int length) tokens[0] != "selecttext" && tokens[0] != "setclientpart" && tokens[0] != "selectclientpart" && + tokens[0] != "moveselectedclientparts" && tokens[0] != "setpage" && tokens[0] != "status" && tokens[0] != "statusupdate" && @@ -342,6 +343,23 @@ bool ClientSession::_handleInput(const char *buffer, int length) } } } + else if (tokens[0] == "moveselectedclientparts") + { + if(!_isTextDocument) + { + int nPosition; + if (tokens.size() != 2 || + !getTokenInteger(tokens[1], "position", nPosition)) + { + sendTextFrame("error: cmd=moveselectedclientparts kind=syntax"); + return false; + } + else + { + return forwardToChild(std::string(buffer, length), docBroker); + } + } + } else if (tokens[0] == "clientzoom") { int tilePixelWidth, tilePixelHeight, tileTwipWidth, tileTwipHeight; diff --git a/wsd/protocol.txt b/wsd/protocol.txt index cb5554e4c..865d3fd1b 100644 --- a/wsd/protocol.txt +++ b/wsd/protocol.txt @@ -142,6 +142,10 @@ selectclientpart part=<partNumber> Informs the server that the client changed the selection state of <partNumber>. +moveselectedclientparts position=<positionNumber> + + Moves the selected parts to a new position. + setpage page=<pageNumber> Valid only for text documents. commit 979a824accd4375170dc4b8bf8e5d69009745684 Author: Ashod Nakashian <[email protected]> AuthorDate: Mon Sep 17 06:45:57 2018 -0400 Commit: Andras Timar <[email protected]> CommitDate: Fri Oct 18 22:29:03 2019 +0200 wsd: leaflet: support statusupdate: messages to sync clients This new message is identical to status: except it doesn't imply (re)connection. It's unfortunate that status: is assumed to be sent only when establishing connection and loading a document, so we need a different notification that can be sent at any time, without triggering initalization logic on the client-side. Change-Id: I9c804119aec292b873aeed132cc32f13c030d845 Reviewed-on: https://gerrit.libreoffice.org/69634 Reviewed-by: Ashod Nakashian <[email protected]> Tested-by: Ashod Nakashian <[email protected]> (cherry picked from commit 6dce712ff4680fb7add00ebf6e4b78f61dda3e79) Reviewed-on: https://gerrit.libreoffice.org/80577 Reviewed-by: Andras Timar <[email protected]> Tested-by: Andras Timar <[email protected]> diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js index 11f2995e7..57acac5f6 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -432,7 +432,7 @@ L.TileLayer = L.GridLayer.extend({ else if (textMsg.startsWith('statechanged:')) { this._onStateChangedMsg(textMsg); } - else if (textMsg.startsWith('status:')) { + else if (textMsg.startsWith('status:') || textMsg.startsWith('statusupdate:')) { this._onStatusMsg(textMsg); } else if (textMsg.startsWith('textselection:')) { diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index 4014a3e58..6a94f9495 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -166,6 +166,7 @@ bool ClientSession::_handleInput(const char *buffer, int length) tokens[0] != "selectclientpart" && tokens[0] != "setpage" && tokens[0] != "status" && + tokens[0] != "statusupdate" && tokens[0] != "tile" && tokens[0] != "tilecombine" && tokens[0] != "uno" && @@ -181,6 +182,7 @@ bool ClientSession::_handleInput(const char *buffer, int length) tokens[0] != "removesession" && tokens[0] != "renamefile") { + LOG_ERR("Session [" << getId() << "] got unknown command [" << tokens[0] << "]."); sendTextFrame("error: cmd=" + tokens[0] + " kind=unknown"); return false; } @@ -243,7 +245,7 @@ bool ClientSession::_handleInput(const char *buffer, int length) { return sendFontRendering(buffer, length, tokens, docBroker); } - else if (tokens[0] == "status") + else if (tokens[0] == "status" || tokens[0] == "statusupdate") { assert(firstLine.size() == static_cast<size_t>(length)); return forwardToChild(firstLine, docBroker); diff --git a/wsd/protocol.txt b/wsd/protocol.txt index a4631c912..cb5554e4c 100644 --- a/wsd/protocol.txt +++ b/wsd/protocol.txt @@ -369,11 +369,16 @@ saveas: url=<url> name=<name> <name> is the resulting name (without path) that was created on the wopi host. It can differ from what was requested in case the file already existed. -status: type=<typeName> parts=<numberOfParts> current=<currentPartNumber> width=<width> height=<height> viewid=<viewId> [partNames] +status: type=<typeName> parts=<numberOfParts> current=<currentPartNumber> width=<width> height=<height> viewid=<viewId> hiddenparts=<part1,part2,...> selectedparts=<part1,part2,...> [partNames] <typeName> is 'text, 'spreadsheet', 'presentation', 'drawing' or 'other. Others are numbers. if the document has multiple parts and those have names, part names follow separated by '\n' +statusupdate: type=<typeName> parts=<numberOfParts> current=<currentPartNumber> width=<width> height=<height> viewid=<viewId> hiddenparts=<part1,part2,...> selectedparts=<part1,part2,...> [partNames] + + Same as status: but issued whenever the document parts have updated significantly. + status: implies document loading. statusupdate: is just an update. + styles: {"styleFamily": ["styles in family"], etc. } statechanged: <key>=<value> _______________________________________________ Libreoffice-commits mailing list [email protected] https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits
