Skip to content

Commit

Permalink
Version 2.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
alxlion committed Dec 7, 2024
1 parent 767c15b commit 056b384
Show file tree
Hide file tree
Showing 39 changed files with 3,439 additions and 943 deletions.
1 change: 1 addition & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ MAIL_FROM_NAME=Claper
# Claper configuration

#ENABLE_ACCOUNT_CREATION=true
#EMAIL_CONFIRMATION=true
#ALLOW_UNLINK_EXTERNAL_PROVIDER=false
#LOGOUT_REDIRECT_URL=https://google.com
#GS_JPG_RESOLUTION=300x300
Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
## v2.2.0

### Features

- Add duplicate feature on finished events
- Add italian translation (thanks to @loviuz and @albanobattistella)
- Add EMAIL_CONFIRMATION environment variable to disable or enable email confirmation after registration

### Fixes and improvements

- Improve performance of global reactions
- Change QR Code background color to white
- Improve auto scroll of messages on the manager
- Fix pinning of questions
- Fix name picker being empty during a reconnect
- Change wording for more options dropdown and access
- Fix dropdown position to be on the front of other elements
- Owner and facilitators of the event can now join the attendee room before the event starts
- Fix email templates

## v2.1.1

### Fixes and improvements
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Claper has a two-sided mission:
- The first one is to help these people presenting an idea or a message by giving them the opportunity to make their presentation unique and to have real-time feedback from their audience.
- The second one is to help each participant to take their place, to be an actor in the presentation, in the meeting and to feel important and useful.

Supported languages: 🇬🇧 English, 🇫🇷 French, 🇩🇪 German, 🇪🇸 Spanish, 🇳🇱 Dutch
Supported languages: 🇬🇧 English, 🇫🇷 French, 🇩🇪 German, 🇪🇸 Spanish, 🇳🇱 Dutch, 🇮🇹 Italian

### Built With

Expand Down
84 changes: 55 additions & 29 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,20 @@ import airdatepickerLocaleFr from "air-datepicker/locale/fr";
import airdatepickerLocaleDe from "air-datepicker/locale/de";
import airdatepickerLocaleEs from "air-datepicker/locale/es";
import airdatepickerLocaleNl from "air-datepicker/locale/nl";
import airdatepickerLocaleIt from "air-datepicker/locale/it";
import "moment/locale/de";
import "moment/locale/fr";
import "moment/locale/es";
import "moment/locale/nl";
import "moment/locale/it";
import QRCodeStyling from "qr-code-styling";
import { Presenter } from "./presenter";
import { Manager } from "./manager";
import Split from "split-grid";
import { TourGuideClient } from "@sjmc11/tourguidejs/src/Tour";
window.moment = moment;

const supportedLocales = ["en", "fr", "de", "es", "nl"];
const supportedLocales = ["en", "fr", "de", "es", "nl", "it"];

