Skip to content

Commit

Permalink
feature: fix the upload, implement scope for upload widget
Browse files Browse the repository at this point in the history
  • Loading branch information
mutantsan committed Sep 12, 2023
1 parent b26cf79 commit e8cb74e
Show file tree
Hide file tree
Showing 15 changed files with 380 additions and 74 deletions.
2 changes: 1 addition & 1 deletion ckanext/tour/assets/js/tour-htmx.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ ckan.module("tour-htmx", function ($) {
return {
initialize: function () {
$.proxyAll(this, /_on/);
console.log('loaded');

document.addEventListener('htmx:beforeRequest', this._onHTMXbeforeRequest);
document.addEventListener('htmx:afterSettle', this._onHTMXafterSettle);
document.addEventListener('htmx:pushedIntoHistory', this._onHTMXpushedIntoHistory);
Expand Down
305 changes: 294 additions & 11 deletions ckanext/tour/assets/js/tour-image-upload.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,300 @@
/**
* Extends core image-upload.js to prevent uploaded URL change.
*
* @param {Event} e
*/
* It's an updated version of core image-upload.js, with a scope support, because
* we need to have multiple uploads on the same page
*/
this.ckan.module('tour-image-upload', function ($) {
return {
/* options object can be extended using data-module-* attributes */
options: {
is_url: false,
is_upload: false,
field_upload: 'image_upload',
field_url: 'image_url',
field_clear: 'clear_upload',
field_name: 'name',
scope: null,
upload_label: '',
previous_upload: false
},

var extendedModule = $.extend({}, ckan.module.registry["image-upload"].prototype);
/* Should be changed to true if user modifies resource's name
*
* @type {Boolean}
*/
_nameIsDirty: false,

extendedModule._fileNameFromUpload = function (url) {
return url;
}
/* Initialises the module setting up elements and event listeners.
*
* Returns nothing.
*/
initialize: function () {
$.proxyAll(this, /_on/);
var options = this.options;

ckan.module("tour-image-upload", function ($, _) {
"use strict";
// firstly setup the fields
var field_upload = 'input[name="' + options.field_upload + '"]';
var field_url = 'input[name="' + options.field_url + '"]';
var field_clear = 'input[name="' + options.field_clear + '"]';
var field_name = 'input[name="' + options.field_name + '"]';

return extendedModule;
this.scope = $(options.scope);

this.input = this.scope.find(field_upload, this.el);
this.field_url = this.scope.find(field_url, this.el).parents('.form-group');
this.field_image = this.input.parents('.form-group');
this.field_url_input = $('input', this.field_url);
this.field_name = this.scope.find(field_name);

// this is the location for the upload/link data/image label
this.label_location = this.scope.find('label[for="field-image-url"]');
// determines if the resource is a data resource
this.is_data_resource = (this.options.field_url === 'url') && (this.options.field_upload === 'upload');
this.previousUpload = this.options.previous_upload;

// Is there a clear checkbox on the form already?
var checkbox = this.scope.find(field_clear, this.el);
if (checkbox.length > 0) {
checkbox.parents('.form-group').remove();
}

// Adds the hidden clear input to the form
this.field_clear = $('<input type="hidden" name="' + options.field_clear + '">')
.appendTo(this.el);

// Button to set the field to be a URL
this.button_url = $('<a href="javascript:;" class="btn btn-default">' +
'<i class="fa fa-globe"></i>' +
this._('Link') + '</a>')
.prop('title', this._('Link to a URL on the internet (you can also link to an API)'))
.on('click', this._onFromWeb)
.insertAfter(this.input);

// Button to attach local file to the form
this.button_upload = $('<a href="javascript:;" class="btn btn-default">' +
'<i class="fa fa-cloud-upload"></i>' +
this._('Upload') + '</a>')
.insertAfter(this.input);

if (this.previousUpload) {
$('<div class="error-inline"><i class="fa fa-warning"></i> ' +
this._('Please select the file to upload again') + '</div>').appendTo(this.field_image);
}

// Button for resetting the form when there is a URL set
var removeText = this._('Remove');
$('<a href="javascript:;" class="btn btn-danger btn-remove-url">'
+ removeText + '</a>')
.prop('title', removeText)
.on('click', this._onRemove)
.insertBefore(this.field_url_input);

// Update the main label (this is displayed when no data/image has been uploaded/linked)
$('label[for="field-image-upload"]').text(options.upload_label || this._('Image'));

// Setup the file input
this.input
.on('mouseover', this._onInputMouseOver)
.on('mouseout', this._onInputMouseOut)
.on('change', this._onInputChange)
.prop('title', this._('Upload a file on your computer'))
.css('width', this.button_upload.outerWidth());

// Fields storage. Used in this.changeState
this.fields = $('<i />')
.add(this.button_upload)
.add(this.button_url)
.add(this.input)
.add(this.field_url)
.add(this.field_image);

// Disables autoName if user modifies name field
this.field_name
.on('change', this._onModifyName);
// Disables autoName if resource name already has value,
// i.e. we on edit page
if (this.field_name.val()) {
this._nameIsDirty = true;
}

if (options.is_url) {
this._showOnlyFieldUrl();

this._updateUrlLabel(this._('URL'));
} else if (options.is_upload) {
this._showOnlyFieldUrl();

this.field_url_input.prop('readonly', true);
this.field_url_input.val(this.field_url_input.val());

this._updateUrlLabel(this._('File'));
} else {
this._showOnlyButtons();
}
},

/* Quick way of getting just the filename from the uri of the resource data
*
* url - The url of the uploaded data file
*
* Returns String.
*/
_fileNameFromUpload: function (url) {
// If it's a local CKAN image return the entire URL.
if (/^\/base\/images/.test(url)) {
return url;
}

// remove fragment (#)
url = url.substring(0, (url.indexOf("#") === -1) ? url.length : url.indexOf("#"));
// remove query string
url = url.substring(0, (url.indexOf("?") === -1) ? url.length : url.indexOf("?"));
// extract the filename
url = url.substring(url.lastIndexOf("/") + 1, url.length);

return url; // filename
},

/* Update the `this.label_location` text
*
* If the upload/link is for a data resource, rather than an image,
* the text for label[for="field-image-url"] will be updated.
*
* label_text - The text for the label of an uploaded/linked resource
*
* Returns nothing.
*/
_updateUrlLabel: function (label_text) {
if (!this.is_data_resource) {
return;
}

this.label_location.text(label_text);
},

/* Event listener for when someone sets the field to URL mode
*
* Returns nothing.
*/
_onFromWeb: function () {
this._showOnlyFieldUrl();

this.field_url_input.focus()
.on('blur', this._onFromWebBlur);

if (this.options.is_upload) {
this.field_clear.val('true');
}

this._updateUrlLabel(this._('URL'));
},

/* Event listener for resetting the field back to the blank state
*
* Returns nothing.
*/
_onRemove: function () {
this._showOnlyButtons();

this.field_url_input.val('');
this.field_url_input.prop('readonly', false);

this.field_clear.val('true');
},

/* Event listener for when someone chooses a file to upload
*
* Returns nothing.
*/
_onInputChange: function () {
var file_name = this.input.val().split(/^C:\\fakepath\\/).pop();

// Internet Explorer 6-11 and Edge 20+
var isIE = !!document.documentMode;
var isEdge = !isIE && !!window.StyleMedia;
// for IE/Edge when 'include filepath option' is enabled
if (isIE || isEdge) {
var fName = file_name.match(/[^\\\/]+$/);
file_name = fName ? fName[0] : file_name;
}

this.field_url_input.val(file_name);
this.field_url_input.prop('readonly', true);

this.field_clear.val('');

this._showOnlyFieldUrl();

this._autoName(file_name);

this._updateUrlLabel(this._('File'));
},

/* Show only the buttons, hiding all others
*
* Returns nothing.
*/
_showOnlyButtons: function () {
this.fields.hide();
this.button_upload
.add(this.field_image)
.add(this.button_url)
.add(this.input)
.show();
},

/* Show only the URL field, hiding all others
*
* Returns nothing.
*/
_showOnlyFieldUrl: function () {
this.fields.hide();
this.field_url.show();
},

/* Event listener for when a user mouseovers the hidden file input
*
* Returns nothing.
*/
_onInputMouseOver: function () {
this.button_upload.addClass('hover');
},

/* Event listener for when a user mouseouts the hidden file input
*
* Returns nothing.
*/
_onInputMouseOut: function () {
this.button_upload.removeClass('hover');
},

/* Event listener for changes in resource's name by direct input from user
*
* Returns nothing
*/
_onModifyName: function () {
this._nameIsDirty = true;
},

/* Event listener for when someone loses focus of URL field
*
* Returns nothing
*/
_onFromWebBlur: function () {
var url = this.field_url_input.val().match(/([^\/]+)\/?$/)
if (url) {
this._autoName(url.pop());
}
},

/* Automatically add file name into field Name
*
* Select by attribute [name] to be on the safe side and allow to change field id
* Returns nothing
*/
_autoName: function (name) {
if (!this._nameIsDirty) {
this.field_name.val(name);
}
}
};
});
1 change: 0 additions & 1 deletion ckanext/tour/assets/js/tour-steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ ckan.module("tour-steps", function ($) {
var steps = $(".tour-steps__steps .tour-accordion")
.not(".draggable-source--is-dragging")
.not(".draggable--original");
console.log(steps.length);

steps.each((idx, step) => this._updateStepIndexes(idx + 1, step));
},
Expand Down
5 changes: 5 additions & 0 deletions ckanext/tour/helpers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import uuid

from ckanext.tour.model import TourStep


Expand All @@ -11,3 +13,6 @@ def tour_get_position_options():
TourStep.Position.left,
)
]

def tour_random_step_id() -> str:
return str(uuid.uuid4())
Loading

0 comments on commit e8cb74e

Please sign in to comment.