summaryrefslogtreecommitdiff
path: root/modules/organize/views
diff options
context:
space:
mode:
Diffstat (limited to 'modules/organize/views')
-rw-r--r--modules/organize/views/organize_dialog.html.php149
-rw-r--r--modules/organize/views/organize_frame.html.php526
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">&nbsp;</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>