var locale =
document.querySelector("html").getAttribute("lang") ||
Expand All @@ -44,6 +46,7 @@ let airdatepickerLocale = {
de: airdatepickerLocaleDe,
es: airdatepickerLocaleEs,
nl: airdatepickerLocaleNl,
it: airdatepickerLocaleIt,
};
let csrfToken = document
.querySelector("meta[name='csrf-token']")
Expand Down Expand Up @@ -169,18 +172,27 @@ Hooks.Scroll = {

Hooks.ScrollIntoDiv = {
mounted() {
this.scrollElement(true);
this.handleEvent("scroll", this.scrollElement.bind(this));
},
scrollElement(firstScroll) {
let t = this.el.parentElement;
if (
firstScroll === true ||
t.scrollHeight - t.scrollTop - t.clientHeight <= 100
) {
t.scrollTo({ top: t.scrollHeight, behavior: "smooth" });
let useParent = this.el.dataset.useParent === "true";
this.scrollElement = this.el.dataset.useParent === "true" ? this.el.parentElement : this.el;
this.checkIfAtBottom();
this.scrollToBottom(true);
this.handleEvent("scroll", () => this.scrollToBottom());
this.scrollElement.addEventListener("scroll", () => this.checkIfAtBottom());
},
checkIfAtBottom() {
this.isAtBottom = this.scrollElement.scrollHeight - this.scrollElement.scrollTop - this.scrollElement.clientHeight <= 30;
},
scrollToBottom(force = false) {
if (force || this.isAtBottom) {
this.scrollElement.scrollTo({ top: this.scrollElement.scrollHeight, behavior: "smooth" });
}
},
updated() {
this.scrollToBottom();
},
destroyed() {
this.scrollElement.removeEventListener("scroll", () => this.checkIfAtBottom());
}
};

Hooks.NicknamePicker = {
Expand All @@ -192,6 +204,12 @@ Hooks.NicknamePicker = {

this.el.addEventListener("click", (e) => this.clicked(e));
},
reconnected() {
let currentNickname = localStorage.getItem("nickname") || "";
if (currentNickname.length > 0) {
this.pushEvent("set-nickname", { nickname: currentNickname });
}
},
destroyed() {
this.el.removeEventListener("click", (e) => this.clicked(e));
},
Expand Down Expand Up @@ -362,18 +380,37 @@ Hooks.OpenPresenter = {
},
};
Hooks.GlobalReacts = {
svgCache: {},

mounted() {
this.preloadSVGs();
this.handleEvent("global-react", (data) => {
var img = document.createElement("img");
img.src = "/images/icons/" + data.type + ".svg";
img.className =
"react-animation absolute transform opacity-0" + this.el.className;
this.el.appendChild(img);
const svgContent = this.svgCache[data.type];
if (svgContent) {
const container = document.createElement("div");
container.innerHTML = svgContent;
const svgElement = container.firstChild;
svgElement.classList.add("react-animation", "absolute", "transform", "opacity-0");
svgElement.classList.add(...this.el.className.split(" "));
this.el.appendChild(svgElement);
}
});
this.handleEvent("reset-global-react", (data) => {
this.el.innerHTML = "";
});
},

preloadSVGs() {
const svgTypes = ["heart", "hundred", "clap", "raisehand"];
svgTypes.forEach(type => {
fetch(`/images/icons/${type}.svg`)
.then(response => response.text())
.then(svgContent => {
this.svgCache[type] = svgContent;
})
.catch(error => console.error(`Error loading SVG for ${type}:`, error));
});
}
};
Hooks.JoinEvent = {
mounted() {
Expand Down Expand Up @@ -461,10 +498,10 @@ Hooks.QRCode = {
},
dotsOptions: {
type: "square",
color: "#ffffff",
color: "#000000",
},
backgroundOptions: {
color: "#000000",
color: "#ffffff",
},
imageOptions: {
crossOrigin: "anonymous",
Expand Down Expand Up @@ -565,16 +602,6 @@ window.addEventListener("phx:page-loading-stop", (info) => {
topbar.hide();
});

const renderOnlineUsers = function (presences) {
let onlineUsers = Presence.list(
presences,
(_id, { metas: [user, ...rest] }) => {
return onlineUserTemplate(user);
}
).join("");

document.querySelector("body").innerHTML = onlineUsers;
};

const onlineUserTemplate = function (user) {
return `
Expand All @@ -587,7 +614,6 @@ const onlineUserTemplate = function (user) {
let presences = {};
liveSocket.on("presence_state", (state) => {
presences = Presence.syncState(presences, state);
renderOnlineUsers(presences);
});

// connect if there are any LiveViews on the page
Expand Down
12 changes: 10 additions & 2 deletions assets/js/manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,16 @@ export class Manager {
let originalSnap = localStorage.getItem("preview-position");
if (originalSnap) {
let snaps = originalSnap.split(":");
preview.style.left = `${snaps[0]}px`;
preview.style.top = `${snaps[1]}px`;
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
const previewWidth = preview.offsetWidth;
const previewHeight = preview.offsetHeight;

const left = Math.min(Math.max(parseInt(snaps[0]), 0), windowWidth - previewWidth);
const top = Math.min(Math.max(parseInt(snaps[1]), 0), windowHeight - previewHeight);

preview.style.left = `${left}px`;
preview.style.top = `${top}px`;
}

const startDrag = (e) => {
Expand Down
3 changes: 2 additions & 1 deletion config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ config :claper, ClaperWeb.Endpoint,
~r"priv/static/[^uploads].*(js|css|png|jpeg|jpg|gif|svg)$",
~r"priv/gettext/.*(po)$",
~r"lib/claper_web/(live|views)/.*(ex)$",
~r"lib/claper_web/templates/.*(eex)$"
~r"lib/claper_web/templates/.*(eex)$",
~r"assets/.*\.(js|css)$"
]
]

Expand Down
5 changes: 5 additions & 0 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ enable_account_creation =
get_var_from_path_or_env(config_dir, "ENABLE_ACCOUNT_CREATION", "true")
|> String.to_existing_atom()

email_confirmation =
get_var_from_path_or_env(config_dir, "EMAIL_CONFIRMATION", "false")
|> String.to_existing_atom()

pool_size = get_int_from_path_or_env(config_dir, "POOL_SIZE", 10)
queue_target = get_int_from_path_or_env(config_dir, "QUEUE_TARGET", 5_000)

Expand Down Expand Up @@ -150,6 +154,7 @@ config :claper, ClaperWeb.Endpoint,

config :claper,
enable_account_creation: enable_account_creation,
email_confirmation: email_confirmation,
allow_unlink_external_provider: allow_unlink_external_provider,
logout_redirect_url: logout_redirect_url

Expand Down
66 changes: 24 additions & 42 deletions lib/claper/accounts/user_notifier.ex
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
defmodule Claper.Accounts.UserNotifier do
import Swoosh.Email
# import Swoosh.Email

alias Claper.Mailer

# Delivers the email using the application mailer.
defp deliver(recipient, subject, body) do
from_name = Application.get_env(:claper, :mail)[:from_name]
from_email = Application.get_env(:claper, :mail)[:from]

email =
new()
|> to(recipient)
|> from({from_name, from_email})
|> subject(subject)
|> text_body(body)

with {:ok, _metadata} <- Mailer.deliver(email) do
{:ok, email}
end
end
# defp deliver(recipient, subject, body) do
# from_name = Application.get_env(:claper, :mail)[:from_name]
# from_email = Application.get_env(:claper, :mail)[:from]

# email =
# new()
# |> to(recipient)
# |> from({from_name, from_email})
# |> subject(subject)
# |> text_body(body)

# with {:ok, _metadata} <- Mailer.deliver(email) do
# {:ok, email}
# end
# end

def deliver_magic_link(email, url) do
email = ClaperWeb.Notifiers.UserNotifier.magic(email, url)
Expand All @@ -40,40 +40,22 @@ defmodule Claper.Accounts.UserNotifier do
Deliver instructions to confirm account.
"""
def deliver_confirmation_instructions(user, url) do
deliver(user.email, "Confirmation instructions", """
==============================
Hi #{user.email},
You can confirm your account by visiting the URL below:
#{url}
If you didn't create an account with us, please ignore this.
email = ClaperWeb.Notifiers.UserNotifier.confirm(user, url)

==============================
""")
with {:ok, _metadata} <- Mailer.deliver(email) do
{:ok, email}
end
end

@doc """
Deliver instructions to reset a user password.
"""
def deliver_reset_password_instructions(user, url) do
deliver(user.email, "Reset password instructions", """
==============================
Hi #{user.email},
You can reset your password by visiting the URL below:
email = ClaperWeb.Notifiers.UserNotifier.reset(user, url)

#{url}
If you didn't request this change, please ignore this.
==============================
""")
with {:ok, _metadata} <- Mailer.deliver(email) do
{:ok, email}
end
end

@doc """
Expand Down
2 changes: 1 addition & 1 deletion lib/claper/events.ex
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ defmodule Claper.Events do

attrs =
Map.from_struct(original_event)
|> Map.drop([:id, :inserted_at, :updated_at, :presentation_file])
|> Map.drop([:id, :inserted_at, :updated_at, :presentation_file, :expired_at])
|> Map.put(:leaders, [])
|> Map.put(:code, "#{new_code}")
|> Map.put(:name, "#{original_event.name} (Copy)")
Expand Down
6 changes: 3 additions & 3 deletions lib/claper_web/controllers/user_confirmation_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ defmodule ClaperWeb.UserConfirmationController do
{:ok, _} ->
conn
|> put_flash(:info, "User confirmed successfully.")
|> redirect(to: "/")
|> redirect(to: ~p"/users/log_in")

:error ->
# If there is a current user and the account was already confirmed,
Expand All @@ -41,12 +41,12 @@ defmodule ClaperWeb.UserConfirmationController do
# a warning message.
case conn.assigns do
%{current_user: %{confirmed_at: confirmed_at}} when not is_nil(confirmed_at) ->
redirect(conn, to: "/")
redirect(conn, to: ~p"/users/log_in")

%{} ->
conn
|> put_flash(:error, "User confirmation link is invalid or it has expired.")
|> redirect(to: "/")
|> redirect(to: ~p"/")
end
end
end
Expand Down
12 changes: 9 additions & 3 deletions lib/claper_web/controllers/user_oidc_auth.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,19 @@ defmodule ClaperWeb.UserOidcAuth do
conn
|> UserAuth.log_in_user(oidc_user.user)
else
{:error, _} ->
{:error, reason} ->
conn
|> put_flash(:error, "Cannot authenticate user.")
|> redirect(to: ~p"/users/log_in")
|> put_status(:unauthorized)
|> put_view(ClaperWeb.ErrorView)
|> render("csrf_error.html", %{error: "Authentication failed: #{inspect(reason)}"})
end
end

def callback(conn, %{"error" => error} = _params) do
conn
|> put_status(:unauthorized)
|> put_view(ClaperWeb.ErrorView)
|> render("csrf_error.html", %{error: "Authentication failed: #{error}"})
end

defp config do
Expand Down
Loading

0 comments on commit 056b384

Please sign in to comment.