Skip to content

Commit

Permalink
Add alchemy-picture-thumbnail component
Browse files Browse the repository at this point in the history
  • Loading branch information
tvdeyen committed Jan 18, 2024
1 parent 0511831 commit 3e04357
Show file tree
Hide file tree
Showing 23 changed files with 177 additions and 137 deletions.
1 change: 0 additions & 1 deletion app/assets/images/alchemy/missing-image.svg

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ class window.Alchemy.ImageOverlay extends Alchemy.Dialog
return

init: ->
Alchemy.ImageLoader(@dialog_body[0])
$('.zoomed-picture-background').on "click", (e) =>
e.stopPropagation()
return if e.target.nodeName == 'IMG'
Expand Down
12 changes: 6 additions & 6 deletions app/assets/stylesheets/alchemy/archive.scss
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 1px 1px $default-border-color;

&.loaded {
box-shadow: 0 0 1px 1px $default-border-color;
}

&:hover {
text-decoration: none;
Expand Down Expand Up @@ -73,12 +76,9 @@
img {
max-width: 100%;
max-height: 100%;
background: $thumbnail-background;

&:not([src*="alchemy/missing-image"]) {
background: $thumbnail-background;
}

&[src$=".svg"]:not([src*="alchemy/missing-image"]) {
&[src$=".svg"] {
width: var(--picture-width);
max-height: var(--picture-height);
}
Expand Down
19 changes: 6 additions & 13 deletions app/assets/stylesheets/alchemy/image_library.scss
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ $image-overlay-transition-easing: ease-in;
}

.zoomed-picture-background {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
padding-top: 2 * $default-padding;
Expand All @@ -147,23 +150,13 @@ $image-overlay-transition-easing: ease-in;
cursor: pointer;
transition: padding-right $image-overlay-transition-duration
$image-overlay-transition-easing;

&:before {
content: "";
vertical-align: middle;
display: inline-block;
height: 100%;
margin-left: -4px;
}
color: $light-gray;

img {
display: inline-block;
height: auto;
max-width: 100%;
max-height: 100%;
box-shadow: 0 0 2 * $default-margin $text-color;
background: $thumbnail-background;
vertical-align: middle;
max-width: 90%;
max-height: 90%;
cursor: default;
}
}
Expand Down
27 changes: 27 additions & 0 deletions app/components/alchemy/admin/picture_thumbnail.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module Alchemy
module Admin
class PictureThumbnail < ViewComponent::Base
attr_reader :picture, :url

def initialize(picture, size: :medium)
@picture = picture
@url = picture.thumbnail_url(size: preview_size(size))
end

def call
content_tag("alchemy-picture-thumbnail") do
image_tag(url, alt: picture.name)
end
end

private

def preview_size(size)
Alchemy::Picture::THUMBNAIL_SIZES.fetch(
size,
Alchemy::Picture::THUMBNAIL_SIZES[:medium]
)
end
end
end
end
14 changes: 0 additions & 14 deletions app/helpers/alchemy/admin/pictures_helper.rb

This file was deleted.

3 changes: 1 addition & 2 deletions app/javascript/alchemy_admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import GUI from "alchemy_admin/gui"
import { translate } from "alchemy_admin/i18n"
import Dirty from "alchemy_admin/dirty"
import IngredientAnchorLink from "alchemy_admin/ingredient_anchor_link"
import ImageLoader from "alchemy_admin/image_loader"
import ImageCropper from "alchemy_admin/image_cropper"
import Initializer from "alchemy_admin/initializer"
import pictureSelector from "alchemy_admin/picture_selector"
Expand All @@ -31,6 +30,7 @@ import "alchemy_admin/components/node_select"
import "alchemy_admin/components/uploader"
import "alchemy_admin/components/overlay"
import "alchemy_admin/components/page_select"
import "alchemy_admin/components/picture_thumbnail"
import "alchemy_admin/components/select"
import "alchemy_admin/components/spinner"
import "alchemy_admin/components/tinymce"
Expand Down Expand Up @@ -68,7 +68,6 @@ Object.assign(Alchemy, {
...Dirty,
GUI,
t: translate, // Global utility method for translating a given string
ImageLoader: ImageLoader.init,
ImageCropper,
Initializer,
IngredientAnchorLink,
Expand Down
2 changes: 0 additions & 2 deletions app/javascript/alchemy_admin/components/element_editor.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import TagsAutocomplete from "alchemy_admin/tags_autocomplete"
import ImageLoader from "alchemy_admin/image_loader"
import fileEditors from "alchemy_admin/file_editors"
import pictureEditors from "alchemy_admin/picture_editors"
import IngredientAnchorLink from "alchemy_admin/ingredient_anchor_link"
Expand Down Expand Up @@ -39,7 +38,6 @@ export class ElementEditor extends HTMLElement {
}

// Init GUI elements
ImageLoader.init(this)
fileEditors(
`#${this.id} .ingredient-editor.file, #${this.id} .ingredient-editor.audio, #${this.id} .ingredient-editor.video`
)
Expand Down
118 changes: 118 additions & 0 deletions app/javascript/alchemy_admin/components/picture_thumbnail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import Spinner from "alchemy_admin/spinner"

const MAX_RETRIES = 10
const WAIT_TIME = 1000
const OFFSET_INCREMENT = 100

class PictureThumbnail extends HTMLElement {
#retries = MAX_RETRIES
#retryOffset = 0
#started = false

constructor() {
super()
this.observer = new MutationObserver(this.handleMutation.bind(this))
this.classList.add("thumbnail_background")
this.spinner = new Spinner(this.spinnerSize)
}

connectedCallback() {
if (this.image && !this.image.complete) {
this.start()
}
this.observer.observe(this, {
subtree: true,
childList: true,
attributes: true,
attributeOldValue: true,
attributeFilter: ["src"]
})
}

disconnectedCallback() {
this.observer.disconnect()
this.reset()
}

handleMutation(mutations) {
mutations.forEach((mutation) => {
const hasImage =
mutation.type == "childList" &&
Array.from(mutation.addedNodes).some((node) => node.nodeName === "IMG")
const sourceChanged =
mutation.type == "attributes" &&
mutation.oldValue !== mutation.target.src
if (hasImage || sourceChanged) {
this.start()
}
})
}

handleEvent(event) {
switch (event.type) {
case "load":
this.showImage()
break
case "error":
this.retry()
break
}
}

start() {
if (this.#started) return

this.#started = true
this.image.classList.add("hidden")
this.spinner.spin(this)
this.image.addEventListener("load", this)
this.image.addEventListener("error", this)
}

showImage() {
this.#started = false
this.image.classList.remove("hidden")
this.classList.add("loaded")
this.spinner.stop()
this.reset()
}

retry() {
if (this.#retries > 0) {
this.#retries--
setTimeout(() => {
this.image.src = this.image.src
}, WAIT_TIME + this.#retryOffset)
this.#retryOffset += OFFSET_INCREMENT
} else {
this.showError()
}
}

showError() {
const message = `Could not load "${this.image.src}"`
console.error(message)
this.innerHTML = `<span class="icon error ri-file-damage-line" title="${message}" />`
this.reset()
}

reset() {
this.#started = false
this.#retries = MAX_RETRIES

if (this.image) {
this.image.removeEventListener("load", this)
this.image.removeEventListener("error", this)
}
}

get spinnerSize() {
return this.getAttribute("spinner-size") || "small"
}

get image() {
return this.querySelector("img")
}
}

customElements.define("alchemy-picture-thumbnail", PictureThumbnail)
52 changes: 0 additions & 52 deletions app/javascript/alchemy_admin/image_loader.js

This file was deleted.

3 changes: 0 additions & 3 deletions app/javascript/alchemy_admin/initializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,6 @@ function Initialize() {
$(this.form).submit()
})

// Attaches the image loader on all images
Alchemy.ImageLoader("#main_content")

// Override the filter of keymaster.js so we can blur the fields on esc key.
key.filter = function (event) {
let tagName = (event.target || event.srcElement).tagName
Expand Down
13 changes: 3 additions & 10 deletions app/javascript/alchemy_admin/picture_editors.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import debounce from "alchemy_admin/utils/debounce"
import max from "alchemy_admin/utils/max"
import { get } from "alchemy_admin/utils/ajax"
import ImageLoader from "alchemy_admin/image_loader"

const UPDATE_DELAY = 125
const IMAGE_PLACEHOLDER = '<i class="icon ri-image-line ri-fw"></i>'
Expand All @@ -16,17 +15,13 @@ class PictureEditor {
this.targetSizeField = container.querySelector("[data-target-size]")
this.imageCropperField = container.querySelector("[data-image-cropper]")
this.image = container.querySelector("img")
this.thumbnailBackground = container.querySelector(".thumbnail_background")
this.pictureThumbnail = container.querySelector("alchemy-picture-thumbnail")
this.deleteButton = container.querySelector(".picture_tool.delete")
this.cropLink = container.querySelector(".crop_link")

this.targetSize = this.targetSizeField.dataset.targetSize
this.pictureId = this.pictureIdField.value

if (this.image) {
this.imageLoader = new ImageLoader(this.image)
}

// The mutation observer is observing multiple fields that all get updated
// simultaneously. We only want to update the image once, so we debounce.
this.update = debounce(() => {
Expand Down Expand Up @@ -62,7 +57,6 @@ class PictureEditor {
this.ensureImage()
this.image.removeAttribute("alt")
this.image.removeAttribute("src")
this.imageLoader.load(true)
get(Alchemy.routes.url_admin_picture_path(this.pictureId), {
crop: this.imageCropperEnabled,
crop_from: this.cropFrom,
Expand All @@ -85,13 +79,12 @@ class PictureEditor {
if (this.image) return

const img = new Image()
this.thumbnailBackground.replaceChildren(img)
this.pictureThumbnail.replaceChildren(img)
this.image = img
this.imageLoader = new ImageLoader(img)
}

removeImage() {
this.thumbnailBackground.innerHTML = IMAGE_PLACEHOLDER
this.pictureThumbnail.innerHTML = IMAGE_PLACEHOLDER
this.pictureIdField.value = ""
this.image = null
this.cropLink.classList.add("disabled")
Expand Down
2 changes: 1 addition & 1 deletion app/models/concerns/alchemy/picture_thumbnails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def picture_url_options
def thumbnail_url
return if picture.nil?

picture.url(thumbnail_url_options) || "alchemy/missing-image.svg"
picture.url(thumbnail_url_options)
end

# Thumbnail rendering options
Expand Down
Loading

0 comments on commit 3e04357

Please sign in to comment.