mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-08 20:26:08 -05:00
Exported images w/ SQ season summary downloadable (#3272)
This commit is contained in:
@@ -7,6 +7,7 @@ import styles from "./Ability.module.css";
|
||||
import { Image } from "./Image";
|
||||
|
||||
const sizeMap = {
|
||||
HUGE: 64,
|
||||
MAIN: 42,
|
||||
SUB: 32,
|
||||
SUBTINY: 26,
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as React from "react";
|
||||
import type { Tables } from "~/db/tables";
|
||||
import { useHydrated } from "~/hooks/useHydrated";
|
||||
import { LRUCache } from "~/modules/cache";
|
||||
import { BLANK_IMAGE_URL, discordAvatarUrl } from "~/utils/urls";
|
||||
import { BLANK_IMAGE_URL, resolveAvatarUrl } from "~/utils/urls";
|
||||
import styles from "./Avatar.module.css";
|
||||
|
||||
const dimensions = {
|
||||
@@ -132,19 +132,23 @@ export function Avatar({
|
||||
|
||||
const identiconSource = identiconInput ?? user?.discordId ?? "unknown";
|
||||
|
||||
const src = url
|
||||
? url
|
||||
: user?.customAvatarUrl && !isErrored
|
||||
? user.customAvatarUrl
|
||||
: user?.discordAvatar && !isErrored
|
||||
? discordAvatarUrl({
|
||||
discordAvatar: user.discordAvatar,
|
||||
discordId: user.discordId,
|
||||
size: size === "lg" || size === "xmd" ? "lg" : "sm",
|
||||
})
|
||||
: isClient
|
||||
? generateIdenticon(identiconSource, dimensions[size], 7)
|
||||
: BLANK_IMAGE_URL;
|
||||
const userAvatarUrl = user
|
||||
? resolveAvatarUrl({
|
||||
customAvatarUrl: user.customAvatarUrl,
|
||||
discordId: user.discordId,
|
||||
discordAvatar: user.discordAvatar,
|
||||
size: size === "lg" || size === "xmd" ? "lg" : "sm",
|
||||
})
|
||||
: undefined;
|
||||
|
||||
const avatarUrl = url ?? userAvatarUrl;
|
||||
|
||||
const src =
|
||||
avatarUrl && !isErrored
|
||||
? avatarUrl
|
||||
: isClient
|
||||
? generateIdenticon(identiconSource, dimensions[size], 7)
|
||||
: BLANK_IMAGE_URL;
|
||||
|
||||
return (
|
||||
<div className={clsx(styles.avatarWrapper, className)}>
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
import clsx from "clsx";
|
||||
import { Lock, MessageCircleMore, SquarePen, Trash } from "lucide-react";
|
||||
import {
|
||||
HardDriveDownload,
|
||||
Lock,
|
||||
MessageCircleMore,
|
||||
SquarePen,
|
||||
Trash,
|
||||
} from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router";
|
||||
import type { Tables } from "~/db/tables";
|
||||
import { useUser } from "~/features/auth/core/user";
|
||||
import type { BuildWeaponWithTop500Info } from "~/features/builds/builds-types";
|
||||
import {
|
||||
BuildGraphic,
|
||||
type BuildGraphicOwner,
|
||||
} from "~/features/img-export/components/BuildGraphic";
|
||||
import { ImageExportDialog } from "~/features/img-export/components/ImageExportDialog";
|
||||
import type {
|
||||
Ability as AbilityType,
|
||||
BuildAbilitiesTuple,
|
||||
@@ -28,6 +40,7 @@ import { Ability } from "./Ability";
|
||||
import styles from "./BuildCard.module.css";
|
||||
import { LinkButton, SendouButton } from "./elements/Button";
|
||||
import { SendouPopover } from "./elements/Popover";
|
||||
import { SendouSwitch } from "./elements/Switch";
|
||||
import { FormWithConfirm } from "./FormWithConfirm";
|
||||
import { Image } from "./Image";
|
||||
import { LocaleTime } from "./LocaleTime";
|
||||
@@ -48,11 +61,21 @@ interface BuildProps {
|
||||
modes: ModeShort[] | null;
|
||||
weapons: Array<BuildWeaponWithTop500Info>;
|
||||
};
|
||||
owner?: Pick<UserWithPlusTier, "discordId" | "username" | "plusTier">;
|
||||
owner?: Pick<UserWithPlusTier, "discordId" | "username" | "plusTier"> &
|
||||
Partial<
|
||||
Pick<BuildGraphicOwner, "customUrl" | "discordAvatar" | "customAvatarUrl">
|
||||
>;
|
||||
/** Set to false when the page context already shows the owner (e.g. their own builds page) */
|
||||
showOwner?: boolean;
|
||||
canEdit?: boolean;
|
||||
}
|
||||
|
||||
export function BuildCard({ build, owner, canEdit = false }: BuildProps) {
|
||||
export function BuildCard({
|
||||
build,
|
||||
owner,
|
||||
showOwner = true,
|
||||
canEdit = false,
|
||||
}: BuildProps) {
|
||||
const user = useUser();
|
||||
const { t } = useTranslation(["weapons", "builds", "common", "game-misc"]);
|
||||
|
||||
@@ -100,15 +123,15 @@ export function BuildCard({ build, owner, canEdit = false }: BuildProps) {
|
||||
</h2>
|
||||
</div>
|
||||
<div className={styles.dateAuthorRow}>
|
||||
{owner && (
|
||||
{owner && showOwner ? (
|
||||
<>
|
||||
<Link to={userBuildsPage(owner)} className={styles.ownerLink}>
|
||||
{owner.username}
|
||||
</Link>
|
||||
<div>•</div>
|
||||
</>
|
||||
)}
|
||||
{owner?.plusTier ? (
|
||||
) : null}
|
||||
{owner?.plusTier && showOwner ? (
|
||||
<>
|
||||
<span>+{owner.plusTier}</span>
|
||||
<div>•</div>
|
||||
@@ -180,6 +203,7 @@ export function BuildCard({ build, owner, canEdit = false }: BuildProps) {
|
||||
path={navIconUrl("analyzer")}
|
||||
/>
|
||||
</LinkButton>
|
||||
{owner ? <BuildImageExportDialog build={build} owner={owner} /> : null}
|
||||
{description ? (
|
||||
<SendouPopover
|
||||
trigger={
|
||||
@@ -229,6 +253,67 @@ export function BuildCard({ build, owner, canEdit = false }: BuildProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function BuildImageExportDialog({
|
||||
build,
|
||||
owner,
|
||||
}: {
|
||||
build: BuildProps["build"];
|
||||
owner: BuildGraphicOwner;
|
||||
}) {
|
||||
const { t } = useTranslation(["common"]);
|
||||
const [showTitle, setShowTitle] = React.useState(true);
|
||||
const [showAbilityPoints, setShowAbilityPoints] = React.useState(true);
|
||||
const [showAbilityChunks, setShowAbilityChunks] = React.useState(false);
|
||||
|
||||
return (
|
||||
<ImageExportDialog
|
||||
trigger={
|
||||
<SendouButton
|
||||
shape="circle"
|
||||
size="small"
|
||||
variant="minimal"
|
||||
icon={<HardDriveDownload />}
|
||||
className={styles.smallText}
|
||||
aria-label={t("common:imageExport.export")}
|
||||
/>
|
||||
}
|
||||
heading={t("common:imageExport.export")}
|
||||
filename={`build-${mySlugify(build.title)}`}
|
||||
qrCodePath={analyzerPage({
|
||||
weaponId: build.weapons[0].weaponSplId,
|
||||
abilities: build.abilities.flat(),
|
||||
})}
|
||||
settings={
|
||||
<>
|
||||
<SendouSwitch isSelected={showTitle} onChange={setShowTitle}>
|
||||
{t("common:imageExport.buildTitle")}
|
||||
</SendouSwitch>
|
||||
<SendouSwitch
|
||||
isSelected={showAbilityPoints}
|
||||
onChange={setShowAbilityPoints}
|
||||
>
|
||||
{t("common:imageExport.abilityPoints")}
|
||||
</SendouSwitch>
|
||||
<SendouSwitch
|
||||
isSelected={showAbilityChunks}
|
||||
onChange={setShowAbilityChunks}
|
||||
>
|
||||
{t("common:imageExport.abilityChunks")}
|
||||
</SendouSwitch>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<BuildGraphic
|
||||
build={build}
|
||||
owner={owner}
|
||||
showTitle={showTitle}
|
||||
showAbilityPoints={showAbilityPoints}
|
||||
showAbilityChunks={showAbilityChunks}
|
||||
/>
|
||||
</ImageExportDialog>
|
||||
);
|
||||
}
|
||||
|
||||
function RoundWeaponImage({ weapon }: { weapon: BuildWeaponWithTop500Info }) {
|
||||
const normalizedWeaponSplId = canonicalWeaponSplId(weapon.weaponSplId);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export function Flag({
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
return (
|
||||
<div
|
||||
<span
|
||||
className={clsx(`twf twf-${countryCode.toLowerCase()}`, {
|
||||
"twf-s": tiny,
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user