Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/ui/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@
--th-lg: 250px;
--th-md: 200px;
--th-sm: 150px;
--th-xs: 118px;
--th-xxs: 64px;

/* border radius */
--br-lg: 16px;
Expand Down
14 changes: 4 additions & 10 deletions src/ui/atoms/pagination/PaginationDot.css
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
.PaginationDot {
display: inline-flex;
justify-content: center;
align-items: center;
gap: var(--sp-xxs);
}

.PaginationDot__item {
width: 8px;
height: 8px;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: var(--bg-surface-border);
cursor: pointer;
}
.PaginationDot__item--active {
.PaginationDot--active {
background-color: var(--bg-primary);
}
6 changes: 4 additions & 2 deletions src/ui/atoms/pagination/PaginationDot.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ export const title = "PaginationDot";

export default function PaginationDotStories() {
return (
<div className="flex">
<PaginationDot max={3} value={1} />
<div className="flex gap-xxs">
{[0, 1, 2].map((v) => (
<PaginationDot key={v} active={v === 0} />
))}
</div>
);
}
30 changes: 14 additions & 16 deletions src/ui/atoms/pagination/PaginationDot.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import classNames from "classnames";
import { ButtonHTMLAttributes, forwardRef } from "react";

import "./PaginationDot.css";

interface PaginationDotProps {
type PaginationDotProps = ButtonHTMLAttributes<HTMLButtonElement> & {
className?: string;
max: number;
value: number;
}
active?: boolean;
};

export function PaginationDot({ className, max, value }: PaginationDotProps) {
return (
<div className={classNames("PaginationDot", className)}>
{Array.from({ length: max }).map((item, index) => (
<span
key={index}
className={classNames("PaginationDot__item", { "PaginationDot__item--active": index + 1 === value })}
/>
))}
</div>
);
}
export const PaginationDot = forwardRef<HTMLButtonElement, PaginationDotProps>(
({ type, className, active, ...props }: PaginationDotProps, ref) => (
<button
type={type ?? "button"}
className={classNames("PaginationDot", { "PaginationDot--active": active })}
{...props}
ref={ref}
/>
)
);
12 changes: 11 additions & 1 deletion src/ui/atoms/thumbnail/Thumbnail.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

background-color: var(--bg-surface-low);
border-radius: var(--br-lg);
box-shadow: 0 0 0 1px var(--bg-surface-border);
overflow: hidden;
position: relative;
}
Expand All @@ -25,6 +24,17 @@
height: var(--th-sm);
}

.Thumbnail--xs {
width: var(--th-xs);
height: var(--th-xs);
border-radius: var(--br-xs);
}

.Thumbnail--xxs {
width: var(--th-xxs);
height: var(--th-xxs);
border-radius: var(--br-xxs);
}
.Thumbnail--outlined::after {
content: "";
position: absolute;
Expand Down
6 changes: 6 additions & 0 deletions src/ui/atoms/thumbnail/Thumbnail.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ export default function ThumbnailStories() {
<ThumbnailImg src={LogoSvg} alt="My thumbnail" />
</Thumbnail>
</ThumbnailBadgeWrapper>
<Thumbnail size="xs">
<ThumbnailImg src={LogoSvg} alt="My thumbnail" />
</Thumbnail>
<Thumbnail size="xxs">
<ThumbnailImg src={LogoSvg} alt="My thumbnail" />
</Thumbnail>
</div>
);
}
2 changes: 1 addition & 1 deletion src/ui/atoms/thumbnail/Thumbnail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "./Thumbnail.css";
interface ThumbnailProps {
className?: string;
bgColor?: string;
size?: "lg" | "md" | "sm";
size?: "lg" | "md" | "sm" | "xs" | "xxs";
outlined?: boolean;
wide?: boolean;
children?: ReactNode;
Expand Down
25 changes: 25 additions & 0 deletions src/ui/hooks/useAutoUpload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { IBlobHandle, Session } from "@thirdroom/hydrogen-view-sdk";
import { useEffect, useState } from "react";

import { useThrottle } from "./useThrottle";
import { useAttachmentUpload } from "./useAttachmentUpload";

export const useAutoUpload = (session: Session, blob?: IBlobHandle) => {
const [progress, setProgress] = useState(0);
const throttledSetProgress = useThrottle(setProgress, 16);
const { mxc, error, upload, cancel } = useAttachmentUpload(session.hsApi, throttledSetProgress);

useEffect(() => {
if (blob) upload(blob);
else {
cancel();
setProgress(0);
}
}, [blob, upload, cancel]);

return {
progress,
mxc,
error,
};
};
69 changes: 69 additions & 0 deletions src/ui/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,75 @@ export function loadImageUrl(url: string): Promise<string> {
});
}

export function loadImageElement(url: string): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const img = document.createElement("img");
img.onload = () => resolve(img);
img.onerror = (err) => reject(err);
img.src = url;
});
}

