Lens restructure (#540)

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman authored and GitHub committed 2020-06-30 14:35:16 +03:00
1 parent e810eab39f
commit b7974827d2
786 files changed
+14567 -24066

No files matched your search

View File
Whitespace-only changes.
+44
View File
@@ -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);
}
});
}
+34
View File
@@ -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);
}
+10
View File
@@ -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)
}
+28
View File
@@ -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`
}
+21
View File
@@ -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;
}
+67
View File
@@ -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);
}
}
+22
View File
@@ -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(' ');
}
+17
View File
@@ -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);
}
}
}
+9
View File
@@ -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);
});
};
+12
View File
@@ -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)
}
+40
View File
@@ -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;
})
}
}
+24
View File
@@ -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;
}
+22
View File
@@ -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'
+32
View File
@@ -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;
}
+9
View File
@@ -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"
}
+25
View File
@@ -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();
}