278 lines
7.2 KiB
TypeScript
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>
|
|
);
|
|
}
|
|
}
|