@fortawesome/free-solid-svg-icons#faDownload TypeScript Examples
The following examples show how to use
@fortawesome/free-solid-svg-icons#faDownload.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.tsx From genshin-optimizer with MIT License | 5 votes |
export default function PageScanner(props: any) {
const { t: ui } = useTranslation('ui')
const { t } = useTranslation('page_scanner')
ReactGA.send({ hitType: "pageview", page: '/scanner' })
return <Box display="flex" flexDirection="column" gap={1} my={1}>
<CardDark><CardContent>
<Trans t={t} i18nKey="intro">
<Typography variant="h5">Automatic Scanners</Typography>
<Typography gutterBottom>Automatic Scanners are Genshin tools that can automatically scan in-game data by manipulating your mouse movements, taking screenshots of the game, and then scanning information from those screenshots. These are low-risk tools that can help you automate a lot of manual process with scanning artifacts for GO. As any tools that indirectly interact with the game, althought their usage is virtually undetectable, <Link href="https://genshin.mihoyo.com/en/news/detail/5763" target="_blank" rel="noreferrer">there could still be risk with using them.</Link> Users discretion is advised.
</Typography>
<Typography >The most important aspect of using these Scanners with GO is the output format:</Typography>
<Typography gutterBottom component="div">
<ul>
<li>As of <code>v5.21.0</code>, GO can import artifact data in the <code>mona-uranai</code> format. </li>
<li>As of <code>v6.0.0</code>, GO can import data in the <code>Genshin Open Object Description (GOOD)</code> format.</li>
</ul>
</Typography>
<Typography gutterBottom>Below are several scanners that have been tested with GO.</Typography>
<Typography>To upload the exported file, go to <Link component={RouterLink} to="/setting">Settings</Link> page, and upload your file in the <strong>Database Upload</strong> section.</Typography>
</Trans>
</CardContent></CardDark>
<CardDark><CardContent><Grid container spacing={2} >
<Grid item xs={12} md={4}><ImgFullwidth src={GIScanner} /></Grid>
<Grid item xs={12} md={8}>
<Typography variant="h5"><Trans t={t} i18nKey="ik.title">Inventory Kamera</Trans><SqBadge color="success" sx={{ ml: 1 }}><Trans t={ui} i18nKey="updatedfor" values={{ version: "2.6" }} /></SqBadge></Typography>
<Typography gutterBottom><Trans t={t} i18nKey="ik.p1">This light-weight app will scan all your characters + weapons + artifacts in your inventory. Follow the instrutions in the app to set it up.</Trans></Typography>
<Typography gutterBottom><Trans t={t} i18nKey="ik.p2">This scanner can also scan materials for <Link href="https://seelie.me/" target="_blank" rel="noreferrer">Seelie.me</Link></Trans></Typography>
<Typography gutterBottom><Trans t={t} i18nKey="goodeng">This scanner only scans in english, and exports to GOOD format.</Trans></Typography>
<Button href="https://github.com/Andrewthe13th/Inventory_Kamera" target="_blank" startIcon={<FontAwesomeIcon icon={faDownload} />} ><Trans t={ui} i18nKey="link.download" /></Button>
</Grid>
</Grid></CardContent></CardDark>
<CardDark><CardContent><Grid container spacing={2} >
<Grid item xs={12} md={8}>
<Typography variant="h5"><Trans t={t} i18nKey="as.title">AdeptiScanner</Trans><SqBadge color="success" sx={{ ml: 1 }}><Trans t={ui} i18nKey="updatedfor" values={{ version: "2.6+" }} /></SqBadge></Typography>
<Typography gutterBottom><Trans t={t} i18nKey="as.p1">Scans all your artifacts in inventory. Has a manual scanning mode.</Trans></Typography>
<Typography gutterBottom><Trans t={t} i18nKey="as.p2">This scanner can also be configured for new artifacts in new game versions without needing an update.</Trans></Typography>
<Typography gutterBottom><Trans t={t} i18nKey="goodeng">This scanner only scans in english, and exports to GOOD format.</Trans></Typography>
<Button href="https://github.com/D1firehail/AdeptiScanner-GI" target="_blank" startIcon={<FontAwesomeIcon icon={faDownload} />} ><Trans t={ui} i18nKey="link.download" /></Button>
</Grid>
<Grid item xs={12} md={4}><ImgFullwidth src={AdScanner} /></Grid>
</Grid></CardContent></CardDark>
<CardDark><CardContent><Grid container spacing={2} >
<Grid item xs={12} md={4}><ImgFullwidth src={Amenoma} /></Grid>
<Grid item xs={12} md={8}>
<Typography variant="h5"><Trans t={t} i18nKey="am.title">「天目」-- Amenoma</Trans><SqBadge color="success" sx={{ ml: 1 }}><Trans t={ui} i18nKey="updatedfor" values={{ version: "2.6" }} /></SqBadge></Typography>
<Typography gutterBottom><Trans t={t} i18nKey="am.p1">Scans all you artifacts in your inventory. Follow the instruction to capture the window and scan. Has both Chinese and English version. (Download the <code>_EN.exe</code> version to scan in english). Both the <code>mona-uranai</code> and <code>GOOD</code> format is accepted in GO. the <code>GOOD</code> format is recommended.</Trans></Typography>
<Typography gutterBottom><Trans t={t} i18nKey="am.p2">Beta version of this scanner can also scan materials for <Link href="https://seelie.me/" target="_blank" rel="noreferrer">Seelie.me</Link></Trans></Typography>
<Button sx={{ mb: 2 }} href="https://github.com/daydreaming666/Amenoma" target="_blank" startIcon={<FontAwesomeIcon icon={faDownload} />} ><Trans t={ui} i18nKey="link.download" /></Button>
<Typography gutterBottom><Trans t={t} i18nKey="am.p3">Please feel free to join their discord if you find any bugs. They are in need of more english testers.</Trans></Typography>
<Button href="https://discord.gg/BTrCYgVGFP" target="_blank" startIcon={<FontAwesomeIcon icon={faDiscord} />} ><Trans t={ui} i18nKey="link.discord" /></Button>
</Grid>
</Grid></CardContent></CardDark>
<CardDark><CardContent>
<Button component={RouterLink} to="/" startIcon={<FontAwesomeIcon icon={faHome} />}><Trans t={t} i18nKey="backHome">Go back to home page</Trans></Button>
</CardContent></CardDark>
</Box >
}
Example #2
Source File: TopupSuccess.tsx From mysterium-vpn-desktop with MIT License | 5 votes |
TopupSuccess: React.FC = observer(() => {
const { payment, identity, navigation } = useStores()
const isOnboarding = identity.identity?.registrationStatus != IdentityRegistrationStatus.Registered
const handleAction = () => {
if (isOnboarding) {
navigation.push(locations.registering)
} else {
navigation.goHome()
}
}
useEffect(() => {
toast.success(`${payment.appCurrency}s will be credited to your wallet within next 1-3 minutes.`)
}, [])
return (
<ViewContainer>
<ViewNavBar>
<div style={{ width: 375, textAlign: "center" }}>
<StepProgressBar step={3} />
</div>
</ViewNavBar>
<ViewSplit>
<ViewSidebar>
<SideTop>
<TitleIcon>
<IconWallet color={brandLight} />
</TitleIcon>
<Title>Payment successful!</Title>
<TitleDescription>
{payment.appCurrency}s will be credited to your wallet within next 1-3 minutes.
</TitleDescription>
</SideTop>
<SideBot>
<BrandButton style={{ marginTop: "auto" }} onClick={handleAction}>
Continue
</BrandButton>
</SideBot>
</ViewSidebar>
<Content>
<div style={{ marginTop: "auto", marginBottom: "auto" }}>
<Heading2>
<FontAwesomeIcon className="icon" icon={faCheckCircle} color="#ffffff44" size="10x" />
<div style={{ marginTop: 20, cursor: "pointer" }} onClick={() => payment.downloadInvoice()}>
<FontAwesomeIcon
style={{ marginRight: 10 }}
className="icon"
icon={faDownload}
color="#ffffff44"
size="1x"
/>
Download invoice
</div>
</Heading2>
</div>
</Content>
</ViewSplit>
</ViewContainer>
)
})
Example #3
Source File: Diploma.tsx From frontend.ro with MIT License | 4 votes |
Diploma = ({
student, tutorial, certification, variant = 'large', shareControls = true,
}: Props) => {
const [fullCertificationUrl, setFullCertificationUrl] = useState(certification.url);
useEffect(() => {
setFullCertificationUrl(`${window.location.origin}${certification.url}`);
}, []);
return (
// If we ever change this attribute ID, make sure we also change
// the `diploma-screenshot` Lambda Function
// https://github.com/FrontEnd-ro/frontend.ro/issues/449
<section data-diploma className={`${styles.Diploma} ${styles[`Diploma-${variant}`]} relative`}>
<div className={`${styles.meta} absolute d-flex justify-content-between align-items-center mb-4`}>
<a href="https://FrontEnd.ro">
FrontEnd.ro
</a>
<time dateTime={format(certification.date.getTime(), 'yyyy-MM-dd')}>
{formatDate(certification.date)}
</time>
</div>
<div className={`${styles.main} text-center`}>
<a
href={`/${student.username}`}
className={`${styles.student} no-underline inline-flex align-items-center text-left`}
>
<div className={`${styles['student-info']} mx-4`}>
{student.name && (
<p className="m-0 text-bold">{student.name}</p>
)}
<p className={`${styles.username} text-xs m-0 truncate`}>
{`@${student.username}`}
</p>
</div>
<img
width="200"
height="200"
src={student.avatar}
alt={`${student.name} avatar`}
/>
</a>
<p>
a completat
<a className="mt-4 mb-4 d-block text-bold" href={`/${tutorial.tutorialId}`}>
{tutorial.name}
</a>
rezolvând cu succes toate cele
{' '}
{certification.exerciseCount}
{' '}
exerciții.
</p>
</div>
<footer className="relative d-flex justify-content-between align-items-end">
<Signature className="mr-2" />
<QRCode
width={140}
background="#f8f8f8"
className={styles.qrcode}
url={fullCertificationUrl}
/>
{shareControls && (
// This attribute means we'll remove this element when generating
// the OG:IMAGE and PDF for the Diploma
// https://github.com/FrontEnd-ro/frontend.ro/issues/449
<div data-diploma-hidden className={`${styles['share-button-wrapper']} absolute`}>
<OptionsDrawer
trigger={{
text: 'Share',
icon: faShare,
}}
direction="up"
>
<OptionsDrawer.Element>
<CopyLinkButton text={fullCertificationUrl} />
</OptionsDrawer.Element>
{certification.pdf && (
<OptionsDrawer.Element>
<a
href={certification.pdf}
className="btn btn--light no-underline btn--with-icon"
>
<FontAwesomeIcon icon={faDownload} height="24" className="mr-2" />
Download PDF
</a>
</OptionsDrawer.Element>
)}
<OptionsDrawer.Element>
<FacebookButton url={fullCertificationUrl} />
</OptionsDrawer.Element>
<OptionsDrawer.Element>
<LinkedInButton url={fullCertificationUrl} />
</OptionsDrawer.Element>
</OptionsDrawer>
</div>
)}
</footer>
</section>
);
}
Example #4
Source File: ImportExport.tsx From devex with GNU General Public License v3.0 | 4 votes |
ImportExport: React.FC<IProps> = ({ type, map, setMapCb, toJson, fromJson }) => {
const onDrop = useCallback((acceptedFiles: Blob[]) => {
acceptedFiles.forEach((file: Blob) => {
const reader = new FileReader()
reader.onload = () => {
const parsedFile = JSON.parse(reader.result as string)
setMapCb(fromJson ? fromJson(parsedFile) : parsedFile)
}
reader.readAsText(file)
})
}, [setMapCb, fromJson])
const { getRootProps, getInputProps } = useDropzone({ noDrag: true, onDrop })
return (
<>
<div className='import-export'>
<span className='mr-1' {...getRootProps()}>
<input {...getInputProps()} />
<OverlayTrigger placement='top'
overlay={<Tooltip id={'import-tt'}>{type === 'labels' ? 'Import Labels' : 'Import Networks'}</Tooltip>}>
<Button>
<FontAwesomeIcon
icon={faDownload}
size='sm' />
</Button>
</OverlayTrigger>
</span>
<span className='ml-2'>
<OverlayTrigger placement='top'
overlay={<Tooltip id={'export-tt'}>{type === 'labels' ? 'Export Labels' : 'Export Networks'}</Tooltip>}>
<Button
onClick={() => {
exportToJson(type, toJson, map)
}}>
<FontAwesomeIcon
icon={faUpload}
size='sm' />
</Button>
</OverlayTrigger>
</span>
</div>
</>
)
}
Example #5
Source File: Editor.tsx From zwo-editor with MIT License | 4 votes |
Editor = ({ match }: RouteComponentProps<TParams>) => {
const { v4: uuidv4 } = require("uuid");
const S3_URL = "https://zwift-workout.s3-eu-west-1.amazonaws.com";
const [id, setId] = useState(
match.params.id === "new"
? localStorage.getItem("id") || generateId()
: match.params.id
);
const [bars, setBars] = useState<Array<Bar>>(
JSON.parse(localStorage.getItem("currentWorkout") || "[]")
);
const [actionId, setActionId] = useState<string | undefined>(undefined);
const [ftp, setFtp] = useState(
parseInt(localStorage.getItem("ftp") || "200")
);
const [weight, setWeight] = useState(
parseInt(localStorage.getItem("weight") || "75")
);
const [instructions, setInstructions] = useState<Array<Instruction>>(
JSON.parse(localStorage.getItem("instructions") || "[]")
);
const [tags, setTags] = useState(
JSON.parse(localStorage.getItem("tags") || "[]")
);
const [name, setName] = useState(localStorage.getItem("name") || "");
const [description, setDescription] = useState(
localStorage.getItem("description") || ""
);
const [author, setAuthor] = useState(localStorage.getItem("author") || "");
const [savePopupIsVisile, setSavePopupVisibility] = useState(false);
const [sharePopupIsVisile, setSharePopupVisibility] = useState(false);
const [user, setUser] = useState<firebase.User | null>(null);
const [visibleForm, setVisibleForm] = useState("login"); // default form is login
const canvasRef = useRef<HTMLInputElement>(null);
const segmentsRef = useRef<HTMLInputElement>(null);
const [segmentsWidth, setSegmentsWidth] = useState(1320);
const [message, setMessage] = useState<Message>();
const [showWorkouts, setShowWorkouts] = useState(false);
// bike or run
const [sportType, setSportType] = useState<SportType>(
(localStorage.getItem("sportType") as SportType) || "bike"
);
// distance or time
const [durationType, setDurationType] = useState<DurationType>(
(localStorage.getItem("durationType") as DurationType) || "time"
);
const [runningTimes, setRunningTimes] = useState(loadRunningTimes());
const [textEditorIsVisible, setTextEditorIsVisible] = useState(false);
const [selectedInstruction, setSelectedInstruction] = useState<Instruction>();
const db = firebase.database();
const location = useLocation();
useEffect(() => {
const params = new URLSearchParams(location.search);
const mode = params.get("mode");
if (mode === "resetPassword") {
setVisibleForm("updatePassword");
setSavePopupVisibility(true);
}
}, [location]);
useEffect(() => {
setMessage({ visible: true, class: "loading", text: "Loading.." });
db.ref("workouts/" + id)
.once("value")
.then(function (snapshot) {
if (snapshot.val()) {
// workout exist on server
setAuthor(snapshot.val().author);
setName(snapshot.val().name);
setDescription(snapshot.val().description);
setBars(snapshot.val().workout || []);
setInstructions(snapshot.val().instructions || []);
setTags(snapshot.val().tags || []);
setDurationType(snapshot.val().durationType);
setSportType(snapshot.val().sportType);
localStorage.setItem("id", id);
} else {
// workout doesn't exist on cloud
if (id === localStorage.getItem("id")) {
// user refreshed the page
} else {
// treat this as new workout
setBars([]);
setInstructions([]);
setName("");
setDescription("");
setAuthor("");
setTags([]);
}
localStorage.setItem("id", id);
}
console.log("useEffect firebase");
//finished loading
setMessage({ visible: false });
});
auth.onAuthStateChanged((user) => {
if (user) {
setUser(user);
}
});
window.history.replaceState("", "", `/editor/${id}`);
ReactGA.initialize("UA-55073449-9");
ReactGA.pageview(window.location.pathname + window.location.search);
}, [id, db]);
useEffect(() => {
localStorage.setItem("currentWorkout", JSON.stringify(bars));
localStorage.setItem("ftp", ftp.toString());
localStorage.setItem("instructions", JSON.stringify(instructions));
localStorage.setItem("weight", weight.toString());
localStorage.setItem("name", name);
localStorage.setItem("description", description);
localStorage.setItem("author", author);
localStorage.setItem("tags", JSON.stringify(tags));
localStorage.setItem("sportType", sportType);
localStorage.setItem("durationType", durationType);
localStorage.setItem("runningTimes", JSON.stringify(runningTimes));
setSegmentsWidth(segmentsRef.current?.scrollWidth || 1320);
}, [
segmentsRef,
bars,
ftp,
instructions,
weight,
name,
description,
author,
tags,
sportType,
durationType,
runningTimes,
]);
function generateId() {
return Math.random().toString(36).substr(2, 16);
}
function newWorkout() {
console.log("New workout");
setId(generateId());
setBars([]);
setInstructions([]);
setName("");
setDescription("");
setAuthor("");
setTags([]);
}
function handleOnChange(id: string, values: Bar) {
const index = bars.findIndex((bar) => bar.id === id);
const updatedArray = [...bars];
updatedArray[index] = values;
setBars(updatedArray);
}
function handleOnClick(id: string) {
if (id === actionId) {
setActionId(undefined);
} else {
setActionId(id);
}
}
function handleKeyPress(event: React.KeyboardEvent<HTMLDivElement>) {
if (event.target instanceof HTMLInputElement) {
// Ignore key presses coming from input elements
return;
}
if (event.target instanceof HTMLTextAreaElement) {
// Ignore key presses coming from textarea elements
return;
}
switch (event.keyCode) {
case 8:
removeBar(actionId || "");
// Prevent navigation to previous page
event.preventDefault();
break;
case 37:
// reduce time
removeTimeToBar(actionId || "");
break;
case 39:
// add time
addTimeToBar(actionId || "");
break;
case 38:
// add power
addPowerToBar(actionId || "");
break;
case 40:
// add power
removePowerToBar(actionId || "");
break;
default:
//console.log(event.keyCode);
break;
}
}
function addBar(
zone: number,
duration: number = 300,
cadence: number = 0,
pace: number = 0,
length: number = 200
) {
setBars((bars) => [
...bars,
{
time:
durationType === "time"
? duration
: helpers.round(
helpers.calculateTime(length, calculateSpeed(pace)),
1
),
length:
durationType === "time"
? helpers.round(
helpers.calculateDistance(duration, calculateSpeed(pace)),
1
)
: length,
power: zone,
cadence: cadence,
type: "bar",
id: uuidv4(),
pace: pace,
},
]);
}
function addTrapeze(
zone1: number,
zone2: number,
duration: number = 300,
pace: number = 0,
length: number = 1000,
cadence: number = 0
) {
setBars((bars) => [
...bars,
{
time:
durationType === "time"
? duration
: helpers.round(
helpers.calculateTime(length, calculateSpeed(pace)),
1
),
length:
durationType === "time"
? helpers.round(
helpers.calculateDistance(duration, calculateSpeed(pace)),
1
)
: length,
startPower: zone1,
endPower: zone2,
cadence: cadence,
pace: pace,
type: "trapeze",
id: uuidv4(),
},
]);
}
function addFreeRide(
duration: number = 600,
cadence: number = 0,
length: number = 1000
) {
setBars((bars) => [
...bars,
{
time: durationType === "time" ? duration : 0,
length: durationType === "time" ? 0 : length,
cadence: cadence,
type: "freeRide",
id: uuidv4(),
},
]);
}
function addInterval(
repeat: number = 3,
onDuration: number = 30,
offDuration: number = 120,
onPower: number = 1,
offPower: number = 0.5,
cadence: number = 0,
restingCadence: number = 0,
pace: number = 0,
onLength: number = 200,
offLength: number = 200
) {
setBars((bars) => [
...bars,
{
time:
durationType === "time"
? (onDuration + offDuration) * repeat
: helpers.round(
helpers.calculateTime(
(onLength + offLength) * repeat,
calculateSpeed(pace)
),
1
),
length:
durationType === "time"
? helpers.round(
helpers.calculateDistance(
(onDuration + offDuration) * repeat,
calculateSpeed(pace)
),
1
)
: (onLength + offLength) * repeat,
id: uuidv4(),
type: "interval",
cadence: cadence,
restingCadence: restingCadence,
repeat: repeat,
onDuration:
durationType === "time"
? onDuration
: helpers.round(
helpers.calculateTime(
(onLength * 1) / onPower,
calculateSpeed(pace)
),
1
),
offDuration:
durationType === "time"
? offDuration
: helpers.round(
helpers.calculateTime(
(offLength * 1) / offPower,
calculateSpeed(pace)
),
1
),
onPower: onPower,
offPower: offPower,
pace: pace,
onLength:
durationType === "time"
? helpers.round(
helpers.calculateDistance(
(onDuration * 1) / onPower,
calculateSpeed(pace)
),
1
)
: onLength,
offLength:
durationType === "time"
? helpers.round(
helpers.calculateDistance(
(offDuration * 1) / offPower,
calculateSpeed(pace)
),
1
)
: offLength,
},
]);
}
function addInstruction(text = "", time = 0, length = 0) {
setInstructions((instructions) => [
...instructions,
{
text: text,
time: time,
length: length,
id: uuidv4(),
},
]);
}
function changeInstruction(id: string, values: Instruction) {
const index = instructions.findIndex(
(instructions) => instructions.id === id
);
const updatedArray = [...instructions];
updatedArray[index] = values;
setInstructions(updatedArray);
}
function deleteInstruction(id: string) {
const updatedArray = [...instructions];
setInstructions(updatedArray.filter((item) => item.id !== id));
}
function removeBar(id: string) {
const updatedArray = [...bars];
setBars(updatedArray.filter((item) => item.id !== id));
setActionId(undefined);
}
function addTimeToBar(id: string) {
const updatedArray = [...bars];
const index = updatedArray.findIndex((bar) => bar.id === id);
const element = updatedArray[index];
if (element && durationType === "time") {
element.time = element.time + 5;
element.length =
(helpers.calculateDistance(
element.time,
calculateSpeed(element.pace || 0)
) *
1) /
(element.power || 1);
setBars(updatedArray);
}
if (element && durationType === "distance") {
element.length = (element.length || 0) + 200;
element.time =
(helpers.calculateTime(
element.length,
calculateSpeed(element.pace || 0)
) *
1) /
(element.power || 1);
setBars(updatedArray);
}
}
function removeTimeToBar(id: string) {
const updatedArray = [...bars];
const index = updatedArray.findIndex((bar) => bar.id === id);
const element = updatedArray[index];
if (element && element.time > 5 && durationType === "time") {
element.time = element.time - 5;
element.length =
(helpers.calculateDistance(
element.time,
calculateSpeed(element.pace || 0)
) *
1) /
(element.power || 1);
setBars(updatedArray);
}
if (element && (element.length || 0) > 200 && durationType === "distance") {
element.length = (element.length || 0) - 200;
element.time =
(helpers.calculateTime(
element.length,
calculateSpeed(element.pace || 0)
) *
1) /
(element.power || 1);
setBars(updatedArray);
}
}
function addPowerToBar(id: string) {
const updatedArray = [...bars];
const index = updatedArray.findIndex((bar) => bar.id === id);
const element = updatedArray[index];
if (element && element.power) {
element.power = parseFloat((element.power + 1 / ftp).toFixed(3));
if (durationType === "time") {
element.length =
(helpers.calculateDistance(
element.time,
calculateSpeed(element.pace || 0)
) *
1) /
element.power;
} else {
element.time =
(helpers.calculateTime(
element.length,
calculateSpeed(element.pace || 0)
) *
1) /
element.power;
}
setBars(updatedArray);
}
}
function removePowerToBar(id: string) {
const updatedArray = [...bars];
const index = updatedArray.findIndex((bar) => bar.id === id);
const element = updatedArray[index];
if (element && element.power && element.power >= Zones.Z1.min) {
element.power = parseFloat((element.power - 1 / ftp).toFixed(3));
if (durationType === "time") {
element.length =
(helpers.calculateDistance(
element.time,
calculateSpeed(element.pace || 0)
) *
1) /
element.power;
} else {
element.time =
(helpers.calculateTime(
element.length,
calculateSpeed(element.pace || 0)
) *
1) /
element.power;
}
setBars(updatedArray);
}
}
function duplicateBar(id: string) {
const index = bars.findIndex((bar) => bar.id === id);
const element = [...bars][index];
if (element.type === "bar")
addBar(
element.power || 80,
element.time,
element.cadence,
element.pace,
element.length
);
if (element.type === "freeRide")
addFreeRide(element.time, element.cadence, element.length);
if (element.type === "trapeze")
addTrapeze(
element.startPower || 80,
element.endPower || 160,
element.time,
element.pace || 0,
element.length,
element.cadence
);
if (element.type === "interval")
addInterval(
element.repeat,
element.onDuration,
element.offDuration,
element.onPower,
element.offPower,
element.cadence,
element.restingCadence,
element.pace,
element.onLength,
element.offLength
);
setActionId(undefined);
}
function moveLeft(id: string) {
const index = bars.findIndex((bar) => bar.id === id);
// not first position of array
if (index > 0) {
const updatedArray = [...bars];
const element = [...bars][index];
updatedArray.splice(index, 1);
updatedArray.splice(index - 1, 0, element);
setBars(updatedArray);
}
}
function moveRight(id: string) {
const index = bars.findIndex((bar) => bar.id === id);
// not first position of array
if (index < bars.length - 1) {
const updatedArray = [...bars];
const element = [...bars][index];
updatedArray.splice(index, 1);
updatedArray.splice(index + 1, 0, element);
setBars(updatedArray);
}
}
function saveWorkout() {
setSavePopupVisibility(true);
}
function deleteWorkout() {
// save to cloud (firebase) if logged in
if (user) {
const itemsRef = firebase.database().ref();
var updates: any = {};
updates[`users/${user.uid}/workouts/${id}`] = null;
updates[`workouts/${id}`] = null;
// save to firebase
itemsRef
.update(updates)
.then(() => {
newWorkout();
})
.catch((error) => {
console.log(error);
setMessage({
visible: true,
class: "error",
text: "Cannot delete workout",
});
});
}
}
function shareWorkout() {
if (user) {
save();
setSharePopupVisibility(true);
} else {
saveWorkout();
}
}
function save() {
setMessage({ visible: true, class: "loading", text: "Saving.." });
const xml = createWorkoutXml({
author,
name,
description,
sportType,
durationType,
tags,
bars,
instructions,
});
const file = new Blob([xml], { type: "application/xml" });
// save to cloud (firebase) if logged in
if (user) {
const itemsRef = firebase.database().ref();
const item = {
id: id,
name: name,
description: description,
author: author,
workout: bars,
tags: tags,
instructions: instructions,
userId: user.uid,
updatedAt: Date(),
sportType: sportType,
durationType: durationType,
};
const item2 = {
name: name,
description: description,
updatedAt: Date(),
sportType: sportType,
durationType: durationType,
workoutTime: helpers.formatDuration(
helpers.getWorkoutLength(bars, durationType)
),
workoutDistance: helpers.getWorkoutDistance(bars),
};
var updates: any = {};
updates[`users/${user.uid}/workouts/${id}`] = item2;
updates[`workouts/${id}`] = item;
// save to firebase
itemsRef
.update(updates)
.then(() => {
//upload to s3
upload(file, false);
setMessage({ visible: false });
})
.catch((error) => {
console.log(error);
setMessage({
visible: true,
class: "error",
text: "Cannot save this",
});
});
} else {
// download workout without saving
setMessage({ visible: false });
}
return file;
}
function logout() {
console.log("logout");
auth.signOut().then(() => setUser(null));
}
function downloadWorkout() {
const tempFile = save();
const url = window.URL.createObjectURL(tempFile);
var a = document.createElement("a");
document.body.appendChild(a);
a.style.display = "none";
a.href = url;
a.download = `${id}.zwo`;
a.click();
window.URL.revokeObjectURL(url);
}
function handleUpload(file: Blob) {
// ask user if they want to overwrite current workout first
if (bars.length > 0) {
if (!window.confirm("Are you sure you want to create a new workout?")) {
return false;
}
}
newWorkout();
upload(file, true);
}
function upload(file: Blob, parse = false) {
fetch(process.env.REACT_APP_UPLOAD_FUNCTION || "https://zwiftworkout.netlify.app/.netlify/functions/upload", {
method: "POST",
body: JSON.stringify({
fileType: "zwo",
fileName: `${id}.zwo`,
}),
})
.then((res) => res.json())
.then(function (data) {
const signedUrl = data.uploadURL;
// upload to S3
fetch(signedUrl, {
method: "PUT",
headers: {
"Content-Type": "zwo",
},
body: file,
})
.then((response) => response.text())
.then((data) => {
console.log("File uploaded");
// can parse now
if (parse) fetchAndParse(id);
})
.catch((error) => {
console.error(error);
});
});
}
function fetchAndParse(id: string) {
// remove previous workout
// TODO fix for running distance based
setBars([]);
setInstructions([]);
fetch(`${S3_URL}/${id}.zwo`)
.then((response) => response.text())
.then((data) => {
// remove xml comments
data = data.replace(/<!--(.*?)-->/gm, "");
//now parse file
const workout = Converter.xml2js(data);
const workout_file = workout.elements[0];
if (workout_file.name === "workout_file") {
// file is valid
const authorIndex = workout_file.elements.findIndex(
(element: { name: string }) => element.name === "author"
);
if (
authorIndex !== -1 &&
workout_file.elements[authorIndex].elements
) {
setAuthor(workout_file.elements[authorIndex].elements[0].text);
}
const nameIndex = workout_file.elements.findIndex(
(element: { name: string }) => element.name === "name"
);
if (nameIndex !== -1 && workout_file.elements[nameIndex].elements) {
setName(workout_file.elements[nameIndex].elements[0].text);
}
const descriptionIndex = workout_file.elements.findIndex(
(element: { name: string }) => element.name === "description"
);
if (
descriptionIndex !== -1 &&
workout_file.elements[descriptionIndex].elements
) {
setDescription(
workout_file.elements[descriptionIndex].elements[0].text
);
}
const workoutIndex = workout_file.elements.findIndex(
(element: { name: string }) => element.name === "workout"
);
var totalTime = 0;
workout_file.elements[workoutIndex].elements.map(
(w: {
name: string;
attributes: {
Power: any;
PowerLow: string;
Duration: string;
PowerHigh: string;
Cadence: string;
CadenceResting: string;
Repeat: string;
OnDuration: string;
OffDuration: string;
OnPower: string;
OffPower: string;
Pace: string;
};
elements: any;
}) => {
let duration = parseFloat(w.attributes.Duration);
if (w.name === "SteadyState")
addBar(
parseFloat(w.attributes.Power || w.attributes.PowerLow),
parseFloat(w.attributes.Duration),
parseFloat(w.attributes.Cadence || "0"),
parseInt(w.attributes.Pace || "0")
);
if (
w.name === "Ramp" ||
w.name === "Warmup" ||
w.name === "Cooldown"
)
addTrapeze(
parseFloat(w.attributes.PowerLow),
parseFloat(w.attributes.PowerHigh),
parseFloat(w.attributes.Duration),
parseInt(w.attributes.Pace || "0"),
undefined,
parseInt(w.attributes.Cadence)
);
if (w.name === "IntervalsT") {
addInterval(
parseFloat(w.attributes.Repeat),
parseFloat(w.attributes.OnDuration),
parseFloat(w.attributes.OffDuration),
parseFloat(w.attributes.OnPower),
parseFloat(w.attributes.OffPower),
parseInt(w.attributes.Cadence || "0"),
parseInt(w.attributes.CadenceResting),
parseInt(w.attributes.Pace || "0")
);
duration =
(parseFloat(w.attributes.OnDuration) +
parseFloat(w.attributes.OffDuration)) *
parseFloat(w.attributes.Repeat);
}
if (w.name === "FreeRide")
addFreeRide(
parseFloat(w.attributes.Duration),
parseInt(w.attributes.Cadence)
);
// check for instructions
const textElements = w.elements;
if (textElements && textElements.length > 0) {
textElements.map(
(t: {
name: string;
attributes: {
message: string | undefined;
timeoffset: string;
};
}) => {
if (t.name.toLowerCase() === "textevent")
addInstruction(
t.attributes.message,
totalTime + parseFloat(t.attributes.timeoffset)
);
return false;
}
);
}
totalTime = totalTime + duration;
// map functions expect return value
return false;
}
);
}
})
.catch((error) => {
console.error(error);
});
}
function calculateSpeed(pace: number = 0) {
if (sportType === "bike") {
return 0;
} else {
// return speed in m/s
// speed = distance / time
const distances = [1.60934, 5, 10, 21.0975, 42.195];
const times = [
runningTimes.oneMile,
runningTimes.fiveKm,
runningTimes.tenKm,
runningTimes.halfMarathon,
runningTimes.marathon,
];
return (distances[pace] * 1000) / helpers.getTimeinSeconds(times[pace]);
}
}
const renderBar = (bar: Bar) => (
<Bar
key={bar.id}
id={bar.id}
time={bar.time}
length={bar.length || 200}
power={bar.power || 100}
cadence={bar.cadence}
ftp={ftp}
weight={weight}
sportType={sportType}
durationType={durationType}
pace={bar.pace || 0}
speed={calculateSpeed(bar.pace || 0)}
onChange={(id: string, value: any) => handleOnChange(id, value)} // Change any to Interface Bar?
onClick={(id: string) => handleOnClick(id)}
selected={bar.id === actionId}
showLabel={true}
/>
);
const renderTrapeze = (bar: Bar) => (
<Trapeze
key={bar.id}
id={bar.id}
time={bar.time}
length={bar.length || 200}
cadence={bar.cadence}
startPower={bar.startPower || 80}
endPower={bar.endPower || 160}
ftp={ftp}
sportType={sportType}
durationType={durationType}
pace={bar.pace || 0}
speed={calculateSpeed(bar.pace || 0)}
onChange={(id: string, value: any) => handleOnChange(id, value)} // Change any to Interface Bar?
onClick={(id: string) => handleOnClick(id)}
selected={bar.id === actionId}
/>
);
const renderFreeRide = (bar: Bar) => (
<FreeRide
key={bar.id}
id={bar.id}
time={bar.time}
length={bar.length}
cadence={bar.cadence}
durationType={durationType}
sportType={sportType}
onChange={(id: string, value: any) => handleOnChange(id, value)} // Change any to Interface Bar?
onClick={(id: string) => handleOnClick(id)}
selected={bar.id === actionId}
/>
);
const renderInterval = (bar: Bar) => (
<Interval
key={bar.id}
id={bar.id}
repeat={bar.repeat || 3}
onDuration={bar.onDuration || 10}
offDuration={bar.offDuration || 50}
onPower={bar.onPower || 250}
offPower={bar.offPower || 120}
onLength={bar.onLength || 200}
offLength={bar.offLength || 200}
cadence={bar.cadence}
restingCadence={bar.restingCadence || 0}
ftp={ftp}
weight={weight}
sportType={sportType}
durationType={durationType}
pace={bar.pace || 0}
speed={calculateSpeed(bar.pace || 0)}
handleIntervalChange={(id: string, value: any) =>
handleOnChange(id, value)
}
handleIntervalClick={(id: string) => handleOnClick(id)}
selected={bar.id === actionId}
/>
);
const renderComment = (instruction: Instruction, index: number) => (
<Comment
key={instruction.id}
instruction={instruction}
durationType={durationType}
width={
durationType === "distance"
? parseInt(helpers.getWorkoutDistance(bars)) * 100
: helpers.getWorkoutLength(bars, durationType) / 3
}
onChange={(id: string, values: Instruction) =>
changeInstruction(id, values)
}
onClick={(id: string) =>
setSelectedInstruction(instructions.find((i) => i.id === id))
}
index={index}
/>
);
const renderRegistrationForm = () => {
switch (visibleForm) {
case "login":
return (
<LoginForm
login={setUser}
showSignup={() => setVisibleForm("signup")}
dismiss={() => setSavePopupVisibility(false)}
showForgotPassword={() => setVisibleForm("forgotPassword")}
/>
);
case "signup":
return (
<SignupForm
signUp={setUser}
showLogin={() => setVisibleForm("login")}
dismiss={() => setSavePopupVisibility(false)}
/>
);
case "forgotPassword":
return (
<ForgotPasswordForm
dismiss={() => setSavePopupVisibility(false)}
workoutId={id}
/>
);
case "updatePassword":
return (
<UpdatePasswordForm
dismiss={() => {
setVisibleForm("login");
setSavePopupVisibility(true);
}}
/>
);
default:
break;
}
};
function setPace(value: string, id: string) {
const index = bars.findIndex((bar) => bar.id === id);
if (index !== -1) {
const updatedArray = [...bars];
const element = [...updatedArray][index];
element.pace = parseInt(value);
if (durationType === "time") {
element.length =
(helpers.calculateDistance(
element.time,
calculateSpeed(element.pace || 0)
) *
1) /
(element.power || 1);
} else {
element.time =
(helpers.calculateTime(
element.length,
calculateSpeed(element.pace || 0)
) *
1) /
(element.power || 1);
}
setBars(updatedArray);
}
}
function getPace(id: string) {
const index = bars.findIndex((bar) => bar.id === id);
if (index !== -1) {
const element = [...bars][index];
return element.pace;
}
}
function switchSportType(newSportType: SportType) {
setSportType(newSportType);
setDurationType(newSportType === "bike" ? "time" : "distance");
}
function toggleTextEditor() {
if (bars.length > 0 && !textEditorIsVisible) {
if (
window.confirm(
"Editing a workout from the text editor will overwrite current workout"
)
)
setTextEditorIsVisible(!textEditorIsVisible);
} else {
setTextEditorIsVisible(!textEditorIsVisible);
}
}
function transformTextToWorkout(textValue: string) {
// reset each time
setBars([]);
setInstructions([]);
// 2 minutes block at 112% FTP
// 2 minutes block at 330 W
// 30 seconds block at ..
//console.log(textValue);
const workoutBlocks = textValue.split("\n");
workoutBlocks.forEach((workoutBlock) => {
if (workoutBlock.includes("steady")) {
// generate a steady state block
// extract watts
const powerInWatts = workoutBlock.match(/([0-9]\d*w)/);
const powerInWattsPerKg = workoutBlock.match(/([0-9]*.?[0-9]wkg)/);
const powerInPercentageFtp = workoutBlock.match(/([0-9]\d*%)/);
let power = powerInWatts ? parseInt(powerInWatts[0]) / ftp : 1;
power = powerInWattsPerKg
? (parseFloat(powerInWattsPerKg[0]) * weight) / ftp
: power;
power = powerInPercentageFtp
? parseInt(powerInPercentageFtp[0]) / 100
: power;
// extract duration in seconds
const durationInSeconds = workoutBlock.match(/([0-9]\d*s)/);
const durationInMinutes = workoutBlock.match(/([0-9]*:?[0-9][0-9]*m)/);
let duration = durationInSeconds && parseInt(durationInSeconds[0]);
duration = durationInMinutes
? parseInt(durationInMinutes[0].split(":")[0]) * 60 +
(parseInt(durationInMinutes[0].split(":")[1]) || 0)
: duration;
// extract cadence in rpm
const cadence = workoutBlock.match(/([0-9]\d*rpm)/);
const rpm = cadence ? parseInt(cadence[0]) : undefined;
// extract multiplier
// const multiplier = workoutBlock.match(/([0-9]\d*x)/)
// const nTimes = multiplier ? Array(parseInt(multiplier[0])) : Array(1)
// for (var i = 0; i < nTimes.length; i++)
addBar(power, duration || 300, rpm);
}
if (
workoutBlock.includes("ramp") ||
workoutBlock.includes("warmup") ||
workoutBlock.includes("cooldown")
) {
// generate a steady ramp block
// extract watts
const startPowerInWatts = workoutBlock.match(/([0-9]\d*w)/);
const startPowerInWattsPerKg = workoutBlock.match(/([0-9]*.?[0-9]wkg)/);
const startPowerInPercentageFtp = workoutBlock.match(/([0-9]\d*%)/);
let startPower = startPowerInWatts
? parseInt(startPowerInWatts[0]) / ftp
: 1;
startPower = startPowerInWattsPerKg
? (parseFloat(startPowerInWattsPerKg[0]) * weight) / ftp
: startPower;
startPower = startPowerInPercentageFtp
? parseInt(startPowerInPercentageFtp[0]) / 100
: startPower;
// extract watts
const endPowerInWatts = workoutBlock.match(/(-[0-9]\d*w)/);
const endPowerInWattsPerKg = workoutBlock.match(/(-[0-9]*.?[0-9]wkg)/);
const endPowerInPercentageFtp = workoutBlock.match(/-([0-9]\d*%)/);
let endPower = endPowerInWatts
? Math.abs(parseInt(endPowerInWatts[0])) / ftp
: 1;
endPower = endPowerInWattsPerKg
? (Math.abs(parseFloat(endPowerInWattsPerKg[0])) * weight) / ftp
: endPower;
endPower = endPowerInPercentageFtp
? Math.abs(parseInt(endPowerInPercentageFtp[0])) / 100
: endPower;
const durationInSeconds = workoutBlock.match(/([0-9]\d*s)/);
const durationInMinutes = workoutBlock.match(/([0-9]*:?[0-9][0-9]*m)/);
let duration = durationInSeconds && parseInt(durationInSeconds[0]);
duration = durationInMinutes
? parseInt(durationInMinutes[0].split(":")[0]) * 60 +
(parseInt(durationInMinutes[0].split(":")[1]) || 0)
: duration;
// extract cadence in rpm
const cadence = workoutBlock.match(/([0-9]\d*rpm)/);
const rpm = cadence ? parseInt(cadence[0]) : undefined;
addTrapeze(
startPower,
endPower,
duration || 300,
undefined,
undefined,
rpm
);
}
if (workoutBlock.includes("freeride")) {
const durationInSeconds = workoutBlock.match(/([0-9]\d*s)/);
const durationInMinutes = workoutBlock.match(/([0-9]*:?[0-9][0-9]*m)/);
let duration = durationInSeconds && parseInt(durationInSeconds[0]);
duration = durationInMinutes
? parseInt(durationInMinutes[0].split(":")[0]) * 60 +
(parseInt(durationInMinutes[0].split(":")[1]) || 0)
: duration;
// extract cadence in rpm
const cadence = workoutBlock.match(/([0-9]\d*rpm)/);
const rpm = cadence ? parseInt(cadence[0]) : undefined;
addFreeRide(duration || 600, rpm);
}
if (workoutBlock.includes("interval")) {
const multiplier = workoutBlock.match(/([0-9]\d*x)/);
const nTimes = multiplier ? parseInt(multiplier[0]) : 3;
const durationInSeconds = workoutBlock.match(/([0-9]\d*s)/);
const durationInMinutes = workoutBlock.match(/([0-9]*:?[0-9][0-9]*m)/);
let duration = durationInSeconds && parseInt(durationInSeconds[0]);
duration = durationInMinutes
? parseInt(durationInMinutes[0].split(":")[0]) * 60 +
(parseInt(durationInMinutes[0].split(":")[1]) || 0)
: duration;
const offDurationInSeconds = workoutBlock.match(/(-[0-9]\d*s)/);
const offDurationInMinutes = workoutBlock.match(
/(-[0-9]*:?[0-9][0-9]*m)/
);
let offDuration =
offDurationInSeconds && Math.abs(parseInt(offDurationInSeconds[0]));
offDuration = offDurationInMinutes
? Math.abs(parseInt(offDurationInMinutes[0].split(":")[0])) * 60 +
(parseInt(offDurationInMinutes[0].split(":")[1]) || 0)
: offDuration;
// extract watts
const startPowerInWatts = workoutBlock.match(/([0-9]\d*w)/);
const startPowerInWattsPerKg = workoutBlock.match(/([0-9]*.?[0-9]wkg)/);
const startPowerInPercentageFtp = workoutBlock.match(/([0-9]\d*%)/);
let startPower = startPowerInWatts
? parseInt(startPowerInWatts[0]) / ftp
: 1;
startPower = startPowerInWattsPerKg
? (parseFloat(startPowerInWattsPerKg[0]) * weight) / ftp
: startPower;
startPower = startPowerInPercentageFtp
? parseInt(startPowerInPercentageFtp[0]) / 100
: startPower;
// extract watts
const endPowerInWatts = workoutBlock.match(/(-[0-9]\d*w)/);
const endPowerInWattsPerKg = workoutBlock.match(/(-[0-9]*.?[0-9]wkg)/);
const endPowerInPercentageFtp = workoutBlock.match(/-([0-9]\d*%)/);
let endPower = endPowerInWatts
? Math.abs(parseInt(endPowerInWatts[0])) / ftp
: 0.5;
endPower = endPowerInWattsPerKg
? (Math.abs(parseFloat(endPowerInWattsPerKg[0])) * weight) / ftp
: endPower;
endPower = endPowerInPercentageFtp
? Math.abs(parseInt(endPowerInPercentageFtp[0])) / 100
: endPower;
// extract cadence in rpm
const cadence = workoutBlock.match(/([0-9]\d*rpm)/);
const rpm = cadence ? parseInt(cadence[0]) : undefined;
const restingCadence = workoutBlock.match(/(-[0-9]\d*rpm)/);
const restingRpm = restingCadence
? Math.abs(parseInt(restingCadence[0]))
: undefined;
addInterval(
nTimes,
duration || 30,
offDuration || 120,
startPower,
endPower,
rpm,
restingRpm
);
}
if (workoutBlock.includes("message")) {
// extract message
const message = workoutBlock.match(/["'](.*?)["']/);
const text = message ? message[0] : "";
const durationInSeconds = workoutBlock.match(/([0-9]\d*s)/);
const durationInMinutes = workoutBlock.match(/([0-9]*:?[0-9][0-9]*m)/);
let duration = durationInSeconds && parseInt(durationInSeconds[0]);
duration = durationInMinutes
? parseInt(durationInMinutes[0].split(":")[0]) * 60 +
(parseInt(durationInMinutes[0].split(":")[1]) || 0)
: duration;
addInstruction(text, duration || 0);
}
});
}
return (
// Adding tabIndex allows div element to receive keyboard events
<div className="container" onKeyDown={handleKeyPress} tabIndex={0}>
<Helmet>
<title>
{name
? `${name} - Zwift Workout Editor`
: "My Workout - Zwift Workout Editor"}
</title>
<meta name="description" content={description} />
<meta
property="og:title"
content={
name
? `${name} - Zwift Workout Editor`
: "My Workout - Zwift Workout Editor"
}
/>
<meta property="og:description" content={description} />
<link
rel="canonical"
href={`https://www.zwiftworkout.com/editor/${id}`}
/>
<meta
property="og:url"
content={`https://www.zwiftworkout.com/editor/${id}`}
/>
</Helmet>
{message?.visible && (
<div className={`message ${message.class}`}>
{message.text}
<button
className="close"
onClick={() => setMessage({ visible: false })}
>
<FontAwesomeIcon icon={faTimesCircle} size="lg" fixedWidth />
</button>
</div>
)}
{showWorkouts && (
<Popup width="500px" dismiss={() => setShowWorkouts(false)}>
{user ? <Workouts userId={user.uid} /> : renderRegistrationForm()}
</Popup>
)}
{selectedInstruction && (
<EditComment
instruction={selectedInstruction}
onChange={(id: string, values: Instruction) => {
changeInstruction(id, values);
setSelectedInstruction(undefined);
}}
dismiss={() => setSelectedInstruction(undefined)}
onDelete={(id: string) => {
deleteInstruction(id);
setSelectedInstruction(undefined);
}}
/>
)}
{savePopupIsVisile && (
<Popup width="500px" dismiss={() => setSavePopupVisibility(false)}>
{user ? (
<SaveForm
name={name}
description={description}
author={author}
tags={tags}
onNameChange={setName}
onDescriptionChange={setDescription}
onAuthorChange={setAuthor}
onTagsChange={setTags}
onSave={() => {
save();
setSavePopupVisibility(false);
}}
onDismiss={() => setSavePopupVisibility(false)}
onLogout={logout}
/>
) : (
renderRegistrationForm()
)}
</Popup>
)}
{sharePopupIsVisile && (
<Popup width="500px" dismiss={() => setSharePopupVisibility(false)}>
<ShareForm id={id} onDismiss={() => setSharePopupVisibility(false)} />
</Popup>
)}
<div className="info">
<div className="title">
<h1>{name}</h1>
<div className="description">{description}</div>
<p>{author ? `by ${author}` : ""}</p>
</div>
<div className="workout">
<div className="form-input">
<label>Workout Time</label>
<input
className="textInput"
value={helpers.formatDuration(
helpers.getWorkoutLength(bars, durationType)
)}
disabled
/>
</div>
{sportType === "run" && (
<div className="form-input">
<label>Workout Distance</label>
<input
className="textInput"
value={helpers.getWorkoutDistance(bars)}
disabled
/>
</div>
)}
{sportType === "bike" && (
<div className="form-input">
<label title="Training Load">Training Load</label>
<input
className="textInput"
value={helpers.getStressScore(bars, ftp)}
disabled
/>
</div>
)}
{sportType === "run" && (
<LeftRightToggle<"time", "distance">
label="Duration Type"
leftValue="time"
rightValue="distance"
leftIcon={faClock}
rightIcon={faRuler}
selected={durationType}
onChange={setDurationType}
/>
)}
<LeftRightToggle<"bike", "run">
label="Sport Type"
leftValue="bike"
rightValue="run"
leftIcon={faBiking}
rightIcon={faRunning}
selected={sportType}
onChange={switchSportType}
/>
</div>
</div>
{sportType === "run" && (
<RunningTimesEditor times={runningTimes} onChange={setRunningTimes} />
)}
{textEditorIsVisible && sportType === "bike" && (
<div className="text-editor">
<textarea
onChange={(e) => transformTextToWorkout(e.target.value)}
rows={10}
spellCheck={false}
className="text-editor-textarea"
placeholder="Add one block per line here: steady 3.0wkg 30s"
></textarea>
<div className="text-editor-instructions">
<h2>Instructions</h2>
<p>
Every row correspond to a workout block. Scroll down to see some
examples.
</p>
<h3>Blocks</h3>
<p>
<span>steady</span> <span>warmup</span> <span>cooldown</span>{" "}
<span>ramp</span> <span>intervals</span> <span>freeride</span>{" "}
<span>message</span>
</p>
<h3>Time</h3>
<p>
<span>30s</span> or <span>0:30m</span>
</p>
<h3>Power</h3>
<p>
<span>250w</span> or <span>3.0wkg</span> or <span>75%</span> (FTP)
</p>
<h3>Cadence</h3>
<p>
<span>120rpm</span>
</p>
<h2>Examples</h2>
<h3>Steady block</h3>
<p>
<code>steady 3.0wkg 30s</code>
<code>steady 120w 10m 85rpm</code>
</p>
<h3>Warmup / Cooldown / Ramp block</h3>
<p>
<code>warmup 2.0wkg-3.5wkg 10m</code>
<code>cooldown 180w-100w 5m 110rpm</code>
</p>
<h3>Intervals</h3>
<p>
<code>interval 10x 30s-30s 4.0wkg-1.0wkg 110rpm-85rpm</code>
<code>interval 3x 1:00m-5:00m 300w-180w</code>
</p>
<h3>Free Ride</h3>
<p>
<code>freeride 10m 85rpm</code>
</p>
<h3>Text Event</h3>
<p>
<code>message "Get ready to your first set!" 30s</code>
<code>message "Last one!" 20:00m</code>
</p>
</div>
</div>
)}
<div id="editor" className="editor">
{actionId && (
<div className="actions">
<button onClick={() => moveLeft(actionId)} title="Move Left">
<FontAwesomeIcon icon={faArrowLeft} size="lg" fixedWidth />
</button>
<button onClick={() => moveRight(actionId)} title="Move Right">
<FontAwesomeIcon icon={faArrowRight} size="lg" fixedWidth />
</button>
<button onClick={() => removeBar(actionId)} title="Delete">
<FontAwesomeIcon icon={faTrash} size="lg" fixedWidth />
</button>
<button onClick={() => duplicateBar(actionId)} title="Duplicate">
<FontAwesomeIcon icon={faCopy} size="lg" fixedWidth />
</button>
{sportType === "run" && (
<select
name="pace"
value={getPace(actionId)}
onChange={(e) => setPace(e.target?.value, actionId)}
className="selectInput"
>
<option value="0">1 Mile Pace</option>
<option value="1">5K Pace</option>
<option value="2">10K Pace</option>
<option value="3">Half Marathon Pace</option>
<option value="4">Marathon Pace</option>
</select>
)}
</div>
)}
<div className="canvas" ref={canvasRef}>
{actionId && (
<div
className="fader"
style={{ width: canvasRef.current?.scrollWidth }}
onClick={() => setActionId(undefined)}
></div>
)}
<div className="segments" ref={segmentsRef}>
{bars.map((bar) => {
if (bar.type === "bar") {
return renderBar(bar);
} else if (bar.type === "trapeze") {
return renderTrapeze(bar);
} else if (bar.type === "freeRide") {
return renderFreeRide(bar);
} else if (bar.type === "interval") {
return renderInterval(bar);
} else {
return false;
}
})}
</div>
<div className="slider">
{instructions.map((instruction, index) =>
renderComment(instruction, index)
)}
</div>
{durationType === "time" ? (
<TimeAxis width={segmentsWidth} />
) : (
<DistanceAxis width={segmentsWidth} />
)}
</div>
<ZoneAxis />
</div>
<div className="cta">
{sportType === "bike" ? (
<div>
<ReactTooltip effect="solid" />
<button
className="btn btn-square"
onClick={() => toggleTextEditor()}
style={{ backgroundColor: "palevioletred" }}
data-tip="New! Workout text editor!"
>
<FontAwesomeIcon icon={faPen} fixedWidth />
</button>
<button
className="btn btn-square"
onClick={() => addBar(0.5)}
style={{ backgroundColor: Colors.GRAY }}
>
Z1
</button>
<button
className="btn btn-square"
onClick={() => addBar(Zones.Z2.min)}
style={{ backgroundColor: Colors.BLUE }}
>
Z2
</button>
<button
className="btn btn-square"
onClick={() => addBar(Zones.Z3.min)}
style={{ backgroundColor: Colors.GREEN }}
>
Z3
</button>
<button
className="btn btn-square"
onClick={() => addBar(Zones.Z4.min)}
style={{ backgroundColor: Colors.YELLOW }}
>
Z4
</button>
<button
className="btn btn-square"
onClick={() => addBar(Zones.Z5.min)}
style={{ backgroundColor: Colors.ORANGE }}
>
Z5
</button>
<button
className="btn btn-square"
onClick={() => addBar(Zones.Z6.min)}
style={{ backgroundColor: Colors.RED }}
>
Z6
</button>
</div>
) : (
<button
className="btn"
onClick={() => addBar(1, 300, 0, 0, 1000)}
style={{ backgroundColor: Colors.WHITE }}
>
<SteadyLogo className="btn-icon" /> Steady Pace
</button>
)}
<button
className="btn"
onClick={() => addTrapeze(0.25, 0.75)}
style={{ backgroundColor: Colors.WHITE }}
>
<WarmupLogo className="btn-icon" /> Warm up
</button>
<button
className="btn"
onClick={() => addTrapeze(0.75, 0.25)}
style={{ backgroundColor: Colors.WHITE }}
>
<WarmdownLogo className="btn-icon" /> Cool down
</button>
<button
className="btn"
onClick={() => addInterval()}
style={{ backgroundColor: Colors.WHITE }}
>
<IntervalLogo className="btn-icon" /> Interval
</button>
<button
className="btn"
onClick={() => addFreeRide()}
style={{ backgroundColor: Colors.WHITE }}
>
<FontAwesomeIcon
icon={sportType === "bike" ? faBicycle : faRunning}
size="lg"
fixedWidth
/>{" "}
Free {sportType === "bike" ? "Ride" : "Run"}
</button>
<button
className="btn"
onClick={() => addInstruction()}
style={{ backgroundColor: Colors.WHITE }}
>
<FontAwesomeIcon icon={faComment} size="lg" fixedWidth /> Text Event
</button>
{sportType === "bike" && (
<div className="form-input">
<label htmlFor="ftp">FTP (W)</label>
<input
className="textInput"
type="number"
name="ftp"
value={ftp}
onChange={(e) => setFtp(parseInt(e.target.value))}
/>
</div>
)}
{sportType === "bike" && (
<div className="form-input">
<label htmlFor="weight">Body Weight (Kg)</label>
<input
className="textInput"
type="number"
name="weight"
value={weight}
onChange={(e) => setWeight(parseInt(e.target.value))}
/>
</div>
)}
<button
className="btn"
onClick={() => {
if (
window.confirm("Are you sure you want to create a new workout?")
)
newWorkout();
}}
>
<FontAwesomeIcon icon={faFile} size="lg" fixedWidth /> New
</button>
<button className="btn" onClick={() => saveWorkout()}>
<FontAwesomeIcon icon={faSave} size="lg" fixedWidth /> Save
</button>
<button
className="btn"
onClick={() => {
if (window.confirm("Are you sure you want to delete this workout?"))
deleteWorkout();
}}
>
<FontAwesomeIcon icon={faTrash} size="lg" fixedWidth /> Delete
</button>
<button className="btn" onClick={() => downloadWorkout()}>
<FontAwesomeIcon icon={faDownload} size="lg" fixedWidth /> Download
</button>
<input
accept=".xml,.zwo"
id="contained-button-file"
type="file"
style={{ display: "none" }}
onChange={(e) => handleUpload(e.target.files![0])}
/>
<button
className="btn"
onClick={() =>
document.getElementById("contained-button-file")!.click()
}
>
<FontAwesomeIcon icon={faUpload} size="lg" fixedWidth /> Upload
</button>
<button className="btn" onClick={() => setShowWorkouts(true)}>
<FontAwesomeIcon icon={faList} size="lg" fixedWidth /> Workouts
</button>
<button className="btn" onClick={() => shareWorkout()}>
<FontAwesomeIcon icon={faShareAlt} size="lg" fixedWidth /> Share
</button>
</div>
<Footer />
</div>
);
}
Example #6
Source File: steps.tsx From videotranscode.space with Apache License 2.0 | 4 votes |
StepComponent = () => {
const [current, setCurrent] = useState(0)
const [statues, setStatues] = useState<Statuses>(defaultStatus)
const { processed, globalReset, CluiStore, FileStore } = ComponentStore
const { allFiles } = FileStore
const { isSubmitted } = CluiStore
const displayable = useActiveUsers()
useEffect(() => {
if (allFiles.length > 0) {
setStatues(cur => ({ ...cur, file: 'finish' }))
setCurrent(1)
}
}, [allFiles])
useEffect(() => {
if (globalReset) {
setStatues(defaultStatus)
setCurrent(0)
}
}, [globalReset])
useEffect(() => {
if (isSubmitted) {
setStatues(cur => ({ ...cur, settings: 'finish' }))
setCurrent(2)
}
}, [isSubmitted])
useEffect(() => {
if (processed) {
setStatues(cur => ({ ...cur, processing: 'finish', download: 'finish' }))
setCurrent(3)
}
}, [processed])
if (!displayable) {
return (
<div className="step-wrapper">
<Steps current={current}>
<Step
title="Add File"
icon={<FontAwesomeIcon icon={faFile} />}
status={statues.file}
onClick={() => {
ComponentStore.FileStore.openFileDrawer()
}}
className={isSubmitted ? 'cursor-not-allowed' : 'cursor-pointer'}
/>
<Step
title="Choose Settings"
icon={<FontAwesomeIcon icon={faCogs} />}
status={statues.settings}
onClick={() => {
if (keyboardStore.toggleModal) {
keyboardStore.toggleModal()
}
}}
className={isSubmitted ? 'cursor-not-allowed' : 'cursor-pointer'}
/>
<Step
title="Processing"
icon={<FontAwesomeIcon icon={faClock} />}
status={statues.processing}
className="cursor-not-allowed"
/>
<Step
title="Download"
icon={<FontAwesomeIcon icon={faDownload} />}
status={statues.download}
className="cursor-not-allowed"
/>
</Steps>
{/* @ts-ignore Styled JSX */}
<style jsx>
{`
.step-wrapper {
color: #3fbd71 !important;
padding-top: ${isSubmitted ? '15vh' : '2vh'};
width: 80vw;
}
`}
</style>
</div>
)
} else {
return null
}
}