Skip to content

Commit

Permalink
Merge pull request #60 from polywrap/progress-loaders
Browse files Browse the repository at this point in the history
feat: multi-interval progress bar
  • Loading branch information
dOrgJelli authored Jan 30, 2024
2 parents 7eb5453 + af596c4 commit 6feac71
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 3 deletions.
91 changes: 91 additions & 0 deletions web/components/ProgressBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React, { useState, useEffect } from 'react';
import clsx from 'clsx';

interface ProgressBarProps {
// seconds
stepTimes: number[];
curStep: number;
className?: string;
}

const ProgressBar: React.FC<ProgressBarProps> = ({ stepTimes, curStep, className }) => {
const [renderState, setRenderState] = useState<{
widthPerc: number, // position (0-100)
percPerSec: number // velocity (%/sec)
}>({
widthPerc: 0,
percPerSec: 0
});

useEffect(() => {
const dt = 1000 / 60; // frame delta time (in ms)
const numSteps = stepTimes.length;
const stepSize = 100 / numSteps;
// avoid floating point rounding errors
const fuzz = 0.01;

const id = setInterval(() => {
setRenderState((prevRenderState) => {
// early out if current step is beyond numSteps provided
if (curStep > numSteps) {
return {
widthPerc: 100,
percPerSec: 0
}
}

const prevWidthPerc = prevRenderState.widthPerc;
const prevPercPerSec = Math.max(prevRenderState.percPerSec, 1);

// Calculate velocity for the curStep in %/sec
const curPercPerSec = stepSize / Math.max(stepTimes[curStep], 1);

// What step did we render last
const renderStep = Math.floor(
((prevWidthPerc + fuzz) / 100) * numSteps
);

// If we need to fast-forward
if (renderStep < curStep) {
return {
widthPerc: stepSize * curStep,
percPerSec: curPercPerSec
}
}
// Do we need to pause?
if (renderStep > curStep) {
return {
widthPerc: stepSize * (curStep + 1),
percPerSec: 0
}
}
// Integrate our velocity like normal
return {
widthPerc: Math.min(prevWidthPerc + ((prevPercPerSec / 1000) * dt), 100),
percPerSec: curPercPerSec
}
});
}, dt);

return () => clearInterval(id);
}, [stepTimes]);

return (
<svg
className={clsx(
"stroke-cyan-500 text-cyan-500/30",
className
)}
width="100%"
height="10"
viewBox="0 0 100 1"
xmlns="http://www.w3.org/2000/svg"
>
<rect x="0" y="0" width="100" height="1" stroke="currentColor" />
<rect x="0" y="0" width={renderState.widthPerc} height="1" stroke="currentStroke" />
</svg>
);
};

export default ProgressBar;

27 changes: 24 additions & 3 deletions web/components/RealtimeLogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,39 @@ import clsx from "clsx";
import { useRouter } from "next/navigation";
import { useState, useEffect } from "react";
import LoadingCircle from "./LoadingCircle";
import ProgressBar from "./ProgressBar";
import Button from "./Button";

const UNSTARTED_TEXTS: Record<Tables<"logs">["step_name"], string> = {
type StepName = Tables<"logs">["step_name"];

const UNSTARTED_TEXTS: Record<StepName, string> = {
FETCH_PROJECTS: "Search for relevant projects",
EVALUATE_PROJECTS: "Evaluate proof of impact",
ANALYZE_FUNDING: "Analyze funding needs",
SYNTHESIZE_RESULTS: "Synthesize results",
}

const LOADING_TEXTS: Record<Tables<"logs">["step_name"], string> = {
const LOADING_TEXTS: Record<StepName, string> = {
FETCH_PROJECTS: "Searching for relevant projects...",
EVALUATE_PROJECTS: "Evaluating proof of impact...",
ANALYZE_FUNDING: "Analyzing funding needs...",
SYNTHESIZE_RESULTS: "Synthesizing results...",
}

const STEPS_ORDER: Record<Tables<"logs">["step_name"], number> = {
const STEPS_ORDER: Record<StepName, number> = {
FETCH_PROJECTS: 1,
EVALUATE_PROJECTS: 2,
ANALYZE_FUNDING: 3,
SYNTHESIZE_RESULTS: 4,
}

const STEP_TIME_ESTS: Record<StepName, number> = {
FETCH_PROJECTS: 60,
EVALUATE_PROJECTS: 25,
ANALYZE_FUNDING: 20,
SYNTHESIZE_RESULTS: 15
}

const getLogMessage = (log: Tables<"logs">) => {
switch (log.status) {
case "NOT_STARTED": return UNSTARTED_TEXTS[log.step_name]
Expand Down Expand Up @@ -111,10 +121,21 @@ export default function RealtimeLogs(props: {
};
}, [supabase, props.run.id]);

const totalSteps = sortedLogsWithSteps.length;
const stepTimes = sortedLogsWithSteps.map((x) => STEP_TIME_ESTS[x.step_name]);
let currentStep = sortedLogsWithSteps.findIndex((x) => x.status === "IN_PROGRESS");

if (currentStep < 0) {
currentStep = sortedLogsWithSteps[totalSteps - 1].status === "COMPLETED" ?
totalSteps + 1 :
0;
}

return (
<>
<div className="flex flex-col gap-4">
<p>Results:</p>
<ProgressBar stepTimes={stepTimes} curStep={currentStep} className={"!stroke-indigo-500 text-indigo-200 rounded-lg"} />
<div className="flex flex-col gap-2">
{ sortedLogsWithSteps.map(log => (
<div key={log.id} className={clsx(
Expand Down

0 comments on commit 6feac71

Please sign in to comment.