inital audio player commits

This commit is contained in:
MathMan05 2025-03-25 12:13:40 -05:00
parent 4b67ed6a9c
commit b0c320cd0a
12 changed files with 693 additions and 39 deletions

View file

@ -1,8 +1,10 @@
import {File} from "./file.js";
class ImagesDisplay { class ImagesDisplay {
images: string[]; files: File[];
index = 0; index = 0;
constructor(srcs: string[], index = 0) { constructor(files: File[], index = 0) {
this.images = srcs; this.files = files;
this.index = index; this.index = index;
} }
weakbg = new WeakRef<HTMLElement>(document.createElement("div")); weakbg = new WeakRef<HTMLElement>(document.createElement("div"));
@ -14,19 +16,64 @@ class ImagesDisplay {
} }
makeHTML(): HTMLElement { makeHTML(): HTMLElement {
//TODO this should be able to display more than one image at a time lol //TODO this should be able to display more than one image at a time lol
const image = document.createElement("img"); const image = this.files[this.index].getHTML(false, true);
image.src = this.images[this.index];
image.classList.add("imgfit", "centeritem"); image.classList.add("imgfit", "centeritem");
return image; return image;
} }
show() { show() {
this.background = document.createElement("div"); this.background = document.createElement("div");
this.background.classList.add("background"); this.background.classList.add("background");
this.background.appendChild(this.makeHTML()); let cur = this.makeHTML();
if (this.files.length !== 1) {
const right = document.createElement("span");
right.classList.add("rightArrow", "svg-intoMenu");
right.onclick = (e) => {
e.preventDefault();
e.stopImmediatePropagation();
this.index++;
this.index %= this.files.length;
cur.remove();
cur = this.makeHTML();
if (this.background) {
this.background.appendChild(cur);
}
};
const left = document.createElement("span");
left.onclick = (e) => {
e.preventDefault();
e.stopImmediatePropagation();
this.index += this.files.length - 1;
this.index %= this.files.length;
cur.remove();
cur = this.makeHTML();
if (this.background) {
this.background.appendChild(cur);
}
};
left.classList.add("leftArrow", "svg-leftArrow");
this.background.append(right, left);
this.background.addEventListener("keydown", (e) => {
if (e.key === "ArrowRight") {
e.preventDefault();
e.stopImmediatePropagation();
right.click();
}
if (e.key === "ArrowLeft") {
e.preventDefault();
e.stopImmediatePropagation();
right.click();
}
});
}
this.background.appendChild(cur);
this.background.onclick = (_) => { this.background.onclick = (_) => {
this.hide(); this.hide();
}; };
document.body.append(this.background); document.body.append(this.background);
this.background.setAttribute("tabindex", "0");
this.background.focus();
} }
hide() { hide() {
if (this.background) { if (this.background) {

View file

@ -5,6 +5,7 @@ import {getapiurls, getInstances} from "./utils/utils.js";
import {Guild} from "./guild.js"; import {Guild} from "./guild.js";
import {I18n} from "./i18n.js"; import {I18n} from "./i18n.js";
import {ImagesDisplay} from "./disimg.js"; import {ImagesDisplay} from "./disimg.js";
import {File} from "./file.js";
class Embed { class Embed {
type: string; type: string;
@ -169,7 +170,9 @@ class Embed {
const img = document.createElement("img"); const img = document.createElement("img");
img.classList.add("messageimg"); img.classList.add("messageimg");
img.onclick = function () { img.onclick = function () {
const full = new ImagesDisplay([img.src]); const full = new ImagesDisplay([
new File({id: "", filename: "", url: img.src, size: -1, content_type: "image"}, null),
]);
full.show(); full.show();
}; };
img.src = this.json.thumbnail.proxy_url; img.src = this.json.thumbnail.proxy_url;
@ -205,7 +208,9 @@ class Embed {
if (this.json.thumbnail) { if (this.json.thumbnail) {
img.classList.add("embedimg"); img.classList.add("embedimg");
img.onclick = function () { img.onclick = function () {
const full = new ImagesDisplay([img.src]); const full = new ImagesDisplay([
new File({id: "", filename: "", url: img.src, size: -1, content_type: "image"}, null),
]);
full.show(); full.show();
}; };
img.src = this.json.thumbnail.proxy_url; img.src = this.json.thumbnail.proxy_url;
@ -381,7 +386,9 @@ class Embed {
}; };
} else { } else {
img.onclick = async () => { img.onclick = async () => {
const full = new ImagesDisplay([img.src]); const full = new ImagesDisplay([
new File({id: "", filename: "", url: img.src, size: -1, content_type: "image"}, null),
]);
full.show(); full.show();
}; };
} }

View file

@ -1,7 +1,7 @@
import {Message} from "./message.js"; import {Message} from "./message.js";
import {filejson} from "./jsontypes.js"; import {filejson} from "./jsontypes.js";
import {ImagesDisplay} from "./disimg.js"; import {ImagesDisplay} from "./disimg.js";
import {makePlayBox, MediaPlayer} from "./media.js";
class File { class File {
owner: Message | null; owner: Message | null;
id: string; id: string;
@ -24,7 +24,7 @@ class File {
this.content_type = fileJSON.content_type; this.content_type = fileJSON.content_type;
this.size = fileJSON.size; this.size = fileJSON.size;
} }
getHTML(temp: boolean = false): HTMLElement { getHTML(temp: boolean = false, fullScreen = false): HTMLElement {
const src = this.proxy_url || this.url; const src = this.proxy_url || this.url;
if (this.width && this.height) { if (this.width && this.height) {
let scale = 1; let scale = 1;
@ -37,19 +37,33 @@ class File {
if (this.content_type.startsWith("image/")) { if (this.content_type.startsWith("image/")) {
const div = document.createElement("div"); const div = document.createElement("div");
const img = document.createElement("img"); const img = document.createElement("img");
if (!fullScreen) {
img.classList.add("messageimg"); img.classList.add("messageimg");
div.classList.add("messageimgdiv"); div.classList.add("messageimgdiv");
img.onclick = function () { }
const full = new ImagesDisplay([img.src]); img.onclick = () => {
if (this.owner) {
const full = new ImagesDisplay(
this.owner.attachments,
this.owner.attachments.indexOf(this),
);
full.show(); full.show();
} else {
const full = new ImagesDisplay([this]);
full.show();
}
}; };
img.src = src; img.src = src;
div.append(img); div.append(img);
if (this.width) { if (this.width && !fullScreen) {
div.style.width = this.width + "px"; div.style.width = this.width + "px";
div.style.height = this.height + "px"; div.style.height = this.height + "px";
} }
if (!fullScreen) {
return div; return div;
} else {
return img;
}
} else if (this.content_type.startsWith("video/")) { } else if (this.content_type.startsWith("video/")) {
const video = document.createElement("video"); const video = document.createElement("video");
const source = document.createElement("source"); const source = document.createElement("source");
@ -63,17 +77,15 @@ class File {
} }
return video; return video;
} else if (this.content_type.startsWith("audio/")) { } else if (this.content_type.startsWith("audio/")) {
const audio = document.createElement("audio"); return this.getAudioHTML();
const source = document.createElement("source");
source.src = src;
audio.append(source);
source.type = this.content_type;
audio.controls = !temp;
return audio;
} else { } else {
return this.createunknown(); return this.createunknown();
} }
} }
private getAudioHTML() {
const src = this.proxy_url || this.url;
return makePlayBox(src, player);
}
upHTML(files: Blob[], file: globalThis.File): HTMLElement { upHTML(files: Blob[], file: globalThis.File): HTMLElement {
const div = document.createElement("div"); const div = document.createElement("div");
const contained = this.getHTML(true); const contained = this.getHTML(true);
@ -149,4 +161,6 @@ class File {
); );
} }
} }
const player = new MediaPlayer();
export {File}; export {File};

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><path fill="none" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="29" d="M109 15 45 90l64 75"/></svg>

After

Width:  |  Height:  |  Size: 189 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="red" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="20" d="M93 55h98v401H93zm220-1h98v401h-98z"/></svg>

After

Width:  |  Height:  |  Size: 204 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="red" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="23.9" d="M85 466 84 52l360 207Z"/></svg>

After

Width:  |  Height:  |  Size: 193 B

View file

@ -107,7 +107,10 @@
</div> </div>
</div> </div>
</div> </div>
<div class="flexttb" id="sideContainDiv">
<div class="flexttb" id="sideDiv"></div> <div class="flexttb" id="sideDiv"></div>
<div id="player" class="flexttb"></div>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -357,7 +357,7 @@ type filejson = {
content_type: string; content_type: string;
width?: number; width?: number;
height?: number; height?: number;
proxy_url: string | undefined; proxy_url?: string;
url: string; url: string;
size: number; size: number;
}; };

View file

@ -2326,9 +2326,10 @@ class Localuser {
}) })
.filter((_) => _ !== undefined); .filter((_) => _ !== undefined);
const sideDiv = document.getElementById("sideDiv"); const sideDiv = document.getElementById("sideDiv");
if (!sideDiv) return; const sideContainDiv = document.getElementById("sideContainDiv");
if (!sideDiv || !sideContainDiv) return;
sideDiv.innerHTML = ""; sideDiv.innerHTML = "";
sideDiv.classList.add("searchDiv"); sideContainDiv.classList.add("searchDiv");
let channel: Channel | undefined = undefined; let channel: Channel | undefined = undefined;
for (const message of messages) { for (const message of messages) {
if (channel !== message.channel) { if (channel !== message.channel) {

507
src/webpage/media.ts Normal file
View file

@ -0,0 +1,507 @@
import {I18n} from "./i18n.js";
type mediaEvents =
| {
type: "play";
}
| {
type: "pause";
}
| {
type: "playing";
time: number;
}
| {
type: "done";
}
| {
type: "audio";
t: "start" | "stop";
}
| {
type: "audio";
t: "skip";
time: number;
}
| {
type: "end";
};
function makePlayBox(mor: string | media, player: MediaPlayer) {
const div = document.createElement("div");
div.classList.add("flexltr", "Mplayer");
const button = document.createElement("img");
button.classList.add("svg-mediaButton", "svg-play");
const vDiv = document.createElement("div");
vDiv.classList.add("flexttb");
const title = document.createElement("span");
title.textContent = I18n.media.loading();
const barDiv = document.createElement("div");
barDiv.classList.add("flexltr");
const bar = document.createElement("input");
bar.type = "range";
bar.disabled = true;
bar.value = "0";
bar.min = "0";
const time = document.createElement("span");
time.textContent = "0:00/..:..";
barDiv.append(bar, time);
vDiv.append(title, barDiv);
div.append(button, vDiv);
MediaPlayer.IdentifyFile(mor).then((thing) => {
let audio: HTMLAudioElement | undefined = undefined;
if (!thing) {
const span = document.createElement("span");
span.textContent = I18n.media.notFound();
return;
}
player.addListener(thing.src, followUpdates, div);
let int = setInterval((_) => {}, 1000);
if (mor instanceof Object) {
const audioo = new Audio(mor.src);
audioo.load();
audioo.autoplay = true;
int = setInterval(() => {
if (button.classList.contains("svg-pause")) {
player.addUpdate(mor.src, {type: "playing", time: audioo.currentTime * 1000});
}
}, 100) as unknown as number;
audioo.onplay = () => {
player.addUpdate(mor.src, {type: "play"});
};
audioo.onpause = () => {
player.addUpdate(thing.src, {type: "pause"});
};
audioo.onloadeddata = () => {
audio = audioo;
};
audioo.onended = () => {
player.addUpdate(mor.src, {type: "end"});
};
}
button.onclick = () => {
if (!player.isPlaying(thing.src)) {
player.setToTopList(thing);
} else {
player.addUpdate(thing.src, {
type: "audio",
t: button.classList.contains("svg-play") ? "start" : "stop",
});
}
};
function followUpdates(cur: mediaEvents) {
if (audio && cur.type !== "playing") {
console.log(cur);
}
if (cur.type == "audio" && audio) {
if (cur.t == "start") {
audio.play();
} else if (cur.t == "stop") {
audio.pause();
} else if (cur.t == "skip" && audio) {
audio.currentTime = cur.time / 1000;
}
}
if (cur.type == "playing") {
regenTime(cur.time);
bar.value = "" + cur.time / 1000;
button.classList.add("svg-pause");
button.classList.remove("svg-play");
} else if (cur.type === "play") {
button.classList.add("svg-pause");
button.classList.remove("svg-play");
} else if (cur.type === "pause") {
button.classList.add("svg-play");
button.classList.remove("svg-pause");
} else if (cur.type === "end") {
clearInterval(int);
if (audio) {
audio.pause();
audio.src = "";
player.end();
}
button.classList.add("svg-play");
button.classList.remove("svg-pause");
regenTime();
}
}
const med = thing;
if (med.img) {
let img: HTMLImageElement;
if (mor instanceof Object) {
img = button;
} else {
img = document.createElement("img");
div.append(img);
}
img.classList.add("media-small");
img.src = med.img.url;
}
function timeToString(time: number) {
const minutes = Math.floor(time / 1000 / 60);
const seconds = Math.round(time / 1000 - minutes * 60) + "";
return `${minutes}:${seconds.padStart(2, "0")}`;
}
bar.oninput = () => {
player.addUpdate(thing.src, {
type: "audio",
t: "skip",
time: +bar.value * 1000,
});
regenTime(+bar.value * 1000);
};
async function regenTime(curTime: number = 0) {
console.log(med.length);
const len = await med.length;
bar.disabled = false;
bar.max = "" + len / 1000;
time.textContent = `${timeToString(curTime)}/${timeToString(len)}`;
}
regenTime();
title.textContent = thing.title;
console.log(thing);
});
return div;
}
interface media {
src: string;
img?: {
url: string;
description?: string;
};
title: string;
artist?: string;
composer?: string;
sourcy?: string;
year?: number;
copyright?: string;
length: number | Promise<number>;
}
class MediaPlayer {
lists: media[] = [];
curAudio?: HTMLElement;
private listeners: [(e: mediaEvents) => void, string][] = [];
elm: HTMLDivElement;
cur = 0;
constructor() {
this.elm = document.getElementById("player") as HTMLDivElement;
}
addElmToList(audio: media) {
this.lists.unshift(audio);
}
addUpdate(e: string, update: mediaEvents) {
for (const thing of this.listeners) {
if (thing[1] === e) {
thing[0](update);
}
}
}
addListener(audio: string, updates: (e: mediaEvents) => void, elm: HTMLElement) {
this.listeners.push([updates, audio]);
const int = setInterval(() => {
if (!document.contains(elm)) {
clearInterval(int);
this.listeners = this.listeners.filter((_) => _[0] !== updates);
console.log("cleared data");
}
}, 1000);
}
isPlaying(str: string) {
const med = this.lists[this.cur];
if (!med) return false;
return med.src === str;
}
setToTopList(audio: media) {
const med = this.lists[this.cur];
if (med) {
this.addUpdate(med.src, {type: "end"});
}
this.lists.splice(this.cur, 0, audio);
this.regenPlayer();
}
end() {
if (this.curAudio) {
this.curAudio.remove();
this.cur++;
this.regenPlayer();
}
}
regenPlayer() {
this.elm.innerHTML = "";
if (this.lists.length > this.cur) {
const audio = this.lists[this.cur];
this.elm.append((this.curAudio = makePlayBox(audio, this)));
}
}
static cache = new Map<string, media | Promise<media>>();
static async IdentifyFile(url: string | media): Promise<media | null> {
if (url instanceof Object) {
return url;
}
const cValue = this.cache.get(url);
if (cValue) {
return cValue;
}
let resMedio = (_: media) => {};
this.cache.set(url, new Promise<media>((res) => (resMedio = res)));
const controller = new AbortController();
const f = await fetch(url, {
method: "get",
signal: controller.signal,
});
if (!f.ok || !f.body) {
return null;
}
let index = 0;
const read = f.body.getReader();
let cbuff = (await read.read()).value;
const output: Partial<media> = {
src: url,
};
try {
let sizeLeft = 0;
async function next() {
return (await get8BitArray(1))[0];
}
async function get8BitArray(size: number) {
sizeLeft -= size;
const arr = new Uint8Array(size);
let arri = 0;
while (size > 0) {
if (!cbuff) throw Error("ran out of file to read");
let itter = Math.min(size, cbuff.length - index);
size -= itter;
for (let i = 0; i < itter; i++, arri++, index++) {
arr[arri] = cbuff[index];
}
if (size !== 0) {
cbuff = (await read.read()).value;
index = 0;
console.log("got more buffer", index, arri, size);
}
}
return arr;
}
const head = String.fromCharCode(await next(), await next(), await next());
if (head === "ID3") {
const version = (await next()) + (await next()) * 256;
if (version === 2) {
//TODO I'm like 90% I can ignore *all* of the flags, but I need to check more sometime
await next();
//debugger;
const sizes = await get8BitArray(4);
sizeLeft = (sizes[0] << 21) + (sizes[1] << 14) + (sizes[2] << 7) + sizes[3];
const mappy = new Map<string, Uint8Array>();
while (sizeLeft > 0) {
const Identify = String.fromCharCode(await next(), await next(), await next());
const sizeArr = await get8BitArray(3);
const size = (sizeArr[0] << 16) + (sizeArr[1] << 8) + sizeArr[2];
console.log(sizeLeft, size, index);
if (Identify === String.fromCharCode(0, 0, 0)) {
break;
}
if (!size) {
throw Error("weirdness");
}
if (!Identify.match(/[A-Z0-9]/gm)) {
console.error(`tried to get ${Identify} which doesn't exist`);
break;
}
if (mappy.has(Identify)) {
await get8BitArray(size);
//console.warn("Got dupe", Identify);
} else {
mappy.set(Identify, await get8BitArray(size));
}
console.warn(sizeLeft);
}
const pic = mappy.get("PIC");
if (pic) {
let i = 5; //skipping info I don't need right now
const desc: number[] = [];
for (; pic[i]; i++) {
desc.push(pic[i]);
}
const description = new TextDecoder().decode(new Uint8Array(desc));
i++;
console.warn(pic, i);
const blob = new Blob([pic.slice(i, pic.length).buffer], {type: "image/jpeg"});
const urlmaker = window.URL || window.webkitURL;
const url = urlmaker.createObjectURL(blob);
output.img = {url, description};
}
function decodeText(buf: ArrayBuffer) {
let str = new TextDecoder().decode(buf);
if (str.startsWith("\u0000")) {
str = str.slice(1, str.length);
}
if (str.endsWith("\u0000")) {
str = str.slice(0, str.length - 1);
}
return str;
}
const mapmap = {
TT2: "title",
TP1: "artist",
TCM: "composer",
TAL: "sourcy",
TCO: "type",
TEN: "copyright",
} as const;
for (const [key, ind] of Object.entries(mapmap)) {
const temp = mappy.get(key);
if (temp) {
//@ts-ignore TS is being weird about this idk why
output[ind] = decodeText(temp);
}
}
const tye = mappy.get("TYE");
if (tye) {
output.year = +decodeText(tye);
}
//TODO more thoroughly check if these two are the same format
} else if (version === 3 || version === 4) {
const flags = await next();
if (flags & 0b01000000) {
//TODO deal with the extended header
}
//debugger;
const sizes = await get8BitArray(4);
sizeLeft = (sizes[0] << 21) + (sizes[1] << 14) + (sizes[2] << 7) + sizes[3];
const mappy = new Map<string, Uint8Array>();
while (sizeLeft > 0) {
const Identify = String.fromCharCode(
await next(),
await next(),
await next(),
await next(),
);
const sizeArr = await get8BitArray(4);
const size = (sizeArr[0] << 24) + (sizeArr[1] << 16) + (sizeArr[2] << 8) + sizeArr[3];
const flags = await get8BitArray(2);
const compression = !!(flags[1] & 0b10000000);
if (compression) {
//TODO Honestly, I don't know if I can do this with normal JS
continue;
}
const encyption = !!(flags[1] & 0b01000000);
if (encyption) {
//TODO not sure what this would even do
continue;
}
console.log(sizeLeft, size, index);
if (Identify === String.fromCharCode(0, 0, 0, 0)) {
break;
}
if (!size) {
//throw Error("weirdness");
}
if (!Identify.match(/[A-Z0-9]/gm)) {
console.error(`tried to get ${Identify} which doesn't exist`);
break;
}
if (mappy.has(Identify)) {
await get8BitArray(size);
//console.warn("Got dupe", Identify);
} else {
mappy.set(Identify, await get8BitArray(size));
}
console.warn(sizeLeft);
}
const pic = mappy.get("APIC");
if (pic) {
const encoding = pic[0];
let i = 1; //skipping info I don't need right now
for (; pic[i]; i++) {}
i += 2;
let desc: number[] = [];
for (; pic[i]; i++) {
desc.push(pic[i]);
}
const description = new TextDecoder().decode(new Uint8Array(desc));
i++;
const blob = new Blob([pic.slice(i, pic.length).buffer], {type: "image/jpeg"});
const urlmaker = window.URL || window.webkitURL;
const url = urlmaker.createObjectURL(blob);
output.img = {url, description};
}
function decodeText(buf: ArrayBuffer) {
let str = new TextDecoder().decode(buf);
if (str.startsWith("\u0000")) {
str = str.slice(1, str.length);
}
if (str.endsWith("\u0000")) {
str = str.slice(0, str.length - 1);
}
return str;
}
const mapmap = {
TIT2: "title",
TPE1: "artist",
TCOM: "composer",
TALB: "sourcy",
TMED: "type",
TENC: "copyright",
} as const;
for (const [key, ind] of Object.entries(mapmap)) {
const temp = mappy.get(key);
if (temp) {
//@ts-ignore TS is being weird about this idk why
output[ind] = decodeText(temp);
}
}
const TYER = mappy.get("TYER");
if (TYER) {
console.log(decodeText(TYER));
output.year = +decodeText(TYER);
}
const TLEN = mappy.get("TLEN");
if (TLEN) {
output.length = +decodeText(TLEN);
}
console.log(mappy);
}
} //TODO implement more metadata types
} catch (e) {
console.error(e);
} finally {
controller.abort();
if (!output.length) {
output.length = new Promise<number>(async (res) => {
const audio = document.createElement("audio");
audio.src = url;
audio.onloadeddata = (_) => {
console.log("Loaded!", audio.duration * 1000);
output.length = audio.duration * 1000;
res(audio.duration * 1000);
};
audio.load();
console.log(audio);
});
}
if (!output.title) {
output.title = url.split("/").at(-1);
}
}
resMedio(output as media);
return output as media;
}
}
export {MediaPlayer, media, makePlayBox};

View file

@ -28,6 +28,19 @@ body {
margin-right: 0.15in; margin-right: 0.15in;
} }
} }
.Mplayer {
padding: 4px;
border-radius: 3px;
background: var(--secondary-bg);
align-items: center;
*{
margin:2px;
}
.flexttb {
display: flex;
align-items: center;
}
}
.flexltr { .flexltr {
min-height: 0; min-height: 0;
display: flex; display: flex;
@ -70,6 +83,12 @@ body {
padding: 0; padding: 0;
border: 0; border: 0;
} }
#player {
height: 128px;
}
#player:empty {
height: 0px;
}
.messageEditContainer { .messageEditContainer {
position: relative; position: relative;
width: 100%; width: 100%;
@ -266,6 +285,42 @@ textarea {
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
} }
.leftArrow {
z-index: 1;
width: 42px;
height: 42px;
display: block;
background: var(--primary-text-soft);
position: absolute;
top: calc(50vh - 42px);
left: 4px;
cursor: pointer;
}
.rightArrow {
z-index: 1;
width: 42px;
height: 42px;
display: block;
background: var(--primary-text-soft);
position: absolute;
top: calc(50vh - 42px);
right: 4px;
cursor: pointer;
}
.svg-mediaButton {
width: 30px;
height: 30px;
background: var(--primary-text);
cursor: pointer;
display: block;
mask-size: cover !important;
}
.svg-play {
mask: url(/icons/play.svg);
}
.svg-pause {
mask: url(/icons/pause.svg);
}
.svg-announce { .svg-announce {
mask: url(/icons/announce.svg); mask: url(/icons/announce.svg);
} }
@ -287,6 +342,9 @@ textarea {
.svg-intoMenu { .svg-intoMenu {
mask: url(/icons/intoMenu.svg); mask: url(/icons/intoMenu.svg);
} }
.svg-leftArrow {
mask: url(/icons/leftArrow.svg);
}
.svg-channel { .svg-channel {
mask: url(/icons/channel.svg); mask: url(/icons/channel.svg);
} }
@ -427,15 +485,18 @@ textarea {
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background: var(--primary-text-soft); background: var(--primary-text-soft);
} }
#sideDiv:empty { #sideContainDiv:empty {
width: 0px; width: 0px;
padding: 0; padding: 0;
} }
#servers::-webkit-scrollbar, #servers::-webkit-scrollbar,
#channels::-webkit-scrollbar, #channels::-webkit-scrollbar,
#sideDiv::-webkit-scrollbar { #sideContainDiv::-webkit-scrollbar {
display: none; display: none;
} }
#sideContainDiv {
width: 0;
}
#servers, #servers,
#channels, #channels,
#sideDiv { #sideDiv {
@ -1255,6 +1316,11 @@ span.instanceStatus {
margin-top: 4px; margin-top: 4px;
border-radius: 4px; border-radius: 4px;
} }
.media-small {
max-width: 0.6in !important;
max-height: 0.4in;
object-fit: contain;
}
.message img { .message img {
max-width: 100%; max-width: 100%;
} }
@ -1525,7 +1591,7 @@ img.bigembedimg {
.acceptinvbutton:disabled { .acceptinvbutton:disabled {
background: color-mix(in hsl, var(--green) 80%, var(--black)); background: color-mix(in hsl, var(--green) 80%, var(--black));
} }
#sideDiv.searchDiv { #sideContainDiv.searchDiv {
width: 30vw; width: 30vw;
.topMessage { .topMessage {
margin-top: 2px; margin-top: 2px;
@ -1541,13 +1607,13 @@ img.bigembedimg {
} }
/* Sidebar */ /* Sidebar */
#sideDiv { #sideDiv {
display: none;
flex: none; flex: none;
width: 240px;
padding: 16px 8px; padding: 16px 8px;
background: var(--sidebar-bg); background: var(--sidebar-bg);
overflow-y: auto; overflow-y: auto;
box-sizing: border-box; box-sizing: border-box;
flex-grow: 1;
flex-shrink: 1;
} }
.memberList { .memberList {
@ -1578,8 +1644,9 @@ img.bigembedimg {
height: 16px; height: 16px;
width: 16px; width: 16px;
} }
#page:has(#memberlisttoggle:checked) #sideDiv { #page:has(#memberlisttoggle:checked) #sideContainDiv {
display: block; width: 240px;
flex-shrink: 0;
} }
#page:has(#memberlisttoggle:checked) #memberlisttoggleicon span { #page:has(#memberlisttoggle:checked) #memberlisttoggleicon span {
background: var(--primary-text-prominent); background: var(--primary-text-prominent);
@ -2201,7 +2268,7 @@ fieldset input[type="radio"] {
} }
@media screen and (max-width: 1000px) { @media screen and (max-width: 1000px) {
#sideDiv { #sideContainDiv {
position: absolute; position: absolute;
right: 0; right: 0;
height: calc(100svh - 50px); height: calc(100svh - 50px);
@ -2240,7 +2307,7 @@ fieldset input[type="radio"] {
background: var(--primary-bg); background: var(--primary-bg);
transition: left 0.3s; transition: left 0.3s;
} }
#sideDiv { #sideContainDiv {
display: block; display: block;
right: -100svw; right: -100svw;
width: 100svw; width: 100svw;
@ -2269,7 +2336,7 @@ fieldset input[type="radio"] {
#page:has(#maintoggle:checked) #mainarea { #page:has(#maintoggle:checked) #mainarea {
left: 0; left: 0;
} }
#page:has(#memberlisttoggle:checked) #sideDiv { #page:has(#memberlisttoggle:checked) #sideContainDiv {
right: 0; right: 0;
} }
#page:has(#maintoggle:checked) #maintoggleicon { #page:has(#maintoggle:checked) #maintoggleicon {
@ -2476,3 +2543,4 @@ fieldset input[type="radio"] {
right: 0.2in; right: 0.2in;
cursor: pointer; cursor: pointer;
} }

View file

@ -10,6 +10,10 @@
"reply": "Reply", "reply": "Reply",
"copyrawtext": "Copy raw text", "copyrawtext": "Copy raw text",
"copymessageid": "Copy message id", "copymessageid": "Copy message id",
"media": {
"notFound": "Media could not be found",
"loading": "Loading"
},
"permissions": { "permissions": {
"descriptions": { "descriptions": {
"CREATE_INSTANT_INVITE": "Allows the user to create invites for the guild", "CREATE_INSTANT_INVITE": "Allows the user to create invites for the guild",