Skip to content

Commit

Permalink
Merge branch 'develop' into jl/activities
Browse files Browse the repository at this point in the history
  • Loading branch information
joaodiaslobo authored Sep 3, 2024
2 parents 05df35b + a78995c commit d418efe
Show file tree
Hide file tree
Showing 25 changed files with 255 additions and 97 deletions.
8 changes: 8 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## Description
_Provide a detailed description of the purpose of the changes included in this pull request. Optionally, include background information, relevant screenshots, and any other context that helps explain the work._

## Related Issues
_If applicable, specify the main parts of the application that will be impacted by this pull request._

## Steps to reproduce or test
_Describe the steps that you did to reproduce this._
19 changes: 3 additions & 16 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,15 @@ import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
import "../vendor/alpine.js";
import topbar from "../vendor/topbar"
import { QrScanner } from "./qr_reading.js";
import { InitSorting } from "./sorting.js";
import { StickyScroll } from "./sticky_scroll.js";
import { QrScanner, InitSorting, StickyScroll, ScrollToTop } from "./hooks";

let Hooks = {
QrScanner: QrScanner,
InitSorting: InitSorting,
StickyScroll: StickyScroll
StickyScroll: StickyScroll,
ScrollToTop: ScrollToTop
};

Hooks.ScrollToTop = {
mounted() {
this.el.addEventListener("click", e => {
e.preventDefault()
window.scrollTo({
top: 0,
behavior: 'smooth'
});
})
}
}

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {
params: { _csrf_token: csrfToken },
Expand Down
4 changes: 4 additions & 0 deletions assets/js/hooks/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { QrScanner } from "./qr_reading.js";
export { InitSorting } from "./sorting.js";
export { StickyScroll } from "./sticky_scroll.js";
export { ScrollToTop } from "./scroll_to_top.js";
2 changes: 1 addition & 1 deletion assets/js/qr_reading.js → assets/js/hooks/qr_reading.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Html5Qrcode, Html5QrcodeSupportedFormats } from "../vendor/html5-qrcode.js"
import { Html5Qrcode, Html5QrcodeSupportedFormats } from "../../vendor/html5-qrcode.js"

