Skip to content

Instantly share code, notes, and snippets.

@davidfurlong
Created January 25, 2024 17:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davidfurlong/b30aa3b6d7b9d175c2c895ae17ffdc52 to your computer and use it in GitHub Desktop.
Save davidfurlong/b30aa3b6d7b9d175c2c895ae17ffdc52 to your computer and use it in GitHub Desktop.
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { createStore } from "https://framer.com/m/framer/store.js@^1.0.0";
import { useEffect, useState, useRef, useCallback } from "react";
import { addPropertyControls, ControlType, RenderTarget } from "framer";
import { motion } from "framer-motion";
import { useSwipeable } from "https://framerusercontent.com/modules/6pcIVukgG0tVOg4yaIck/a3hKVXMwf9T1lIRSVt0U/Helpers.js";
import { rgbToArray } from "https://framerusercontent.com/modules/LxTINgasBGoX7JtSU6qZ/h7jvPi5x1iTndnDxH6ha/Utils.js";
const useStore = createStore({ current: 0, maxCurrent: 0, cursor: "" });
function calculateScalingFactor(
screenWidth,
screenHeight,
divWidth,
divHeight
) {
const widthScale = screenWidth / divWidth;
const heightScale = screenHeight / divHeight;
const screenDirection = screenWidth < screenHeight; // Calculate the scaling factor to fill the screen, maximizing size
const scalingFactor = Math.min(widthScale, heightScale);
if (widthScale !== heightScale && screenDirection === false) {
return scalingFactor;
} else if (widthScale !== heightScale && screenDirection === true) {
return Math.min(widthScale, heightScale);
} else if (widthScale === heightScale) {
return 1;
}
}
export function OriginSlides(props) {
const [store, setStore] = useStore();
const [load, setLoad] = useState(false);
const [scaleFactorArray, setScaleFactorArray] = useState([]);
const [combinedArray, setCombinedArray] = useState([]);
const [divSizeArray, setDivSizeArray] = useState([]);
const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 });
const [canvasSlideSize, setCanvasSlideSize] = useState({
width: 0,
height: 0,
});
const [windowSize, setWindowSize] = useState({ width: 0, height: 0 });
const slideRef = useRef(null);
const swipeConfig = {
delta: 10,
preventScrollOnSwipe: true,
trackTouch: true,
trackMouse: false,
rotationAngle: 0,
swipeDuration: 250,
touchEventOptions: { passive: true },
};
const [isPortrait, setIsPortrait] = useState(false);
useEffect(() => {
if (
RenderTarget.current() === RenderTarget.canvas &&
slideRef.current !== null &&
slideRef.current !== undefined &&
slideRef.current.getBoundingClientRect() !== undefined &&
slideRef.current.getBoundingClientRect() !== null &&
divSizeArray.length !== 0
) {
console.log("canvas");
setCanvasSize({
width: slideRef.current.getBoundingClientRect().width,
height: slideRef.current.getBoundingClientRect().height,
});
if (
slideRef.current.getBoundingClientRect().width >
slideRef.current.getBoundingClientRect().height ||
props.rotateOnPortrait === false
) {
setIsPortrait(false);
} else {
setIsPortrait(true);
}
}
}, [
RenderTarget.current(),
slideRef.current,
props.rotateOnPortrait,
divSizeArray,
]);
const swipeHandler = useSwipeable({
onSwipedDown: (e) => {
movePrevPage();
},
onSwipedUp: (e) => {
moveNextPage();
},
...swipeConfig,
});
const [cursor, setCursor] = useState("");
const [cursorPos, setCursorPos] = useState({ x: null, y: null });
const moveNextPage = () => {
if (store.current < store.maxCurrent - 1) {
setStore({ current: store.current + 1 });
} // else {
// setStore({ toast: "This is the last page!" })
// }
};
const movePrevPage = () => {
if (store.current > 0) {
setStore({ current: store.current - 1 });
} // else {
// setStore({ toast: "This is the first page!" })
// }
}; // useEffect(() => {
// console.log("windowSize: " + windowSize)
// }, [windowSize])
// useEffect(() => {
// if (divSizeArray.length !== 0) {
// console.log(
// "divSizeArray: " + divSizeArray[0].width,
// divSizeArray[0].height
// )
// }
// }, [divSizeArray])
// useEffect(() => {
// console.log("scaleFactorArray: " + scaleFactorArray)
// }, [scaleFactorArray])
// useEffect(() => {
// console.log("isPortrait: " + isPortrait)
// }, [isPortrait])
useEffect(() => {
setLoad(true);
function handleResize() {
setWindowSize({ width: window.innerWidth, height: window.innerHeight });
if (
window.innerWidth > window.innerHeight ||
props.rotateOnPortrait === false
) {
setIsPortrait(false);
} else {
setIsPortrait(true);
}
}
window.addEventListener("resize", handleResize);
window.addEventListener("mousemove", function (event) {
if (event.target.tagName === "A" || event.target.tagName === "BUTTON") {
setStore({ cursor: "pointer" });
}
}); // Remove the event listener when the component unmounts
handleResize();
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
useEffect(() => {
setStore({ maxCurrent: props.frameArray.length }); // console.log(props.frameArray)
for (let i = 0; i < store.maxCurrent; i++) {
if (
isPortrait === false &&
document.getElementById("slide" + i) !== undefined &&
document.getElementById("slide" + i) !== null &&
document.getElementById("slide" + i).getBoundingClientRect() !==
undefined &&
document.getElementById("slide" + i).getBoundingClientRect() !== null
) {
let divWidth = document
.getElementById("slide" + i)
.getBoundingClientRect().width;
let divHeight = document
.getElementById("slide" + i)
.getBoundingClientRect().height;
divSizeArray[i] = { width: divWidth, height: divHeight };
setDivSizeArray([...divSizeArray]);
} else if (
isPortrait === true &&
document.getElementById("slide" + i) !== undefined &&
document.getElementById("slide" + i) !== null &&
document.getElementById("slide" + i).getBoundingClientRect() !==
undefined &&
document.getElementById("slide" + i).getBoundingClientRect() !== null
) {
let divWidth = document
.getElementById("slide" + i)
.getBoundingClientRect().width;
let divHeight = document
.getElementById("slide" + i)
.getBoundingClientRect().height;
divSizeArray[i] = { width: divHeight, height: divWidth };
setDivSizeArray([...divSizeArray]);
}
}
}, [load]);
useEffect(() => {
if (
store.maxCurrent !== 0 &&
divSizeArray.length !== 0 &&
windowSize.width !== 0
) {
for (let i = 0; i < store.maxCurrent; i++) {
if (isPortrait === false) {
scaleFactorArray[i] = calculateScalingFactor(
windowSize.width,
windowSize.height,
divSizeArray[i].width,
divSizeArray[i].height
);
setScaleFactorArray([...scaleFactorArray]);
} else {
scaleFactorArray[i] = calculateScalingFactor(
windowSize.height,
windowSize.width,
divSizeArray[i].width,
divSizeArray[i].height
);
setScaleFactorArray([...scaleFactorArray]);
}
}
}
}, [windowSize, isPortrait, load]);
if (RenderTarget.current() === RenderTarget.canvas) {
return /*#__PURE__*/ _jsx("div", {
ref: slideRef,
style: {
width: "100%",
height: "100%",
background: props.background, // background: "blue",
display: "flex",
justifyContent: "center",
alignItems: "center",
},
children:
props.frameArray.length !== 0 &&
props.frameArray.map((info, index) => {
return /*#__PURE__*/ _jsxs(
motion.div,
{
style: {
width: "100%",
height: "100%",
display:
index < store.current + 1 ||
(index > store.current - 1 && store.current > 0)
? "flex"
: "none",
justifyContent: "center",
alignItems: "center",
x:
isPortrait === true
? "0"
: "calc(-100% * " + store.current + ")",
y:
isPortrait === true
? "calc(-100% * " + store.current + ")"
: 0,
},
children: [
/*#__PURE__*/ _jsxs("div", {
// current info
style: {
position: "absolute",
background: "yellow",
zIndex: 100,
top: 0,
left: 0,
display: "none",
},
children: [
"window size: " +
windowSize.width +
" " +
windowSize.height,
/*#__PURE__*/ _jsx("br", {}),
"scaling factor : " + scaleFactorArray[0],
" ",
/*#__PURE__*/ _jsx("br", {}),
divSizeArray.length !== 0 &&
divSizeArray[0].width + " " + divSizeArray[0].height,
/*#__PURE__*/ _jsx("br", {}),
],
}),
/*#__PURE__*/ _jsx(motion.div, {
id: "slide" + index,
style: {
width: "fit-content",
height: "fit-content",
display: "flex",
justifyContent: "center",
alignItems: "center",
opacity: 1,
scale:
divSizeArray.length !== 0 && isPortrait === false
? calculateScalingFactor(
canvasSize.width,
canvasSize.height,
divSizeArray[0].width,
divSizeArray[0].height
)
: divSizeArray.length !== 0 && isPortrait === true
? calculateScalingFactor(
canvasSize.height,
canvasSize.width,
divSizeArray[0].width,
divSizeArray[0].height
)
: 1, // scale: scaleFactorArray[0],
rotate: isPortrait === true ? 90 : 0,
},
transition: { duration: 0 },
children: info,
}),
],
},
index
);
}),
});
}
return /*#__PURE__*/ _jsxs("div", {
style: {
width: "100vw",
height: "100dvh",
overflow: "hidden",
background: props.background,
userSelect: "none",
touchAction: "pan-y",
cursor: props.hideDefaultCursor === true ? "none" : "auto",
},
ref: slideRef,
...swipeHandler,
onMouseMove: (e) => {
setCursorPos({ x: e.clientX, y: e.clientY });
if (isPortrait === false && e.clientX < windowSize.width / 2) {
setStore({ cursor: "left" });
} else if (isPortrait === false && e.clientX >= windowSize.width / 2) {
setStore({ cursor: "right" });
} else if (isPortrait === true && e.clientY < windowSize.height / 2) {
setStore({ cursor: "left" });
} else {
setStore({ cursor: "right" });
}
},
onMouseDown: (e) => {
if (isPortrait === true && e.clientY < windowSize.height / 2) {
movePrevPage();
} else if (isPortrait === true && e.clientY >= windowSize.height / 2) {
moveNextPage();
}
},
children: [
/*#__PURE__*/ _jsx(motion.div, {
// code for cursor
style: {
width: store.cursor === "pointer" ? 0 : 1,
height: store.cursor === "pointer" ? 0 : 1,
x: cursorPos.x,
y: cursorPos.y,
position: "fixed",
zIndex: 1,
rotate: isPortrait === true ? 90 : 0,
display: props.arrowCursor === true ? "flex" : "none",
justifyContent: "center",
alignItems: "center",
opacity: isPortrait === true ? 0 : 1,
},
whileTap: {
opacity: 1,
scale:
(store.current === 0 && store.cursor === "left") ||
(store.current === store.maxCurrent - 1 && store.cursor === "right")
? 1
: 1.1,
},
onClick: () => {
if (store.cursor === "left") {
movePrevPage();
} else {
moveNextPage();
}
},
children: /*#__PURE__*/ _jsx(motion.div, {
style: {
borderRadius: 40,
color: "white",
position: "absolute",
background:
(store.current === 0 && store.cursor === "left") ||
(store.current === store.maxCurrent - 1 &&
store.cursor === "right")
? "rgba(" +
rgbToArray(props.cursorColor)[0] +
"," +
rgbToArray(props.cursorColor)[1] +
"," +
rgbToArray(props.cursorColor)[2] +
"," +
"0.2)"
: "rgba(" +
rgbToArray(props.cursorColor)[0] +
"," +
rgbToArray(props.cursorColor)[1] +
"," +
rgbToArray(props.cursorColor)[2] +
"," +
"0.8)",
fontSize: 20,
display: "flex",
justifyContent: "center",
alignItems: "center",
opacity: store.cursor === "" ? 0 : 1,
fontFamily: "Inter, sans-serif",
userSelect: "none",
lineHeight: 0,
pointerEvents: "none",
},
animate: {
width: store.cursor === "pointer" ? 0 : 40,
height: store.cursor === "pointer" ? 0 : 40,
left: store.cursor === "pointer" ? 0 : -20,
top: store.cursor === "pointer" ? 0 : -20,
},
children:
store.cursor === "left"
? "<-"
: store.cursor === "right"
? "->"
: "",
}),
}),
/*#__PURE__*/ _jsx(motion.div, {
style: {
width: isPortrait === true ? "100vw" : "fit-content",
height: isPortrait === true ? "fit-content" : "100dvh",
display: "flex",
flexDirection: isPortrait === true ? "column" : "row",
alignItems: "center",
opacity: load === true ? 1 : 0.5,
filter: load === true ? "blur(0px)" : "blur(40px)",
transition: "0.6s",
},
children:
props.frameArray.length !== 0 &&
props.frameArray.map((info, index) => {
return /*#__PURE__*/ _jsx(
motion.div,
{
style: {
width: "100vw",
height: "100dvh",
display:
index < store.current + 1 ||
(index > store.current - 1 && store.current > 0)
? "flex"
: "none",
justifyContent: "center",
alignItems: "center",
x:
isPortrait === true
? "0"
: "calc(-100% * " + store.current + ")",
y:
isPortrait === true
? "calc(-100% * " + store.current + ")"
: 0,
},
children: /*#__PURE__*/ _jsx(motion.div, {
id: "slide" + index,
style: {
width: "fit-content",
height: "fit-content",
display: "flex",
justifyContent: "center",
alignItems: "center",
opacity: 1,
scale: scaleFactorArray[0],
rotate: isPortrait === true ? 90 : 0,
},
transition: { duration: 0 },
children: info,
}),
},
index
);
}),
}),
],
});
}
addPropertyControls(OriginSlides, {
frameArray: {
type: ControlType.Array,
control: { type: ControlType.ComponentInstance },
},
background: { type: ControlType.Color, defaultValue: "black" },
arrowCursor: { type: ControlType.Boolean, defaultValue: true },
hideDefaultCursor: { type: ControlType.Boolean, defaultValue: false },
cursorColor: {
type: ControlType.Color,
defaultValue: "#000000",
hidden(props) {
return props.arrowCursor === false;
},
},
rotateOnPortrait: {
type: ControlType.Boolean,
defaultValue: true,
description: "Auto rotate the slides when the device ratio is portrait",
},
});
export function OriginNav(props) {
const [store, setStore] = useStore();
const [cursor, setCursor] = useState("");
const [cursorPos, setCursorPos] = useState({ x: null, y: null });
const ref = useRef(null);
const moveNextPage = () => {
if (store.current < store.maxCurrent - 1) {
setStore({ current: store.current + 1 });
}
};
const movePrevPage = () => {
if (store.current > 0) {
setStore({ current: store.current - 1 });
}
};
const handleKeyDown = useCallback(
(event) => {
ref.current.focus();
if (event.key === "ArrowRight" || event.key === "ArrowUp") {
moveNextPage();
} else if (event.key === "ArrowLeft" || event.key === "ArrowDown") {
movePrevPage();
}
},
[moveNextPage, moveNextPage]
);
useEffect(() => {
ref.current.focus();
window.addEventListener("keydown", handleKeyDown); // Remove the event listener when the component unmounts
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, []);
return /*#__PURE__*/ _jsxs(motion.div, {
tabIndex: 0,
ref: ref,
onKeyDown: handleKeyDown,
style: {
width: props.isMobile === true ? "fit-content" : "100%",
height: "fit-content",
background: props.navigationBG,
color: "white",
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
padding: props.isMobile === true ? 8 : 15,
userSelect: "none",
zIndex: 3,
cursor: "default",
borderRadius: props.isMobile === true ? 300 : 0,
outline: "none",
},
initial: { opacity: 1 },
animate: { opacity: props.alwaysOn === true ? 1 : 0 },
transition: { duration: 0.4 },
whileHover: { opacity: 1 },
onMouseOver: () => {
setStore({ cursor: "" });
},
children: [
/*#__PURE__*/ _jsx("div", {
style: {
display: props.isMobile === true ? "none" : "block",
width: 120,
overflow: "visible",
textAlign: "left",
whiteSpace: "nowrap",
},
children: props.title,
}),
/*#__PURE__*/ _jsxs("div", {
style: {
width: "fit-content",
height: "fit-content",
fontSize: 12,
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
gap: 20,
fontFamily: "Inter, san-serif",
overflow: "visible",
},
children: [
/*#__PURE__*/ _jsx(motion.div, {
style: {
background:
"rgb(" +
(parseInt(rgbToArray(props.navigationBG)[0]) + 20) +
"," +
(parseInt(rgbToArray(props.navigationBG)[1]) + 20) +
"," +
(parseInt(rgbToArray(props.navigationBG)[2]) + 20) +
")",
display: props.isMobile === true ? "none" : "flex",
justifyContent: "center",
alignItems: "center",
width: 30,
height: 30,
borderRadius: 30,
cursor: store.current === 0 ? "default" : "pointer",
opacity: store.current === 0 ? 0.4 : 1,
paddingRight: 0,
lineHeight: 0,
},
onClick: () => {
movePrevPage();
},
whileHover: {
background:
"rgb(" +
(parseInt(rgbToArray(props.navigationBG)[0]) + 30) +
"," +
(parseInt(rgbToArray(props.navigationBG)[1]) + 30) +
"," +
(parseInt(rgbToArray(props.navigationBG)[2]) + 30) +
")",
paddingRight: store.current === 0 ? 0 : 3,
},
whileTap: {
background:
"rgb(" +
(parseInt(rgbToArray(props.navigationBG)[0]) + 40) +
"," +
(parseInt(rgbToArray(props.navigationBG)[1]) + 40) +
"," +
(parseInt(rgbToArray(props.navigationBG)[2]) + 40) +
")",
scale: 1.1,
},
children: "<-",
}),
/*#__PURE__*/ _jsxs("div", {
style: {
width: props.isMobile === true ? "fit-content" : 50,
height: "fit-content",
textAlign: "center",
writingMode:
props.isMobile === true ? "vertical-rl" : "horizontal-tb",
},
children: [store.current + 1, " / ", store.maxCurrent],
}),
/*#__PURE__*/ _jsx(motion.div, {
style: {
background:
"rgb(" +
(parseInt(rgbToArray(props.navigationBG)[0]) + 20) +
"," +
(parseInt(rgbToArray(props.navigationBG)[1]) + 20) +
"," +
(parseInt(rgbToArray(props.navigationBG)[2]) + 20) +
")",
display: props.isMobile === true ? "none" : "flex",
justifyContent: "center",
alignItems: "center",
width: 30,
height: 30,
borderRadius: 30,
lineHeight: 0,
cursor:
store.current === store.maxCurrent - 1 ? "default" : "pointer",
opacity: store.current === store.maxCurrent - 1 ? 0.4 : 1,
paddingLeft: 0,
},
onClick: () => {
moveNextPage();
},
whileHover: {
background:
"rgb(" +
(parseInt(rgbToArray(props.navigationBG)[0]) + 30) +
"," +
(parseInt(rgbToArray(props.navigationBG)[1]) + 30) +
"," +
(parseInt(rgbToArray(props.navigationBG)[2]) + 30) +
")",
paddingLeft: store.current === store.maxCurrent - 1 ? 0 : 3,
},
whileTap: {
background:
"rgb(" +
(parseInt(rgbToArray(props.navigationBG)[0]) + 40) +
"," +
(parseInt(rgbToArray(props.navigationBG)[1]) + 40) +
"," +
(parseInt(rgbToArray(props.navigationBG)[2]) + 40) +
")",
scale: 1.1,
},
children: "->",
}),
],
}),
/*#__PURE__*/ _jsx(motion.div, {
style: {
width: 120,
display: props.isMobile === true ? "none" : "block",
overflow: "visible",
textAlign: "right",
whiteSpace: "nowrap",
textDecoration: "none",
cursor: "pointer",
},
whileHover: { textDecoration: "underline" },
onClick: () => {
if (props.linkType === "custom-link") {
window.open(props.link, "_blank");
} else {
navigator.clipboard.writeText(window.location.href);
}
},
children:
props.linkType === "custom-link" ? props.linkTitle + "↗" : "Copy URL",
}),
],
});
}
addPropertyControls(OriginNav, {
navigationBG: { type: ControlType.Color, defaultValue: "#000000" },
alwaysOn: { type: ControlType.Boolean, defaultValue: false },
title: { type: ControlType.String, defaultValue: "Seungmee Lee Portfolio" },
link: { type: ControlType.Link },
linkType: {
type: ControlType.Enum,
options: ["custom-link", "copy-link"],
optionTitles: ["Custom Link", "Copy Link"],
defaultValue: "copy-link",
},
linkTitle: {
type: ControlType.String,
defaultValue: "Website",
hidden(props) {
return props.linkType === "copy-link";
},
},
isMobile: { type: ControlType.Boolean, defaultValue: false },
});
export function OriginPageIndicator(props) {
const [store, setStore] = useStore();
return /*#__PURE__*/ _jsx(motion.div, {
style: {
width: "100%",
height: "100%",
display: "flex",
gap: 5,
justifyContent: "center",
alignItems: "center",
},
children: Array(store.maxCurrent)
.fill(null)
.map((info, index) => {
return /*#__PURE__*/ _jsx(
motion.a,
{
style: {
width: "100%",
height: "100%",
borderRadius: 10,
background: props.color,
cursor: "pointer",
},
animate: { opacity: store.current === index ? 1 : 0.4 },
onClick: () => {
setStore({ current: index });
},
whileHover: { opacity: 0.7 },
},
"indi" + index
);
}),
});
}
addPropertyControls(OriginPageIndicator, {
color: { type: ControlType.Color, defaultValue: "white" },
});
export default { OriginSlides, OriginPageIndicator, OriginNav };
export const __FramerMetadata__ = {
exports: {
OriginSlides: {
type: "reactComponent",
name: "OriginSlides",
slots: [],
annotations: { framerContractVersion: "1" },
},
OriginNav: {
type: "reactComponent",
name: "OriginNav",
slots: [],
annotations: { framerContractVersion: "1" },
},
default: { type: "variable", annotations: { framerContractVersion: "1" } },
OriginPageIndicator: {
type: "reactComponent",
name: "OriginPageIndicator",
slots: [],
annotations: { framerContractVersion: "1" },
},
__FramerMetadata__: { type: "variable" },
},
};
//# sourceMappingURL=./Slides.map
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment