Skip to content

Commit

Permalink
feat: add visual progress indicators
Browse files Browse the repository at this point in the history
  • Loading branch information
issacgerges authored Jun 5, 2024
1 parent 774120f commit a8f40b7
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 35 deletions.
3 changes: 2 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@
"omnibox",
"swiftshader",
"hoge",
"subsubcomain"
"subsubcomain",
"noselect"
],
"ignorePaths": [
"CHANGELOG.md",
Expand Down
14 changes: 14 additions & 0 deletions client-src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { log, logEnabledFeatures, setLogLevel } from "./utils/log.js";
import sendMessage from "./utils/sendMessage.js";
import reloadApp from "./utils/reloadApp.js";
import createSocketURL from "./utils/createSocketURL.js";
import { isProgressSupported, defineProgressElement } from "./progress.js";

/**
* @typedef {Object} OverlayOptions
Expand Down Expand Up @@ -236,6 +237,19 @@ const onSocketMessage = {
);
}

if (isProgressSupported()) {
if (typeof options.progress === "string") {
let progress = document.querySelector("wds-progress");
if (!progress) {
defineProgressElement();
progress = document.createElement("wds-progress");
document.body.appendChild(progress);
}
progress.setAttribute("progress", data.percent);
progress.setAttribute("type", options.progress);
}
}

sendMessage("Progress", data);
},
"still-ok": function stillOk() {
Expand Down
205 changes: 205 additions & 0 deletions client-src/progress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
class WebpackDevServerProgress extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.maxDashOffset = -219.99078369140625;
this.animationTimer = null;
}

#reset() {
clearTimeout(this.animationTimer);
this.animationTimer = null;

const typeAttr = this.getAttribute("type")?.toLowerCase();
this.type = typeAttr === "circular" ? "circular" : "linear";

const innerHTML =
this.type === "circular"
? WebpackDevServerProgress.#circularTemplate()
: WebpackDevServerProgress.#linearTemplate();
this.shadowRoot.innerHTML = innerHTML;

this.initialProgress = Number(this.getAttribute("progress")) ?? 0;

this.#update(this.initialProgress);
}

static #circularTemplate() {
return `
<style>
:host {
width: 200px;
height: 200px;
position: fixed;
right: 5%;
top: 5%;
transition: opacity .25s ease-in-out;
z-index: 2147483645;
}
circle {
fill: #282d35;
}
path {
fill: rgba(0, 0, 0, 0);
stroke: rgb(186, 223, 172);
stroke-dasharray: 219.99078369140625;
stroke-dashoffset: -219.99078369140625;
stroke-width: 10;
transform: rotate(90deg) translate(0px, -80px);
}
text {
font-family: 'Open Sans', sans-serif;
font-size: 18px;
fill: #ffffff;
dominant-baseline: middle;
text-anchor: middle;
}
tspan#percent-super {
fill: #bdc3c7;
font-size: 0.45em;
baseline-shift: 10%;
}
@keyframes fade {
0% { opacity: 1; transform: scale(1); }
100% { opacity: 0; transform: scale(0); }
}
.disappear {
animation: fade 0.3s;
animation-fill-mode: forwards;
animation-delay: 0.5s;
}
.hidden {
display: none;
}
</style>
<svg id="progress" class="hidden noselect" viewBox="0 0 80 80">
<circle cx="50%" cy="50%" r="35"></circle>
<path d="M5,40a35,35 0 1,0 70,0a35,35 0 1,0 -70,0"></path>
<text x="50%" y="51%">
<tspan id="percent-value">0</tspan>
<tspan id="percent-super">%</tspan>
</text>
</svg>
`;
}

static #linearTemplate() {
return `
<style>
:host {
position: fixed;
top: 0;
left: 0;
height: 4px;
width: 100vw;
z-index: 2147483645;
}
#bar {
width: 0%;
height: 4px;
background-color: rgb(186, 223, 172);
}
@keyframes fade {
0% { opacity: 1; }
100% { opacity: 0; }
}
.disappear {
animation: fade 0.3s;
animation-fill-mode: forwards;
animation-delay: 0.5s;
}
.hidden {
display: none;
}
</style>
<div id="progress"></div>
`;
}

connectedCallback() {
this.#reset();
}

static get observedAttributes() {
return ["progress", "type"];
}

attributeChangedCallback(name, oldValue, newValue) {
if (name === "progress") {
this.#update(Number(newValue));
} else if (name === "type") {
this.#reset();
}
}

#update(percent) {
const element = this.shadowRoot.querySelector("#progress");
if (this.type === "circular") {
const path = this.shadowRoot.querySelector("path");
const value = this.shadowRoot.querySelector("#percent-value");
const offset = ((100 - percent) / 100) * this.maxDashOffset;

path.style.strokeDashoffset = offset;
value.textContent = percent;
} else {
element.style.width = `${percent}%`;
}

if (percent >= 100) {
this.#hide();
} else if (percent > 0) {
this.#show();
}
}

#show() {
const element = this.shadowRoot.querySelector("#progress");
element.classList.remove("hidden");
}

#hide() {
const element = this.shadowRoot.querySelector("#progress");
if (this.type === "circular") {
element.classList.add("disappear");
element.addEventListener(
"animationend",
() => {
element.classList.add("hidden");
this.#update(0);
},
{ once: true },
);
} else if (this.type === "linear") {
element.classList.add("disappear");
this.animationTimer = setTimeout(() => {
element.classList.remove("disappear");
element.classList.add("hidden");
element.style.width = "0%";
this.animationTimer = null;
}, 800);
}
}
}

export function isProgressSupported() {
return "customElements" in window && !!HTMLElement.prototype.attachShadow;
}

export function defineProgressElement() {
if (customElements.get("wds-progress")) {
return;
}

customElements.define("wds-progress", WebpackDevServerProgress);
}
4 changes: 3 additions & 1 deletion examples/client/progress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module.exports = {
// ...
devServer: {
client: {
progress: true,
progress: true | "linear" | "circular",
},
},
};
Expand All @@ -17,6 +17,8 @@ Usage via CLI:

```shell
npx webpack serve --open --client-progress
npx webpack serve --open --client-progress linear
npx webpack serve --open --client-progress circular
```

To disable:
Expand Down
7 changes: 4 additions & 3 deletions lib/options.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,12 @@
]
},
"ClientProgress": {
"description": "Prints compilation progress in percentage in the browser.",
"description": "Displays compilation progress in the browser. Options include 'linear' and 'circular' for visual indicators.",
"link": "https://webpack.js.org/configuration/dev-server/#progress",
"type": "boolean",
"type": ["boolean", "string"],
"enum": [true, false, "linear", "circular"],
"cli": {
"negatedDescription": "Does not print compilation progress in percentage in the browser."
"negatedDescription": "Does not display compilation progress in the browser."
}
},
"ClientReconnect": {
Expand Down
5 changes: 3 additions & 2 deletions test/__snapshots__/validate-options.test.js.snap.webpack5
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,9 @@ exports[`options validate should throw an error on the "client" option with '{"o

exports[`options validate should throw an error on the "client" option with '{"progress":""}' value 1`] = `
"ValidationError: Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
- options.client.progress should be a boolean.
-> Prints compilation progress in percentage in the browser.
- options.client.progress should be one of these:
true | false | "linear" | "circular"
-> Displays compilation progress in the browser. Options include 'linear' and 'circular' for visual indicators.
-> Read more at https://webpack.js.org/configuration/dev-server/#progress"
`;

Expand Down
4 changes: 2 additions & 2 deletions test/cli/__snapshots__/basic.test.js.snap.webpack5
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ Options:
--client-overlay-runtime-errors Enables a full-screen overlay in the browser when there are uncaught runtime errors.
--no-client-overlay-runtime-errors Disables the full-screen overlay in the browser when there are uncaught runtime errors.
--client-overlay-trusted-types-policy-name <value> The name of a Trusted Types policy for the overlay. Defaults to 'webpack-dev-server#overlay'.
--client-progress Prints compilation progress in percentage in the browser.
--no-client-progress Does not print compilation progress in percentage in the browser.
--client-progress [value] Displays compilation progress in the browser. Options include 'linear' and 'circular' for visual indicators.
--no-client-progress Does not display compilation progress in the browser.
--client-reconnect [value] Tells dev-server the number of times it should try to reconnect the client.
--no-client-reconnect Tells dev-server to not to try to reconnect the client.
--client-web-socket-transport <value> Allows to set custom web socket transport to communicate with dev server.
Expand Down
Loading

0 comments on commit a8f40b7

Please sign in to comment.