UX: Lightbox support for image uploader. ()

This commit is contained in:
Guo Xiang Tan 2019-02-21 10:13:37 +08:00 committed by GitHub
parent 3cb676bf42
commit 58b0e945bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 160 additions and 55 deletions
app
assets
javascripts/discourse
components
templates/components
stylesheets/common/base
controllers
models
serializers
config
lib
spec/requests
test/javascripts

@ -1,9 +1,25 @@
import computed from "ember-addons/ember-computed-decorators"; import {
default as computed,
observes
} from "ember-addons/ember-computed-decorators";
import UploadMixin from "discourse/mixins/upload"; import UploadMixin from "discourse/mixins/upload";
import lightbox from "discourse/lib/lightbox";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default Ember.Component.extend(UploadMixin, { export default Ember.Component.extend(UploadMixin, {
classNames: ["image-uploader"], classNames: ["image-uploader"],
infoHidden: true,
init() {
this._super(...arguments);
this._applyLightbox();
},
willDestroyElement() {
this._super(...arguments);
$("a.lightbox").magnificPopup("close");
},
@computed("imageUrl") @computed("imageUrl")
backgroundStyle(imageUrl) { backgroundStyle(imageUrl) {
@ -20,11 +36,6 @@ export default Ember.Component.extend(UploadMixin, {
return imageUrl.split("/").slice(-1)[0]; return imageUrl.split("/").slice(-1)[0];
}, },
@computed("infoHidden", "imageBaseName")
showInfo(infoHidden, imageBaseName) {
return !infoHidden && imageBaseName;
},
@computed("backgroundStyle") @computed("backgroundStyle")
hasBackgroundStyle(backgroundStyle) { hasBackgroundStyle(backgroundStyle) {
return !Ember.isEmpty(backgroundStyle.string); return !Ember.isEmpty(backgroundStyle.string);
@ -35,16 +46,51 @@ export default Ember.Component.extend(UploadMixin, {
}, },
uploadDone(upload) { uploadDone(upload) {
this.setProperties({ imageUrl: upload.url, imageId: upload.id }); this.setProperties({
imageUrl: upload.url,
imageId: upload.id,
imageFilesize: upload.human_filesize,
imageFilename: upload.original_filename,
imageWidth: upload.width,
imageHeight: upload.height
});
this._applyLightbox();
if (this.onUploadDone) { if (this.onUploadDone) {
this.onUploadDone(upload); this.onUploadDone(upload);
} }
}, },
_openLightbox() {
Ember.run.next(() => this.$("a.lightbox").magnificPopup("open"));
},
_applyLightbox() {
if (this.get("imageUrl")) Ember.run.next(() => lightbox(this.$()));
},
actions: { actions: {
toggleInfo() { toggleLightbox() {
this.toggleProperty("infoHidden"); if (this.get("imageFilename")) {
this._openLightbox();
} else {
ajax(`/uploads/lookup-metadata`, {
type: "POST",
data: { url: this.get("imageUrl") }
})
.then(json => {
this.setProperties({
imageFilename: json.original_filename,
imageFilesize: json.human_filesize,
imageWidth: json.width,
imageHeight: json.height
});
this._openLightbox();
})
.catch(popupAjaxError);
}
}, },
trash() { trash() {

@ -9,18 +9,28 @@
<button {{action "trash"}} class="btn btn-danger pad-left no-text">{{d-icon "far-trash-alt"}}</button> <button {{action "trash"}} class="btn btn-danger pad-left no-text">{{d-icon "far-trash-alt"}}</button>
{{/if}} {{/if}}
{{#if imageBaseName}} {{#if imageUrl}}
{{d-button icon="info-circle" class="btn image-uploader-info-btn no-text" {{d-button icon="discourse-expand"
title="upload_selector.filename" title='expand'
action=(action "toggleInfo")}} class="btn image-uploader-lightbox-btn no-text"
action=(action "toggleLightbox")}}
{{/if}} {{/if}}
<span class="btn {{unless uploading 'hidden'}}">{{i18n 'upload_selector.uploading'}} {{uploadProgress}}%</span> <span class="btn {{unless uploading 'hidden'}}">{{i18n 'upload_selector.uploading'}} {{uploadProgress}}%</span>
</div> </div>
{{#if showInfo}}
<div class="image-uploader-info"> {{#if imageUrl}}
<a href={{imageUrl}} target="_blank">{{imageBaseName}}</a> <a class="lightbox"
</div> href={{imageUrl}}
title={{imageFilename}}
rel="nofollow noopener">
<div class="meta">
<span class="informations">
{{imageWidth}}x{{imageHeight}} {{imageFilesize}}
</span>
</div>
</a>
{{/if}} {{/if}}
</div> </div>

@ -3,26 +3,6 @@
background-size: cover; background-size: cover;
position: relative; position: relative;
.image-uploader-info {
position: absolute;
bottom: 0;
width: 100%;
background: $primary;
text-align: center;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
opacity: 0.6;
a {
color: $secondary;
&:hover {
text-decoration: underline;
}
}
}
.image-upload-controls { .image-upload-controls {
display: flex; display: flex;
@ -30,7 +10,7 @@
margin-right: 5px; margin-right: 5px;
} }
.image-uploader-info-btn { .image-uploader-lightbox-btn {
background: none; background: none;
margin-right: 0; margin-right: 0;
margin-left: auto; margin-left: auto;

@ -86,6 +86,19 @@ class UploadsController < ApplicationController
end end
end end
def metadata
params.require(:url)
upload = Upload.get_from_url(params[:url])
raise Discourse::NotFound unless upload
render json: {
original_filename: upload.original_filename,
width: upload.width,
height: upload.height,
human_filesize: upload.human_filesize
}
end
protected protected
def render_404 def render_404

@ -7,6 +7,8 @@ require_dependency "file_store/local_store"
require_dependency "base62" require_dependency "base62"
class Upload < ActiveRecord::Base class Upload < ActiveRecord::Base
include ActionView::Helpers::NumberHelper
SHA1_LENGTH = 40 SHA1_LENGTH = 40
belongs_to :user belongs_to :user
@ -208,6 +210,10 @@ class Upload < ActiveRecord::Base
upload || Upload.find_by("url LIKE ?", "%#{data[1]}") upload || Upload.find_by("url LIKE ?", "%#{data[1]}")
end end
def human_filesize
number_to_human_size(self.filesize)
end
def self.migrate_to_new_scheme(limit = nil) def self.migrate_to_new_scheme(limit = nil)
problems = [] problems = []

@ -9,5 +9,6 @@ class UploadSerializer < ApplicationSerializer
:thumbnail_height, :thumbnail_height,
:extension, :extension,
:short_url, :short_url,
:retain_hours :retain_hours,
:human_filesize
end end

@ -206,6 +206,7 @@ en:
us_west_2: "US West (Oregon)" us_west_2: "US West (Oregon)"
edit: "edit the title and category of this topic" edit: "edit the title and category of this topic"
expand: "Expand"
not_implemented: "That feature hasn't been implemented yet, sorry!" not_implemented: "That feature hasn't been implemented yet, sorry!"
no_value: "No" no_value: "No"
yes_value: "Yes" yes_value: "Yes"
@ -1569,7 +1570,6 @@ en:
select_file: "Select File" select_file: "Select File"
image_link: "link your image will point to" image_link: "link your image will point to"
default_image_alt_text: image default_image_alt_text: image
filename: "Filename"
search: search:
sort_by: "Sort by" sort_by: "Sort by"

@ -461,6 +461,7 @@ Discourse::Application.routes.draw do
get "stylesheets/:name.css" => "stylesheets#show", constraints: { name: /[-a-z0-9_]+/ } get "stylesheets/:name.css" => "stylesheets#show", constraints: { name: /[-a-z0-9_]+/ }
get "theme-javascripts/:digest.js" => "theme_javascripts#show", constraints: { digest: /\h{40}/ } get "theme-javascripts/:digest.js" => "theme_javascripts#show", constraints: { digest: /\h{40}/ }
post "uploads/lookup-metadata" => "uploads#metadata"
post "uploads" => "uploads#create" post "uploads" => "uploads#create"
post "uploads/lookup-urls" => "uploads#lookup_urls" post "uploads/lookup-urls" => "uploads#lookup_urls"

@ -6,8 +6,6 @@ require_dependency 'pretty_text'
require_dependency 'quote_comparer' require_dependency 'quote_comparer'
class CookedPostProcessor class CookedPostProcessor
include ActionView::Helpers::NumberHelper
INLINE_ONEBOX_LOADING_CSS_CLASS = "inline-onebox-loading" INLINE_ONEBOX_LOADING_CSS_CLASS = "inline-onebox-loading"
INLINE_ONEBOX_CSS_CLASS = "inline-onebox" INLINE_ONEBOX_CSS_CLASS = "inline-onebox"
LOADING_SIZE = 10 LOADING_SIZE = 10
@ -422,7 +420,7 @@ class CookedPostProcessor
filename = get_filename(upload, img["src"]) filename = get_filename(upload, img["src"])
informations = "#{original_width}×#{original_height}" informations = "#{original_width}×#{original_height}"
informations << " #{number_to_human_size(upload.filesize)}" if upload informations << " #{upload.human_filesize}" if upload
a["title"] = CGI.escapeHTML(img["title"] || filename) a["title"] = CGI.escapeHTML(img["title"] || filename)

@ -283,4 +283,51 @@ describe UploadsController do
expect(result[0]["url"]).to eq(upload.url) expect(result[0]["url"]).to eq(upload.url)
end end
end end
describe '#metadata' do
let(:upload) { Fabricate(:upload) }
describe 'when url is missing' do
it 'should return the right response' do
post "/uploads/lookup-metadata.json"
expect(response.status).to eq(403)
end
end
describe 'when not signed in' do
it 'should return the right response' do
post "/uploads/lookup-metadata.json", params: { url: upload.url }
expect(response.status).to eq(403)
end
end
describe 'when signed in' do
before do
sign_in(Fabricate(:user))
end
describe 'when url is invalid' do
it 'should return the right response' do
post "/uploads/lookup-metadata.json", params: { url: 'abc' }
expect(response.status).to eq(404)
end
end
it "should return the right response" do
post "/uploads/lookup-metadata.json", params: { url: upload.url }
expect(response.status).to eq(200)
result = JSON.parse(response.body)
expect(result["original_filename"]).to eq(upload.original_filename)
expect(result["width"]).to eq(upload.width)
expect(result["height"]).to eq(upload.height)
expect(result["human_filesize"]).to eq(upload.human_filesize)
end
end
end
end end

@ -17,18 +17,12 @@ componentTest("with image", {
"it displays the trash icon" "it displays the trash icon"
); );
assert.equal( await click(".image-uploader-lightbox-btn");
this.$(".image-uploader-info").length,
0,
"it does not display the image info"
);
await click(".image-uploader-info-btn");
assert.equal( assert.equal(
this.$(".image-uploader-info").length, $(".mfp-container").length,
1, 1,
"it displays the image info" "it displays the image lightbox"
); );
} }
}); });
@ -50,9 +44,9 @@ componentTest("without image", {
); );
assert.equal( assert.equal(
this.$(".image-uploader-info-btn").length, this.$(".image-uploader-lightbox-btn").length,
0, 0,
"it does not display the image info button toggle" "it does not display the button to open image lightbox"
); );
} }
}); });

@ -536,6 +536,15 @@ export default function() {
}); });
}); });
this.post("/uploads/lookup-metadata", () => {
return response(200, {
imageFilename: "somefile.png",
imageFilesize: "10 KB",
imageWidth: "1",
imageHeight: "1"
});
});
this.get("/inline-onebox", request => { this.get("/inline-onebox", request => {
if ( if (
request.queryParams.urls.includes( request.queryParams.urls.includes(