function parseURL(url) {
try {
Expand Down
11 changes: 11 additions & 0 deletions assets/js/hooks/scroll_to_top.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const ScrollToTop = {
mounted() {
this.el.addEventListener("click", e => {
e.preventDefault()
window.scrollTo({
top: 0,
behavior: 'smooth'
});
})
}
}
2 changes: 1 addition & 1 deletion assets/js/sorting.js → assets/js/hooks/sorting.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Sortable from "../vendor/sortable.js"
import Sortable from "../../vendor/sortable.js"

export const InitSorting = {
mounted() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export const StickyScroll = {
mounted() {
window.addEventListener("scroll",() => {
const panel = document.getElementById("scroll-panel");
if(panel == null) { window.removeEventListener("scroll", this); return; }
if(window.innerHeight > panel.offsetHeight) return;
panel.style.top = -Math.min(Math.max(window.scrollY, 0), panel.offsetHeight - window.innerHeight) + "px";
});
Expand Down
2 changes: 1 addition & 1 deletion config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ config :atomic, AtomicWeb.Endpoint,
patterns: [
~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
~r"priv/gettext/.*(po)$",
~r"lib/atomic_web/(live|views)/.*(ex)$",
~r"lib/atomic_web/(live|views|components)/.*(ex)$",
~r"lib/atomic_web/templates/.*(eex)$",
~r"storybook/.*(exs)$"
]
Expand Down
28 changes: 7 additions & 21 deletions lib/atomic/uploader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,23 @@ defmodule Atomic.Uploader do
Put `use Atomic.Uploader` on top of your uploader module to use it.
"""

@versions [:original, :medium, :thumb]
@extensions_whitelist ~w(.svg .jpg .jpeg .png)

defmacro __using__(_) do
defmacro __using__(opts) do
quote do
use Waffle.Definition
use Waffle.Ecto.Definition

def validate({file, _}) do
file.file_name
|> Path.extname()
|> String.downcase()
|> then(&Enum.member?(Atomic.Uploader.extensions_whitelist(), &1))
|> case do
file_extension = file.file_name |> Path.extname() |> String.downcase()

case Enum.member?(extension_whitelist(), file_extension) do
true -> :ok
false -> {:error, "invalid file type"}
false -> {:error, "invalid file extension"}
end
end

def transform(:thumb, _) do
{:convert, "-strip -thumbnail 100x150^ -gravity center -extent 100x150 -format png", :png}
end

def transform(:medium, _) do
{:convert, "-strip -thumbnail 400x600^ -gravity center -extent 400x600 -format png", :png}
def extension_whitelist do
Keyword.get(unquote(opts), :extensions, [])
end

def filename(version, _), do: version
end
end

def versions, do: @versions
def extensions_whitelist, do: @extensions_whitelist
end
12 changes: 9 additions & 3 deletions lib/atomic/uploaders/banner.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ defmodule Atomic.Uploaders.Banner do
@moduledoc """
Uploader for department banners.
"""
use Atomic.Uploader
use Atomic.Uploader, extensions: ~w(.jpg .jpeg .png)

alias Atomic.Organizations.Department

def storage_dir(_version, {_file, %Department{} = scope}) do
"uploads/atomic/departments/#{scope.id}/banner"
@versions [:original]

def storage_dir(_version, {_file, %Department{} = department}) do
"uploads/atomic/departments/#{department.id}/banner"
end

def filename(version, _) do
version
end
end
12 changes: 9 additions & 3 deletions lib/atomic/uploaders/logo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ defmodule Atomic.Uploaders.Logo do
@moduledoc """
Uploader for organization logos.
"""
use Atomic.Uploader
use Atomic.Uploader, extensions: ~w(.jpg .jpeg .png .svg)
alias Atomic.Organizations.Organization

def storage_dir(_version, {_file, %Organization{} = scope}) do
"uploads/atomic/logos/#{scope.id}"
@versions [:original]

def storage_dir(_version, {_file, %Organization{} = organization}) do
"uploads/atomic/organizations/#{organization.id}/logo"
end

def filename(version, _) do
version
end
end
12 changes: 9 additions & 3 deletions lib/atomic/uploaders/partner_image.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ defmodule Atomic.Uploaders.PartnerImage do
@moduledoc """
Uploader for partner images.
"""
use Atomic.Uploader
use Atomic.Uploader, extensions: ~w(.jpg .jpeg .png)

alias Atomic.Organizations.Partner

def storage_dir(_version, {_file, %Partner{} = scope}) do
"uploads/atomic/partners/#{scope.id}"
@versions [:original]

def storage_dir(_version, {_file, %Partner{} = partner}) do
"uploads/atomic/partners/#{partner.id}/logo"
end

def filename(version, _) do
version
end
end
16 changes: 11 additions & 5 deletions lib/atomic/uploaders/post.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@ defmodule Atomic.Uploaders.Post do
@moduledoc """
Uploader for posts.
"""
use Atomic.Uploader
use Atomic.Uploader, extensions: ~w(.jpg .jpeg .png .svg)

alias Atomic.Activities.Activity
alias Atomic.Organizations.Announcement

def storage_dir(_version, {_file, %Activity{} = scope}) do
"uploads/atomic/activities/#{scope.id}"
@versions [:original]

def storage_dir(_version, {_file, %Activity{} = activity}) do
"uploads/atomic/activities/#{activity.id}/image"
end

def storage_dir(_version, {_file, %Announcement{} = announcement}) do
"uploads/atomic/announcements/#{announcement.id}/image"
end

def storage_dir(_version, {_file, %Announcement{} = scope}) do
"uploads/atomic/announcements/#{scope.id}"
def filename(version, _) do
version
end
end
12 changes: 9 additions & 3 deletions lib/atomic/uploaders/profile_picture.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ defmodule Atomic.Uploaders.ProfilePicture do
@moduledoc """
Uploader for profile pictures.
"""
use Atomic.Uploader
use Atomic.Uploader, extensions: ~w(.jpg .jpeg .png .gif)
alias Atomic.Accounts.User

def storage_dir(_version, {_file, %User{} = scope}) do
"uploads/atomic/profile_pictures/#{scope.id}"
@versions [:original]

def storage_dir(_version, {_file, %User{} = user}) do
"uploads/atomic/users/#{user.id}/profile_picture"
end

def filename(version, _) do
version
end
end
4 changes: 2 additions & 2 deletions lib/atomic_web/components/calendar/calendar.ex
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ defmodule AtomicWeb.Components.Calendar do
id="calendar-dropdown"
orientation={:down}
items={[
%{name: gettext("Week view"), link: @current_week_path},
%{name: gettext("Month view"), link: @current_month_path}
%{name: gettext("Week view"), patch: @current_week_path},
%{name: gettext("Month view"), patch: @current_month_path}
]}
>
<:wrapper>
Expand Down
34 changes: 26 additions & 8 deletions lib/atomic_web/components/dropdown.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
defmodule AtomicWeb.Components.Dropdown do
@moduledoc false
@moduledoc """
A customizable dropdown component for displaying a list of items, with flexible styling and behavior options.
"""
use Phoenix.Component

import AtomicWeb.Components.Icon
Expand All @@ -25,25 +27,41 @@ defmodule AtomicWeb.Components.Dropdown do

def dropdown(assigns) do
~H"""
<div class="relative inline-block text-left" phx-click={JS.toggle(to: "##{@id}", in: "block", out: "hidden")} phx-click-away={JS.hide(to: "##{@id}")}>
<%= render_slot(@wrapper) %>
<div id={@id} class={"#{if @orientation == :down, do: "origin-top-right top-full", else: "origin-bottom-right bottom-full"} absolute right-0 z-10 mt-2 hidden w-56 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5"}>
<div class="relative inline-block text-left" phx-click-away={JS.hide(to: "##{@id}", transition: {"ease-in duration-75", "transform opacity-100 scale-100", "transform opacity-0 scale-95"})}>
<div phx-click={JS.toggle(to: "##{@id}", in: {"ease-out duration-100", "transform opacity-0 scale-95", "transform opacity-100 scale-100"}, out: {"ease-in duration-75", "transform opacity-100 scale-100", "transform opacity-0 scale-95"}, display: "block")}>
<%= render_slot(@wrapper) %>
</div>
<div id={@id} class={"#{if @orientation == :down, do: "origin-top-right top-full mt-3", else: "origin-bottom-right bottom-full mb-3"} absolute right-0 z-10 hidden w-56 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5"}>
<div class="py-1" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
<%= for item <- @items do %>
<%= if Map.has_key?(item, :link) do %>
<.link href={item.link} class="block flex items-center gap-x-2 px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem" method={Map.get(item, :method, "get")}>
<%= if item[:patch] || item[:navigate] || item[:href] || item[:phx_click] do %>
<.link
patch={item[:patch]}
navigate={item[:navigate]}
href={item[:href]}
phx-click={
if item[:phx_click] do
JS.push(item[:phx_click]) |> JS.hide(to: "##{@id}", transition: {"ease-in duration-75", "transform opacity-100 scale-100", "transform opacity-0 scale-95"})
else
JS.hide(to: "##{@id}", transition: {"ease-in duration-75", "transform opacity-100 scale-100", "transform opacity-0 scale-95"})
end
}
class={"#{item[:class]} flex items-center gap-x-2 px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900"}
role="menuitem"
method={Map.get(item, :method, "get")}
>
<%= if item[:icon] do %>
<.icon solid={@icon_variant == :solid} mini={@icon_variant == :mini} name={item.icon} class="ml-2 inline-block h-5 w-5" />
<% end %>
<%= item.name %>
</.link>
<% else %>
<button phx-click={item.event} class="block flex w-full items-center gap-x-2 px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem">
<div class={"#{item[:class]} flex items-center gap-x-2 px-4 py-2 text-sm text-gray-700"}>
<%= if item[:icon] do %>
<.icon solid={@icon_variant == :solid} mini={@icon_variant == :mini} name={item.icon} class="ml-2 inline-block h-5 w-5" />
<% end %>
<%= item.name %>
</button>
</div>
<% end %>
<% end %>
</div>
Expand Down
62 changes: 62 additions & 0 deletions lib/atomic_web/components/map.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
defmodule AtomicWeb.Components.Map do
@moduledoc """
A configurable component to display a map using Google Maps.
"""
use Phoenix.Component

attr :location, :string, required: true, doc: "The location to be displayed on the map"
attr :zoom, :integer, default: 16, doc: "The zoom level of the map"

attr :type, :atom,
values: [:normal, :satellite],
default: :normal,
doc: "The view type of the map"

attr :height, :integer, default: 200, doc: "The height of the map"
attr :width, :integer, doc: "The width of the map"

attr :full_width, :boolean,
default: true,
doc: "If true, the map will take up the full width of its container"

attr :controls, :boolean,
default: false,
doc: "If true, the map will be interactive and show controls"

def map(assigns) do
if assigns[:controls] do
map_interactive(assigns)
else
map_static(assigns)
end
end

defp map_static(assigns) do
~H"""
<.link href={"https://www.google.com/maps/search/?api=1&query=#{@location}"} target="_blank" class="select-none overflow-hidden" style={"height: #{@height}px; width: #{generate_width(assigns)}"}>
<iframe width={generate_width(assigns)} height={@height + 300} src={generate_request(assigns)} frameborder="0" scrolling="no" marginheight="0" marginwidth="0" style="border:0; margin-top: -150px;" class="pointer-events-none"></iframe>
</.link>
"""
end

defp map_interactive(assigns) do
~H"""
<iframe width={generate_width(assigns)} height={@height} src={generate_request(assigns)} frameborder="0" scrolling="no" marginheight="0" marginwidth="0"></iframe>
"""
end

defp generate_request(assigns) do
location = assigns[:location]
zoom = assigns[:zoom]
type = assigns[:type]

"https://maps.google.com/maps?q=#{location}&t=#{type_request_value(type)}&z=#{zoom}&output=embed"
end

defp generate_width(assigns) do
if assigns[:full_width] && !assigns[:width], do: "100%", else: "#{assigns[:width]}px"
end

defp type_request_value(:normal), do: "m"
defp type_request_value(:satellite), do: "k"
end
3 changes: 1 addition & 2 deletions lib/atomic_web/live/activity_live/form_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ defmodule AtomicWeb.ActivityLive.FormComponent do
use AtomicWeb, :live_component

alias Atomic.Activities
alias Atomic.Uploader
alias AtomicWeb.Components.ImageUploader

import AtomicWeb.Components.Forms
Expand All @@ -15,7 +14,7 @@ defmodule AtomicWeb.ActivityLive.FormComponent do
socket
|> assign(assigns)
|> assign_form(changeset)
|> allow_upload(:image, accept: Uploader.extensions_whitelist(), max_entries: 1)}
|> allow_upload(:image, accept: Uploaders.Post.extension_whitelist(), max_entries: 1)}
end

@impl true
Expand Down
Loading

0 comments on commit d418efe

Please sign in to comment.