mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
1 parent
e810eab39f
commit
b7974827d2
786 files changed
+14567
-24066
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,44 @@
|
||||
// Decorator for binding class methods
|
||||
// Can be applied to class or single method as @autobind()
|
||||
type Constructor<T = {}> = new (...args: any[]) => T;
|
||||
|
||||
export function autobind() {
|
||||
return function (target: Constructor | object, prop?: string, descriptor?: PropertyDescriptor) {
|
||||
if (target instanceof Function) return bindClass(target);
|
||||
else return bindMethod(target, prop, descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
function bindClass<T extends Constructor>(constructor: T) {
|
||||
const proto = constructor.prototype;
|
||||
const descriptors = Object.getOwnPropertyDescriptors(proto);
|
||||
|
||||
const skipMethod = (methodName: string) => {
|
||||
return methodName === "constructor"
|
||||
|| typeof descriptors[methodName].value !== "function";
|
||||
};
|
||||
|
||||
Object.keys(descriptors).forEach(prop => {
|
||||
if (skipMethod(prop)) return;
|
||||
const boundDescriptor = bindMethod(proto, prop, descriptors[prop]);
|
||||
Object.defineProperty(proto, prop, boundDescriptor);
|
||||
})
|
||||
}
|
||||
|
||||
function bindMethod(target: object, prop?: string, descriptor?: PropertyDescriptor) {
|
||||
if (!descriptor || typeof descriptor.value !== "function") {
|
||||
throw new Error(`@autobind() must be used on class or method only`)
|
||||
}
|
||||
const { value: func, enumerable, configurable } = descriptor;
|
||||
const boundFunc = new WeakMap<object, Function>();
|
||||
|
||||
return Object.defineProperty(target, prop, {
|
||||
enumerable,
|
||||
configurable,
|
||||
get() {
|
||||
if (this === target) return func; // direct access from prototype
|
||||
if (!boundFunc.has(this)) boundFunc.set(this, func.bind(this));
|
||||
return boundFunc.get(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Allow to cancel request for window.fetch()
|
||||
|
||||
export interface CancelablePromise<T> extends Promise<T> {
|
||||
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): CancelablePromise<TResult1 | TResult2>;
|
||||
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): CancelablePromise<T | TResult>;
|
||||
finally(onfinally?: (() => void) | undefined | null): CancelablePromise<T>;
|
||||
cancel(): void;
|
||||
}
|
||||
|
||||
interface WrappingFunction {
|
||||
<T>(result: Promise<T>): CancelablePromise<T>;
|
||||
<T>(result: T): T;
|
||||
}
|
||||
|
||||
export function cancelableFetch(reqInfo: RequestInfo, reqInit: RequestInit = {}) {
|
||||
const abortController = new AbortController();
|
||||
const signal = abortController.signal;
|
||||
const cancel = abortController.abort.bind(abortController);
|
||||
|
||||
const wrapResult: WrappingFunction = function (result: any) {
|
||||
if (result instanceof Promise) {
|
||||
const promise: CancelablePromise<any> = result as any;
|
||||
promise.then = function (onfulfilled, onrejected) {
|
||||
const data = Object.getPrototypeOf(this).then.call(this, onfulfilled, onrejected);
|
||||
return wrapResult(data);
|
||||
}
|
||||
promise.cancel = cancel;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const req = fetch(reqInfo, { ...reqInit, signal });
|
||||
return wrapResult(req);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Helper to convert CPU K8S units to numbers
|
||||
|
||||
export function cpuUnitsToNumber(cpu: string) {
|
||||
const cpuNum = parseInt(cpu)
|
||||
const billion = 1000000 * 1000
|
||||
if (cpu.includes("m")) return cpuNum / 1000
|
||||
if (cpu.includes("u")) return cpuNum / 1000000
|
||||
if (cpu.includes("n")) return cpuNum / billion
|
||||
return parseFloat(cpu)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Helper to convert memory from units Ki, Mi, Gi, Ti, Pi to bytes and vise versa
|
||||
|
||||
const base = 1024
|
||||
const suffixes = ['K', 'M', 'G', 'T', 'P', 'E'] // Equivalents: Ki, Mi, Gi, Ti, Pi, Ei
|
||||
|
||||
export function unitsToBytes(value: string) {
|
||||
if (!suffixes.some(suffix => value.includes(suffix))) {
|
||||
return parseFloat(value)
|
||||
}
|
||||
const index = suffixes.findIndex(suffix =>
|
||||
suffix == value.replace(/[0-9]|i|\./g, '')
|
||||
)
|
||||
return parseInt(
|
||||
(parseFloat(value) * Math.pow(base, index + 1)).toFixed(1)
|
||||
)
|
||||
}
|
||||
|
||||
export function bytesToUnits(bytes: number, precision = 1) {
|
||||
const sizes = ["B", ...suffixes]
|
||||
const index = Math.floor(Math.log(bytes) / Math.log(base))
|
||||
if (!bytes) {
|
||||
return "N/A"
|
||||
}
|
||||
if (index === 0) {
|
||||
return `${bytes}${sizes[index]}`
|
||||
}
|
||||
return `${(bytes / (1024 ** index)).toFixed(precision)}${sizes[index]}i`
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Helper for selecting element's text content and copy in clipboard
|
||||
|
||||
export function copyToClipboard(elem: HTMLElement, resetSelection = true): boolean {
|
||||
let clearSelection: () => void;
|
||||
if (isSelectable(elem)) {
|
||||
elem.select();
|
||||
clearSelection = () => elem.setSelectionRange(0, 0)
|
||||
}
|
||||
else {
|
||||
const selection = window.getSelection();
|
||||
selection.selectAllChildren(elem);
|
||||
clearSelection = () => selection.removeAllRanges();
|
||||
}
|
||||
const copyResult = document.execCommand("copy");
|
||||
if (resetSelection) clearSelection();
|
||||
return copyResult;
|
||||
}
|
||||
|
||||
function isSelectable(elem: HTMLElement): elem is HTMLInputElement {
|
||||
return !!(elem as HTMLInputElement).select;
|
||||
}
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
// Helper to work with browser's local/session storage api
|
||||
|
||||
export interface IStorageHelperOptions {
|
||||
addKeyPrefix?: boolean;
|
||||
useSession?: boolean; // use `sessionStorage` instead of `localStorage`
|
||||
}
|
||||
|
||||
export function createStorage<T>(key: string, defaultValue?: T, options?: IStorageHelperOptions) {
|
||||
return new StorageHelper(key, defaultValue, options);
|
||||
}
|
||||
|
||||
export class StorageHelper<T> {
|
||||
static keyPrefix = "lens_";
|
||||
|
||||
static defaultOptions: IStorageHelperOptions = {
|
||||
addKeyPrefix: true,
|
||||
useSession: false,
|
||||
}
|
||||
|
||||
constructor(protected key: string, protected defaultValue?: T, protected options?: IStorageHelperOptions) {
|
||||
this.options = Object.assign({}, StorageHelper.defaultOptions, options);
|
||||
|
||||
if (this.options.addKeyPrefix) {
|
||||
this.key = StorageHelper.keyPrefix + key;
|
||||
}
|
||||
}
|
||||
|
||||
protected get storage() {
|
||||
if (this.options.useSession) return window.sessionStorage;
|
||||
return window.localStorage;
|
||||
}
|
||||
|
||||
get(): T {
|
||||
const strValue = this.storage.getItem(this.key);
|
||||
if (strValue != null) {
|
||||
try {
|
||||
return JSON.parse(strValue);
|
||||
} catch (e) {
|
||||
console.error(`Parsing json failed for pair: ${this.key}=${strValue}`)
|
||||
}
|
||||
}
|
||||
return this.defaultValue;
|
||||
}
|
||||
|
||||
set(value: T) {
|
||||
this.storage.setItem(this.key, JSON.stringify(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
merge(value: Partial<T>) {
|
||||
const currentValue = this.get();
|
||||
return this.set(Object.assign(currentValue, value));
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.storage.removeItem(this.key);
|
||||
return this;
|
||||
}
|
||||
|
||||
getDefaultValue() {
|
||||
return this.defaultValue;
|
||||
}
|
||||
|
||||
restoreDefaultValue() {
|
||||
return this.set(this.defaultValue);
|
||||
}
|
||||
}
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
// Helper for combining css classes inside components
|
||||
|
||||
export type IClassName = string | string[] | IClassNameMap;
|
||||
export type IClassNameMap = {
|
||||
[className: string]: boolean | any;
|
||||
};
|
||||
|
||||
export function cssNames(...args: IClassName[]): string {
|
||||
const map: IClassNameMap = {};
|
||||
args.forEach(className => {
|
||||
if (typeof className === "string" || Array.isArray(className)) {
|
||||
[].concat(className).forEach(name => map[name] = true);
|
||||
}
|
||||
else {
|
||||
Object.assign(map, className);
|
||||
}
|
||||
});
|
||||
return Object.entries(map)
|
||||
.filter(([className, isActive]) => !!isActive)
|
||||
.map(([className]) => className.trim())
|
||||
.join(' ');
|
||||
}
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
// Helper for getting/setting css-variables
|
||||
|
||||
export function cssVar(elem: HTMLElement) {
|
||||
return {
|
||||
get(name: string) {
|
||||
const value = window.getComputedStyle(elem).getPropertyValue(name).trim();
|
||||
return {
|
||||
toString: () => value,
|
||||
valueOf: () => parseFloat(value)
|
||||
};
|
||||
},
|
||||
set(name: string, value: number | string) {
|
||||
if (typeof value === "number") value = value + "px";
|
||||
elem.style.setProperty(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
// Debouncing promise evaluation
|
||||
|
||||
export const debouncePromise = function (promisedFunc: Function, timeout = 0) {
|
||||
let timer: number;
|
||||
return (...params: any[]) => new Promise((resolve, reject) => {
|
||||
clearTimeout(timer);
|
||||
timer = window.setTimeout(() => resolve(promisedFunc.apply(this, params)), timeout);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
export function downloadFile(filename: string, contents: any, type: string) {
|
||||
const data = new Blob([contents], { type: type });
|
||||
const url = URL.createObjectURL(data);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
link.style.display = "none";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Custom event emitter
|
||||
|
||||
interface Options {
|
||||
once?: boolean; // call once and remove
|
||||
prepend?: boolean; // put listener to the beginning
|
||||
}
|
||||
|
||||
type Callback<D extends [...any[]]> = (...data: D) => void | boolean;
|
||||
|
||||
export class EventEmitter<D extends [...any[]]> {
|
||||
protected listeners = new Map<Callback<D>, Options>();
|
||||
|
||||
addListener(callback: Callback<D>, options: Options = {}) {
|
||||
if (options.prepend) {
|
||||
const listeners = [...this.listeners];
|
||||
listeners.unshift([callback, options]);
|
||||
this.listeners = new Map(listeners);
|
||||
}
|
||||
else {
|
||||
this.listeners.set(callback, options);
|
||||
}
|
||||
}
|
||||
|
||||
removeListener(callback: Callback<D>) {
|
||||
this.listeners.delete(callback);
|
||||
}
|
||||
|
||||
removeAllListeners() {
|
||||
this.listeners.clear();
|
||||
}
|
||||
|
||||
emit(...data: D) {
|
||||
[...this.listeners].every(([callback, options]) => {
|
||||
if (options.once) this.removeListener(callback);
|
||||
const result = callback(...data);
|
||||
if (result === false) return; // break cycle
|
||||
return true;
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Formatting date duration in shorten format, e.g. "2d", or "25m"
|
||||
|
||||
import moment from "moment";
|
||||
|
||||
export function formatDuration(timeValue: number, compact: boolean) {
|
||||
let result = "";
|
||||
const duration = moment.duration(timeValue);
|
||||
const suffixes = ["d", "h", "m"];
|
||||
const durationValues = [
|
||||
Math.round(duration.asDays()),
|
||||
duration.hours(),
|
||||
duration.minutes(),
|
||||
];
|
||||
durationValues.forEach((value, index) => {
|
||||
if (value) result += value + suffixes[index] + " ";
|
||||
});
|
||||
if (compact) {
|
||||
result = result.split(" ")[0];
|
||||
}
|
||||
if (!result) {
|
||||
return "<1m";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
// Common usage utils & helpers
|
||||
|
||||
export const noop: any = Function();
|
||||
export const isElectron = !!navigator.userAgent.match(/Electron/);
|
||||
|
||||
export * from '../../common/utils/camelCase'
|
||||
export * from '../../common/utils/base64'
|
||||
|
||||
export * from './autobind'
|
||||
export * from './cssVar'
|
||||
export * from './cssNames'
|
||||
export * from './eventEmitter'
|
||||
export * from './downloadFile'
|
||||
export * from './prevDefault'
|
||||
export * from './createStorage'
|
||||
export * from './interval'
|
||||
export * from './debouncePromise'
|
||||
export * from './copyToClipboard'
|
||||
export * from './formatDuration'
|
||||
export * from './isReactNode'
|
||||
export * from './convertMemory'
|
||||
export * from './convertCpu'
|
||||
@@ -0,0 +1,32 @@
|
||||
// Helper for working with time updates / data-polling callbacks
|
||||
|
||||
type IntervalCallback = (count: number) => void;
|
||||
|
||||
export function interval(timeSec = 1, callback: IntervalCallback, autoRun = false) {
|
||||
let count = 0;
|
||||
let timer = -1;
|
||||
let isRunning = false;
|
||||
const intervalManager = {
|
||||
start: function (runImmediately = false) {
|
||||
if (isRunning) return;
|
||||
const tick = () => callback(++count);
|
||||
isRunning = true;
|
||||
timer = window.setInterval(tick, 1000 * timeSec);
|
||||
if (runImmediately) tick();
|
||||
},
|
||||
stop: function () {
|
||||
count = 0;
|
||||
isRunning = false;
|
||||
clearInterval(timer);
|
||||
},
|
||||
restart: function (runImmediately = false) {
|
||||
this.stop();
|
||||
this.start(runImmediately);
|
||||
},
|
||||
get isRunning() {
|
||||
return isRunning;
|
||||
}
|
||||
}
|
||||
if (autoRun) intervalManager.start();
|
||||
return intervalManager;
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
// Type guard for checking valid react node to use in render
|
||||
import React, { ReactNode } from "react"
|
||||
|
||||
export function isReactNode(node: ReactNode): node is ReactNode {
|
||||
return React.isValidElement(node)
|
||||
|| Array.isArray(node) && node.every(isReactNode)
|
||||
|| node == null
|
||||
|| typeof node !== "object"
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
|
||||
// Helper for preventing default event action and performing custom callback
|
||||
// 1)
|
||||
// <form onSubmit={prevDefault(() => console.log('do some action'))}>
|
||||
// <input name="text"/>
|
||||
// <button type="submit">Action</button>
|
||||
// </form>
|
||||
// 2)
|
||||
// <a href="/some-page-url">
|
||||
// <span>Link text</span>
|
||||
// <Icon onClick={prevDefault(() => console.log('stay on the page and open dialog'))}/>
|
||||
// </a>
|
||||
|
||||
export function prevDefault<E extends React.SyntheticEvent | Event>(callback: (evt: E) => any) {
|
||||
return function (evt: E) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
return callback(evt);
|
||||
}
|
||||
}
|
||||
|
||||
export function stopPropagation(evt: Event | React.SyntheticEvent) {
|
||||
evt.stopPropagation();
|
||||
}
|
||||
Reference in new issue
Block a user