export function loadVideoElement(url: string): Promise<HTMLVideoElement> {
return new Promise((resolve, reject) => {
const video = document.createElement("video");
video.preload = "metadata";
video.playsInline = true;
video.muted = true;

video.onloadeddata = () => {
resolve(video);
video.pause();
};
video.onerror = (e) => {
reject(e);
};

video.src = url;
video.load();
video.play();
});
}

export function getThumbnailDimensions(width: number, height: number): [number, number] {
const MAX_WIDTH = 800;
const MAX_HEIGHT = 600;
let targetWidth = width;
let targetHeight = height;
if (targetHeight > MAX_HEIGHT) {
targetWidth = Math.floor(targetWidth * (MAX_HEIGHT / targetHeight));
targetHeight = MAX_HEIGHT;
}
if (targetWidth > MAX_WIDTH) {
targetHeight = Math.floor(targetHeight * (MAX_WIDTH / targetWidth));
targetWidth = MAX_WIDTH;
}
return [targetWidth, targetHeight];
}

export function getThumbnail(
img: HTMLImageElement | SVGImageElement | HTMLVideoElement,
width: number,
height: number,
thumbnailMimeType?: string
): Promise<Blob | undefined> {
return new Promise((resolve) => {
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const context = canvas.getContext("2d");
if (!context) {
resolve(undefined);
return;
}
context.drawImage(img, 0, 0, width, height);

canvas.toBlob((thumbnail) => {
resolve(thumbnail ?? undefined);
}, thumbnailMimeType ?? "image/jpeg");
});
}

export function linkifyText(body: string) {
const msgParts = [];

Expand Down
16 changes: 3 additions & 13 deletions src/ui/views/components/AutoFileUpload.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { useEffect, useState } from "react";
import { useEffect } from "react";
import { IBlobHandle } from "@thirdroom/hydrogen-view-sdk";

import { FileUploadCard, FileUploadErrorCard } from "./file-upload-card/FileUploadCard";
import { useAttachmentUpload } from "../../hooks/useAttachmentUpload";
import { useHydrogen } from "../../hooks/useHydrogen";
import { useFilePicker } from "../../hooks/useFilePicker";
import { useThrottle } from "../../hooks/useThrottle";
import { useAutoUpload } from "../../hooks/useAutoUpload";

export interface AutoUploadInfo {
mxc?: string;
Expand All @@ -23,16 +22,7 @@ export function AutoFileUpload({ renderButton, mimeType, onUploadInfo }: AutoFil
const { session, platform } = useHydrogen(true);

const { fileData, pickFile, dropFile } = useFilePicker(platform, mimeType);
const [progress, setProgress] = useState(0);
const throttledSetProgress = useThrottle(setProgress, 16);
const { mxc, error, upload, cancel } = useAttachmentUpload(session.hsApi, throttledSetProgress);
useEffect(() => {
if (fileData.blob) upload(fileData.blob);
else {
cancel();
setProgress(0);
}
}, [fileData.blob, upload, cancel]);
const { progress, mxc, error } = useAutoUpload(session, fileData.blob);

useEffect(() => {
onUploadInfo({
Expand Down
6 changes: 6 additions & 0 deletions src/ui/views/components/attribution-card/AttributionCard.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.AttributionCard {
padding: var(--sp-xs);
background-color: var(--bg-surface);
border: 1px solid var(--bg-surface-border);
border-radius: var(--br-lg);
}
35 changes: 35 additions & 0 deletions src/ui/views/components/attribution-card/AttributionCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { AllHTMLAttributes, forwardRef } from "react";
import classNames from "classnames";

import { Input } from "../../../atoms/input/Input";
import { Label } from "../../../atoms/text/Label";
import { SettingTile } from "../setting-tile/SettingTile";
import "./AttributionCard.css";

export const AttributionCard = forwardRef<HTMLFieldSetElement, AllHTMLAttributes<HTMLFieldSetElement>>(
({ className, ...props }, ref) => (
<fieldset className={classNames("AttributionCard flex flex-column gap-xs", className)} {...props} ref={ref}>
<div>
<SettingTile label={<Label>Title</Label>}>
<Input />
</SettingTile>
</div>
<div className="flex gap-xs">
<SettingTile className="grow basis-0" label={<Label>Author Name</Label>}>
<Input />
</SettingTile>
<SettingTile className="grow basis-0" label={<Label>Author URL</Label>}>
<Input />
</SettingTile>
</div>
<div className="flex gap-xs">
<SettingTile className="grow basis-0" label={<Label>License</Label>}>
<Input />
</SettingTile>
<SettingTile className="grow basis-0" label={<Label>Source URL</Label>}>
<Input />
</SettingTile>
</div>
</fieldset>
)
);
Loading