From 160510a298ebb69a733cda6a231d81763e6b9f68 Mon Sep 17 00:00:00 2001 From: Tienson Qin Date: Wed, 8 Apr 2020 10:09:15 +0800 Subject: [PATCH] Image wip --- web/public/static/js/manifest.edn | 2 +- web/src/frontend/blob.cljs | 29 ++++++++ web/src/frontend/components/journal.cljs | 30 +++++++- web/src/frontend/exif.js | 54 ++++++++++++++ web/src/frontend/image.cljs | 92 ++++++++++++++++++++++++ web/src/frontend/util.cljs | 7 ++ 6 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 web/src/frontend/blob.cljs create mode 100644 web/src/frontend/exif.js create mode 100644 web/src/frontend/image.cljs diff --git a/web/public/static/js/manifest.edn b/web/public/static/js/manifest.edn index 8bf7e4e20..d0e144940 100644 --- a/web/public/static/js/manifest.edn +++ b/web/public/static/js/manifest.edn @@ -1 +1 @@ -[{:module-id :main, :name :main, :output-name "main.js", :entries [devtools.preload shadow.cljs.devtools.client.browser cljs.user shadow.cljs.devtools.client.env frontend.core], :depends-on nil, :sources ["goog/base.js" "goog/debug/error.js" "goog/dom/nodetype.js" "goog/asserts/asserts.js" "goog/reflect/reflect.js" "goog/math/long.js" "goog/math/integer.js" "goog/dom/asserts.js" "goog/functions/functions.js" "goog/array/array.js" "goog/dom/htmlelement.js" "goog/dom/tagname.js" "goog/object/object.js" "goog/dom/tags.js" "goog/html/trustedtypes.js" "goog/string/typedstring.js" "goog/string/const.js" "goog/html/safescript.js" "goog/fs/url.js" "goog/i18n/bidi.js" "goog/html/trustedresourceurl.js" "goog/string/internal.js" "goog/html/safeurl.js" "goog/html/safestyle.js" "goog/html/safestylesheet.js" "goog/labs/useragent/util.js" "goog/labs/useragent/browser.js" "goog/html/safehtml.js" "goog/html/uncheckedconversions.js" "goog/dom/safe.js" "goog/string/string.js" "goog/structs/structs.js" "goog/math/math.js" "goog/iter/iter.js" "goog/structs/map.js" "goog/uri/utils.js" "goog/uri/uri.js" "goog/string/stringbuffer.js" "cljs/core.cljs" "devtools/defaults.cljs" "devtools/prefs.cljs" "devtools/context.cljs" "clojure/string.cljs" "cljs/stacktrace.cljc" "devtools/hints.cljs" "goog/labs/useragent/engine.js" "goog/labs/useragent/platform.js" "goog/useragent/useragent.js" "clojure/set.cljs" "clojure/data.cljs" "devtools/version.cljs" "cljs/pprint.cljs" "devtools/util.cljs" "devtools/format.cljs" "devtools/protocols.cljs" "devtools/reporter.cljs" "clojure/walk.cljs" "devtools/munging.cljs" "devtools/formatters/helpers.cljs" "devtools/formatters/state.cljs" "devtools/formatters/templating.cljs" "devtools/formatters/printing.cljs" "devtools/formatters/markup.cljs" "devtools/formatters/budgeting.cljs" "devtools/formatters/core.cljs" "devtools/formatters.cljs" "goog/debug/entrypointregistry.js" "goog/dom/browserfeature.js" "goog/math/coordinate.js" "goog/math/size.js" "goog/dom/dom.js" "goog/async/nexttick.js" "devtools/async.cljs" "devtools/toolbox.cljs" "devtools/core.cljs" "devtools/preload.cljs" "cljs/tools/reader/impl/utils.cljs" "cljs/tools/reader/reader_types.cljs" "cljs/tools/reader/impl/inspect.cljs" "cljs/tools/reader/impl/errors.cljs" "cljs/tools/reader/impl/commons.cljs" "cljs/tools/reader.cljs" "cljs/tools/reader/edn.cljs" "cljs/reader.cljs" "goog/useragent/product.js" "goog/promise/thenable.js" "goog/async/freelist.js" "goog/async/workqueue.js" "goog/async/run.js" "goog/promise/resolver.js" "goog/promise/promise.js" "goog/disposable/idisposable.js" "goog/disposable/disposable.js" "goog/debug/errorcontext.js" "goog/debug/debug.js" "goog/events/browserfeature.js" "goog/events/eventid.js" "goog/events/event.js" "goog/events/eventtype.js" "goog/events/browserevent.js" "goog/events/listenable.js" "goog/events/listener.js" "goog/events/listenermap.js" "goog/events/events.js" "goog/events/eventtarget.js" "goog/timer/timer.js" "goog/json/json.js" "goog/json/hybrid.js" "goog/debug/logrecord.js" "goog/debug/logbuffer.js" "goog/debug/logger.js" "goog/log/log.js" "goog/net/errorcode.js" "goog/net/eventtype.js" "goog/net/httpstatus.js" "goog/net/xhrlike.js" "goog/net/xmlhttpfactory.js" "goog/net/wrapperxmlhttpfactory.js" "goog/net/xmlhttp.js" "goog/net/xhrio.js" "shadow/cljs/devtools/client/env.cljs" "shadow/cljs/devtools/client/console.cljs" "goog/dom/inputtype.js" "goog/window/window.js" "goog/dom/forms.js" "goog/dom/classlist.js" "goog/dom/vendor.js" "goog/math/box.js" "goog/math/irect.js" "goog/math/rect.js" "goog/style/style.js" "goog/style/transition.js" "cljs/core/async/impl/protocols.cljs" "cljs/core/async/impl/buffers.cljs" "cljs/core/async/impl/dispatch.cljs" "cljs/core/async/impl/channels.cljs" "cljs/core/async/impl/timers.cljs" "cljs/core/async/impl/ioc_helpers.cljs" "cljs/core/async.cljs" "shadow/dom.cljs" "goog/result/result_interface.js" "goog/result/dependentresult.js" "goog/result/simpleresult.js" "goog/result/resultutil.js" "goog/labs/net/xhr.js" "shadow/util.cljs" "shadow/object.cljs" "shadow/xhr.cljs" "shadow/animate.cljs" "goog/string/stringformat.js" "shadow/cljs/devtools/client/hud.cljs" "shadow/cljs/devtools/client/browser.cljs" "cljs/spec/gen/alpha.cljs" "cljs/spec/alpha.cljs" "cljs/repl.cljs" "cljs/user.cljs" "shadow/js.js" "node_modules/object-assign/index.js" "node_modules/prop-types/lib/ReactPropTypesSecret.js" "node_modules/prop-types/checkPropTypes.js" "node_modules/react/cjs/react.development.js" "node_modules/react/index.js" "cljsjs/react.cljs" "node_modules/scheduler/cjs/scheduler.development.js" "node_modules/scheduler/index.js" "node_modules/scheduler/cjs/scheduler-tracing.development.js" "node_modules/scheduler/tracing.js" "node_modules/react-dom/cjs/react-dom.development.js" "node_modules/react-dom/index.js" "cljsjs/react/dom.cljs" "sablono/util.cljc" "sablono/normalize.cljc" "sablono/interpreter.cljc" "sablono/core.cljs" "rum/cursor.cljs" "rum/util.cljc" "rum/derived_atom.cljc" "rum/core.cljs" "promesa/protocols.cljc" "promesa/util.cljc" "promesa/exec.cljc" "promesa/impl.cljc" "promesa/core.cljc" "cljs_bean/from/cljs/core.cljs" "cljs_bean/core.cljs" "frontend/util.cljs" "frontend/git.cljs" "frontend/fs.cljs" "frontend/state.cljs" "me/tonsky/persistent_sorted_set/arrays.cljc" "me/tonsky/persistent_sorted_set.cljs" "datascript/db.cljc" "datascript/pull_parser.cljc" "datascript/pull_api.cljc" "datascript/lru.cljc" "datascript/impl/entity.cljc" "datascript/parser.cljc" "datascript/query.cljc" "datascript/core.cljc" "medley/core.cljc" "com/cognitect/transit/util.js" "com/cognitect/transit/delimiters.js" "com/cognitect/transit/caching.js" "com/cognitect/transit/eq.js" "com/cognitect/transit/types.js" "com/cognitect/transit/impl/decoder.js" "com/cognitect/transit/impl/reader.js" "com/cognitect/transit/handlers.js" "com/cognitect/transit/impl/writer.js" "com/cognitect/transit.js" "cognitect/transit.cljs" "datascript/transit.cljs" "shadow$empty.js" "node_modules/constants-browserify/constants.json" "node_modules/mldoc_org/index.js" "frontend/format/protocol.cljs" "frontend/format/org_mode.cljs" "frontend/format/org/block.cljs" "frontend/utf8.cljs" "frontend/db.cljs" "frontend/storage.cljs" "frontend/config.cljs" "meta_merge/core.cljc" "reitit/exception.cljc" "reitit/trie.cljc" "reitit/impl.cljc" "reitit/core.cljc" "reitit/coercion.cljc" "reitit/frontend.cljs" "reitit/frontend/history.cljs" "reitit/frontend/easy.cljs" "goog/crypt/crypt.js" "goog/crypt/base64.js" "goog/events/eventhandler.js" "frontend/handler.cljs" "frontend/page.cljs" "frontend/rum.cljs" "node_modules/react-is/cjs/react-is.development.js" "node_modules/react-is/index.js" "node_modules/prop-types/factoryWithTypeCheckers.js" "node_modules/prop-types/index.js" "node_modules/@babel/runtime/helpers/interopRequireDefault.js" "node_modules/dom-helpers/cjs/hasClass.js" "node_modules/dom-helpers/cjs/addClass.js" "node_modules/dom-helpers/cjs/removeClass.js" "node_modules/react-transition-group/cjs/config.js" "node_modules/react-transition-group/cjs/utils/PropTypes.js" "node_modules/react-transition-group/cjs/TransitionGroupContext.js" "node_modules/react-transition-group/cjs/Transition.js" "node_modules/react-transition-group/cjs/CSSTransition.js" "node_modules/react-transition-group/cjs/utils/ChildMapping.js" "node_modules/react-transition-group/cjs/TransitionGroup.js" "node_modules/react-transition-group/cjs/ReplaceTransition.js" "node_modules/react-transition-group/cjs/SwitchTransition.js" "node_modules/react-transition-group/cjs/index.js" "node_modules/@babel/runtime/helpers/extends.js" "node_modules/@babel/runtime/helpers/objectWithoutPropertiesLoose.js" "node_modules/@babel/runtime/helpers/assertThisInitialized.js" "node_modules/@babel/runtime/helpers/inheritsLoose.js" "node_modules/react-textarea-autosize/dist/react-textarea-autosize.cjs.browser.js" "frontend/mixins.cljs" "frontend/ui.cljs" "node_modules/showdown/dist/showdown.js" "frontend/format/markdown.cljs" "frontend/format.cljs" "frontend/components/repo.cljs" "frontend/components/journal.cljs" "frontend/components/sidebar.cljs" "frontend/components/home.cljs" "frontend/components/auth.cljs" "frontend/components/file.cljs" "frontend/components/agenda.cljs" "frontend/routes.cljs" "frontend/core.cljs" "shadow/module/main/append.js"]}] \ No newline at end of file +[{:module-id :main, :name :main, :output-name "main.js", :entries [devtools.preload shadow.cljs.devtools.client.browser cljs.user shadow.cljs.devtools.client.env frontend.core], :depends-on nil, :sources ["goog/base.js" "goog/debug/error.js" "goog/dom/nodetype.js" "goog/asserts/asserts.js" "goog/reflect/reflect.js" "goog/math/long.js" "goog/math/integer.js" "goog/dom/asserts.js" "goog/functions/functions.js" "goog/array/array.js" "goog/dom/htmlelement.js" "goog/dom/tagname.js" "goog/object/object.js" "goog/dom/tags.js" "goog/html/trustedtypes.js" "goog/string/typedstring.js" "goog/string/const.js" "goog/html/safescript.js" "goog/fs/url.js" "goog/i18n/bidi.js" "goog/html/trustedresourceurl.js" "goog/string/internal.js" "goog/html/safeurl.js" "goog/html/safestyle.js" "goog/html/safestylesheet.js" "goog/labs/useragent/util.js" "goog/labs/useragent/browser.js" "goog/html/safehtml.js" "goog/html/uncheckedconversions.js" "goog/dom/safe.js" "goog/string/string.js" "goog/structs/structs.js" "goog/math/math.js" "goog/iter/iter.js" "goog/structs/map.js" "goog/uri/utils.js" "goog/uri/uri.js" "goog/string/stringbuffer.js" "cljs/core.cljs" "devtools/defaults.cljs" "devtools/prefs.cljs" "devtools/context.cljs" "clojure/string.cljs" "cljs/stacktrace.cljc" "devtools/hints.cljs" "goog/labs/useragent/engine.js" "goog/labs/useragent/platform.js" "goog/useragent/useragent.js" "clojure/set.cljs" "clojure/data.cljs" "devtools/version.cljs" "cljs/pprint.cljs" "devtools/util.cljs" "devtools/format.cljs" "devtools/protocols.cljs" "devtools/reporter.cljs" "clojure/walk.cljs" "devtools/munging.cljs" "devtools/formatters/helpers.cljs" "devtools/formatters/state.cljs" "devtools/formatters/templating.cljs" "devtools/formatters/printing.cljs" "devtools/formatters/markup.cljs" "devtools/formatters/budgeting.cljs" "devtools/formatters/core.cljs" "devtools/formatters.cljs" "goog/debug/entrypointregistry.js" "goog/dom/browserfeature.js" "goog/math/coordinate.js" "goog/math/size.js" "goog/dom/dom.js" "goog/async/nexttick.js" "devtools/async.cljs" "devtools/toolbox.cljs" "devtools/core.cljs" "devtools/preload.cljs" "cljs/tools/reader/impl/utils.cljs" "cljs/tools/reader/reader_types.cljs" "cljs/tools/reader/impl/inspect.cljs" "cljs/tools/reader/impl/errors.cljs" "cljs/tools/reader/impl/commons.cljs" "cljs/tools/reader.cljs" "cljs/tools/reader/edn.cljs" "cljs/reader.cljs" "goog/useragent/product.js" "goog/promise/thenable.js" "goog/async/freelist.js" "goog/async/workqueue.js" "goog/async/run.js" "goog/promise/resolver.js" "goog/promise/promise.js" "goog/disposable/idisposable.js" "goog/disposable/disposable.js" "goog/debug/errorcontext.js" "goog/debug/debug.js" "goog/events/browserfeature.js" "goog/events/eventid.js" "goog/events/event.js" "goog/events/eventtype.js" "goog/events/browserevent.js" "goog/events/listenable.js" "goog/events/listener.js" "goog/events/listenermap.js" "goog/events/events.js" "goog/events/eventtarget.js" "goog/timer/timer.js" "goog/json/json.js" "goog/json/hybrid.js" "goog/debug/logrecord.js" "goog/debug/logbuffer.js" "goog/debug/logger.js" "goog/log/log.js" "goog/net/errorcode.js" "goog/net/eventtype.js" "goog/net/httpstatus.js" "goog/net/xhrlike.js" "goog/net/xmlhttpfactory.js" "goog/net/wrapperxmlhttpfactory.js" "goog/net/xmlhttp.js" "goog/net/xhrio.js" "shadow/cljs/devtools/client/env.cljs" "shadow/cljs/devtools/client/console.cljs" "goog/dom/inputtype.js" "goog/window/window.js" "goog/dom/forms.js" "goog/dom/classlist.js" "goog/dom/vendor.js" "goog/math/box.js" "goog/math/irect.js" "goog/math/rect.js" "goog/style/style.js" "goog/style/transition.js" "cljs/core/async/impl/protocols.cljs" "cljs/core/async/impl/buffers.cljs" "cljs/core/async/impl/dispatch.cljs" "cljs/core/async/impl/channels.cljs" "cljs/core/async/impl/timers.cljs" "cljs/core/async/impl/ioc_helpers.cljs" "cljs/core/async.cljs" "shadow/dom.cljs" "goog/result/result_interface.js" "goog/result/dependentresult.js" "goog/result/simpleresult.js" "goog/result/resultutil.js" "goog/labs/net/xhr.js" "shadow/util.cljs" "shadow/object.cljs" "shadow/xhr.cljs" "shadow/animate.cljs" "goog/string/stringformat.js" "shadow/cljs/devtools/client/hud.cljs" "shadow/cljs/devtools/client/browser.cljs" "cljs/spec/gen/alpha.cljs" "cljs/spec/alpha.cljs" "cljs/repl.cljs" "cljs/user.cljs" "shadow/js.js" "node_modules/object-assign/index.js" "node_modules/prop-types/lib/ReactPropTypesSecret.js" "node_modules/prop-types/checkPropTypes.js" "node_modules/react/cjs/react.development.js" "node_modules/react/index.js" "cljsjs/react.cljs" "node_modules/scheduler/cjs/scheduler.development.js" "node_modules/scheduler/index.js" "node_modules/scheduler/cjs/scheduler-tracing.development.js" "node_modules/scheduler/tracing.js" "node_modules/react-dom/cjs/react-dom.development.js" "node_modules/react-dom/index.js" "cljsjs/react/dom.cljs" "sablono/util.cljc" "sablono/normalize.cljc" "sablono/interpreter.cljc" "sablono/core.cljs" "rum/cursor.cljs" "rum/util.cljc" "rum/derived_atom.cljc" "rum/core.cljs" "promesa/protocols.cljc" "promesa/util.cljc" "promesa/exec.cljc" "promesa/impl.cljc" "promesa/core.cljc" "cljs_bean/from/cljs/core.cljs" "cljs_bean/core.cljs" "frontend/util.cljs" "frontend/git.cljs" "frontend/fs.cljs" "frontend/state.cljs" "me/tonsky/persistent_sorted_set/arrays.cljc" "me/tonsky/persistent_sorted_set.cljs" "datascript/db.cljc" "datascript/pull_parser.cljc" "datascript/pull_api.cljc" "datascript/lru.cljc" "datascript/impl/entity.cljc" "datascript/parser.cljc" "datascript/query.cljc" "datascript/core.cljc" "medley/core.cljc" "com/cognitect/transit/util.js" "com/cognitect/transit/delimiters.js" "com/cognitect/transit/caching.js" "com/cognitect/transit/eq.js" "com/cognitect/transit/types.js" "com/cognitect/transit/impl/decoder.js" "com/cognitect/transit/impl/reader.js" "com/cognitect/transit/handlers.js" "com/cognitect/transit/impl/writer.js" "com/cognitect/transit.js" "cognitect/transit.cljs" "datascript/transit.cljs" "shadow$empty.js" "node_modules/constants-browserify/constants.json" "node_modules/mldoc_org/index.js" "frontend/format/protocol.cljs" "frontend/format/org_mode.cljs" "frontend/format/org/block.cljs" "frontend/utf8.cljs" "frontend/db.cljs" "frontend/storage.cljs" "frontend/config.cljs" "meta_merge/core.cljc" "reitit/exception.cljc" "reitit/trie.cljc" "reitit/impl.cljc" "reitit/core.cljc" "reitit/coercion.cljc" "reitit/frontend.cljs" "reitit/frontend/history.cljs" "reitit/frontend/easy.cljs" "goog/crypt/crypt.js" "goog/crypt/base64.js" "goog/events/eventhandler.js" "frontend/handler.cljs" "frontend/page.cljs" "frontend/rum.cljs" "node_modules/react-is/cjs/react-is.development.js" "node_modules/react-is/index.js" "node_modules/prop-types/factoryWithTypeCheckers.js" "node_modules/prop-types/index.js" "node_modules/@babel/runtime/helpers/interopRequireDefault.js" "node_modules/dom-helpers/cjs/hasClass.js" "node_modules/dom-helpers/cjs/addClass.js" "node_modules/dom-helpers/cjs/removeClass.js" "node_modules/react-transition-group/cjs/config.js" "node_modules/react-transition-group/cjs/utils/PropTypes.js" "node_modules/react-transition-group/cjs/TransitionGroupContext.js" "node_modules/react-transition-group/cjs/Transition.js" "node_modules/react-transition-group/cjs/CSSTransition.js" "node_modules/react-transition-group/cjs/utils/ChildMapping.js" "node_modules/react-transition-group/cjs/TransitionGroup.js" "node_modules/react-transition-group/cjs/ReplaceTransition.js" "node_modules/react-transition-group/cjs/SwitchTransition.js" "node_modules/react-transition-group/cjs/index.js" "node_modules/@babel/runtime/helpers/extends.js" "node_modules/@babel/runtime/helpers/objectWithoutPropertiesLoose.js" "node_modules/@babel/runtime/helpers/assertThisInitialized.js" "node_modules/@babel/runtime/helpers/inheritsLoose.js" "node_modules/react-textarea-autosize/dist/react-textarea-autosize.cjs.browser.js" "frontend/mixins.cljs" "frontend/ui.cljs" "node_modules/showdown/dist/showdown.js" "frontend/format/markdown.cljs" "frontend/format.cljs" "frontend/components/repo.cljs" "frontend/blob.cljs" "frontend/exif.js" "frontend/image.cljs" "frontend/components/journal.cljs" "frontend/components/sidebar.cljs" "frontend/components/home.cljs" "frontend/components/auth.cljs" "frontend/components/file.cljs" "frontend/components/agenda.cljs" "frontend/routes.cljs" "frontend/core.cljs" "shadow/module/main/append.js"]}] \ No newline at end of file diff --git a/web/src/frontend/blob.cljs b/web/src/frontend/blob.cljs new file mode 100644 index 000000000..6232b9120 --- /dev/null +++ b/web/src/frontend/blob.cljs @@ -0,0 +1,29 @@ +(ns frontend.blob) + +(defn- decode + "Decodes the data portion of a data url from base64" + [[media-type data]] + [media-type (js/atob data)]) + +(defn- uint8 + "Converts a base64 decoded data string to a Uint8Array" + [[media-type data]] + (->> (map #(.charCodeAt %1) data) + js/Uint8Array. + (vector media-type))) + +(defn- make-blob + "Creates a JS Blob object from a media type and a Uint8Array" + [[media-type uint8]] + (js/Blob. (array uint8) (js-obj "type" media-type))) + +(defn blob + "Converts a data-url into a JS Blob. This is useful for uploading + image data from JavaScript." + [data-url] + {:pre [(string? data-url)]} + (-> (re-find #"^data:([^;]+);base64,(.*)$" data-url) + rest + decode + uint8 + make-blob)) diff --git a/web/src/frontend/components/journal.cljs b/web/src/frontend/components/journal.cljs index b52c3f591..600018bb4 100644 --- a/web/src/frontend/components/journal.cljs +++ b/web/src/frontend/components/journal.cljs @@ -8,14 +8,15 @@ [frontend.mixins :as mixins] [frontend.db :as db] [frontend.state :as state] - [frontend.format.org-mode :as org])) + [frontend.format.org-mode :as org] + [goog.object :as gobj] + [frontend.image :as image])) (def edit-content (atom "")) (rum/defc editor-box < (mixins/event-mixin (fn [state] (let [heading (first (:rum/args state))] - (prn "heading: " heading) (mixins/hide-when-esc-or-outside state nil @@ -33,7 +34,30 @@ :style {:border "none" :border-radius 0 :background "transparent" - :margin-top 12.5}})]) + :margin-top 12.5}}) + [:input + {:id "files" + :type "file" + :on-change (fn [e] + (let [files (.-files (.-target e))] + (image/upload + files + (fn [file file-form-data file-name file-type] + ;; TODO: set uploading + (.append file-form-data "name" file-name) + (.append file-form-data file-type true) + + ;; (citrus/dispatch! + ;; :image/upload + ;; file-form-data + ;; (fn [url] + ;; (reset! uploading? false) + ;; (swap! form assoc name url) + ;; (if on-uploaded + ;; (on-uploaded form name url)))) + )))) + ;; :hidden true + }]]) (defn split-first [re s] (clojure.string/split s re 2)) diff --git a/web/src/frontend/exif.js b/web/src/frontend/exif.js new file mode 100644 index 000000000..57a133824 --- /dev/null +++ b/web/src/frontend/exif.js @@ -0,0 +1,54 @@ +// copied from https://stackoverflow.com/questions/7584794/accessing-jpeg-exif-rotation-data-in-javascript-on-the-client-side + +function objectURLToBlob(url, callback) { + var http = new XMLHttpRequest(); + http.open("GET", url, true); + http.responseType = "blob"; + http.onload = function(e) { + if (this.status == 200 || this.status === 0) { + callback(this.response); + } + }; + http.send(); +} + +export var getEXIFOrientation = function (img, callback) { + var reader = new FileReader(); + reader.onload = e => { + var view = new DataView(e.target.result) + + if (view.getUint16(0, false) !== 0xFFD8) { + return callback(-2) + } + var length = view.byteLength + var offset = 2 + while (offset < length) { + var marker = view.getUint16(offset, false) + offset += 2 + if (marker === 0xFFE1) { + if (view.getUint32(offset += 2, false) !== 0x45786966) { + return callback(-1) + } + var little = view.getUint16(offset += 6, false) === 0x4949 + offset += view.getUint32(offset + 4, little) + var tags = view.getUint16(offset, little) + offset += 2 + for (var i = 0; i < tags; i++) { + if (view.getUint16(offset + (i * 12), little) === 0x0112) { + var o = view.getUint16(offset + (i * 12) + 8, little); + return callback(o) + } + } + } else if ((marker & 0xFF00) !== 0xFF00) { + break + } else { + offset += view.getUint16(offset, false) + } + } + return callback(-1) + }; + + objectURLToBlob(img.src, function (blob) { + reader.readAsArrayBuffer(blob.slice(0, 65536)); + }); +} diff --git a/web/src/frontend/image.cljs b/web/src/frontend/image.cljs new file mode 100644 index 000000000..795c62d44 --- /dev/null +++ b/web/src/frontend/image.cljs @@ -0,0 +1,92 @@ +(ns frontend.image + (:require [goog.object :as gobj] + [frontend.blob :as blob] + ["/frontend/exif" :as exif] + [frontend.util :as util])) + +(defn reverse? + [exif-orientation] + (contains? #{5 6 7 8} exif-orientation)) + +(defn re-scale + [exif-orientation width height max-width max-height] + (let [[width height] + (if (reverse? exif-orientation) + [height width] + [width height])] + (let [ratio (/ width height) + to-width (if (> width max-width) max-width width) + to-height (if (> height max-height) max-height height) + new-ratio (/ to-width to-height)] + (let [[w h] (cond + (> new-ratio ratio) + [(* ratio to-height) to-height] + + (< new-ratio ratio) + [to-width (/ to-width ratio)] + + :else + [to-width to-height])] + [(int w) (int h)])))) + + +(defn fix-orientation + "Given image and exif orientation, ensure the photo is displayed + rightside up" + [img exif-orientation cb max-width max-height] + (let [off-canvas (js/document.createElement "canvas") + ctx ^js (.getContext off-canvas "2d") + width (gobj/get img "width") + height (gobj/get img "height") + [to-width to-height] (re-scale exif-orientation width height max-width max-height)] + (gobj/set ctx "imageSmoothingEnabled" false) + (set! (.-width off-canvas) to-width) + (set! (.-height off-canvas) to-height) + ;; rotate + (let [[width height] (if (reverse? exif-orientation) + [to-height to-width] + [to-width to-height])] + (case exif-orientation + 2 (.transform ctx -1 0 0 1 width 0) + 3 (.transform ctx -1 0 0 -1 width height) + 4 (.transform ctx 1 0 0 -1 0 height) + 5 (.transform ctx 0 1 1 0 0 0) + 6 (.transform ctx 0 1 -1 0 height 0) + 7 (.transform ctx 0 -1 -1 0 height width) + 8 (.transform ctx 0 -1 1 0 0 width) + (.transform ctx 1 0 0 1 0 0)) + (.drawImage ctx img 0 0 width height)) + (cb off-canvas))) + +(defn get-orientation + [img cb max-width max-height] + (exif/getEXIFOrientation + img + (fn [orientation] + (fix-orientation img orientation cb max-width max-height)))) + +(defn upload + [files file-cb & {:keys [max-width max-height] + :or {max-width 1920 + max-height 1080}}] + (doseq [file (array-seq files)] + (let [file-type (gobj/get file "type") + file-name (str (util/year-month-day-concat) "_" + (gobj/get file "name"))] + (if (= 0 (.indexOf type "image/")) + (let [img (js/Image.)] + (set! (.-onload img) + (fn [] + (get-orientation img + (fn [^js off-canvas] + (let [file-form-data ^js (js/FormData.) + data-url (.toDataURL off-canvas) + blob (blob/blob data-url)] + (.append file-form-data "file" blob) + (file-cb file file-form-data file-name file-type))) + max-width + max-height))) + (set! (.-src img) + (.createObjectURL (or (.-URL js/window) + (.-webkitURL js/window)) + file))))))) diff --git a/web/src/frontend/util.cljs b/web/src/frontend/util.cljs index 75ce71125..b16c5a4f4 100644 --- a/web/src/frontend/util.cljs +++ b/web/src/frontend/util.cljs @@ -134,3 +134,10 @@ :year "numeric" :day "numeric" :weekday "long"}))) + +(defn year-month-day-concat + [] + (let [{:keys [year month day]} (get-date) + month (if (< month 10) (str "0" month) month) + day (if (< day 10) (str "0" day) day)] + (str year "_" month "_" day)))