f179d6572e
* web: fix storybook `build` css import issue This is an incredibly frustrating issue, because Storybook works in `dev` mode but not in `build` mode, and that's not at all what you'd expecte from a mature piece of software. Lit uses the native CSS adoptedStylesheets field, which takes only a constructedStylesheet. Lit provides a way of generating those, but the imports from Patternfly (or any `.css` file) are text, and converting those to stylesheets required a bit of magic. What this means going forward is that any Storied components will have to have their CSS wrapped in a way that ensures it is managed correctly by Lit (well, to be pedantic, by the shadowDOM.adoptedStylesheets). That wrapper is provided and the components that need it have been wrapped. This problem deserves further investigation, but for the time being this actually does solve it with a minimum amount of surgical pain. * web: fix storybook build issue This commit further fixes the typing issues around strings, CSSResults, and CSSStyleSheets by providing overloaded functions that assist consumers in knowing that if they send an array to expect an array in return, and if they send a scalar expect a scalar in return. * replace any with unknown Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io> Co-authored-by: Jens Langhammer <jens@goauthentik.io>
145 lines
5.4 KiB
TypeScript
145 lines
5.4 KiB
TypeScript
import { SentryIgnoredError } from "@goauthentik/common/errors";
|
|
|
|
import { CSSResult, css } from "lit";
|
|
|
|
export function getCookie(name: string): string {
|
|
let cookieValue = "";
|
|
if (document.cookie && document.cookie !== "") {
|
|
const cookies = document.cookie.split(";");
|
|
for (let i = 0; i < cookies.length; i++) {
|
|
const cookie = cookies[i].trim();
|
|
// Does this cookie string begin with the name we want?
|
|
if (cookie.substring(0, name.length + 1) === name + "=") {
|
|
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return cookieValue;
|
|
}
|
|
|
|
export function convertToSlug(text: string): string {
|
|
return text
|
|
.toLowerCase()
|
|
.replace(/ /g, "-")
|
|
.replace(/[^\w-]+/g, "");
|
|
}
|
|
|
|
export function convertToTitle(text: string): string {
|
|
return text.replace(/\w\S*/g, function (txt) {
|
|
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Truncate a string based on maximum word count
|
|
*/
|
|
export function truncateWords(string: string, length = 10): string {
|
|
string = string || "";
|
|
const array = string.trim().split(" ");
|
|
const ellipsis = array.length > length ? "..." : "";
|
|
|
|
return array.slice(0, length).join(" ") + ellipsis;
|
|
}
|
|
|
|
/**
|
|
* Truncate a string based on character count
|
|
*/
|
|
export function truncate(string: string, length = 10): string {
|
|
return string.length > length ? `${string.substring(0, length)}...` : string;
|
|
}
|
|
|
|
export function camelToSnake(key: string): string {
|
|
const result = key.replace(/([A-Z])/g, " $1");
|
|
return result.split(" ").join("_").toLowerCase();
|
|
}
|
|
|
|
export function groupBy<T>(objects: T[], callback: (obj: T) => string): Array<[string, T[]]> {
|
|
const m = new Map<string, T[]>();
|
|
objects.forEach((obj) => {
|
|
const group = callback(obj);
|
|
if (!m.has(group)) {
|
|
m.set(group, []);
|
|
}
|
|
const tProviders = m.get(group) || [];
|
|
tProviders.push(obj);
|
|
});
|
|
return Array.from(m).sort();
|
|
}
|
|
|
|
export function first<T>(...args: Array<T | undefined | null>): T {
|
|
for (let index = 0; index < args.length; index++) {
|
|
const element = args[index];
|
|
if (element !== undefined && element !== null) {
|
|
return element;
|
|
}
|
|
}
|
|
throw new SentryIgnoredError(`No compatible arg given: ${args}`);
|
|
}
|
|
|
|
export function hexEncode(buf: Uint8Array): string {
|
|
return Array.from(buf)
|
|
.map(function (x) {
|
|
return ("0" + x.toString(16)).substr(-2);
|
|
})
|
|
.join("");
|
|
}
|
|
|
|
// Taken from python's string module
|
|
export const ascii_lowercase = "abcdefghijklmnopqrstuvwxyz";
|
|
export const ascii_uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
export const ascii_letters = ascii_lowercase + ascii_uppercase;
|
|
export const digits = "0123456789";
|
|
export const hexdigits = digits + "abcdef" + "ABCDEF";
|
|
export const octdigits = "01234567";
|
|
export const punctuation = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
|
|
|
|
export function randomString(len: number, charset: string): string {
|
|
const chars = [];
|
|
const array = new Uint8Array(len);
|
|
self.crypto.getRandomValues(array);
|
|
for (let index = 0; index < len; index++) {
|
|
chars.push(charset[Math.floor(charset.length * (array[index] / Math.pow(2, 8)))]);
|
|
}
|
|
return chars.join("");
|
|
}
|
|
|
|
export function dateTimeLocal(date: Date): string {
|
|
// So for some reason, the datetime-local input field requires ISO Datetime as value
|
|
// But the standard javascript date.toISOString() returns everything with seconds and
|
|
// milliseconds, which the input field doesn't like (on chrome, on firefox its fine)
|
|
// On chrome, setting .valueAsNumber works, but that causes an error on firefox, so go
|
|
// figure.
|
|
// Additionally, toISOString always returns the date without timezone, which we would like
|
|
// to include for better usability
|
|
const tzOffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds
|
|
const localISOTime = new Date(date.getTime() - tzOffset).toISOString().slice(0, -1);
|
|
const parts = localISOTime.split(":");
|
|
return `${parts[0]}:${parts[1]}`;
|
|
}
|
|
|
|
// Lit is extremely well-typed with regard to CSS, and Storybook's `build` does not currently have a
|
|
// coherent way of importing CSS-as-text into CSSStyleSheet. It works well when Storybook is running
|
|
// in `dev,` but in `build` it fails. Storied components will have to map their textual CSS imports
|
|
// using the function below.
|
|
type AdaptableStylesheet = Readonly<string | CSSResult | CSSStyleSheet>;
|
|
type AdaptedStylesheets = CSSStyleSheet | CSSStyleSheet[];
|
|
|
|
const isCSSResult = (v: unknown): v is CSSResult =>
|
|
v instanceof CSSResult && v.styleSheet !== undefined;
|
|
|
|
// prettier-ignore
|
|
export const _adaptCSS = (sheet: AdaptableStylesheet): CSSStyleSheet =>
|
|
(typeof sheet === "string" ? css([sheet] as unknown as TemplateStringsArray, ...[]).styleSheet
|
|
: isCSSResult(sheet) ? sheet.styleSheet
|
|
: sheet) as CSSStyleSheet;
|
|
|
|
// Overloaded function definitions inform consumers that if you pass it an array, expect an array in
|
|
// return; if you pass it a scaler, expect a scalar in return.
|
|
|
|
export function adaptCSS(sheet: AdaptableStylesheet): CSSStyleSheet;
|
|
export function adaptCSS(sheet: AdaptableStylesheet[]): CSSStyleSheet[];
|
|
export function adaptCSS(sheet: AdaptableStylesheet | AdaptableStylesheet[]): AdaptedStylesheets {
|
|
return Array.isArray(sheet) ? sheet.map(_adaptCSS) : _adaptCSS(sheet);
|
|
}
|