mirror of
https://codeberg.org/JasterV/intisync.ex.git
synced 2026-04-26 18:10:07 +00:00
Merge pull request #4 from JasterV/feat/share-session
feat: share session
This commit is contained in:
commit
68834cbcb2
9 changed files with 196 additions and 104 deletions
20
assets/js/copyToClipboardHook.js
Normal file
20
assets/js/copyToClipboardHook.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
const CopyToClipboard = () => {
|
||||
return {
|
||||
mounted() {
|
||||
const initialInnerHTML = this.el.innerHTML;
|
||||
const { textToCopy } = this.el.dataset;
|
||||
|
||||
this.el.addEventListener("click", () => {
|
||||
navigator.clipboard.writeText(textToCopy);
|
||||
|
||||
this.el.innerHTML = "Copied!";
|
||||
|
||||
setTimeout(() => {
|
||||
this.el.innerHTML = initialInnerHTML;
|
||||
}, 2000);
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default CopyToClipboard;
|
||||
|
|
@ -2,9 +2,13 @@ import { socketConfig } from "./setup";
|
|||
import { Socket } from "phoenix";
|
||||
import { LiveSocket } from "phoenix_live_view";
|
||||
import HubHook from "./hubHook";
|
||||
import NativeShareHook from "./nativeShareHook";
|
||||
import CopyToClipboardHook from "./copyToClipboardHook";
|
||||
|
||||
const Hooks = {
|
||||
HubHook: HubHook(),
|
||||
Hub: HubHook(),
|
||||
NativeShare: NativeShareHook(),
|
||||
CopyToClipboard: CopyToClipboardHook(),
|
||||
};
|
||||
|
||||
const liveSocket = new LiveSocket("/live", Socket, {
|
||||
|
|
|
|||
27
assets/js/nativeShareHook.js
Normal file
27
assets/js/nativeShareHook.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
const NativeShare = () => {
|
||||
return {
|
||||
mounted() {
|
||||
this.el.addEventListener("click", async () => {
|
||||
// Check if sharing is supported
|
||||
if (!navigator.canShare) {
|
||||
this.pushEvent("url_share_error", {
|
||||
error: "Unfortunately, sharing is not supported",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.share({
|
||||
title: this.el.dataset.title,
|
||||
text: this.el.dataset.text,
|
||||
url: this.el.dataset.url,
|
||||
});
|
||||
this.pushEvent("url_shared", {});
|
||||
} catch (error) {
|
||||
this.pushEvent("url_share_error", { error: error.message });
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
export default NativeShare;
|
||||
|
|
@ -18,17 +18,17 @@ defmodule IntisyncWeb.LiveViewMonitor do
|
|||
end
|
||||
|
||||
def handle_call({:monitor, pid, view_module, meta}, _, %{views: views} = state) do
|
||||
mref = Process.monitor(pid)
|
||||
{:reply, :ok, %{state | views: Map.put(views, pid, {view_module, meta, mref})}}
|
||||
_ref = Process.monitor(pid)
|
||||
{:reply, :ok, %{state | views: Map.put(views, pid, {view_module, meta})}}
|
||||
end
|
||||
|
||||
def handle_info({:DOWN, _ref, :process, pid, reason}, state) do
|
||||
{{module, meta, mref}, new_views} = Map.pop(state.views, pid)
|
||||
def handle_info({:DOWN, ref, :process, pid, reason}, state) do
|
||||
{{module, meta}, new_views} = Map.pop(state.views, pid)
|
||||
|
||||
Process.demonitor(ref)
|
||||
|
||||
Task.start(fn -> module.unmount(reason, meta) end)
|
||||
|
||||
Process.demonitor(mref)
|
||||
|
||||
{:noreply, %{state | views: new_views}}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,68 +18,6 @@ defmodule IntisyncWeb.CoreComponents do
|
|||
|
||||
alias Phoenix.LiveView.JS
|
||||
|
||||
@doc """
|
||||
Renders a modal.
|
||||
|
||||
## Examples
|
||||
|
||||
<.modal id="confirm-modal">
|
||||
This is a modal.
|
||||
</.modal>
|
||||
|
||||
JS commands may be passed to the `:on_cancel` to configure
|
||||
the closing/cancel event, for example:
|
||||
|
||||
<.modal id="confirm" on_cancel={JS.navigate(~p"/posts")}>
|
||||
This is another modal.
|
||||
</.modal>
|
||||
|
||||
"""
|
||||
attr :id, :string, required: true
|
||||
slot :inner_block, required: true
|
||||
|
||||
def modal(assigns) do
|
||||
~H"""
|
||||
<div id={@id} phx-remove={"#{@id}-close"} class="relative z-50">
|
||||
<div id={"#{@id}-bg"} class="bg-zinc-50/90 fixed inset-0" aria-hidden="true" />
|
||||
<div
|
||||
class="fixed inset-0 overflow-y-auto"
|
||||
aria-labelledby={"#{@id}-title"}
|
||||
aria-describedby={"#{@id}-description"}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="flex min-h-full items-center justify-center">
|
||||
<div class="w-full max-w-3xl p-4 sm:p-6 lg:py-8">
|
||||
<.focus_wrap
|
||||
id={"#{@id}-container"}
|
||||
phx-window-keydown={"#{@id}-close"}
|
||||
phx-key="escape"
|
||||
phx-click-away={"#{@id}-close"}
|
||||
class="shadow-zinc-700/10 ring-zinc-700/10 relative rounded-2xl bg-white p-14 shadow-lg ring-1"
|
||||
>
|
||||
<div class="absolute top-6 right-5">
|
||||
<button
|
||||
phx-click={"#{@id}-close"}
|
||||
type="button"
|
||||
class="-m-3 flex-none p-3 opacity-20 hover:opacity-40"
|
||||
aria-label="close"
|
||||
>
|
||||
<.icon name="hero-x-mark-solid" class="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div id={"#{@id}-content"}>
|
||||
<%= render_slot(@inner_block) %>
|
||||
</div>
|
||||
</.focus_wrap>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Renders flash notices.
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main class="px-4 py-20 sm:px-6 lg:px-8">
|
||||
<main class="px-4 py-10 sm:px-6 lg:px-8">
|
||||
<div class="mx-auto max-w-2xl">
|
||||
<.flash_group flash={@flash} />
|
||||
<%= @inner_content %>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ defmodule IntisyncWeb.HubLive do
|
|||
|> assign(:intiface_client_status, nil)
|
||||
|> assign(:devices, %{})
|
||||
|> assign(:session_id, nil)
|
||||
|> assign(:share_url, nil)
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
|
@ -31,6 +32,7 @@ defmodule IntisyncWeb.HubLive do
|
|||
|> assign(:remote_controller_status, :disconnected)
|
||||
|> assign(:intiface_client_status, :disconnected)
|
||||
|> assign(:session_id, session_id)
|
||||
|> assign(:share_url, share_url(session_id))
|
||||
|
||||
if exists_session?(session_id) do
|
||||
socket = socket |> put_flash(:error, "Unauthorized") |> redirect(to: "/")
|
||||
|
|
@ -63,6 +65,8 @@ defmodule IntisyncWeb.HubLive do
|
|||
SessionPubSub.subscribe!(session_id, "devices", "vibrate")
|
||||
end
|
||||
|
||||
defp share_url(session_id), do: IntisyncWeb.Endpoint.url() <> ~p"/sessions/#{session_id}/remote"
|
||||
|
||||
############################
|
||||
# Remote controller events #
|
||||
############################
|
||||
|
|
@ -92,6 +96,18 @@ defmodule IntisyncWeb.HubLive do
|
|||
{:noreply, socket}
|
||||
end
|
||||
|
||||
#############
|
||||
# UI events #
|
||||
#############
|
||||
|
||||
def handle_event("url_shared", %{}, socket) do
|
||||
{:noreply, put_flash(socket, :info, "Session shared! :)")}
|
||||
end
|
||||
|
||||
def handle_event("url_share_error", %{"error" => error}, socket) do
|
||||
{:noreply, put_flash(socket, :error, "Failed to share session. #{error}")}
|
||||
end
|
||||
|
||||
##########################
|
||||
# Intiface Client events #
|
||||
##########################
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
You are hosting an IntiSync session
|
||||
</p>
|
||||
|
||||
<div id="hub-status" class="flex flex-col sm:flex-row items-left gap-3 mt-6">
|
||||
<div id="hub-status" class="flex flex-row gap-3 mt-6">
|
||||
<.badge
|
||||
:if={@intiface_client_status == :disconnected}
|
||||
id="intiface-client-disconnected-badge"
|
||||
|
|
@ -41,34 +41,90 @@
|
|||
</div>
|
||||
</header>
|
||||
|
||||
<section
|
||||
:if={@intiface_client_status == :disconnected}
|
||||
id="connect-buttons"
|
||||
class="flex flex-col gap-6 items-center"
|
||||
>
|
||||
<.button id="intiface-connect-button" type="button" phx-click="connect">
|
||||
Connect to Intiface Central
|
||||
</.button>
|
||||
</section>
|
||||
<main class="flex flex-col gap-5 mt-10">
|
||||
<section
|
||||
:if={@remote_controller_status == :disconnected}
|
||||
id="share-session"
|
||||
class="text-center rounded-lg border-solid border-zinc-200 border-2 p-6"
|
||||
>
|
||||
<h3 class="text-2xl mb-6 font-semibold text-zinc-600">
|
||||
Share this session to start having fun
|
||||
</h3>
|
||||
|
||||
<section :if={@intiface_client_status == :connected} id="connected-devices-section">
|
||||
<h2 class="text-3xl mb-4 font-bold text-indigo-500">
|
||||
Connected devices
|
||||
</h2>
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<div class="flex w-full items-center">
|
||||
<span class="rounded-s-lg z-10 inline-flex flex-shrink-0 items-center border border-gray-300 bg-indigo-500 px-4 py-2.5 text-center text-sm font-medium text-white">
|
||||
URL
|
||||
</span>
|
||||
<div class="w-full">
|
||||
<input
|
||||
id="share-url"
|
||||
type="text"
|
||||
aria-describedby="helper-text-explanation"
|
||||
class="border-e-0 border-s-0 block w-full border border-gray-300 p-2.5 text-sm text-gray-500"
|
||||
value={@share_url}
|
||||
readonly
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<.button
|
||||
id="copyButton"
|
||||
phx-hook="CopyToClipboard"
|
||||
data-text-to-copy={@share_url}
|
||||
class="rounded-r-lg rounded-l-none flex-shrink-0 text-sm px-3 py-2 text-center "
|
||||
>
|
||||
<.icon name="hero-document-duplicate-solid" class="mr-1.5 -ml-0.5" /> Copy
|
||||
</.button>
|
||||
</div>
|
||||
|
||||
<p :if={Enum.empty?(@devices)} class="text-lg text-amber-500 font-semibold">
|
||||
<.icon name="hero-signal-slash" class="mr-1.5" /> No devices connected yet
|
||||
</p>
|
||||
<p class="font-semibold text-zinc-600">Or</p>
|
||||
|
||||
<div class="flex flex-col gap-5 mt-2">
|
||||
<%= for {index, device} <- @devices do %>
|
||||
<IntisyncWeb.DeviceCardComponent.view
|
||||
id={"device-#{index}"}
|
||||
device={device}
|
||||
disabled={true}
|
||||
/>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
<.button
|
||||
id="shareButton"
|
||||
phx-hook="NativeShare"
|
||||
data-url={@share_url}
|
||||
data-title="Share session url"
|
||||
data-text="Join an IntiSync session"
|
||||
phx-update="ignore"
|
||||
>
|
||||
<.icon name="hero-share-solid" class="mr-1.5 -ml-0.5" /> Share
|
||||
</.button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div id={@session_id} phx-hook="HubHook"></div>
|
||||
<section
|
||||
:if={@intiface_client_status == :disconnected}
|
||||
id="intiface-central-connect"
|
||||
class="text-center rounded-lg border-solid border-zinc-200 border-2 p-6"
|
||||
>
|
||||
<h3 class="text-2xl mb-6 font-semibold text-zinc-600">
|
||||
Connect to Intiface Central to control your devices
|
||||
</h3>
|
||||
|
||||
<.button id="intiface-connect-button" type="button" phx-click="connect">
|
||||
<.icon name="hero-link-solid" class="mr-1.5 -ml-0.5" /> Connect
|
||||
</.button>
|
||||
</section>
|
||||
|
||||
<section :if={@intiface_client_status == :connected} id="connected-devices-section">
|
||||
<h2 class="text-3xl mb-4 font-bold text-indigo-500">
|
||||
Connected devices
|
||||
</h2>
|
||||
|
||||
<p :if={Enum.empty?(@devices)} class="text-lg text-amber-500 font-semibold">
|
||||
<.icon name="hero-signal-slash" class="mr-1.5" /> No devices connected yet
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col gap-5 mt-2">
|
||||
<%= for {index, device} <- @devices do %>
|
||||
<IntisyncWeb.DeviceCardComponent.view
|
||||
id={"device-#{index}"}
|
||||
device={device}
|
||||
disabled={true}
|
||||
/>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div id={@session_id} phx-hook="Hub"></div>
|
||||
|
|
|
|||
|
|
@ -108,16 +108,47 @@ defmodule IntisyncWeb.HubLiveTest do
|
|||
refute view |> element("#remote-controller-connected-badge") |> has_element?()
|
||||
end
|
||||
|
||||
test "Share session section is shown when controller is disconnected", %{
|
||||
conn: conn,
|
||||
session_id: session_id
|
||||
} do
|
||||
{:ok, view, _html} = live(conn, ~p"/sessions/#{session_id}")
|
||||
|
||||
assert view |> element("#share-session") |> has_element?()
|
||||
end
|
||||
|
||||
test "Share session section is not shown when controller connects", %{
|
||||
conn: conn,
|
||||
session_id: session_id
|
||||
} do
|
||||
{:ok, view, _html} = live(conn, ~p"/sessions/#{session_id}")
|
||||
{:ok, remote_view, _html} = live(conn, ~p"/sessions/#{session_id}/remote")
|
||||
|
||||
refute view |> element("#share-session") |> has_element?()
|
||||
end
|
||||
|
||||
test "Share session section is shown again when controller disconnects", %{
|
||||
conn: conn,
|
||||
session_id: session_id
|
||||
} do
|
||||
{:ok, view, _html} = live(conn, ~p"/sessions/#{session_id}")
|
||||
{:ok, remote_view, _html} = live(conn, ~p"/sessions/#{session_id}/remote")
|
||||
|
||||
assert remote_view |> element("#nav-home-btn") |> render_click() |> follow_redirect(conn)
|
||||
|
||||
assert view |> element("#share-session") |> has_element?()
|
||||
end
|
||||
|
||||
test "The connect buttons section gets replaced by the devices section when intiface client connects",
|
||||
%{conn: conn, session_id: session_id} do
|
||||
{:ok, view, _html} = live(conn, ~p"/sessions/#{session_id}")
|
||||
|
||||
assert view |> element("#connect-buttons") |> has_element?()
|
||||
assert view |> element("#intiface-connect-button") |> has_element?()
|
||||
refute view |> element("#connected-devices-section") |> has_element?()
|
||||
|
||||
render_click(view, "connected", %{})
|
||||
|
||||
refute view |> element("#connect-buttons") |> has_element?()
|
||||
refute view |> element("#intiface-connect-button") |> has_element?()
|
||||
assert view |> element("#connected-devices-section") |> has_element?()
|
||||
end
|
||||
|
||||
|
|
@ -127,12 +158,12 @@ defmodule IntisyncWeb.HubLiveTest do
|
|||
|
||||
render_click(view, "connected", %{})
|
||||
|
||||
refute view |> element("#connect-buttons") |> has_element?()
|
||||
refute view |> element("#intiface-connect-button") |> has_element?()
|
||||
assert view |> element("#connected-devices-section") |> has_element?()
|
||||
|
||||
render_click(view, "disconnected", %{})
|
||||
|
||||
assert view |> element("#connect-buttons") |> has_element?()
|
||||
assert view |> element("#intiface-connect-button") |> has_element?()
|
||||
refute view |> element("#connected-devices-section") |> has_element?()
|
||||
end
|
||||
|
||||
|
|
@ -140,7 +171,7 @@ defmodule IntisyncWeb.HubLiveTest do
|
|||
%{conn: conn, session_id: session_id} do
|
||||
{:ok, view, _html} = live(conn, ~p"/sessions/#{session_id}")
|
||||
|
||||
assert view |> element("#connect-buttons") |> has_element?()
|
||||
assert view |> element("#intiface-connect-button") |> has_element?()
|
||||
|
||||
assert view |> element("#intiface-connect-button") |> render_click()
|
||||
|
||||
|
|
@ -229,7 +260,7 @@ defmodule IntisyncWeb.HubLiveTest do
|
|||
|
||||
render_click(view, "disconnected", %{})
|
||||
|
||||
assert view |> element("#connect-buttons") |> has_element?()
|
||||
assert view |> element("#intiface-connect-button") |> has_element?()
|
||||
refute view |> element("#connected-devices-section") |> has_element?()
|
||||
|
||||
render_click(view, "connected", %{})
|
||||
|
|
|
|||
Loading…
Reference in a new issue