diff options
Diffstat (limited to 'modules/organize/views')
-rw-r--r-- | modules/organize/views/organize_dialog.html.php | 149 | ||||
-rw-r--r-- | modules/organize/views/organize_frame.html.php | 526 |
2 files changed, 540 insertions, 135 deletions
diff --git a/modules/organize/views/organize_dialog.html.php b/modules/organize/views/organize_dialog.html.php index 3ea1143d..a386fa77 100644 --- a/modules/organize/views/organize_dialog.html.php +++ b/modules/organize/views/organize_dialog.html.php @@ -1,144 +1,23 @@ <?php defined("SYSPATH") or die("No direct script access.") ?> -<script type="text/javascript" src="<?= url::file("lib/swfobject.js") ?>"></script> -<style type="text/css" media="screen"> - .g-organize { - padding: 0; - margins: 0; - } - - object { - display: block; - outline: none; - } - - #g-dialog { - padding: 0; - } -</style> - +<link rel="stylesheet" type="text/css" href="<?= url::file("modules/organize/css/organize_dialog.css") ?>" /> <script type="text/javascript"> - $("#g-dialog").bind("dialogclose", function(event, ui) { - // @todo do a call to organize/closing to end the batch - if ($(this).data("reload.location")) { - window.location = $(this).data("reload.location"); - } else { - window.location.reload(); - } - }); - - function closeOrganizeDialog() { + var ORGANIZE_TITLE = + <?= t("Organize :: %album_title", array("album_title" => "__TITLE__"))->for_js() ?>; + var done_organizing = function(album_id) { $("#g-dialog").dialog("close"); + window.location = '<?= url::site("items/__ID__") ?>'.replace("__ID__", album_id); } - function setLocation(url) { - $("#g-dialog").data("reload.location", url); - } - - function setTitle(title) { - $("#ui-dialog-title-g-dialog").text(<?= t("Organize :: ")->for_js() ?> + title); - } - - function getOrganizeStyles() { - return { - color: colorToHex($("#g-organize").css("color")), - backgroundColor: colorToHex($("#g-organize").css("backgroundColor")), - borderColor: colorToHex($("#g-organize").css("borderLeftColor")), - rollOverColor: colorToHex($("#g-organize-hover").css("backgroundColor")), - selectionColor: colorToHex($("#g-organize-active").css("backgroundColor")) - }; + var set_title = function(title) { + $("#g-dialog").dialog("option", "title", ORGANIZE_TITLE.replace("__TITLE__", title)); } + set_title("<?= $album->title ?>"); - function colorToHex(color) { - // Surprising no one, the color extracted from the css is in a different format - // in IE than it is when extracted from FF or Chrome. FF and Chrome return - // the of "rgb(nn,nn,nn)". Where as IE returns it as #hhhhhh. - - if (color.indexOf("#") === 0) { - return '0x' + color.substring(1); - } else { - var digits = /(.*?)rgb\((\d+), (\d+), (\d+)\)/.exec(color); - - var red = parseInt(digits[2]); - var green = parseInt(digits[3]); - var blue = parseInt(digits[4]); - - var rgb = blue | (green << 8) | (red << 16); - return digits[1] + '0x' + rgb.toString(16); - } + var done_loading = function() { + $("#g-organize-app-loading").hide(); } - - function getTextStrings() { - return { - statusText: <?= t("Drag and drop photos to re-order or move between album")->for_js() ?>, - remoteError: - <?= t("Remote server error, please contact your gallery administrator")->for_js() ?>, - addAlbumError: <?= t("The above highlighted fields are invalid")->for_js() ?>, - errorOccurred: <?= t("Remote error ocurred")->for_js() ?>, - addAlbum: <?= t("Add album")->for_js() ?>, - addImages: <?= t("Add photo")->for_js() ?>, - deleteSelected: <?= t("Delete")->for_js() ?>, - uploadedText: <?= t("Uploaded {0}")->for_js() ?>, - removeFileText: <?= t("Remove")->for_js() ?>, - progressLabel: <?= t("Completed image %1 of %2")->for_js() ?>, - uploadLabel: <?= t("Loaded %1 of %2 bytes")->for_js() ?>, - moveTitle: <?= t("Move images")->for_js() ?>, - deleteTitle: <?= t("Delete image")->for_js() ?>, - uploadTitle: <?= t("Upload image")->for_js() ?>, - cancel: <?= t("Cancel")->for_js() ?>, - close: <?= t("Close")->for_js() ?> - }; - } - - function getGalleryParameters() { - return { - domain: "<?= $domain ?>", - accessKey: "<?= $access_key ?>", - protocol: "<?= request::protocol() ?>", - fileFilter: "<?= $file_filter ?>", - sortOrder: "<?= $sort_order ?>", - sortFields: "<?= $sort_fields ?>", - albumId: "<?= $album->id ?>", - selectedId: "<?= $selected_id ?>", - restUri: "<?= $rest_uri ?>", - controllerUri: "<?= $controller_uri ?>" - }; - }; - - // For version detection, set to minimum required Flash Player version, or 0 (or 0.0.0), - // for no version detection. - var swfVersionStr = "<?= $flash_minimum_version = "10.0.0" ?>"; - - // To use express install, set to playerProductInstall.swf, otherwise the empty string. - var xiSwfUrlStr = ""; - var flashvars = {}; - - var size = $.gallery_get_viewport_size(); - - var params = {}; - params.quality = "high"; - params.bgcolor = "#ffffff"; - params.allowNetworking = "all"; - params.allowscriptaccess = "sameDomain"; - params.allowfullscreen = "true"; - var attributes = {}; - attributes.id = "Gallery3WebClient"; - attributes.name = "Gallery3WebClient"; - attributes.align = "middle"; - swfobject.embedSWF("<?= $swf_uri ?>", - "flashContent", size.width() - 100, size.height() - 135, - swfVersionStr, xiSwfUrlStr, flashvars, params, attributes); </script> -<div id="g-organize" class="g-dialog-panel"> - <!-- The following spans are placeholders so we can load the hover and active styles for the flex component --> - <span id="g-organize-hover" /><span id="g-organize-active" /> - <h1 style="display:none"><?= t("Organize :: %name", array("name" => html::purify($album->title))) ?></h1> - <div id="flashContent"> - <p> - <?= t("Your browser must have Adobe Flash Player version %flash_minimum_version or greater installed to use this feature.", array("flash_minimum_version" => $flash_minimum_version)) ?> - </p> - <a href="http://www.adobe.com/go/getflashplayer"> - <img src="<?= request::protocol() ?>://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" - alt=<?= t("Get Adobe Flash Player")->for_js() ?> /> - </a> - </div> -</div> +<div id="g-organize-app-loading"> </div> +<iframe id="g-organize-frame" src="<?= url::site("organize/frame/{$album->id}") ?>"> +</iframe> + diff --git a/modules/organize/views/organize_frame.html.php b/modules/organize/views/organize_frame.html.php new file mode 100644 index 00000000..650574ab --- /dev/null +++ b/modules/organize/views/organize_frame.html.php @@ -0,0 +1,526 @@ +<?php defined("SYSPATH") or die("No direct script access.") ?> +<link rel="stylesheet" type="text/css" href="<?= url::file("modules/organize/vendor/ext/css/ext-all.css") ?>" /> +<link rel="stylesheet" type="text/css" href="<?= url::file("modules/organize/vendor/ext/css/ux-all.css") ?>" /> +<link rel="stylesheet" type="text/css" href="<?= url::file("modules/organize/css/organize_frame.css") ?>" /> +<style type="text/css"> + .g-organize div.thumb-album div.icon { + background-image: url(<?= url::file("modules/organize/vendor/ext/images/default/tree/folder.gif") ?>); + } +</style> +<script type="text/javascript" src="<?= url::file("modules/organize/vendor/ext/js/ext-organize-bundle.js") ?>"></script> +<script type="text/javascript"> + Ext.BLANK_IMAGE_URL = "<?= url::file("modules/organize/vendor/ext/images/default/s.gif") ?>"; + Ext.Ajax.timeout = 1000000; // something really large + // I18N for dialog boxes. + Ext.Msg.buttonText = { + ok: <?= t("OK")->for_js() ?>, + cancel: <?= t("Cancel")->for_js() ?>, + yes: <?= t("Yes")->for_js() ?>, + no: <?= t("No")->for_js() ?> + }; + + Ext.onReady(function() { + /* + * ******************************************************************************** + * Utility functions for loading data and making changes + * ******************************************************************************** + */ + var start_busy = function(msg) { + thumb_data_view.el.mask(msg, "loading"); + } + + var stop_busy = function() { + thumb_data_view.el.unmask(); + } + + // Notify the parent dialog that the ExtJS app is loaded + if (parent.done_loading) { + parent.done_loading(); + } + + var show_generic_error = function() { + stop_busy(); + Ext.Msg.alert( + <?= t("An error occurred. Consult your system administrator.")->for_js() ?>); + } + + var current_album_id = null; + var current_album_editable = null; + var load_album_data = function(id) { + if (current_album_id) { + // Don't show the loading message on the initial load, it + // feels a little jarring. + start_busy(<?= t("Loading...")->for_js() ?>); + } + Ext.Ajax.request({ + url: '<?= url::site("organize/album_info/__ID__") ?>'.replace("__ID__", id), + success: function(xhr, opts) { + stop_busy(); + var album_info = Ext.util.JSON.decode(xhr.responseText); + var store = new Ext.data.JsonStore({ + autoDestroy: true, + fields: ["id", "thumb_url", "width", "height", "type", "title"], + idProperty: "id", + root: "children", + data: album_info + }); + current_album_id = id; + thumb_data_view.bindStore(store); + sort_column_combobox.setValue(album_info.sort_column); + sort_order_combobox.setValue(album_info.sort_order); + + current_album_editable = album_info.editable; + if (current_album_editable) { + thumb_data_view.dragZone.unlock(); + } else { + thumb_data_view.dragZone.lock(); + } + if (parent.set_title) { + parent.set_title(album_info.title); + } + }, + failure: show_generic_error + }); + }; + + var reload_album_data = function() { + if (current_album_id) { + load_album_data(current_album_id); + } + }; + + var set_album_sort = function(params) { + start_busy(<?= t("Changing sort...")->for_js() ?>); + params["csrf"] = '<?= access::csrf_token() ?>'; + Ext.Ajax.request({ + url: '<?= url::site("organize/set_sort/__ID__") ?>'.replace("__ID__", current_album_id), + method: "post", + success: function() { + stop_busy(); + reload_album_data(); + }, + failure: show_generic_error, + params: params + }); + } + + var delete_selected_items = function() { + var nodes = thumb_data_view.getSelectedNodes(); + item_ids = []; + for (var i = 0; i != nodes.length; i++) { + var node = Ext.fly(nodes[i]); + item_ids.push(node.getAttribute("rel")); + } + start_busy(<?= t("Deleting...")->for_js() ?>); + Ext.Ajax.request({ + url: '<?= url::site("organize/delete") ?>', + method: "post", + success: function() { + stop_busy(); + reload_album_data(); + }, + failure: show_generic_error, + params: { + item_ids: item_ids.join(","), + csrf: '<?= access::csrf_token() ?>' + } + }); + }; + + /* + * ******************************************************************************** + * JsonStore, DataView and Panel for viewing albums + * ******************************************************************************** + */ + var thumb_data_view = new Ext.DataView({ + autoScroll: true, + enableDragDrop: true, + itemSelector: "div.thumb", + plugins: [ + new Ext.DataView.DragSelector({dragSafe: true}) + ], + listeners: { + "dblclick": function(v, index, node, e) { + node = Ext.get(node); + if (node.hasClass("thumb-album")) { + var id = node.getAttribute("rel"); + tree_panel.fireEvent("click", tree_panel.getNodeById(id)) + } + }, + "render": function(v) { + v.dragZone = new Ext.dd.DragZone(v.getEl(), { + ddGroup: "organizeDD", + containerScroll: true, + getDragData: function(e) { + var target = e.getTarget(v.itemSelector, 10); + if (target) { + if (!v.isSelected(target)) { + v.onClick(e); + } + var selected_nodes = v.getSelectedNodes(); + var drag_data = { + nodes: selected_nodes, + repair_xy: Ext.fly(target).getXY() + }; + if (selected_nodes.length == 1) { + drag_data.ddel = target; + } else { + var drag_ghost = document.createElement("div"); + drag_ghost.className = "drag-ghost"; + for (var i = 0; i != selected_nodes.length; i++) { + var inner = document.createElement("div"); + drag_ghost.appendChild(inner); + + var img = Ext.get(selected_nodes[i]).dom.firstChild; + var child = inner.appendChild(img.cloneNode(true)); + Ext.get(child).setWidth(Ext.fly(img).getWidth() / 2); + Ext.get(child).setHeight(Ext.fly(img).getHeight() / 2); + } + // The contents of the ghost float, and the ghost is wide enough for + // 4 images across so make sure that the ghost is tall enough. Thumbnails + // are max 120px high max, and ghost thumbs are half of that, but leave some + // padding because IE is unpredictable. + drag_ghost.style.height = Math.ceil(i/4) * 72 + "px"; + drag_data.ddel = drag_ghost; + } + return drag_data; + } + }, + getRepairXY: function() { + return this.dragData.repair_xy; + } + }); + + v.dropZone = new Ext.dd.DropZone(v.getEl(), { + ddGroup: "organizeDD", + getTargetFromEvent: function(e) { + return e.getTarget("div.thumb", 10); + }, + onNodeOut: function(target, dd, e, data) { + Ext.fly(target).removeClass("active-left"); + Ext.fly(target).removeClass("active-right"); + }, + onNodeOver: function(target, dd, e, data) { + var target_x = Ext.lib.Dom.getX(target); + var target_center = target_x + (target.offsetWidth / 2); + if (Ext.lib.Event.getPageX(e) < target_center) { + Ext.fly(target).addClass("active-left"); + Ext.fly(target).removeClass("active-right"); + this.drop_side = "before"; + } else { + Ext.fly(target).removeClass("active-left"); + Ext.fly(target).addClass("active-right"); + this.drop_side = "after"; + } + return Ext.dd.DropZone.prototype.dropAllowed; + }, + onNodeDrop: function(target, dd, e, data) { + var nodes = data.nodes; + source_ids = []; + for (var i = 0; i != nodes.length; i++) { + source_ids.push(Ext.fly(nodes[i]).getAttribute("rel")); + } + start_busy(<?= t("Rearranging...")->for_js() ?>); + target = Ext.fly(target); + Ext.Ajax.request({ + url: '<?= url::site("organize/rearrange") ?>', + method: "post", + success: function() { + stop_busy(); + reload_album_data(); + }, + failure: show_generic_error, + params: { + source_ids: source_ids.join(","), + target_id: target.getAttribute("rel"), + relative: this.drop_side, // calculated in onNodeOver + csrf: '<?= access::csrf_token() ?>' + } + }); + return true; + } + }); + }, + "selectionchange": function(v, selections) { + delete_button.setDisabled(!selections.length || !current_album_editable); + } + }, + multiSelect: true, + selectedClass: "selected", + tpl: new Ext.XTemplate( + '<tpl for=".">', + '<tpl if="thumb_url">', + '<div class="thumb thumb-{type}" id="thumb-{id}" rel="{id}">', + '<img src="{thumb_url}" width="{width}" height="{height}" title="{title}">', + '<div class="icon"></div>', + '</div>', + '</tpl>', + '<tpl if="!thumb_url">', + '<div class="thumb thumb-missing thumb-{type}" id="thumb-{id}" rel="{id}">', + '<span>' + <?= t("No thumbnail")->for_js() ?> + '</span>', + '<div class="icon"></div>', + '</div>', + '</tpl>', + '</tpl>') + }); + + /* + * ******************************************************************************** + * Toolbar with sort column, sort order, delete and close buttons. + * ******************************************************************************** + */ + + sort_order_data = []; + <? foreach (album::get_sort_order_options() as $key => $value): ?> + sort_order_data.push(["<?= $key ?>", <?= $value->for_js() ?>]); + <? endforeach ?> + var sort_column_combobox = new Ext.form.ComboBox({ + mode: "local", + editable: false, + allowBlank: false, + forceSelection: true, + triggerAction: "all", + flex: 3, + store: new Ext.data.ArrayStore({ + id: 0, + fields: ["key", "value"], + data: sort_order_data + }), + listeners: { + "select": function(combo, record, index) { + set_album_sort({sort_column: record.id}); + } + }, + valueField: "key", + displayField: "value" + }); + + var sort_order_combobox = new Ext.form.ComboBox({ + mode: "local", + editable: false, + allowBlank: false, + forceSelection: true, + triggerAction: "all", + flex: 2, + store: new Ext.data.ArrayStore({ + id: 0, + fields: ["key", "value"], + data: [ + ["ASC", <?= t("Ascending")->for_js() ?>], + ["DESC", <?= t("Descending")->for_js() ?>]] + }), + listeners: { + "select": function(combo, record, index) { + set_album_sort({sort_order: record.id}); + } + }, + valueField: "key", + displayField: "value" + }); + + var delete_button = new Ext.Button({ + flex: 2, + text: <?= t("Delete")->for_js() ?>, + cls: "x-btn-text-icon", + iconCls: "delete", + id: "delete-button", + disabled: true, + listeners: { + "click": function() { + Ext.Msg.show({ + title: <?= t("Are you sure you want to delete the selected items?")->for_js() ?>, + buttons: Ext.Msg.YESNO, + fn: function(buttonId) { + if (buttonId == "yes") { + delete_selected_items(); + } + } + }); + return true; + } + } + }); + + var button_panel = new Ext.Panel({ + layout: "hbox", + region: "south", + height: 24, + layoutConfig: { + align: "stretch" + }, + items: [ + { + xtype: "panel", + layout: "hbox", + width: 300, + items: [ + { + xtype: "label", + cls: "sort", + flex: 2, + text: <?= t("Sort order: ")->for_js() ?> + }, + sort_column_combobox, + sort_order_combobox + ] + }, { + xtype: "spacer", + flex: 10 + }, + delete_button, + { + xtype: "button", + flex: 2, + text: <?= t("Close")->for_js() ?>, + listeners: { + "click": function() { + parent.done_organizing(current_album_id); + } + } + } + ] + }); + + var album_panel = new Ext.Panel({ + layout: "fit", + region: "center", + title: <?= t("Drag and drop photos to re-order or move between albums")->for_js() ?>, + items: [thumb_data_view], + bbar: button_panel + }); + + /* + * ******************************************************************************** + * TreeLoader and TreePanel + * ******************************************************************************** + */ + var tree_loader = new Ext.tree.TreeLoader({ + dataUrl: '<?= url::site("organize/tree/{$album->id}") ?>', + nodeParameter: "root_id", + requestMethod: "post" + }); + + var tree_panel = new Ext.tree.TreePanel({ + useArrows: true, + autoScroll: true, + animate: true, + border: false, + containerScroll: true, + enableDD: true, + dropConfig: { + appendOnly: true, + ddGroup: "organizeDD" + }, + listeners: { + "click": function(node) { + load_album_data(node.id); + if (node.isExpandable() && !node.isExpanded()) { + node.expand(); + } + }, + "afterrender": function(v) { + // Override Ext.tree.TreeDropZone.onNodeOver to change the + // x-tree-drop-ok-append CSS class to be x-dd-drop-ok since + // that connotes "ok" instead of "adding something new" and we're + // moving the item, not adding it. + // + // There's probably a better way of overriding the parent method, but + // my JavaScript-fu is weak. + v.dropZone.super_onNodeOver = v.dropZone.onNodeOver; + v.dropZone.onNodeOver = function(target, dd, e, data) { + var returnCls = this.super_onNodeOver(target, dd, e, data); + if (returnCls == "x-tree-drop-ok-append") { + return "x-dd-drop-ok"; + } + return returnCls; + } + + // Override Ext.tree.TreeDropZone.getDropPoint so that it allows dropping + // on any node. The standard function won't let you drop on leaves, but + // in our model we consider an album without sub-albums a leaf. + v.dropZone.getDropPoint = function(e, n, dd) { + return "append"; + } + + v.dropZone.onNodeDrop = function(target, dd, e, data) { + var nodes = data.nodes; + source_ids = []; + var moving_albums = 0; + for (var i = 0; i != nodes.length; i++) { + var node = Ext.fly(nodes[i]); + source_ids.push(node.getAttribute("rel")); + moving_albums |= node.hasClass("thumb-album"); + } + start_busy(<?= t("Moving...")->for_js() ?>); + Ext.Ajax.request({ + url: '<?= url::site("organize/reparent") ?>', + method: "post", + success: function() { + stop_busy(); + reload_album_data(); + + // If we're moving albums around then we need to refresh the tree when we're done + if (moving_albums) { + target.node.reload(); + + // If the target node contains the selected node, then the selected + // node just got strafed by the target's reload and no longer exists, + // so we can't reload it. + var selected_node = v.getNodeById(current_album_id); + if (selected_node) { + selected_node.reload(); + } + } + }, + failure: show_generic_error, + params: { + source_ids: source_ids.join(","), + target_id: target.node.id, + csrf: '<?= access::csrf_token() ?>' + } + }); + return true; + } + } + }, + loader: tree_loader, + + region: "west", + split: true, + minSize: 200, + maxSize: 350, + width: 200, + + root: { + allowDrop: Boolean(<?= access::can("edit", item::root()) ?>), + nodeType: "async", + text: "<?= item::root()->title ?>", + draggable: false, + id: "<?= item::root()->id ?>", + expanded: true + } + }); + + var first_organize_load = true; + tree_loader.addListener("load", function() { + if (first_organize_load) { + tree_panel.getNodeById(<?= $album->id ?>).select(); + load_album_data(<?= $album->id ?>); + first_organize_load = false; + + // This is a hack that allows us to reload tree nodes asynchronously + // even though they came with preloaded hierarchical data from the + // initial tree load. Without this, any nodes that were preloaded + // initially won't refresh if you call node.reload() on them. + tree_loader.doPreload = function() { return false; } + } + }); + tree_panel.getRootNode().expand(); + + var outer = new Ext.Viewport({ + layout: "border", + cls: "g-organize", + items: [tree_panel, album_panel] + }); + }); +</script> |