mirror of
https://github.com/algora-io/tv.git
synced 2024-11-26 01:00:20 +02:00
add typescript (#5)
* use typescript * fix typing issues * update tailwind config * upgrade deps * update comment
This commit is contained in:
parent
b7ed13e06f
commit
446efb3c62
328
assets/js/app.js
328
assets/js/app.js
@ -1,328 +0,0 @@
|
||||
import "phoenix_html";
|
||||
import { Socket } from "phoenix";
|
||||
import { LiveSocket } from "phoenix_live_view";
|
||||
import Chat from "./user_socket";
|
||||
import topbar from "../vendor/topbar";
|
||||
import videojs from "../vendor/video";
|
||||
import "../vendor/videojs-youtube";
|
||||
|
||||
let isVisible = (el) =>
|
||||
!!(el.offsetWidth || el.offsetHeight || el.getClientRects().length > 0);
|
||||
|
||||
let execJS = (selector, attr) => {
|
||||
document
|
||||
.querySelectorAll(selector)
|
||||
.forEach((el) => liveSocket.execJS(el, el.getAttribute(attr)));
|
||||
};
|
||||
|
||||
let Hooks = {};
|
||||
|
||||
Hooks.Flash = {
|
||||
mounted() {
|
||||
let hide = () =>
|
||||
liveSocket.execJS(this.el, this.el.getAttribute("phx-click"));
|
||||
this.timer = setTimeout(() => hide(), 8000);
|
||||
this.el.addEventListener("phx:hide-start", () => clearTimeout(this.timer));
|
||||
this.el.addEventListener("mouseover", () => {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => hide(), 8000);
|
||||
});
|
||||
},
|
||||
destroyed() {
|
||||
clearTimeout(this.timer);
|
||||
},
|
||||
};
|
||||
|
||||
Hooks.Menu = {
|
||||
getAttr(name) {
|
||||
let val = this.el.getAttribute(name);
|
||||
if (val === null) {
|
||||
throw new Error(`no ${name} attribute configured for menu`);
|
||||
}
|
||||
return val;
|
||||
},
|
||||
reset() {
|
||||
this.enabled = false;
|
||||
this.activeClass = this.getAttr("data-active-class");
|
||||
this.deactivate(this.menuItems());
|
||||
this.activeItem = null;
|
||||
window.removeEventListener("keydown", this.handleKeyDown);
|
||||
},
|
||||
destroyed() {
|
||||
this.reset();
|
||||
},
|
||||
mounted() {
|
||||
this.menuItemsContainer = document.querySelector(
|
||||
`[aria-labelledby="${this.el.id}"]`
|
||||
);
|
||||
this.reset();
|
||||
this.handleKeyDown = (e) => this.onKeyDown(e);
|
||||
this.el.addEventListener("keydown", (e) => {
|
||||
if (
|
||||
(e.key === "Enter" || e.key === " ") &&
|
||||
e.currentTarget.isSameNode(this.el)
|
||||
) {
|
||||
this.enabled = true;
|
||||
}
|
||||
});
|
||||
this.el.addEventListener("click", (e) => {
|
||||
if (!e.currentTarget.isSameNode(this.el)) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", this.handleKeyDown);
|
||||
// disable if button clicked and click was not a keyboard event
|
||||
if (this.enabled) {
|
||||
window.requestAnimationFrame(() => this.activate(0));
|
||||
}
|
||||
});
|
||||
this.menuItemsContainer.addEventListener("phx:hide-start", () =>
|
||||
this.reset()
|
||||
);
|
||||
},
|
||||
activate(index, fallbackIndex) {
|
||||
let menuItems = this.menuItems();
|
||||
this.activeItem = menuItems[index] || menuItems[fallbackIndex];
|
||||
this.activeItem.classList.add(this.activeClass);
|
||||
this.activeItem.focus();
|
||||
},
|
||||
deactivate(items) {
|
||||
items.forEach((item) => item.classList.remove(this.activeClass));
|
||||
},
|
||||
menuItems() {
|
||||
return Array.from(
|
||||
this.menuItemsContainer.querySelectorAll("[role=menuitem]")
|
||||
);
|
||||
},
|
||||
onKeyDown(e) {
|
||||
if (e.key === "Escape") {
|
||||
document.body.click();
|
||||
this.el.focus();
|
||||
this.reset();
|
||||
} else if (e.key === "Enter" && !this.activeItem) {
|
||||
this.activate(0);
|
||||
} else if (e.key === "Enter") {
|
||||
this.activeItem.click();
|
||||
}
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
let menuItems = this.menuItems();
|
||||
this.deactivate(menuItems);
|
||||
this.activate(menuItems.indexOf(this.activeItem) + 1, 0);
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
let menuItems = this.menuItems();
|
||||
this.deactivate(menuItems);
|
||||
this.activate(
|
||||
menuItems.indexOf(this.activeItem) - 1,
|
||||
menuItems.length - 1
|
||||
);
|
||||
} else if (e.key === "Tab") {
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Hooks.VideoPlayer = {
|
||||
mounted() {
|
||||
const backdrop = document.querySelector("#video-backdrop");
|
||||
|
||||
this.player = videojs("video-player", {
|
||||
autoplay: true,
|
||||
liveui: true,
|
||||
html5: {
|
||||
vhs: {
|
||||
llhls: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const playVideo = ({ detail }) => {
|
||||
const { player } = detail;
|
||||
this.player.options({
|
||||
techOrder: [player.type === "video/youtube" ? "youtube" : "html5"],
|
||||
});
|
||||
this.player.src({ src: player.src, type: player.type });
|
||||
this.player.play();
|
||||
this.player.el().parentElement.classList.remove("hidden");
|
||||
this.player.el().parentElement.classList.add("flex");
|
||||
backdrop.classList.remove("opacity-10");
|
||||
backdrop.classList.add("opacity-20");
|
||||
window.scrollTo(0, 0);
|
||||
};
|
||||
|
||||
window.addEventListener("js:play_video", playVideo);
|
||||
this.handleEvent("js:play_video", playVideo);
|
||||
|
||||
this.handleEvent("join_chat", Chat.join);
|
||||
},
|
||||
};
|
||||
|
||||
Hooks.NavBar = {
|
||||
mounted() {
|
||||
const offset = 16;
|
||||
this.isOpaque = false;
|
||||
|
||||
const onScroll = () => {
|
||||
if (!this.isOpaque && window.scrollY > offset) {
|
||||
this.isOpaque = true;
|
||||
this.el.classList.add("bg-gray-950");
|
||||
this.el.classList.remove("bg-transparent");
|
||||
} else if (this.isOpaque && window.scrollY <= offset) {
|
||||
this.isOpaque = false;
|
||||
this.el.classList.add("bg-transparent");
|
||||
this.el.classList.remove("bg-gray-950");
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
},
|
||||
};
|
||||
|
||||
// Accessible focus handling
|
||||
let Focus = {
|
||||
focusMain() {
|
||||
let target =
|
||||
document.querySelector("main h1") || document.querySelector("main");
|
||||
if (target) {
|
||||
let origTabIndex = target.tabIndex;
|
||||
target.tabIndex = -1;
|
||||
target.focus();
|
||||
target.tabIndex = origTabIndex;
|
||||
}
|
||||
},
|
||||
// Subject to the W3C Software License at https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
|
||||
isFocusable(el) {
|
||||
if (
|
||||
el.tabIndex > 0 ||
|
||||
(el.tabIndex === 0 && el.getAttribute("tabIndex") !== null)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (el.disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (el.nodeName) {
|
||||
case "A":
|
||||
return !!el.href && el.rel !== "ignore";
|
||||
case "INPUT":
|
||||
return el.type != "hidden" && el.type !== "file";
|
||||
case "BUTTON":
|
||||
case "SELECT":
|
||||
case "TEXTAREA":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
// Subject to the W3C Software License at https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
|
||||
attemptFocus(el) {
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
if (!this.isFocusable(el)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
el.focus();
|
||||
} catch (e) {}
|
||||
|
||||
return document.activeElement === el;
|
||||
},
|
||||
// Subject to the W3C Software License at https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
|
||||
focusFirstDescendant(el) {
|
||||
for (let i = 0; i < el.childNodes.length; i++) {
|
||||
let child = el.childNodes[i];
|
||||
if (this.attemptFocus(child) || this.focusFirstDescendant(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
// Subject to the W3C Software License at https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
|
||||
focusLastDescendant(element) {
|
||||
for (let i = element.childNodes.length - 1; i >= 0; i--) {
|
||||
let child = element.childNodes[i];
|
||||
if (this.attemptFocus(child) || this.focusLastDescendant(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
let csrfToken = document
|
||||
.querySelector("meta[name='csrf-token']")
|
||||
.getAttribute("content");
|
||||
let liveSocket = new LiveSocket("/live", Socket, {
|
||||
hooks: Hooks,
|
||||
params: { _csrf_token: csrfToken },
|
||||
dom: {
|
||||
onNodeAdded(node) {
|
||||
if (node instanceof HTMLElement && node.autofocus) {
|
||||
node.focus();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let routeUpdated = () => {
|
||||
// TODO: uncomment
|
||||
// Focus.focusMain();
|
||||
};
|
||||
|
||||
// Show progress bar on live navigation and form submits
|
||||
topbar.config({
|
||||
barColors: { 0: "rgba(147, 51, 234, 1)" },
|
||||
shadowColor: "rgba(0, 0, 0, .3)",
|
||||
});
|
||||
window.addEventListener("phx:page-loading-start", (info) =>
|
||||
topbar.delayedShow(200)
|
||||
);
|
||||
window.addEventListener("phx:page-loading-stop", (info) => topbar.hide());
|
||||
|
||||
// Accessible routing
|
||||
window.addEventListener("phx:page-loading-stop", routeUpdated);
|
||||
|
||||
window.addEventListener("js:exec", (e) =>
|
||||
e.target[e.detail.call](...e.detail.args)
|
||||
);
|
||||
window.addEventListener("js:focus", (e) => {
|
||||
let parent = document.querySelector(e.detail.parent);
|
||||
if (parent && isVisible(parent)) {
|
||||
e.target.focus();
|
||||
}
|
||||
});
|
||||
window.addEventListener("js:focus-closest", (e) => {
|
||||
let el = e.target;
|
||||
let sibling = el.nextElementSibling;
|
||||
while (sibling) {
|
||||
if (isVisible(sibling) && Focus.attemptFocus(sibling)) {
|
||||
return;
|
||||
}
|
||||
sibling = sibling.nextElementSibling;
|
||||
}
|
||||
sibling = el.previousElementSibling;
|
||||
while (sibling) {
|
||||
if (isVisible(sibling) && Focus.attemptFocus(sibling)) {
|
||||
return;
|
||||
}
|
||||
sibling = sibling.previousElementSibling;
|
||||
}
|
||||
Focus.attemptFocus(el.parent) || Focus.focusMain();
|
||||
});
|
||||
window.addEventListener("phx:remove-el", (e) =>
|
||||
document.getElementById(e.detail.id).remove()
|
||||
);
|
||||
|
||||
// connect if there are any LiveViews on the page
|
||||
liveSocket.getSocket().onOpen(() => execJS("#connection-status", "js-hide"));
|
||||
liveSocket.getSocket().onError(() => execJS("#connection-status", "js-show"));
|
||||
liveSocket.connect();
|
||||
|
||||
// expose liveSocket on window for web console debug logs and latency simulation:
|
||||
// >> liveSocket.enableDebug()
|
||||
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
|
||||
// >> liveSocket.disableLatencySim()
|
||||
window.liveSocket = liveSocket;
|
336
assets/js/app.ts
Normal file
336
assets/js/app.ts
Normal file
@ -0,0 +1,336 @@
|
||||
import "phoenix_html";
|
||||
import { Socket } from "phoenix";
|
||||
import { LiveSocket, type ViewHook } from "phoenix_live_view";
|
||||
import Chat from "./user_socket";
|
||||
import topbar from "../vendor/topbar";
|
||||
import videojs from "../vendor/video";
|
||||
import "../vendor/videojs-youtube";
|
||||
|
||||
// TODO: add eslint & biome
|
||||
// TODO: enable strict mode
|
||||
// TODO: eliminate anys
|
||||
|
||||
let isVisible = (el) =>
|
||||
!!(el.offsetWidth || el.offsetHeight || el.getClientRects().length > 0);
|
||||
|
||||
let execJS = (selector, attr) => {
|
||||
document
|
||||
.querySelectorAll(selector)
|
||||
.forEach((el) => liveSocket.execJS(el, el.getAttribute(attr)));
|
||||
};
|
||||
|
||||
const Hooks = {
|
||||
Flash: {
|
||||
mounted() {
|
||||
let hide = () =>
|
||||
liveSocket.execJS(this.el, this.el.getAttribute("phx-click"));
|
||||
this.timer = setTimeout(() => hide(), 8000);
|
||||
this.el.addEventListener("phx:hide-start", () =>
|
||||
clearTimeout(this.timer)
|
||||
);
|
||||
this.el.addEventListener("mouseover", () => {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => hide(), 8000);
|
||||
});
|
||||
},
|
||||
destroyed() {
|
||||
clearTimeout(this.timer);
|
||||
},
|
||||
},
|
||||
Menu: {
|
||||
getAttr(name) {
|
||||
let val = this.el.getAttribute(name);
|
||||
if (val === null) {
|
||||
throw new Error(`no ${name} attribute configured for menu`);
|
||||
}
|
||||
return val;
|
||||
},
|
||||
reset() {
|
||||
this.enabled = false;
|
||||
this.activeClass = this.getAttr("data-active-class");
|
||||
this.deactivate(this.menuItems());
|
||||
this.activeItem = null;
|
||||
window.removeEventListener("keydown", this.handleKeyDown);
|
||||
},
|
||||
destroyed() {
|
||||
this.reset();
|
||||
},
|
||||
mounted() {
|
||||
this.menuItemsContainer = document.querySelector(
|
||||
`[aria-labelledby="${this.el.id}"]`
|
||||
);
|
||||
this.reset();
|
||||
this.handleKeyDown = (e) => this.onKeyDown(e);
|
||||
this.el.addEventListener("keydown", (e) => {
|
||||
if (
|
||||
(e.key === "Enter" || e.key === " ") &&
|
||||
e.currentTarget.isSameNode(this.el)
|
||||
) {
|
||||
this.enabled = true;
|
||||
}
|
||||
});
|
||||
this.el.addEventListener("click", (e) => {
|
||||
if (!e.currentTarget.isSameNode(this.el)) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", this.handleKeyDown);
|
||||
// disable if button clicked and click was not a keyboard event
|
||||
if (this.enabled) {
|
||||
window.requestAnimationFrame(() => this.activate(0));
|
||||
}
|
||||
});
|
||||
this.menuItemsContainer.addEventListener("phx:hide-start", () =>
|
||||
this.reset()
|
||||
);
|
||||
},
|
||||
activate(index, fallbackIndex) {
|
||||
let menuItems = this.menuItems();
|
||||
this.activeItem = menuItems[index] || menuItems[fallbackIndex];
|
||||
this.activeItem.classList.add(this.activeClass);
|
||||
this.activeItem.focus();
|
||||
},
|
||||
deactivate(items) {
|
||||
items.forEach((item) => item.classList.remove(this.activeClass));
|
||||
},
|
||||
menuItems() {
|
||||
return Array.from(
|
||||
this.menuItemsContainer.querySelectorAll("[role=menuitem]")
|
||||
);
|
||||
},
|
||||
onKeyDown(e) {
|
||||
if (e.key === "Escape") {
|
||||
document.body.click();
|
||||
this.el.focus();
|
||||
this.reset();
|
||||
} else if (e.key === "Enter" && !this.activeItem) {
|
||||
this.activate(0);
|
||||
} else if (e.key === "Enter") {
|
||||
this.activeItem.click();
|
||||
}
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
let menuItems = this.menuItems();
|
||||
this.deactivate(menuItems);
|
||||
this.activate(menuItems.indexOf(this.activeItem) + 1, 0);
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
let menuItems = this.menuItems();
|
||||
this.deactivate(menuItems);
|
||||
this.activate(
|
||||
menuItems.indexOf(this.activeItem) - 1,
|
||||
menuItems.length - 1
|
||||
);
|
||||
} else if (e.key === "Tab") {
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
},
|
||||
VideoPlayer: {
|
||||
mounted() {
|
||||
const backdrop = document.querySelector("#video-backdrop");
|
||||
|
||||
this.player = videojs("video-player", {
|
||||
autoplay: true,
|
||||
liveui: true,
|
||||
html5: {
|
||||
vhs: {
|
||||
llhls: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const playVideo = ({ detail }) => {
|
||||
const { player } = detail;
|
||||
this.player.options({
|
||||
techOrder: [player.type === "video/youtube" ? "youtube" : "html5"],
|
||||
});
|
||||
this.player.src({ src: player.src, type: player.type });
|
||||
this.player.play();
|
||||
this.player.el().parentElement.classList.remove("hidden");
|
||||
this.player.el().parentElement.classList.add("flex");
|
||||
window.scrollTo(0, 0);
|
||||
|
||||
if (backdrop) {
|
||||
backdrop.classList.remove("opacity-10");
|
||||
backdrop.classList.add("opacity-20");
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("js:play_video", playVideo);
|
||||
this.handleEvent("js:play_video", playVideo);
|
||||
|
||||
this.handleEvent("join_chat", Chat.join);
|
||||
},
|
||||
},
|
||||
NavBar: {
|
||||
mounted() {
|
||||
const offset = 16;
|
||||
this.isOpaque = false;
|
||||
|
||||
const onScroll = () => {
|
||||
if (!this.isOpaque && window.scrollY > offset) {
|
||||
this.isOpaque = true;
|
||||
this.el.classList.add("bg-gray-950");
|
||||
this.el.classList.remove("bg-transparent");
|
||||
} else if (this.isOpaque && window.scrollY <= offset) {
|
||||
this.isOpaque = false;
|
||||
this.el.classList.add("bg-transparent");
|
||||
this.el.classList.remove("bg-gray-950");
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
},
|
||||
},
|
||||
} satisfies Record<string, Partial<ViewHook> & Record<string, unknown>>;
|
||||
|
||||
// Accessible focus handling
|
||||
let Focus = {
|
||||
focusMain() {
|
||||
let target =
|
||||
document.querySelector<HTMLElement>("main h1") ||
|
||||
document.querySelector<HTMLElement>("main");
|
||||
if (target) {
|
||||
let origTabIndex = target.tabIndex;
|
||||
target.tabIndex = -1;
|
||||
target.focus();
|
||||
target.tabIndex = origTabIndex;
|
||||
}
|
||||
},
|
||||
// Subject to the W3C Software License at https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
|
||||
isFocusable(el) {
|
||||
if (
|
||||
el.tabIndex > 0 ||
|
||||
(el.tabIndex === 0 && el.getAttribute("tabIndex") !== null)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (el.disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (el.nodeName) {
|
||||
case "A":
|
||||
return !!el.href && el.rel !== "ignore";
|
||||
case "INPUT":
|
||||
return el.type != "hidden" && el.type !== "file";
|
||||
case "BUTTON":
|
||||
case "SELECT":
|
||||
case "TEXTAREA":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
// Subject to the W3C Software License at https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
|
||||
attemptFocus(el) {
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
if (!this.isFocusable(el)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
el.focus();
|
||||
} catch (e) {}
|
||||
|
||||
return document.activeElement === el;
|
||||
},
|
||||
// Subject to the W3C Software License at https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
|
||||
focusFirstDescendant(el) {
|
||||
for (let i = 0; i < el.childNodes.length; i++) {
|
||||
let child = el.childNodes[i];
|
||||
if (this.attemptFocus(child) || this.focusFirstDescendant(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
// Subject to the W3C Software License at https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
|
||||
focusLastDescendant(element) {
|
||||
for (let i = element.childNodes.length - 1; i >= 0; i--) {
|
||||
let child = element.childNodes[i];
|
||||
if (this.attemptFocus(child) || this.focusLastDescendant(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
let csrfToken = document
|
||||
.querySelector("meta[name='csrf-token']")!
|
||||
.getAttribute("content");
|
||||
let liveSocket = new LiveSocket("/live", Socket, {
|
||||
hooks: Hooks,
|
||||
params: { _csrf_token: csrfToken },
|
||||
dom: {
|
||||
onNodeAdded(node) {
|
||||
if (node instanceof HTMLElement && node.autofocus) {
|
||||
node.focus();
|
||||
}
|
||||
return node;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let routeUpdated = () => {
|
||||
// TODO: uncomment
|
||||
// Focus.focusMain();
|
||||
};
|
||||
|
||||
// Show progress bar on live navigation and form submits
|
||||
topbar.config({
|
||||
barColors: { 0: "rgba(147, 51, 234, 1)" },
|
||||
shadowColor: "rgba(0, 0, 0, .3)",
|
||||
});
|
||||
window.addEventListener("phx:page-loading-start", (info) =>
|
||||
topbar.delayedShow(200)
|
||||
);
|
||||
window.addEventListener("phx:page-loading-stop", (info) => topbar.hide());
|
||||
|
||||
// Accessible routing
|
||||
window.addEventListener("phx:page-loading-stop", routeUpdated);
|
||||
|
||||
window.addEventListener("js:exec", (e) =>
|
||||
e.target[e.detail.call](...e.detail.args)
|
||||
);
|
||||
window.addEventListener("js:focus", (e) => {
|
||||
let parent = document.querySelector(e.detail.parent);
|
||||
if (parent && isVisible(parent)) {
|
||||
(e.target as any).focus();
|
||||
}
|
||||
});
|
||||
window.addEventListener("js:focus-closest", (e) => {
|
||||
let el = e.target;
|
||||
let sibling = el.nextElementSibling;
|
||||
while (sibling) {
|
||||
if (isVisible(sibling) && Focus.attemptFocus(sibling)) {
|
||||
return;
|
||||
}
|
||||
sibling = sibling.nextElementSibling;
|
||||
}
|
||||
sibling = el.previousElementSibling;
|
||||
while (sibling) {
|
||||
if (isVisible(sibling) && Focus.attemptFocus(sibling)) {
|
||||
return;
|
||||
}
|
||||
sibling = sibling.previousElementSibling;
|
||||
}
|
||||
Focus.attemptFocus((el as any).parent) || Focus.focusMain();
|
||||
});
|
||||
window.addEventListener("phx:remove-el", (e) =>
|
||||
document.getElementById(e.detail.id)?.remove()
|
||||
);
|
||||
|
||||
// connect if there are any LiveViews on the page
|
||||
liveSocket.getSocket().onOpen(() => execJS("#connection-status", "js-hide"));
|
||||
liveSocket.getSocket().onError(() => execJS("#connection-status", "js-show"));
|
||||
liveSocket.connect();
|
||||
|
||||
// expose liveSocket on window for web console debug logs and latency simulation:
|
||||
// >> liveSocket.enableDebug()
|
||||
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
|
||||
// >> liveSocket.disableLatencySim()
|
||||
window.liveSocket = liveSocket;
|
25
assets/js/global.d.ts
vendored
Normal file
25
assets/js/global.d.ts
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
import { type Channel } from "phoenix";
|
||||
import { type LiveSocket } from "phoenix_live_view";
|
||||
|
||||
interface PhxEvent extends Event {
|
||||
target: Element;
|
||||
detail: Record<string, any>;
|
||||
}
|
||||
|
||||
type PhxEventKey = `js:${string}` | `phx:${string}`;
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
liveSocket: LiveSocket;
|
||||
userToken?: string;
|
||||
channel?: Channel;
|
||||
addEventListener<K extends keyof WindowEventMap | PhxEventKey>(
|
||||
type: K,
|
||||
listener: (
|
||||
this: Window,
|
||||
ev: K extends keyof WindowEventMap ? WindowEventMap[K] : PhxEvent
|
||||
) => any,
|
||||
options?: boolean | AddEventListenerOptions | undefined
|
||||
): void;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { Socket } from "phoenix";
|
||||
import { type Channel, Socket } from "phoenix";
|
||||
|
||||
const system_user = (sender) => sender === "algora";
|
||||
const systemUser = (sender) => sender === "algora";
|
||||
|
||||
const init = () => {
|
||||
let socket = new Socket("/socket", { params: { token: window.userToken } });
|
||||
@ -9,12 +9,16 @@ const init = () => {
|
||||
const main = document.querySelector("body");
|
||||
const sidePanel = document.querySelector("#video-side-panel");
|
||||
|
||||
let channel;
|
||||
if (!main || !sidePanel) {
|
||||
throw new Error("Could not initialize chat");
|
||||
}
|
||||
|
||||
let channel: Channel;
|
||||
let chatInput;
|
||||
let chatMessages;
|
||||
let handleSend;
|
||||
|
||||
const leave = (channel) => {
|
||||
const leave = (channel: Channel) => {
|
||||
channel.leave();
|
||||
if (chatInput) {
|
||||
chatInput.value = "";
|
||||
@ -57,9 +61,7 @@ const init = () => {
|
||||
const senderItem = document.createElement("span");
|
||||
senderItem.innerText = `${payload.user.handle}: `;
|
||||
senderItem.className = `font-semibold ${
|
||||
system_user(payload.user.handle)
|
||||
? "text-emerald-400"
|
||||
: "text-indigo-400"
|
||||
systemUser(payload.user.handle) ? "text-emerald-400" : "text-indigo-400"
|
||||
}`;
|
||||
|
||||
const bodyItem = document.createElement("span");
|
22
assets/package.json
Normal file
22
assets/package.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@algora/tv",
|
||||
"version": "0.0.1",
|
||||
"description": "The interactive livestreaming & video sharing service for developers",
|
||||
"main": "app.ts",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"video",
|
||||
"hls",
|
||||
"rtmp",
|
||||
"video-sharing",
|
||||
"livestreaming"
|
||||
],
|
||||
"author": "Algora PBC",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@types/phoenix": "^1.6.4",
|
||||
"@types/phoenix_live_view": "^0.18.4"
|
||||
}
|
||||
}
|
25
assets/pnpm-lock.yaml
Normal file
25
assets/pnpm-lock.yaml
Normal file
@ -0,0 +1,25 @@
|
||||
lockfileVersion: '6.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
devDependencies:
|
||||
'@types/phoenix':
|
||||
specifier: ^1.6.4
|
||||
version: 1.6.4
|
||||
'@types/phoenix_live_view':
|
||||
specifier: ^0.18.4
|
||||
version: 0.18.4
|
||||
|
||||
packages:
|
||||
|
||||
/@types/phoenix@1.6.4:
|
||||
resolution: {integrity: sha512-B34A7uot1Cv0XtaHRYDATltAdKx0BvVKNgYNqE4WjtPUa4VQJM7kxeXcVKaH+KS+kCmZ+6w+QaUdcljiheiBJA==}
|
||||
dev: true
|
||||
|
||||
/@types/phoenix_live_view@0.18.4:
|
||||
resolution: {integrity: sha512-9mq6zRZfCtY8f4Kiu9ca0YodlNoL5kPfz6AO8AMIylftoT59re5+l5BxPISt/NJXyj1S/gRJmwpHgyVsyEk8cg==}
|
||||
dependencies:
|
||||
'@types/phoenix': 1.6.4
|
||||
dev: true
|
@ -19,7 +19,12 @@ const gray = {
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
content: ["./js/**/*.js", "../lib/*_web.ex", "../lib/*_web/**/*.*ex"],
|
||||
content: [
|
||||
"./js/**/*.js",
|
||||
"./js/**/*.ts",
|
||||
"../lib/*_web.ex",
|
||||
"../lib/*_web/**/*.*ex",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
|
@ -29,17 +29,18 @@ config :algora, AlgoraWeb.Endpoint,
|
||||
]
|
||||
|
||||
config :esbuild,
|
||||
version: "0.12.18",
|
||||
default: [
|
||||
args: ~w(js/app.js --bundle --target=es2016 --outdir=../priv/static/assets),
|
||||
version: "0.17.11",
|
||||
tv: [
|
||||
args:
|
||||
~w(js/app.ts --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
|
||||
cd: Path.expand("../assets", __DIR__),
|
||||
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
|
||||
]
|
||||
|
||||
# Configure tailwind (the version is required)
|
||||
config :tailwind,
|
||||
version: "3.1.8",
|
||||
default: [
|
||||
version: "3.4.0",
|
||||
tv: [
|
||||
args: ~w(
|
||||
--config=tailwind.config.js
|
||||
--input=css/app.css
|
||||
|
@ -52,18 +52,8 @@ config :algora, AlgoraWeb.Endpoint,
|
||||
code_reloader: true,
|
||||
check_origin: false,
|
||||
watchers: [
|
||||
# Start the esbuild watcher by calling Esbuild.install_and_run(:default, args)
|
||||
esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]},
|
||||
tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}
|
||||
|
||||
# npx: [
|
||||
# "tailwindcss",
|
||||
# "--input=css/app.css",
|
||||
# "--output=../priv/static/assets/app.css",
|
||||
# "--postcss",
|
||||
# "--watch",
|
||||
# cd: Path.expand("../assets", __DIR__)
|
||||
# ]
|
||||
esbuild: {Esbuild, :install_and_run, [:tv, ~w(--sourcemap=inline --watch)]},
|
||||
tailwind: {Tailwind, :install_and_run, [:tv, ~w(--watch)]}
|
||||
]
|
||||
|
||||
# ## SSL Support
|
||||
|
@ -97,8 +97,10 @@ defmodule AlgoraWeb do
|
||||
|
||||
defp html_helpers do
|
||||
quote do
|
||||
# Use all HTML functionality (forms, tags, etc)
|
||||
use Phoenix.HTML
|
||||
import Phoenix.HTML
|
||||
import Phoenix.HTML.Form
|
||||
# TODO: is this needed?
|
||||
use PhoenixHTMLHelpers
|
||||
|
||||
# Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc)
|
||||
use Phoenix.Component
|
||||
|
19
mix.exs
19
mix.exs
@ -37,7 +37,7 @@ defmodule Algora.MixProject do
|
||||
{:dialyxir, "~> 1.3", only: [:dev], runtime: false},
|
||||
{:ecto_network, "~> 1.3.0"},
|
||||
{:ecto_sql, "~> 3.6"},
|
||||
{:esbuild, "~> 0.2", runtime: Mix.env() == :dev},
|
||||
{:esbuild, "~> 0.8", runtime: Mix.env() == :dev},
|
||||
{:ex_m3u8, "~> 0.9.0"},
|
||||
{:exsync, "~> 0.2", only: :dev},
|
||||
{:ffmpex, "~> 0.10.0"},
|
||||
@ -53,15 +53,16 @@ defmodule Algora.MixProject do
|
||||
{:mint, "~> 1.0"},
|
||||
{:oban, "~> 2.16"},
|
||||
{:phoenix_ecto, "~> 4.4"},
|
||||
{:phoenix_html, "~> 3.3", override: true},
|
||||
{:phoenix_live_dashboard, "~> 0.7.2"},
|
||||
{:phoenix_html, "~> 4.0", override: true},
|
||||
{:phoenix_html_helpers, "~> 1.0"},
|
||||
{:phoenix_live_dashboard, "~> 0.8.3"},
|
||||
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
||||
{:phoenix_live_view, "~> 0.18.16"},
|
||||
{:phoenix, "~> 1.7.1"},
|
||||
{:phoenix_live_view, "~> 0.20.2"},
|
||||
{:phoenix, "~> 1.7.11"},
|
||||
{:plug_cowboy, "~> 2.5"},
|
||||
{:postgrex, ">= 0.0.0"},
|
||||
{:swoosh, "~> 1.3"},
|
||||
{:tailwind, "~> 0.1"},
|
||||
{:tailwind, "~> 0.2", runtime: Mix.env() == :dev},
|
||||
{:telemetry_metrics, "~> 0.6"},
|
||||
{:telemetry_poller, "~> 1.0"},
|
||||
{:thumbnex, "~> 0.5.0"},
|
||||
@ -86,9 +87,11 @@ defmodule Algora.MixProject do
|
||||
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
|
||||
"ecto.reset": ["ecto.drop", "ecto.setup"],
|
||||
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
|
||||
"assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"],
|
||||
"assets.build": ["tailwind tv", "esbuild tv"],
|
||||
"assets.deploy": [
|
||||
"tailwind default --minify",
|
||||
"esbuild default --minify",
|
||||
"tailwind tv --minify",
|
||||
"esbuild tv --minify",
|
||||
"phx.digest"
|
||||
]
|
||||
]
|
||||
|
31
mix.lock
31
mix.lock
@ -9,9 +9,9 @@
|
||||
"coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"},
|
||||
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
|
||||
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
|
||||
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
|
||||
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
|
||||
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
||||
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
|
||||
"cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
|
||||
"credo": {:hex, :credo, "1.7.4", "68ca5cf89071511c12fd9919eb84e388d231121988f6932756596195ccf7fd35", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9cf776d062c78bbe0f0de1ecaee183f18f2c3ec591326107989b054b7dddefc2"},
|
||||
"db_connection": {:hex, :db_connection, "2.4.2", "f92e79aff2375299a16bcb069a14ee8615c3414863a6fef93156aee8e86c2ff3", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4fe53ca91b99f55ea249693a0229356a08f4d1a7931d8ffa79289b145fe83668"},
|
||||
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
||||
@ -22,7 +22,7 @@
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.9.0", "2bb21210a2a13317e098a420a8c1cc58b0c3421ab8e3acfa96417dab7817918c", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a8f3f720073b8b1ac4c978be25fa7960ed7fd44997420c304a4a2e200b596453"},
|
||||
"elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"},
|
||||
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
|
||||
"esbuild": {:hex, :esbuild, "0.5.0", "d5bb08ff049d7880ee3609ed5c4b864bd2f46445ea40b16b4acead724fb4c4a3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "f183a0b332d963c4cfaf585477695ea59eef9a6f2204fdd0efa00e099694ffe5"},
|
||||
"esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"},
|
||||
"ex_aws": {:hex, :ex_aws, "2.5.1", "7418917974ea42e9e84b25e88b9f3d21a861d5f953ad453e212f48e593d8d39f", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1b95431f70c446fa1871f0eb9b183043c5a625f75f9948a42d25f43ae2eff12b"},
|
||||
"ex_aws_s3": {:hex, :ex_aws_s3, "2.5.3", "422468e5c3e1a4da5298e66c3468b465cfd354b842e512cb1f6fbbe4e2f5bdaf", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "4f09dd372cc386550e484808c5ac5027766c8d0cd8271ccc578b82ee6ef4f3b8"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"},
|
||||
@ -74,17 +74,18 @@
|
||||
"numbers": {:hex, :numbers, "5.2.4", "f123d5bb7f6acc366f8f445e10a32bd403c8469bdbce8ce049e1f0972b607080", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "eeccf5c61d5f4922198395bf87a465b6f980b8b862dd22d28198c5e6fab38582"},
|
||||
"oban": {:hex, :oban, "2.17.3", "ddfd5710aadcd550d2e174c8d73ce5f1865601418cf54a91775f20443fb832b7", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "452eada8bfe0d0fefd0740ab5fa8cf3ef6c375df0b4a3c3805d179022a04738a"},
|
||||
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
|
||||
"phoenix": {:hex, :phoenix, "1.7.1", "a029bde19d9c3b559e5c3d06c78b76e81396bedd456a6acedb42f9c7b2e535a9", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.4", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "ea9d4a85c3592e37efa07d0dc013254fda445885facaefddcbf646375c116457"},
|
||||
"phoenix": {:hex, :phoenix, "1.7.11", "1d88fc6b05ab0c735b250932c4e6e33bfa1c186f76dcf623d8dd52f07d6379c7", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "b1ec57f2e40316b306708fe59b92a16b9f6f4bf50ccfa41aa8c7feb79e0ec02a"},
|
||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"},
|
||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"},
|
||||
"phoenix_html_helpers": {:hex, :phoenix_html_helpers, "1.0.1", "7eed85c52eff80a179391036931791ee5d2f713d76a81d0d2c6ebafe1e11e5ec", [:mix], [{:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cffd2385d1fa4f78b04432df69ab8da63dc5cf63e07b713a4dcf36a3740e3090"},
|
||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.3", "7ff51c9b6609470f681fbea20578dede0e548302b0c8bdf338b5a753a4f045bf", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "f9470a0a8bae4f56430a23d42f977b5a6205fdba6559d76f932b876bfaec652d"},
|
||||
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.0", "4fe222c0be55fdc3f9c711e24955fc42a7cd9b7a2f5f406f2580a567c335a573", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "bebf0fc2d2113b61cb5968f585367234b7b4c21d963d691de7b4b2dc6cdaae6f"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.16", "781c6a3ac49e0451ca403848b40807171caea400896fe8ed8e5ddd6106ad5580", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "09e6ae2babe62f74bfcd1e3cac1a9b0e2c262557cc566300a843425c9cb6842a"},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
|
||||
"phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.20.12", "3f4b5849b8018023c01e41a7da2e6c986222cc3f0282858f8af11221638645cb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ae3a143cc33325f3a4c192b7da1726e6665e154c50e1461af4cd7d561ccfd9ab"},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
|
||||
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
|
||||
"plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.7.0", "3ae9369c60641084363b08fe90267cbdd316df57e3557ea522114b30b63256ea", [:mix], [{:cowboy, "~> 2.7.0 or ~> 2.8.0 or ~> 2.9.0 or ~> 2.10.0", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d85444fb8aa1f2fc62eabe83bbe387d81510d773886774ebdcb429b3da3c1a4a"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"},
|
||||
"postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"},
|
||||
"qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"},
|
||||
"rambo": {:hex, :rambo, "0.3.4", "8962ac3bd1a633ee9d0e8b44373c7913e3ce3d875b4151dcd060886092d2dce7", [:mix], [], "hexpm", "0cc54ed089fbbc84b65f4b8a774224ebfe60e5c80186fafc7910b3e379ad58f1"},
|
||||
@ -96,9 +97,9 @@
|
||||
"stream_split": {:hex, :stream_split, "0.1.7", "2d3fd1fd21697da7f91926768d65f79409086052c9ec7ae593987388f52425f8", [:mix], [], "hexpm", "1dc072ff507a64404a0ad7af90df97096183fee8eeac7b300320cea7c4679147"},
|
||||
"sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"},
|
||||
"swoosh": {:hex, :swoosh, "1.8.2", "af9a22ab2c0d20b266f61acca737fa11a121902de9466a39e91bacdce012101c", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d058ba750eafadb6c09a84a352c14c5d1eeeda6e84945fcc95785b7f3067b7db"},
|
||||
"tailwind": {:hex, :tailwind, "0.1.9", "25ba09d42f7bfabe170eb67683a76d6ec2061952dc9bd263a52a99ba3d24bd4d", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "9213f87709c458aaec313bb5f2df2b4d2cedc2b630e4ae821bf3c54c47a56d0b"},
|
||||
"tailwind": {:hex, :tailwind, "0.2.2", "9e27288b568ede1d88517e8c61259bc214a12d7eed271e102db4c93fcca9b2cd", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "ccfb5025179ea307f7f899d1bb3905cd0ac9f687ed77feebc8f67bdca78565c4"},
|
||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
||||
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
|
||||
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"},
|
||||
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
|
||||
"thumbnex": {:hex, :thumbnex, "0.5.0", "9f3c20c8c70d17e108710830e1495548b45c7433f30dc318f1075d76eb6f7f00", [:mix], [{:ffmpex, "~> 0.10.0", [hex: :ffmpex, repo: "hexpm", optional: false]}, {:mogrify, "~> 0.9.0", [hex: :mogrify, repo: "hexpm", optional: false]}], "hexpm", "a187948110e2de8dc2e9a73d5a3489398ba6a44d285293c174b6285717c5e5fc"},
|
||||
"timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
|
||||
@ -107,7 +108,7 @@
|
||||
"tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
||||
"unifex": {:hex, :unifex, "1.1.0", "26b1bcb6c3b3454e1ea15f85b2e570aaa5b5c609566aa9f5c2e0a8b213379d6b", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}], "hexpm", "d8f47e9e3240301f5b20eec5792d1d4341e1a3a268d94f7204703b48da4aaa06"},
|
||||
"websock": {:hex, :websock, "0.5.0", "f6bbce90226121d62a0715bca7c986c5e43de0ccc9475d79c55381d1796368cc", [:mix], [], "hexpm", "b51ac706df8a7a48a2c622ee02d09d68be8c40418698ffa909d73ae207eb5fb8"},
|
||||
"websock_adapter": {:hex, :websock_adapter, "0.4.5", "30038a3715067f51a9580562c05a3a8d501126030336ffc6edb53bf57d6d2d26", [:mix], [{:bandit, "~> 0.6", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.4", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "1d9812dc7e703c205049426fd4fe0852a247a825f91b099e53dc96f68bafe4c8"},
|
||||
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
|
||||
"websock_adapter": {:hex, :websock_adapter, "0.5.5", "9dfeee8269b27e958a65b3e235b7e447769f66b5b5925385f5a569269164a210", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "4b977ba4a01918acbf77045ff88de7f6972c2a009213c515a445c48f224ffce9"},
|
||||
"zarex": {:hex, :zarex, "1.0.5", "58239e3ee5d75f343262bb4df5cf466555a1c689f920e5d3651a9333972f7c7e", [:mix], [], "hexpm", "9fb72ef0567c2b2742f5119a1ba8a24a2fabb21b8d09820aefbf3e592fa9a46a"},
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user