Skip to content

Commit

Permalink
Improved image-catalog-picker on mobile (#137)
Browse files Browse the repository at this point in the history
* Improved image-catalog-picker on mobile

* Update 100vh-fix.js

Co-authored-by: Jeroen Vloothuis <[email protected]>
  • Loading branch information
mellelieuwes and vloothuis authored Aug 27, 2021
1 parent 6ee1fbd commit 8f42519
Show file tree
Hide file tree
Showing 16 changed files with 314 additions and 99 deletions.
13 changes: 11 additions & 2 deletions core/assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { decode } from "blurhash";
import { urlBase64ToUint8Array } from "./tools";
import { registerAPNSDeviceToken } from "./apns";
import "./100vh-fix";
import { ViewportResize } from "./viewport_resize"

window.registerAPNSDeviceToken = registerAPNSDeviceToken;

Expand Down Expand Up @@ -65,10 +66,12 @@ window.blurHash = () => {
let csrfToken = document
.querySelector("meta[name='csrf-token']")
.getAttribute("content");
let Hooks = {};

let Hooks = { ViewportResize };

Hooks.NativeWrapper = {
mounted() {
console.log("NativeWrapper mounted")
window.nativeWrapperHook = this
},
toggleSidePanel() {
Expand All @@ -83,6 +86,8 @@ Hooks.PythonUploader = {
this.worker && this.worker.terminate();
},
mounted(){
console.log("PythonUploader mounted")

this.worker = new Worker("/js/pyworker.js");
this.worker.onerror = console.log;
this.worker.onmessage = (event) => {
Expand Down Expand Up @@ -149,7 +154,11 @@ let liveSocket = new LiveSocket('/live', Socket, {
}
},
params: {
_csrf_token: csrfToken
_csrf_token: csrfToken,
viewport: {
width: window.innerWidth,
height: window.innerHeight
}
},
hooks: Hooks
})
Expand Down
28 changes: 28 additions & 0 deletions core/assets/js/viewport_resize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import _ from 'lodash'

let resizeHandler

export const ViewportResize = {

mounted () {
// Direct push of current window size to properly update view
this.pushResizeEvent()

resizeHandler = _.debounce(() => {
this.pushResizeEvent()
}, 100)
window.addEventListener('resize', resizeHandler)
},

pushResizeEvent () {
console.log("pushResizeEvent")
this.pushEvent('viewport_resize', {
width: window.innerWidth,
height: window.innerHeight
})
},

turbolinksDisconnected () {
window.removeEventListener('resize', resizeHandler)
}
}
20 changes: 13 additions & 7 deletions core/assets/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ module.exports = {
"sheet": "760px",
"form" : "400px",
"card": "376px",
"image-preview": "200px",
"image-preview-circle": "150px",
"popup-md": "730px",
"popup-lg": "1228px",
"image-preview": "120px",
"image-preview-sm": "200px",
"image-preview-circle": "120px",
"image-preview-circle-sm": "150px",
"button-sm": "14px",
},
height: {
Expand All @@ -103,8 +103,10 @@ module.exports = {
"image-header": "375px",
"image-header-sm": "500px",
"image-card": "200px",
"image-preview": "150px",
"image-preview-circle": "150px",
"image-preview": "90px",
"image-preview-sm": "150px",
"image-preview-circle": "120px",
"image-preview-circle-sm": "150px",
"campaign-banner": "224px",
"button-sm": "14px",
},
Expand Down Expand Up @@ -153,7 +155,11 @@ module.exports = {
maxWidth: {
"card": "376px",
"form": "400px",
"sheet": "760px"
"sheet": "760px",
"popup": "480px",
"popup-sm": "520px",
"popup-md": "730px",
"popup-lg": "1228px"
},
maxHeight: {
"header1": "376px",
Expand Down
21 changes: 17 additions & 4 deletions core/bundles/link/lib/survey/web/content.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule Link.Survey.Content do
use CoreWeb, :live_view
use CoreWeb.MultiFormAutoSave
use CoreWeb.Layouts.Workspace.Component, :survey
use Coreweb.UI.ViewportHelpers

import CoreWeb.Gettext

Expand All @@ -30,7 +31,6 @@ defmodule Link.Survey.Content do
data(initial_image_query, :any)
data(uri_origin, :any)


@impl true
def get_authorization_context(%{"id" => id}, _session, _socket) do
Tools.get_survey_tool!(id)
Expand All @@ -55,6 +55,8 @@ defmodule Link.Survey.Content do
hide_flash_timer: nil
)
|> update_menus()
|> assign_viewport()
|> assign_breakpoint()
}
end

Expand Down Expand Up @@ -171,18 +173,29 @@ defmodule Link.Survey.Content do
{:noreply, socket}
end

defp marginX(:mobile), do: "mx-6"
defp marginX(_), do: "mx-10"

def render(assigns) do
~H"""
<Workspace
title={{ dgettext("link-survey", "content.title") }}
menus={{ @menus }}
>
<div phx-click="reset_focus">
<div id={{ :survey_content }} phx-hook="ViewportResize" phx-click="reset_focus">
<div x-data="{ open: false }">
<div class="fixed z-20 left-0 top-0 w-full h-full" x-show="open">
<div class="flex flex-row items-center justify-center w-full h-full">
<div class="w-5/6 md:w-popup-md lg:w-popup-lg" @click.away="open = false, $parent.$parent.overlay = false">
<ImageCatalogPicker conn={{@socket}} static_path={{&Routes.static_path/2}} initial_query={{initial_image_query(assigns)}} id={{:image_picker}} image_catalog={{Core.ImageCatalog.Unsplash}} />
<div class="{{marginX(@breakpoint)}} w-full max-w-popup sm:max-w-popup-sm md:max-w-popup-md lg:max-w-popup-lg" @click.away="open = false, $parent.$parent.overlay = false">
<ImageCatalogPicker
id={{:image_picker}}
conn={{@socket}}
viewport={{@viewport}}
breakpoint={{@breakpoint}}
static_path={{&Routes.static_path/2}}
initial_query={{initial_image_query(assigns)}}
image_catalog={{Core.ImageCatalog.Unsplash}}
/>
</div>
</div>
</div>
Expand Down
185 changes: 118 additions & 67 deletions core/lib/core_web/image_catalog_picker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,121 @@ defmodule CoreWeb.ImageCatalogPicker do
alias EyraUI.Grid.ImageGrid

prop(conn, :any, required: true)
prop(viewport, :any)
prop(breakpoint, :any)
prop(static_path, :any, required: true)
prop(image_catalog, :any, required: true)
prop(initial_query, :string, default: "")
data(query, :string, default: "")
data(search_results, :list, default: [])
data(meta, :any)
prop(target, :any, default: "")

data(query, :string, default: nil)
data(search_results, :list, default: nil)
data(meta, :any, default: nil)

defp gap(%{"width" => width}, :mobile) when width < 400, do: "gap-4"
defp gap(%{"width" => width}, :mobile) when width < 500, do: "gap-8"
defp gap(_, _), do: "gap-10"

defp page_size(_, :mobile), do: 6
defp page_size(_, :sm), do: 6
defp page_size(_, :md), do: 9
defp page_size(_, :lg), do: 8
defp page_size(_, _), do: 10

defp page_count(%{"width" => width}, :mobile), do: Integer.floor_div(width, 100) + 1
defp page_count(_, :sm), do: 8
defp page_count(_, :md), do: 10
defp page_count(_, :lg), do: 10
defp page_count(_, _), do: 10

def update(
%{viewport: new_viewport, breakpoint: new_breakpoint},
%{assigns: %{viewport: current_viewport}} = socket
) do
socket =
if new_viewport != current_viewport do
socket
|> assign(viewport: new_viewport)
|> assign(breakpoint: new_breakpoint)
|> assign(search_results: nil)
|> assign(query: nil)
|> assign(meta: nil)
else
socket
end

{
:ok,
socket
}
end

def update(
%{
id: id,
conn: conn,
viewport: viewport,
breakpoint: breakpoint,
static_path: static_path,
image_catalog: image_catalog,
initial_query: initial_query,
target: target
},
socket
) do
{
:ok,
socket
|> assign(id: id)
|> assign(conn: conn)
|> assign(viewport: viewport)
|> assign(breakpoint: breakpoint)
|> assign(static_path: static_path)
|> assign(image_catalog: image_catalog)
|> assign(initial_query: initial_query)
|> assign(target: target)
}
end

def handle_event(
"search",
%{"q" => query},
%{assigns: %{image_catalog: image_catalog}} = socket
) do
search(socket, query, image_catalog, 1)
end

def handle_event(
"select_page",
%{"page" => page},
%{assigns: %{query: query, image_catalog: image_catalog}} = socket
) do
search(socket, query, image_catalog, String.to_integer(page))
end

def handle_event("select_image", %{"image" => image_id}, %{assigns: %{id: id}} = socket) do
send(self(), {id, image_id})
{:noreply, socket}
end

defp search(
%{assigns: %{viewport: viewport, breakpoint: breakpoint}} = socket,
query,
image_catalog,
page
) do
page_size = page_size(viewport, breakpoint)
results = image_catalog.search_info(query, page, page_size, width: 400, height: 300)

{:noreply,
socket
|> assign(
query: query,
initial_query: query,
search_results: results.images,
meta: results.meta
)}
end

def render(assigns) do
~H"""
Expand All @@ -37,34 +146,13 @@ defmodule CoreWeb.ImageCatalogPicker do
<Spacing value="S" />
<BodyLarge>{{dgettext("eyra-imagecatalog", "no.results.found.message")}}</BodyLarge>
</div>
<div :if={{@search_results != []}}>
<div :if={{@search_results && @search_results != []}}>
<Spacing value="S" />
<BodyLarge>{{dngettext("eyra-imagecatalog", "images.found.message", "images.found.message.%{count}", @meta.image_count )}}</BodyLarge>
<Spacing value="S" />
<ImageGrid>
<ImageGrid gap={{ gap(@viewport, @breakpoint) }}>
<div :for={{ {image, index} <- Enum.with_index(@search_results) }}>
<div
id="clickable-area-{{index}}"
class="relative w-image-preview h-image-preview bg-grey4 ring-4 hover:ring-primary cursor-pointer rounded overflow-hidden"
:class="{ 'ring-primary': selected === {{index}}, 'ring-white': selected != {{index}} }"
x-on:click="selected = {{index}}"
:on-click="select_image"
phx-value-image={{image.id}}
>
<div
class="absolute z-10 w-full h-full bg-primary bg-opacity-50"
:class="{ 'visible': selected === {{index}}, 'invisible': selected != {{index}} }"
/>
<div
class="absolute z-20 w-full h-full"
:class="{ 'visible': selected === {{index}}, 'invisible': selected != {{index}} }"
>
<img class="w-full h-full object-none" src={{ @static_path.(@conn, "/images/checkmark.svg")}} />
</div>
<div class="w-full h-full">
<img class="object-cover w-full h-full image" src={{image.url}} srcset={{image.srcset}}/>
</div>
</div>
<ImageGrid.Image vm={{ Map.merge(image, %{index: index, target: @myself }) }} />
</div>
</ImageGrid>
<Spacing value="S" />
Expand All @@ -73,13 +161,10 @@ defmodule CoreWeb.ImageCatalogPicker do
<Caption text_alignment="left" padding="p-0" margin="m-0" color="text-grey2">{{dngettext("eyra-imagecatalog", "page.info.message", "page.info.message.%{count}", @meta.image_count, begin: @meta.begin, end: @meta.end)}}</Caption>
</div>
<div class="flex-wrap">
<div class="flex flex-row" x-data="{ selected_page: {{@meta.page}} }" >
<For each={{ page <- 1..Enum.min([10, @meta.page_count]) }}>
<If condition={{ page > 1 }}>
<Spacing value="XS" direction="l" />
</If>
<div class="flex flex-row w-full gap-4" x-data="{ selected_page: {{@meta.page}} }" >
<For each={{ page <- 1..Enum.min([page_count(@viewport, @breakpoint), @meta.page_count]) }} >
<div
class="rounded w-8 h-8 cursor-pointer"
class="rounded w-8 h-8 cursor-pointer flex-shrink-0 overflow-hidden"
:class="{ 'bg-primary text-white': selected_page === {{page}}, 'bg-grey5': selected_page != {{page}} }"
x-on:click="selected = {{page}}, $parent.selected = -1"
:on-click="select_page"
Expand All @@ -101,38 +186,4 @@ defmodule CoreWeb.ImageCatalogPicker do
</div>
"""
end

def handle_event(
"search",
%{"q" => query},
%{assigns: %{image_catalog: image_catalog}} = socket
) do
search(socket, query, image_catalog, 1)
end

def handle_event(
"select_page",
%{"page" => page},
%{assigns: %{query: query, image_catalog: image_catalog}} = socket
) do
search(socket, query, image_catalog, String.to_integer(page))
end

def handle_event("select_image", %{"image" => image_id}, %{assigns: %{id: id}} = socket) do
send(self(), {id, image_id})
{:noreply, socket}
end

defp search(socket, query, image_catalog, page, page_size \\ 10) do
results = image_catalog.search_info(query, page, page_size, width: 400, height: 300)

{:noreply,
socket
|> assign(
query: query,
initial_query: query,
search_results: results.images,
meta: results.meta
)}
end
end
Loading

0 comments on commit 8f42519

Please sign in to comment.