278 lines
7.2 KiB
TypeScript

import { TempstreamJSX } from "tempstream";
import type {
ComponentToHTMLArgs,
ExtractStructuredComponentArgumentsParsed,
JDDContext,
} from "@sealcode/jdd";
import { Component, ComponentArguments } from "@sealcode/jdd";
import arrow from "./autoscrolling-images-arrow.svg";
import type { FilePointer } from "@sealcode/file-manager";
import type { makeJDDContext } from "../../jdd-context.js";
const images = new ComponentArguments.List(
new ComponentArguments.Structured({
image: new ComponentArguments.Image(),
alt: new ComponentArguments.ShortText(),
})
);
images.getExampleCount = () => {
return Math.round(4 + Math.random() * 9);
};
const component_arguments = {
title: new ComponentArguments.ShortText(),
imagesPerPage: new ComponentArguments.ShortText().setExampleValues(["6"]),
background_mode: new ComponentArguments.Enum([
"transparent",
"white",
"white-square",
]),
speed: new ComponentArguments.ShortText().setExampleValues(["5"]),
desktop_interval: new ComponentArguments.ShortText().setExampleValues(["5"]),
endless: new ComponentArguments.Enum(["true", "false"]),
images,
} as const;
function renderimagePageArrows({
radioButtonIdPrefix,
pageIndex,
imagePages,
}: {
radioButtonIdPrefix: string;
pageIndex: number;
imagePages: Array<Array<Record<string, unknown>>>;
}) {
return (
<div
class="autoscrolling-images__arrow-container"
data-page={pageIndex.toString()}
>
<label
for={`${radioButtonIdPrefix}-autoscrolling-images__radio-${
pageIndex == 0 ? imagePages.length - 1 : pageIndex - 1
}`}
class="autoscrolling-images__arrow"
>
<img
class="autoscrolling-images__img-arrow-left"
src={arrow.url}
alt="←"
/>
</label>
<label
for={`${radioButtonIdPrefix}-autoscrolling-images__radio-${
pageIndex == imagePages.length - 1 ? 0 : pageIndex + 1
}`}
class="autoscrolling-images__arrow"
>
<img src={arrow.url} alt="→" />
</label>
</div>
);
}
export function renderImagePage({
page,
render_image,
mode = "regular",
background_mode,
}: {
page: Array<{ image: FilePointer | null; alt: string }>;
render_image: ReturnType<typeof makeJDDContext>["render_image"];
mode?: "regular" | "looping-tail" | "looping-head";
background_mode: "white" | "white-square" | "transparent";
}): JSX.Element {
return (
<div
class={[
"autoscrolling-images__carousel-page",
`autoscrolling-images__carousel-page--${mode}`,
]}
>
{page.map((image) => (
<div class="autoscrolling-images__img-wrapper">
{render_image(image.image, {
container: {
...(background_mode == "white-square"
? { width: 128, height: 128 }
: { width: 212, height: 150 }),
objectFit: "contain",
},
alt: image.alt,
lossless: true,
thumbnailSize: 0,
})}
</div>
))}
</div>
);
}
export class AutoscrollingImages extends Component<typeof component_arguments> {
getArguments() {
return component_arguments;
}
getTitle(
_: JDDContext,
args: ExtractStructuredComponentArgumentsParsed<typeof component_arguments>
) {
return args.title || null;
}
toHTML({
args: {
title,
imagesPerPage,
images,
speed,
desktop_interval,
background_mode,
endless,
},
jdd_context: { render_image },
classes,
index,
}: ComponentToHTMLArgs<typeof component_arguments>) {
const imageNumberPerPage = parseInt(imagesPerPage);
const imagePages: (typeof images)[] = [];
for (let i = 0; i < images.length; i += imageNumberPerPage) {
imagePages.push(images.slice(i, i + imageNumberPerPage));
}
const radioButtonIdPrefix = "r" + Math.floor(100 + Math.random() * 900);
const radioButton_name = `autoscrolling-images__radio--${radioButtonIdPrefix}`;
return (
<div
class={[
"autoscrolling-images",
`autoscrolling-images--mode-${background_mode}`,
endless == "true" ? "endless" : "",
...classes,
]}
style={`--jdd-index: ${index}`}
data-controller="autoscrolling-images"
data-autoscrolling-images-interval={desktop_interval}
>
<style>
{imagePages
.map((_, pageIndex) => {
const radio_id = `${radioButtonIdPrefix}-autoscrolling-images__radio-${pageIndex}`;
return `#${radio_id}:checked ~ .autoscrolling-images__imgs-carousel > .autoscrolling-images__carousel {
transform: translateX(calc(${pageIndex} * (-100%)));
}
#${radio_id}:checked ~ .autoscrolling-images__arrow-carousel-container
> .autoscrolling-images__arrow-carousel {
transform: translateX(calc(${pageIndex} * (-100%)));
}
#${radio_id}:checked ~ .autoscrolling-images__dots-container
> label:nth-child(${pageIndex + 1}) {
background-color: var(--color-brand-accent);
}
.autoscrolling-images:has(#${radio_id}:checked)
.autoscrolling-images__arrow-container[data-page="${pageIndex}"]{
display:flex;
}
`;
})
.join("\n")}
</style>
<div
class="autoscrolling-images-wrapper"
data-carousel-id-prefix={radioButtonIdPrefix}
>
<div class="autoscrolling-images__title-wrapper">
<h2 class="autoscrolling-images__title">{title}</h2>
{imagePages.length > 1 ? (
<div class="autoscrolling-images__arrow-carousel-container">
<div class="autoscrolling-images__arrow-carousel">
{imagePages.map((_, pageIndex) =>
renderimagePageArrows({
pageIndex,
imagePages,
radioButtonIdPrefix,
})
)}
</div>
</div>
) : (
""
)}
</div>
<div class="autoscrolling-images__carousel-container">
{imagePages.map((_, pageIndex) => (
<input
class="autoscrolling-images__radio"
type="radio"
name={radioButton_name}
title={`page ${pageIndex + 1}`}
value={pageIndex}
id={`${radioButtonIdPrefix}-autoscrolling-images__radio-${pageIndex}`}
checked={pageIndex === 0}
data-action="autoscrolling-images#handleRadioChange"
/>
))}
<div class="autoscrolling-images__imgs-carousel">
<div
class="autoscrolling-images__carousel"
style={`--animation-length: ${
100 / parseInt(speed || "15")
}s`}
>
{renderImagePage({
page: imagePages.at(-1)!,
render_image,
mode: "looping-head",
background_mode,
})}
{imagePages.map((page) =>
renderImagePage({
page,
render_image,
background_mode,
})
)}
{/* for looping endless scroll*/}
{imagePages[0]
? renderImagePage({
page: imagePages[0],
render_image,
mode: "looping-tail",
background_mode,
})
: ""}
</div>
</div>
{imagePages.length > 1 ? (
<div class="autoscrolling-images__dots-container">
{imagePages.map((_, pageIndex) => (
<label
for={`${radioButtonIdPrefix}-autoscrolling-images__radio-${pageIndex}`}
class="autoscrolling-images__dots"
></label>
))}
</div>
) : (
""
)}
</div>
</div>
</div>
);
}
}