diff --git a/CHANGELOG.md b/CHANGELOG.md index d53a831e79105e4a8f39270e7cdc09867bef75aa..5ef6c2a114c1ca5c8dd4440e0e7e122444d6d8e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added (for new features, dependecies etc.) +- added preview for tif images * new function `form_elements.make_alert` which generates a proceed/cancel dialog which intercepts a function call and asks the user for confirmation. diff --git a/build.properties.d/00_default.properties b/build.properties.d/00_default.properties index a64d39fec41232a8fd889e4b61679b0ffb15ec9c..33567a800f7f18449228c21e3a65d723093d7538 100644 --- a/build.properties.d/00_default.properties +++ b/build.properties.d/00_default.properties @@ -45,6 +45,8 @@ BUILD_MODULE_EXT_PREVIEW=ENABLED BUILD_MODULE_EXT_RESOLVE_REFERENCES=ENABLED BUILD_MODULE_EXT_SSS_MARKDOWN=DISABLED BUILD_MODULE_EXT_TRIGGER_CRAWLER_FORM=DISABLED +BUILD_MODULE_EXT_BOTTOM_LINE=DISABLED +BUILD_MODULE_EXT_BOTTOM_LINE_TIFF_PREVIEW=DISABLED ############################################################################## # Navbar properties diff --git a/libs/UTIF-8205c1f.zip b/libs/UTIF-8205c1f.zip new file mode 100644 index 0000000000000000000000000000000000000000..7069cd288d5d629a7fc3a03d8d92415e78aeb728 Binary files /dev/null and b/libs/UTIF-8205c1f.zip differ diff --git a/libs/pako-dummy.zip b/libs/pako-dummy.zip new file mode 100644 index 0000000000000000000000000000000000000000..e493ee9d673c81a523ad8e488c509c392765da52 Binary files /dev/null and b/libs/pako-dummy.zip differ diff --git a/makefile b/makefile index 3bf4e967fd8581eb6678a3d6546c8b83e3d71ad7..41c7cf51acaf47caa38a5306af924633801c130e 100644 --- a/makefile +++ b/makefile @@ -40,7 +40,7 @@ SRC_EXT_DIR = $(abspath src/ext) LIBS_DIR = $(abspath libs) TEST_CORE_DIR = $(abspath test/core/) TEST_EXT_DIR = $(abspath test/ext) -LIBS = fonts css/bootstrap.css js/bootstrap.js js/state-machine.js js/jquery.js js/showdown.js js/dropzone.js css/dropzone.css js/loglevel.js js/leaflet.js css/leaflet.css css/images js/leaflet-latlng-graticule.js js/proj4.js js/proj4leaflet.js js/leaflet-coordinates.js css/leaflet-coordinates.css js/leaflet-graticule.js js/bootstrap-select.js css/bootstrap-select.css js/bootstrap-autocomplete.min.js js/plotly.js +LIBS = fonts css/bootstrap.css js/bootstrap.js js/state-machine.js js/jquery.js js/showdown.js js/dropzone.js css/dropzone.css js/loglevel.js js/leaflet.js css/leaflet.css css/images js/leaflet-latlng-graticule.js js/proj4.js js/proj4leaflet.js js/leaflet-coordinates.js css/leaflet-coordinates.css js/leaflet-graticule.js js/bootstrap-select.js css/bootstrap-select.css js/bootstrap-autocomplete.min.js js/plotly.js js/pako.js js/utif.js TEST_LIBS = $(LIBS) js/qunit.js css/qunit.css $(subst $(TEST_CORE_DIR)/,,$(shell find $(TEST_CORE_DIR)/)) @@ -265,6 +265,12 @@ $(LIBS_DIR)/js/bootstrap-autocomplete.min.js: unzip $(LIBS_DIR)/js $(LIBS_DIR)/js/plotly.js: unzip $(LIBS_DIR)/js ln -s $(LIBS_DIR)/plotly.js-1.52.2/dist/plotly.min.js $@ +$(LIBS_DIR)/js/pako.js: unzip $(LIBS_DIR)/js + ln -s $(LIBS_DIR)/pako-dummy/pako.js $@ + +$(LIBS_DIR)/js/utif.js: unzip $(LIBS_DIR)/js + ln -s $(LIBS_DIR)/UTIF-8205c1f/UTIF.js $@ + $(addprefix $(LIBS_DIR)/, js css): mkdir $@ || true diff --git a/src/core/js/ext_bottom_line.js b/src/core/js/ext_bottom_line.js index 1bcf73dea17889bda9858ed78a2f6848e1dc21ee..4f563f049da55a532c663350cc2b4ca50e09104a 100644 --- a/src/core/js/ext_bottom_line.js +++ b/src/core/js/ext_bottom_line.js @@ -42,8 +42,9 @@ * @requires load_config (function from caosdb.js) * @requires getEntityPath (function from caosdb.js) * @requires connection (module from webcaosdb.js) + * @requires UTIF (from utif.js library) */ -var ext_bottom_line = function($, logger, is_in_view_port, load_config, getEntityPath, connection) { +var ext_bottom_line = function($, logger, is_in_view_port, load_config, getEntityPath, connection, UTIF) { /** * @property {string|function} create - a function with one parameter @@ -103,7 +104,75 @@ var ext_bottom_line = function($, logger, is_in_view_port, load_config, getEntit */ const _create_video_preview = function(entity) { var path = connection.getFileSystemPath() + getEntityPath(entity); - return $(`<div class="caosdb-v-bottom-line-video-preview"><video controls="controls"><source src="${path}"/></video></div>`)[0]; + return $(`<div class="caosdb-v-bottom-line-video-preview"> + <video controls="controls"><source src="${path}"/></video></div>`)[0]; + } + + /** + * Error class which has the special to_html method. + * + * The to_html method creates a html representation of the error which is + * intended for displaying in the bottom_line container. + */ + const BottomLineError = function(arg) { + this._is_bottom_line_error = true; + + if (arg.message) { + // arg is an Error object + this.message = arg.message; + this.stack = arg.stack; + } else { + this.message = arg; + } + + this.to_html = function() { + return $(`<div><p>An error occured while loading this preview.<p>${ + this.message}<div>`)[0]; + } + } + + /** + * Create a preview for tiff files. + * + * Tiff files are decompressed if necessary and converted into png by UTIF library. + * + * @param {HTMLElement} entity + * @return {Promise for HTMLElement} Promise for an IMG element. + */ + const _create_tiff_preview = function(entity) { + const path = connection.getFileSystemPath() + getEntityPath(entity); + const result = $(`<div class="caosdb-v-bottom-line-image-preview"></div>`); + const img = $(`<img src="${path}"/>`)[0]; + result.append(img); + + /** + * Promise which retrieves the tiff file and calls the UTIF library for + * decompression and conversion into png. + */ + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + UTIF._xhrs.push(xhr); + UTIF._imgs.push(img); + xhr.open("GET", path); + xhr.responseType = "arraybuffer"; + xhr.onload = (e) => { + try { + // decompress and convert tiff file + UTIF._imgLoaded(e); + + // return the result if no error occured. + resolve(result); + } catch(err) { + // throw errors from UTIF to the awaiting caller. + reject(new BottomLineError(err)); + } + } + // throw http errors to the awaiting caller. + xhr.onerror = reject; + + // this finally triggers the retrieval + xhr.send(); + }); } /** @@ -119,6 +188,8 @@ var ext_bottom_line = function($, logger, is_in_view_port, load_config, getEntit var fallback_preview = undefined; + const _tiff_preview_enabled = "${BUILD_MODULE_EXT_BOTTOM_LINE_TIFF_PREVIEW}" == "ENABLED"; + /** * Default creators. * @@ -128,7 +199,12 @@ var ext_bottom_line = function($, logger, is_in_view_port, load_config, getEntit id: "_default_creators.pictures", is_applicable: (entity) => _path_has_file_extension( entity, ["jpg", "png", "gif", "svg"]), - create: _create_picture_preview + create: _create_picture_preview, + }, { + id: "_default_creators.tiff_images", + is_applicable: (entity) => _tiff_preview_enabled && _path_has_file_extension( + entity, ["tif", "tiff","dng","cr2","nef"]), + create: _create_tiff_preview, }, { // videos id: "_default_creators.videos", is_applicable: (entity) => _path_has_file_extension( @@ -231,8 +307,10 @@ var ext_bottom_line = function($, logger, is_in_view_port, load_config, getEntit } } catch (err) { logger.error(err); - const err_msg = "An error occured while loading this preview"; - set_preview_container(entity, err_msg); + if (!err._is_bottom_line_error) { + err = new BottomLineError(err); + } + set_preview_container(entity, err.to_html()); } } @@ -460,8 +538,9 @@ var ext_bottom_line = function($, logger, is_in_view_port, load_config, getEntit _css_class_preview_container, _css_class_preview_container_button, _css_class_preview_container_resolvable, + BottomLineError: BottomLineError, } -}($, log.getLogger("ext_bottom_line"), resolve_references.is_in_viewport_vertically, load_config, getEntityPath, connection); +}($, log.getLogger("ext_bottom_line"), resolve_references.is_in_viewport_vertically, load_config, getEntityPath, connection, UTIF); /** @@ -525,7 +604,7 @@ var plotly_preview = function(logger, ext_bottom_line, plotly) { // this will be replaced by require.js in the future. $(document).ready(function() { - if ("${BUILD_MODULE_EXT_BOTTOM_LINE}" === "ENABLED") { + if ("${BUILD_MODULE_EXT_BOTTOM_LINE}" == "ENABLED") { caosdb_modules.register(plotly_preview); caosdb_modules.register(ext_bottom_line); } diff --git a/src/core/xsl/main.xsl b/src/core/xsl/main.xsl index efbf2e6b6e4db0ad7b14cb1c2483157bc79001f1..91f75733d7746c9e99bf5501a3cbb77155cddea3 100644 --- a/src/core/xsl/main.xsl +++ b/src/core/xsl/main.xsl @@ -142,6 +142,16 @@ <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/js/webcaosdb.js')"/> </xsl:attribute> </xsl:element> + <xsl:element name="script"> + <xsl:attribute name="src"> + <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/js/pako.js')"/> + </xsl:attribute> + </xsl:element> + <xsl:element name="script"> + <xsl:attribute name="src"> + <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/js/utif.js')"/> + </xsl:attribute> + </xsl:element> <script> $(document).ready(() => paging.initPaging(window.location.href, <xsl:value-of select="/Response/@count"/> )); </script> diff --git a/test/core/index.html b/test/core/index.html index 50d8cbef8003d6fb9ab383f94405bcfa07270774..c57f6c1f46dd5ff18c6d3508d135c96b10d7b198 100644 --- a/test/core/index.html +++ b/test/core/index.html @@ -37,6 +37,8 @@ <script src="js/bootstrap.js"></script> <script src="js/bootstrap-select.js"></script> <script src="js/bootstrap-autocomplete.min.js"></script> + <script src="js/utif.js"></script> + <script src="js/pako.js"></script> <script src="js/webcaosdb.js"></script> <script src="js/plotly.js"></script> <script> diff --git a/test/core/js/modules/ext_bottom_line.js b/test/core/js/modules/ext_bottom_line.js.js similarity index 90% rename from test/core/js/modules/ext_bottom_line.js rename to test/core/js/modules/ext_bottom_line.js.js index 21c92167271f87554a9af881554d33db686d9194..a26cc2188eacef210d1acc9eb7de59c53f06b3e7 100644 --- a/test/core/js/modules/ext_bottom_line.js +++ b/test/core/js/modules/ext_bottom_line.js.js @@ -67,7 +67,7 @@ var ext_bottom_line_test_suite = function ($, ext_bottom_line, QUnit) { }); QUnit.test("_creators", function (assert) { - assert.equal(ext_bottom_line._creators.length, 7, "seven creators"); + assert.equal(ext_bottom_line._creators.length, 8, "eight creators"); }); QUnit.test("add_preview_container", function(assert) { @@ -105,7 +105,7 @@ var ext_bottom_line_test_suite = function ($, ext_bottom_line, QUnit) { assert.equal(container.text(), "blablabla"); break; case "error": - assert.equal(container.text(), "An error occured while loading this preview"); + assert.equal(container.text(), "An error occured while loading this preview.Test Error"); break; case "load-forever": assert.equal(container.text(), "Please wait..."); @@ -121,4 +121,12 @@ var ext_bottom_line_test_suite = function ($, ext_bottom_line, QUnit) { }); + QUnit.test("tiff converter", async function(assert) { + let entity_xml = `<Response><File path="../pics/saturn.tif"/></Response>`; + const entity = (await transformation.transformEntities(str2xml(entity_xml)))[0]; + const tiff_preview = await ext_bottom_line._creators.filter((c) => c.id == "_default_creators.tiff_images")[0].create(entity); + + assert.equal($(tiff_preview).find("img").attr("src").slice(0,21), "data:image/png;base64", "decoded tiff to png"); + }); + }($, ext_bottom_line, QUnit); diff --git a/test/core/pics/saturn.tif b/test/core/pics/saturn.tif new file mode 100644 index 0000000000000000000000000000000000000000..f226ebab7fca3c9caa306f78d80eaa7b4068f994 Binary files /dev/null and b/test/core/pics/saturn.tif differ