feat: init

This commit is contained in:
montaghanmy
2023-03-23 11:03:16 +01:00
commit 10fe6f78d1
11518 changed files with 509786 additions and 0 deletions
@@ -0,0 +1,283 @@
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-explicit-any */
import Globals from 'app/features/global/services/globals-twake-app-service';
import Requests from 'app/features/global/framework/requests-api-service';
class GroupedQueryApi {
private groupedQueryBuffer: any;
private groupedQueryTimeout: any;
post(route: string, data: any, callback: any) {
if (!this.groupedQueryBuffer) {
this.groupedQueryBuffer = [];
}
this.groupedQueryBuffer.push({
route: route,
data: data,
callback: callback,
});
if (this.groupedQueryTimeout) {
clearTimeout(this.groupedQueryTimeout);
}
this.groupedQueryTimeout = setTimeout(() => {
const queries = this.groupedQueryBuffer;
this.groupedQueryBuffer = [];
const request: any[] = [];
queries.forEach((query: any) => {
request.push(query.data);
});
Api.GroupedQueryApiPost(queries[0].route, { multiple: request }, (res: any) => {
if (res.data && res.data.length) {
res.data.forEach((result: any, i: string | number) => {
queries[i] && queries[i].callback && queries[i].callback(result);
});
}
});
}, 50);
}
}
const GroupedQueryApiInstance = new GroupedQueryApi();
export default class Api {
static getWithParams<Response>(
route: string,
params: any,
options: { disableJWTAuthentication?: boolean; withBlob?: boolean } = {},
) {
let query = '';
if (params) {
for (const k of Object.keys(params)) {
query += `&${k}=${params[k]}`;
}
if (!~route.indexOf('?')) {
query = '?' + query.slice(1);
}
}
return Api.get<Response>(route + query, () => {}, false, options);
}
static get<Response>(
route: string,
callback?: (result: Response) => void,
raw = false,
options: { disableJWTAuthentication?: boolean; withBlob?: boolean } = {},
): Promise<Response> {
return new Promise((resolve, reject) => {
route = Globals.api_root_url + route;
Requests.request(
'get',
route,
'',
(resp: any) => {
const result = raw ? resp : JSON.parse(resp);
if (resp.statusCode === 500) {
callback && callback(result);
return reject(result);
}
resolve({ ...result, _statusCode: resp.statusCode });
callback && callback({ ...result, _statusCode: resp.statusCode });
},
options,
);
});
}
static put<Request, Response>(
route: string,
data: Request,
callback?: (result: Response) => void,
raw = false,
options: {
disableJWTAuthentication?: boolean;
} = {},
) {
return Api.request<Request, Response>(route, data, callback, raw, {
...options,
requestType: 'put',
});
}
static post<Request, Response>(
route: string,
data: Request,
callback?: (result: Response) => void,
raw = false,
options: {
disableJWTAuthentication?: boolean;
} = {},
): Promise<Response> {
return Api.request<Request, Response>(route, data, callback, raw, {
...options,
requestType: 'post',
});
}
static GroupedQueryApiPost<
Request extends { _grouped?: unknown; multiple?: unknown[] },
Response,
>(route: string, data: Request, callback?: (result: Response) => void): Promise<void> {
return new Promise(resolve => {
if (data && data._grouped && route === 'core/collections/init') {
GroupedQueryApiInstance.post(route, data, callback);
return;
}
resolve();
});
}
static delete<Response>(
route: string,
callback?: (result: Response) => void,
raw = false,
options: {
disableJWTAuthentication?: boolean;
} = {},
): Promise<Response> {
return Api.request(route, null, callback, raw, { ...options, requestType: 'delete' });
}
static request<Request, Response>(
route: string,
data: Request | null,
callback: any = false,
raw = false,
options: {
disableJWTAuthentication?: boolean;
requestType?: 'post' | 'get' | 'put' | 'delete';
} = {},
): Promise<Response> {
return new Promise(resolve => {
Requests.request(
options.requestType ? options.requestType : 'post',
new URL(route, Globals.api_root_url).toString(),
data === null ? '' : JSON.stringify(data),
(resp: any) => {
if (raw) {
resolve(resp);
if (callback) callback(resp);
return;
}
let response: any = '';
try {
response = JSON.parse(resp);
} catch (e) {
console.log('Server internal error, bad JSON.');
response = { errors: 'bad_json' };
}
resolve(response);
if (callback) callback(response);
},
options,
);
});
}
static searching_last_query: any;
static searching_http_timeout: any;
static searching_http: any;
static searching_javascript: any;
static search(source: any, _query: any, collectionService: any, callback: any) {
if (!Api.searching_http_timeout) {
Api.searching_http_timeout = {};
}
if (!Api.searching_javascript) {
Api.searching_javascript = {};
}
if (!Api.searching_http) {
Api.searching_http = {};
}
if (!this.searching_last_query) {
Api.searching_last_query = {};
}
const query = _query;
const http = source.http;
const http_data = source.http_data || { query: query };
let collection: any = null,
collection_filter: any = null,
collection_find_by: any = null;
if (source.collection && collectionService.get) {
collection = collectionService.get(source.collection);
collection_find_by = source.collection_find_by || {};
collection_filter =
source.collection_filter ||
(() => {
return true;
});
}
const search_key = source.http + '_' + source.collection;
this.searching_last_query[search_key] = query;
//JavaScript search
if (collection && !this.searching_javascript[search_key]) {
this.searching_javascript[search_key] = true;
const results = collection
.findBy(collection_find_by)
.filter((item: any) => collection_filter(item, query));
callback(results);
this.searching_javascript[search_key] = false;
}
//HTTP Search
if (http) {
if (this.searching_http_timeout[search_key]) {
clearTimeout(this.searching_http_timeout[search_key]);
}
if (!this.searching_http[search_key]) {
this.searching_http[search_key] = true;
try {
Api.post(
http,
http_data,
(res: any) => {
if (res.data) {
if (collection) {
res.data.forEach((item: any) => {
collection.completeObject(item, item.front_id);
});
collection.notify();
delete source.http;
delete source.http_data;
Api.search(
source,
this.searching_last_query[search_key],
collectionService,
callback,
);
}
callback && callback(res.data);
this.searching_http[search_key] = false;
}
},
false,
);
} catch (e) {
this.searching_http[search_key] = false;
}
} else {
this.searching_http_timeout[search_key] = setTimeout(() => {
Api.search(source, query, collectionService, callback);
}, 500);
}
}
}
static route(route: string) {
return Globals.api_root_url + route;
}
}
(window as any).Api = Api;
@@ -0,0 +1,28 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
class Electron {
isElectron() {
return (window as any).electron !== undefined;
}
setBadge(value: any) {
if (!this.isElectron()) return;
try {
if (
(window as any).electron &&
(window as any).electron.remote &&
(window as any).electron.remote.app &&
(window as any).electron.remote.app.dock
) {
(window as any).electron.remote.app.dock.setBadge(value);
}
if ((window as any).electron && (window as any).electron.ipcRenderer) {
(window as any).electron.ipcRenderer.send('application:update_badge', value);
}
} catch (err) {
console.log('No electron app available for setting dock badge');
}
}
}
const instanceElectron = new Electron();
export default instanceElectron;
@@ -0,0 +1,7 @@
class Environment {
isProduction() {
return process.env?.NODE_ENV === "production";
}
}
export default new Environment();
@@ -0,0 +1,43 @@
export default class LocalStorage {
static prefix = 'twake:';
static setItem(key: string, value: unknown) {
window.localStorage.setItem(`${LocalStorage.prefix}${key}`, JSON.stringify(value));
}
static getItem<T>(key: string): T | string | null {
let value = window.localStorage.getItem(`${LocalStorage.prefix}${key}`);
if (!value) {
return null;
}
try {
value = JSON.parse(value);
} catch (e) {
value = null;
console.log(e);
}
return value as unknown as T;
}
static clear() {
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key) {
const depreciatedKeysRemove =
[
'twake-collections-db',
'm_input',
'language',
'jwt',
'autoload_workspaces',
'oidc.',
].some(m => key.indexOf(m) === 0) || key.indexOf(':channel') > 0;
if (key.indexOf(LocalStorage.prefix) === 0 || depreciatedKeysRemove)
window.localStorage.removeItem(key);
}
}
}
}
@@ -0,0 +1,23 @@
import log from 'loglevel';
import prefix from 'loglevel-plugin-prefix';
import EnvironmentService from './environment-service';
const isProduction = EnvironmentService.isProduction();
isProduction ? log.setDefaultLevel(log.levels.WARN) : log.setDefaultLevel(log.levels.DEBUG);
prefix.reg(log);
prefix.apply(log, {
template: `${isProduction ? '' : '[%t] '}%l - %n -`,
levelFormatter(level) {
return level.toUpperCase();
},
nameFormatter(name) {
return name || 'Twake';
},
timestampFormatter(date) {
return date.toISOString();
},
});
export default log;
@@ -0,0 +1,35 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ServiceRegistry from 'app/features/global/framework/registry-service';
const SERVICE_SUFFIX = 'ChannelServiceImpl';
export function TwakeService(name: string): ClassDecorator {
return function DecoratedTwakeService(target: any): any {
const originalConstrutor = target;
const decorated: any = function (...args: any) {
const newService = new originalConstrutor(...args);
if (name) {
const serviceName =
name.endsWith(SERVICE_SUFFIX) || name.endsWith(SERVICE_SUFFIX.toLowerCase())
? name
: `${name}${SERVICE_SUFFIX}`;
ServiceRegistry.register(serviceName, newService);
}
(window as any)[name] = newService;
return newService;
};
decorated.prototype = originalConstrutor.prototype;
Object.keys(originalConstrutor).forEach((name: string) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
decorated[name] = (<any>originalConstrutor)[name];
});
return decorated;
};
}
@@ -0,0 +1,17 @@
class ServiceRegistry {
services: { [key: string]: unknown };
constructor() {
this.services = {};
}
register(name: string, service: unknown) {
if (!service || !name) {
return;
}
this.services[name] = service;
}
}
export default new ServiceRegistry();
@@ -0,0 +1,70 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import JWTStorage from 'app/features/auth/jwt-storage-service';
import Logger from './logger-service';
class Requests {
logger: Logger.Logger;
constructor() {
this.logger = Logger.getLogger('HTTPRequest');
}
request(
type: 'post' | 'get' | 'put' | 'delete',
route: string,
data: string,
callback: (result: string | any) => void,
options: { disableJWTAuthentication?: boolean; withBlob?: boolean } = {},
) {
this.logger.trace(`${type} ${route}`);
if (options?.disableJWTAuthentication) {
fetch(route, {
credentials: 'same-origin',
method: type,
headers: {
Accept: 'application/json',
...(data ? { 'Content-Type': 'application/json' } : {}),
Authorization: JWTStorage.getAutorizationHeader(),
},
body: type === 'post' ? data || '{}' : undefined,
})
.then(response => {
if (options.withBlob) {
response.blob().then(blob => {
this.retrieveJWTToken(JSON.stringify(blob));
callback && callback(blob);
});
} else {
response.text().then(text => {
if (text) this.retrieveJWTToken(text);
callback && callback(text);
});
}
})
.catch(err => {
this.logger.error('Error while sending HTTP request', err);
callback && callback(JSON.stringify({ errors: [err] }));
});
return;
}
JWTStorage.authenticateCall(() => {
options = options || {};
options.disableJWTAuthentication = true;
this.request(type, route, data, callback, options);
});
}
retrieveJWTToken(rawBody: string) {
try {
const body = JSON.parse(rawBody);
if (body.access_token) {
JWTStorage.updateJWT(body.access_token);
}
} catch (err) {
console.error('Error while reading jwt tokens from: ' + rawBody, err);
}
}
}
export default new Requests();
@@ -0,0 +1,18 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { isEqual } from 'lodash';
import { useEffect } from 'react';
const globalEffectDepsMap = new Map<string, ReadonlyArray<any>>();
export const flushGlobalEffects = () => {
globalEffectDepsMap.clear();
};
export const useGlobalEffect = (key: string, callback: () => void, deps: ReadonlyArray<any>) => {
useEffect(() => {
if (isEqual(globalEffectDepsMap.get(key), deps) === false) {
globalEffectDepsMap.set(key, deps);
callback();
}
}, deps);
};
@@ -0,0 +1,40 @@
import { useEffect, useState } from 'react';
import { Maybe } from 'app/features/global/types/global-types';
import Globals from '../services/globals-twake-app-service';
import JWTStorage from '../../auth/jwt-storage-service';
import Logger from '../framework/logger-service';
const logger = Logger.getLogger('useHTTP');
const useGetHTTP = <T>(path: string): [Maybe<T>, Maybe<Error>] => {
const url = `${Globals.api_root_url}/internal/services/${path}`;
const [response, setResponse] = useState<T>();
const [error, setError] = useState<Error>();
const headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: JWTStorage.getAutorizationHeader(),
};
useEffect(() => {
fetch(url, {
method: 'GET',
headers,
})
.then(response => {
logger.debug(url, response.status);
return response;
})
.then(response => response.json() as unknown as T)
.then(r => setResponse(r))
.catch(err => setError(err));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url]);
return [response, error];
};
export {
useGetHTTP,
// TODO: Other methods
};
@@ -0,0 +1,34 @@
import { useEffect, useLayoutEffect, useRef } from 'react';
import Logger from 'app/features/global/framework/logger-service';
const logger = Logger.getLogger('useInterval');
const useInterval = (callback: () => void, delay: number | null) => {
const savedCallback = useRef(callback);
// Remember the latest callback if it changes.
useLayoutEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
// Don't schedule if no delay is specified.
if (!delay) {
return;
}
const id = setInterval(() => {
logger.debug(`Running interval ${id}`);
savedCallback.current();
}, delay);
logger.debug(`Created interval ${id} with delay ${delay}`);
return () => {
logger.debug(`Clearing interval ${id}`);
clearInterval(id);
};
}, [delay]);
};
export default useInterval;
@@ -0,0 +1,18 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { RefObject, useEffect, useState } from 'react';
export default function useOnScreen(ref: RefObject<Element>) {
const [isIntersecting, setIntersecting] = useState(false);
const observer = new IntersectionObserver(([entry]) => setIntersecting(entry.isIntersecting));
/* eslint-disable react-hooks/exhaustive-deps */
useEffect(() => {
observer.observe(ref.current!);
// Remove the observer as soon as the component is unmounted
return () => observer.disconnect();
}, []);
/* eslint-enable react-hooks/exhaustive-deps */
return isIntersecting;
}
@@ -0,0 +1,96 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useCallback, useEffect, useRef, useState } from 'react';
import useWebSocket from 'app/features/global/hooks/use-websocket';
import Logger from 'app/features/global/framework/logger-service';
import {
RealtimeBaseAction,
RealtimeBaseEvent,
RealtimeResourceEvent,
} from '../types/realtime-types';
import { WebsocketRoom } from 'app/features/global/types/websocket-types';
const logger = Logger.getLogger('useRealtimeRoom');
export type RealtimeRoomService<T> = {
lastEvent: T;
send: (data: T) => void;
emit: (event: string, data: T) => void;
};
/**
* Subscribe to a room using websocket channel.
*
* Note: It will subscribe only once, even if the component using it re renders. If you need to unsubscribe and subscribe again, call unsubscribe on the returned object.
*
* @param roomName
* @param tagName
* @param onEvent
* @returns
*/
const useRealtimeRoom = <T>(
roomConf: WebsocketRoom,
tagName: string,
onEvent: (action: RealtimeBaseAction, event: T) => void,
) => {
const { websocket } = useWebSocket();
const [lastEvent, setLastEvent] = useState<{ action: RealtimeBaseAction; payload: T }>();
const [room, setRoom] = useState(roomConf);
const [tag] = useState(tagName);
// subscribe once
const subscribed = useRef(false);
const newEvent = useCallback(
(event: { action: RealtimeBaseAction; payload: T }) => {
if (event) {
setLastEvent(event);
onEvent(event.action, event.payload);
}
},
[onEvent],
);
useEffect(() => {
if (room !== roomConf) {
setRoom({ ...roomConf });
if (room && subscribed.current && websocket) {
websocket.leave(room.room, tag);
subscribed.current = false;
}
}
}, [roomConf?.room, roomConf?.token, tagName]);
useEffect(() => {
if (room && room.room && websocket && !subscribed.current) {
websocket.join(room.room, room.token, tag, (type: string, event: RealtimeBaseEvent) => {
logger.debug('Received WebSocket event', type, event);
if (type === 'realtime:resource') {
newEvent({
action: (event as RealtimeResourceEvent<T>).action,
payload: {
...(event as RealtimeResourceEvent<T>).resource,
_type: (event as RealtimeResourceEvent<T>).type,
},
});
} else if (type === 'realtime:event') {
newEvent({ action: 'event', payload: event.data });
} else if (type === 'realtime:join:success') {
logger.debug(`Room ${room} has been joined`);
} else {
logger.debug('Event type is not supported', type);
}
});
subscribed.current = true;
}
}, [websocket, tag, room, onEvent]);
return {
lastEvent,
send: (data: any) => websocket?.send(room.room, room.token, data),
unsubscribe: () => {
subscribed.current = false;
websocket?.leave(room.room, tagName);
},
};
};
export { useRealtimeRoom };
@@ -0,0 +1,21 @@
import { useEffect, useRef } from 'react';
function useTimeout(callback: () => void, delay: number | null) {
const savedCallback = useRef(callback);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay === null) {
return;
}
const id = setTimeout(() => savedCallback.current(), delay);
return () => clearTimeout(id);
}, [delay]);
}
export default useTimeout;
@@ -0,0 +1,35 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useEffect } from 'react';
import { useCurrentUser } from 'app/features/users/hooks/use-current-user';
import Globals from 'app/features/global/services/globals-twake-app-service';
import Logger from 'app/features/global/framework/logger-service';
let initiatedService = false;
const logger = Logger.getLogger(`app/features/global/use-usetiful`);
export default function useUsetiful() {
const { user } = useCurrentUser();
useEffect(() => {
if (user && user.id && !initiatedService) {
initiatedService = true;
if (Globals.environment.usetiful_token?.length) {
(window as any).usetifulTags = { userId: user.id };
(function (w, d, s) {
const a = d.getElementsByTagName('head')[0];
const r = d.createElement('script');
r.async = true;
r.src = s;
r.setAttribute('id', 'usetifulScript');
r.dataset.token = Globals.environment.usetiful_token;
a.appendChild(r);
})(window, document, 'https://www.usetiful.com/dist/usetiful.js');
}
} else {
if (!initiatedService) {
logger.warn(`Usetiful not enabled`);
}
}
}, [user]);
}
@@ -0,0 +1,34 @@
import { useEffect, useRef, useState } from 'react';
import WebSocketFactory, { WebsocketEvents } from 'app/features/global/types/websocket-types';
import WebSocketService from 'app/features/global/services/websocket-service';
const useWebSocket = () => {
const wsRef = useRef<WebSocketService>();
// having this will allow consumers to be updated with the io instance once connected
const [connected, setConnected] = useState(false);
useEffect(() => {
wsRef.current = WebSocketFactory.get();
if (wsRef.current.isConnected()) {
setConnected(() => true);
}
wsRef.current.on(WebsocketEvents.Connected, () => {
setConnected(() => true);
});
wsRef.current.on(WebsocketEvents.Disconnected, () => {
setConnected(() => false);
});
return () => undefined;
}, []);
return {
websocket: wsRef.current,
connected,
};
};
export default useWebSocket;
@@ -0,0 +1,35 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import { Modal } from 'antd';
import Languages from 'app/features/global/services/languages-service';
import { TwakeService } from '../framework/registry-decorator-service';
const { confirm, info } = Modal;
type Options = {
title?: string;
text?: string;
};
@TwakeService('Alert')
class AlertServiceService {
alert(onClose: () => void, options?: Options) {
info({
title: options?.title || options?.text || '',
content: options?.text || '',
onCancel: onClose,
});
}
confirm(onConfirm: () => void, onClose: (() => void) | false = () => {}, options?: Options) {
confirm({
title: options?.title || Languages.t('components.alert.confirm'),
content: options?.text || Languages.t('components.alert.confirm_click'),
onOk: onConfirm,
onCancel: onClose || undefined,
cancelButtonProps: onClose ? {} : { style: { display: 'none' } },
});
}
}
const AlertManager = new AlertServiceService();
export default AlertManager;
@@ -0,0 +1,72 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// Define feature names here
export enum FeatureNames {
GUESTS = 'chat:guests',
MESSAGE_HISTORY = 'chat:message_history',
MESSAGE_HISTORY_LIMIT = 'chat:message_history_limit',
MULTIPLE_WORKSPACES = 'chat:multiple_workspaces',
EDIT_FILES = 'chat:edit_files',
UNLIMITED_STORAGE = 'chat:unlimited_storage', //Currently inactive
COMPANY_INVITE_MEMBER = 'company:invite_member',
}
export type FeatureValueType = boolean | number;
const availableFeaturesWithDefaults = new Map<FeatureNames, any>();
// Define available features with defaults here
availableFeaturesWithDefaults.set(FeatureNames.GUESTS, true);
availableFeaturesWithDefaults.set(FeatureNames.MESSAGE_HISTORY, true);
availableFeaturesWithDefaults.set(FeatureNames.MESSAGE_HISTORY_LIMIT, 10000);
availableFeaturesWithDefaults.set(FeatureNames.MULTIPLE_WORKSPACES, true);
availableFeaturesWithDefaults.set(FeatureNames.EDIT_FILES, true);
availableFeaturesWithDefaults.set(FeatureNames.UNLIMITED_STORAGE, true);
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_INVITE_MEMBER, true);
/**
* ChannelServiceImpl that allow you to manage feature flipping in Twake using react feature toggles
*/
class FeatureTogglesService {
public activeFeatureNames: FeatureNames[];
private activeFeatureValues: Map<FeatureNames, FeatureValueType>;
constructor() {
(window as any).FeatureTogglesService = this;
this.activeFeatureNames = [];
this.activeFeatureValues = new Map<FeatureNames, FeatureValueType>();
// We need to set with default features
this.setFeaturesFromCompanyPlan({
features: {},
});
}
public setFeaturesFromCompanyPlan(plan: { features: { [key: string]: FeatureValueType } }): void {
for (const [featureName, defaultValue] of availableFeaturesWithDefaults) {
this.setActiveFeatureName(
featureName,
plan.features[featureName] !== undefined ? plan.features[featureName] : defaultValue,
);
}
}
private setActiveFeatureName(featureName: FeatureNames, value: FeatureValueType): void {
if (typeof value === 'boolean') {
this.activeFeatureNames = this.activeFeatureNames.filter(name => name !== featureName);
if (value) this.activeFeatureNames.push(featureName);
this.activeFeatureValues.set(featureName, !!value);
} else {
this.activeFeatureValues.set(featureName, value);
}
}
public isActiveFeatureName(featureName: FeatureNames) {
return this.activeFeatureNames.includes(featureName);
}
public getFeatureValue<T>(featureName: FeatureNames): T {
return this.activeFeatureValues.get(featureName) as unknown as T;
}
}
export default new FeatureTogglesService();
@@ -0,0 +1,72 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import environment from 'environment/environment';
import version from 'environment/version';
import * as Sentry from '@sentry/browser';
import LocalStorage from '../framework/local-storage-service';
import { EnvironmentType, EnvironmentVersionType } from 'app/environment/types';
import ServiceRegistry from '../framework/registry-service';
if (process.env.NODE_ENV === 'production' && (window as any).sentry_dsn) {
Sentry.init({
dsn: (window as any).sentry_dsn,
});
}
(window as any).getBoundingClientRect = (element: Element) => {
const rect = element.getBoundingClientRect();
return {
top: rect.top,
right: rect.right,
bottom: rect.bottom,
left: rect.left,
width: rect.width,
height: rect.height,
x: rect.x || rect.left,
y: rect.y || rect.top,
};
};
document.body.addEventListener('dragover', e => e.preventDefault());
document.body.addEventListener('dragenter', e => e.preventDefault());
document.body.addEventListener('drop', e => e.preventDefault());
class TwakeApp {
window: Window;
environment: EnvironmentType;
store_public_access_get_data: any;
version: EnvironmentVersionType;
services: any;
api_root_url: string;
constructor() {
this.services = ServiceRegistry;
this.environment = environment as EnvironmentType;
this.version = version;
this.window = window;
this.api_root_url = this.environment.api_root_url || '';
// FIXME: Deprecated, need to check all the places where the values are used
Object.keys(environment).forEach(key => {
if (!(this.window as any)[key]) {
(this.window as any)[key] = (environment as any)[key];
}
});
const apiRootUrl = LocalStorage.getItem<string>('api_root_url');
if (apiRootUrl) {
this.api_root_url = apiRootUrl;
}
this.store_public_access_get_data = undefined;
}
getDefaultLanguage(): string {
return (navigator || {}).language || 'en';
}
}
const app = new TwakeApp();
(window as any).TwakeApp = app;
export default app;
@@ -0,0 +1,102 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import Logger from 'app/features/global/framework/logger-service';
import Observable from 'app/deprecated/Observable/Observable';
import Api from 'app/features/global/framework/api-service';
import { getCompany } from 'app/features/companies/state/companies';
import WorkspaceService from 'app/deprecated/workspaces/workspaces.jsx';
export type ConsoleConfiguration = {
authority: string;
client_id: string;
max_unverified_days: number;
account_management_url: string;
company_subscription_url: string;
company_management_url: string;
collaborators_management_url: string;
};
export type InternalConfiguration = {
disable_account_creation: boolean;
disable_email_verification: boolean;
};
export type ServerInfoType = null | {
status: 'ready';
version: {
current: string;
minimal: {
web: string;
mobile: string;
};
};
branding?: {
logo: string;
};
auth: Array<string>;
configuration: {
branding: any;
help_url: string | null;
pricing_plan_url: string | null;
app_download_url: string | null;
mobile: {
mobile_redirect: string;
mobile_appstore: string;
mobile_googleplay: string;
};
accounts: {
type: 'console' | 'internal';
console?: ConsoleConfiguration;
internal?: InternalConfiguration;
};
};
};
class InitService extends Observable {
public server_infos: ServerInfoType = null;
public server_infos_loaded = false;
public app_ready = false;
private logger = Logger.getLogger('InitService');
getConsoleLink(
link:
| 'account_management_url'
| 'company_management_url'
| 'collaborators_management_url'
| 'company_subscription_url',
companyId?: string,
) {
companyId = companyId || WorkspaceService.currentGroupId;
const identity_provider_id =
getCompany(companyId || '')?.identity_provider_id || getCompany(companyId || '')?.id;
return (this.server_infos?.configuration?.accounts?.console?.[link] || '').replace(
/\{company_id\}/gm,
identity_provider_id,
);
}
async getServer() {
return await Api.get<ServerInfoType>('/internal/services/general/v1/server', undefined, false, {
disableJWTAuthentication: true,
});
}
async init() {
this.server_infos = await this.getServer();
if (this.server_infos?.status !== 'ready') {
this.logger.debug('Server is not ready', this.server_infos);
this.app_ready = false;
this.notify();
setTimeout(() => {
this.init();
}, 1000);
} else {
this.logger.debug('Server is ready', this.server_infos);
this.server_infos_loaded = true;
this.app_ready = true;
this.notify();
}
}
}
export default new InitService();
@@ -0,0 +1,94 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-types */
import Observable from 'app/deprecated/CollectionsV1/observable';
import DateTime from 'app/features/global/utils/datetime';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import Version from 'app/environment/version';
class LanguagesService extends Observable {
private i18nt: Function | null = null;
private language = '';
private default = 'en';
private available = [
'de',
'en',
'eo',
'es',
'eu',
'fi',
'fr',
'it',
'ja',
'nb_NO',
'ru',
'si',
'tr',
'vi',
'zh_Hans',
];
constructor() {
super();
this.setObservableName('i18n');
(window as any).languageService = this;
this.init();
}
async init() {
this.i18nt = await i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next) // passes i18n down to react-i18next
.init({
fallbackLng: this.default,
supportedLngs: this.available,
backend: { loadPath: '/locales/{{lng}}.json' + '?v=' + Version.version_detail },
interpolation: {
escapeValue: false, // react already safes from xss
},
});
}
async setLanguage(language: string) {
if (!language) language = this.default;
if (this.language === language) {
return;
}
this.language = language;
if (!language) {
language = this.default;
}
await i18n.changeLanguage(language);
DateTime.setCurrentLanguage(language);
this.notify();
}
t(route: string, parameters: any[] = [], fallback?: string) {
let replace: any = {};
try {
if (Array.isArray(parameters)) {
(parameters || []).forEach((r, i) => {
replace[`$${i + 1}`] = r;
});
} else if (typeof parameters === 'object') {
replace = parameters;
}
} catch (e) {
console.log(e);
}
if (this.i18nt) {
return this.i18nt(route, fallback, { replace });
}
return '';
}
}
const Languages = new LanguagesService();
export default Languages;
@@ -0,0 +1,869 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
import UserService from 'app/features/users/services/current-user-service';
import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js';
import PseudoMarkdownDictionary from 'components/twacode/pseudo-markdown-dictionary';
import anchorme from 'anchorme';
import emojis_original_service from 'emojione';
import Globals from 'app/features/global/services/globals-twake-app-service';
class PseudoMarkdownCompiler {
saved_messages: { [key: string]: any } = {};
alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
bullets: { [key: string]: any } = {
'• ': () => '• ',
'- ': () => '- ',
'([0-9]+)\\. ': (match: any) => {
const i = parseInt(match[1]);
return i + 1 + '. ';
},
'([a-z])\\. ': (match: any) => {
const i = this.alphabet.toLowerCase().indexOf(match[1]);
return this.alphabet.toLowerCase()[i + 1] + '. ';
},
'([A-Z])\\. ': (match: any) => {
const i = this.alphabet.indexOf(match[1]);
return this.alphabet[i + 1] + '. ';
},
};
pseudo_markdown: { [key: string]: any } = {
text_block_parent: {
name: 'text',
object: PseudoMarkdownDictionary.render_block.text_block_parent.object,
simple_object: (child: any) => child,
},
text: {
name: 'text',
object: PseudoMarkdownDictionary.render_block.text.object,
simple_object: (child: any) => child,
},
'\n': {
name: 'br',
end: '^',
allowed_chars: '.',
object: PseudoMarkdownDictionary.render_block.br.object,
simple_object: (child: any) => child,
},
'[': {
name: 'markdown_link',
allowed_char_before: '', //"(^| )",
allowed_chars: '.+?\\]\\([^ ]+',
disable_recursion: true,
end: '\\)',
object: PseudoMarkdownDictionary.render_block.markdown_link.object,
},
':': {
name: 'emoji',
allowed_char_before: '', //"(^| )",
allowed_chars: '[a-z_]+',
disable_recursion: true,
end: ':',
object: PseudoMarkdownDictionary.render_block.emoji.object,
},
'@': {
name: 'user',
allowed_char_before: '(^|\\B)',
allowed_chars: '[a-z_.-A-Z0-9:]+',
disable_recursion: true,
after_end: ' |$|[^a-zA-Z0-9]',
object: PseudoMarkdownDictionary.render_block.user.object,
simple_object: (_child: any, obj: any) => '@' + (obj.content || '').split(':')[0] + ' ',
text_transform: (PseudoMarkdownDictionary.render_block.user as any).text_transform,
},
'#': {
name: 'channel',
allowed_char_before: '(^|\\B)',
allowed_chars: '[a-z_.-A-Z0-9\u00C0-\u017F:]+',
disable_recursion: true,
after_end: ' |$[^a-zA-Z0-9]',
object: PseudoMarkdownDictionary.render_block.channel.object,
simple_object: (_child: any, obj: any) => '#' + (obj.content || '').split(':')[0] + ' ',
text_transform: (PseudoMarkdownDictionary.render_block.channel as any).text_transform,
},
'```': {
name: 'mcode',
end: '```',
after_end: '$|\n',
view: true,
allowed_chars: '(.|\n)',
disable_recursion: true,
object: PseudoMarkdownDictionary.render_block.mcode.object,
text_transform: (PseudoMarkdownDictionary.render_block.mcode as any).text_transform,
simple_object: (_child: any, obj: any) => {
const str = (obj.content || '').trim();
return str.length > 40 ? str.substr(0, 37) + '...' : str;
},
},
'`': {
name: 'icode',
end: '`',
allowed_char_before: '(^|\\B)',
allowed_chars: '.',
disable_recursion: true,
object: PseudoMarkdownDictionary.render_block.icode.object,
text_transform: (PseudoMarkdownDictionary.render_block.icode as any).text_transform,
},
__: {
name: 'underline',
end: '__',
allowed_char_before: '(^|\\B)',
allowed_chars: '.',
object: PseudoMarkdownDictionary.render_block.underline.object,
text_transform: (PseudoMarkdownDictionary.render_block.underline as any).text_transform,
},
'~~': {
name: 'strikethrough',
end: '~~',
allowed_char_before: '(^|\\B)',
allowed_chars: '.',
object: PseudoMarkdownDictionary.render_block.strikethrough.object,
text_transform: (PseudoMarkdownDictionary.render_block.strikethrough as any).text_transform,
},
'**': {
name: 'bold',
end: '\\*\\*',
allowed_char_before: '(^|\\B|.)',
allowed_chars: '.',
object: PseudoMarkdownDictionary.render_block.bold.object,
text_transform: PseudoMarkdownDictionary.render_block.bold.text_transform,
},
'*': {
name: 'italic',
end: '\\*',
allowed_char_before: '(^|\\B)',
allowed_chars: '.',
object: PseudoMarkdownDictionary.render_block.italic.object,
text_transform: (PseudoMarkdownDictionary.render_block.italic as any).text_transform,
},
_: {
name: 'italic',
end: '_',
allowed_char_before: '(^|\\B)',
allowed_chars: '.',
object: PseudoMarkdownDictionary.render_block.italic.object,
text_transform: (PseudoMarkdownDictionary.render_block.italic as any).text_transform,
},
'>>>': {
name: 'mquote',
allowed_char_before: '^',
view: true,
end: false,
allowed_chars: '(.|\n)',
object: PseudoMarkdownDictionary.render_block.mquote.object,
simple_object: () => '',
text_transform: (PseudoMarkdownDictionary.render_block.mquote as any).text_transform,
},
'>': {
name: 'quote',
view: true,
allowed_char_before: '^',
after_end: '$|\n',
object: PseudoMarkdownDictionary.render_block.quote.object,
simple_object: () => '',
text_transform: (PseudoMarkdownDictionary.render_block.quote as any).text_transform,
},
};
pseudo_markdown_types: { [key: string]: any } = {
nop: {
object: PseudoMarkdownDictionary.render_block.nop.object,
simple_object: (child: any) => child,
text_transform: (PseudoMarkdownDictionary.render_block.nop as any).text_transform,
},
url: {
object: PseudoMarkdownDictionary.render_block.url.object,
text_transform: (PseudoMarkdownDictionary.render_block.url as any).text_transform,
},
email: {
object: PseudoMarkdownDictionary.render_block.email.object,
text_transform: (PseudoMarkdownDictionary.render_block.email as any).text_transform,
},
system: {
apps_only: true,
object: PseudoMarkdownDictionary.render_block.system.object,
simple_object: (child: any) => child,
text_transform: (PseudoMarkdownDictionary.render_block.system as any).text_transform,
},
file: {
view: true,
object: PseudoMarkdownDictionary.render_block.file.object,
simple_object: () => '',
text_transform: (PseudoMarkdownDictionary.render_block.file as any).text_transform,
},
iframe: {
view: true,
apps_only: true,
object: PseudoMarkdownDictionary.render_block.iframe.object,
simple_object: () => '',
text_transform: (PseudoMarkdownDictionary.render_block.iframe as any).text_transform,
},
image: {
view: true,
apps_only: true,
object: PseudoMarkdownDictionary.render_block.image.object,
simple_object: () => '',
text_transform: (PseudoMarkdownDictionary.render_block.image as any).text_transform,
},
icon: {
apps_only: true,
object: PseudoMarkdownDictionary.render_block.icon.object,
text_transform: (PseudoMarkdownDictionary.render_block.icon as any).text_transform,
},
progress_bar: {
view: true,
apps_only: true,
object: PseudoMarkdownDictionary.render_block.progress_bar.object,
simple_object: (_child: any, object: any) => (object.progress || 0) + '%',
text_transform: (PseudoMarkdownDictionary.render_block.progress_bar as any).text_transform,
},
attachment: {
view: true,
apps_only: true,
object: PseudoMarkdownDictionary.render_block.attachment.object,
simple_object: (child: any) => child,
text_transform: (PseudoMarkdownDictionary.render_block.attachment as any).text_transform,
},
button: {
view: true,
apps_only: true,
object: PseudoMarkdownDictionary.render_block.button.object,
simple_object: (_child: any) => '',
text_transform: (PseudoMarkdownDictionary.render_block.button as any).text_transform,
},
copiable: {
view: true,
apps_only: true,
object: PseudoMarkdownDictionary.render_block.copiable.object,
simple_object: (_child: any) => '',
text_transform: (PseudoMarkdownDictionary.render_block.copiable as any).text_transform,
},
input: {
view: true,
apps_only: true,
object: PseudoMarkdownDictionary.render_block.input.object,
simple_object: (_child: any) => '',
text_transform: (PseudoMarkdownDictionary.render_block.input as any).text_transform,
},
select: {
view: true,
apps_only: true,
object: PseudoMarkdownDictionary.render_block.select.object,
simple_object: (_child: any) => '',
text_transform: (PseudoMarkdownDictionary.render_block.select as any).text_transform,
},
};
constructor() {
Object.keys(this.pseudo_markdown).forEach(id => {
const item = this.pseudo_markdown[id];
this.pseudo_markdown_types[item.name] = item;
});
(Globals.window as any).pmc = this;
}
compileStringToLinkObject(string: string) {
//Monkey hack for new markdown links, not the best place for this code
const link_found = anchorme(string.replace(/\[.*?\]\(.*?\)/gm, ''), {
list: true,
ips: false,
files: false,
});
let result: any[] = [];
if (link_found.length === 0) {
return [string];
} else {
const first_link = link_found[0];
const pos = string.indexOf(first_link.raw);
if (pos > 0) {
result = result.concat(this.compileStringToLinkObject(string.slice(0, pos)));
}
result.push({
type: first_link.reason,
content: first_link.raw,
});
if (pos + first_link.raw.length < string.length) {
result = result.concat(
this.compileStringToLinkObject(string.slice(pos + first_link.raw.length)),
);
}
}
return result;
}
transformChannelsUsers(str: string) {
//Users
str = (str || '').replace(
/(\B@)([a-z_.-A-Z0-9]*[a-z_A-Z0-9-])(( |$|([^a-zA-Z0-9]|$){2}))/g,
(full_match, match1, username, match3) => {
const values = username.split(':');
if (values.length === 1) {
if (username === 'me') {
username = UserService.getCurrentUser().username;
}
let user_id = Collections.get('users').findBy({ username: username })[0];
if (user_id && user_id.id) {
user_id = user_id.id;
return match1 + username + ':' + user_id + match3;
} else {
return full_match;
}
} else {
return full_match;
}
},
);
//Channels
str = str.replace(
/(\B#)([a-z_.-A-Z0-9\u00C0-\u017F]*[a-z_A-Z0-9-])(( |$|([^a-zA-Z0-9]|$){2}))/g,
(full_match, match1, channel, match3) => {
const values = channel.split(':');
if (values.length === 1) {
let channel_id = Collections.get('channels')
.findBy({})
.filter(
(item: { [key: string]: any }) =>
(item.name || '').toLocaleLowerCase().replace(/[^a-z0-9_\-.\u00C0-\u017F]/g, '') ===
channel,
)[0];
if (channel_id && channel_id.id) {
channel_id = channel_id.id;
return match1 + channel + ':' + channel_id + match3;
} else {
return full_match;
}
} else {
return full_match;
}
},
);
return str;
}
transformBackChannelsUsers(str: string) {
//Users
str = (str || '').replace(/\B(@[^\s]*?):.*?(( |$|[^a-zA-Z0-9-]))/g, '$1$2');
//Channels
str = str.replace(/\B(#[^\s]*?):.*?(( |$|[^a-zA-Z0-9-]))/g, '$1$2');
return str;
}
compileToJSON(str: string, recursive: any = false) {
if (!recursive) {
const result: any[] = [];
const original_str = str;
const _str = str.split('```'); //Priority to code
_str.forEach((str, i) => {
if (i % 2 === 0) {
if (str) {
str = this.transformChannelsUsers(str);
emojis_original_service.ascii = true;
str = emojis_original_service.shortnameToUnicode(str);
str = emojis_original_service.toShort(str);
const links = this.compileStringToLinkObject(str);
links.forEach(item => {
if (typeof item === 'string') {
result.push(this.compileToJSON(item, true));
} else {
result.push(item);
}
});
}
} else {
const object = {
start: '```',
content: str,
end: '\n```',
};
result.push(object);
//original_str += "```\n"+str+"\n```";
}
});
const all = {
original_str: original_str,
fallback_string: original_str.substr(0, 280) + (original_str.length > 280 ? '...' : ''),
prepared: result,
};
return all;
}
// eslint-disable-next-line no-redeclare
const original_str = str;
// eslint-disable-next-line no-redeclare
let result: any = [];
let min_index_of = -1;
let min_index_of_key: any = null;
let ret: any = [];
Object.keys(this.pseudo_markdown)
.sort((a, b) => b.length - a.length)
.forEach(starting_value => {
if (starting_value === 'text') {
return;
}
const allowed_char_before = this.pseudo_markdown[starting_value].allowed_char_before;
let tmp = str;
let offset = 0;
const indexes = [];
let did_match = -1;
do {
did_match = tmp.indexOf(starting_value);
let match_char_before =
!allowed_char_before ||
null !== tmp.slice(0, did_match).match(new RegExp(allowed_char_before + '$', 'gmi'));
match_char_before = match_char_before && tmp[did_match - 1] !== '\\';
tmp = tmp.slice(did_match + 1);
if (did_match >= 0 && match_char_before) indexes.push(did_match + offset);
offset = offset + did_match + 1;
} while (did_match >= 0);
if (indexes.length > 0) {
const mini = Math.min(...indexes);
if (min_index_of < 0 || mini < min_index_of) {
min_index_of = mini;
min_index_of_key = starting_value;
}
}
});
str = original_str;
if (min_index_of_key) {
let str_left = str.substr(0, min_index_of);
const char = min_index_of_key;
let str_right = str.substr(min_index_of + char.length);
//Seach end of element in str_right
let match: any = -1;
let add_to_value = '';
while (match < 0 || (match && match[1][match[1].length - 1] === '\\')) {
if (match && match !== -1) {
//It mean we found an antislashed element
add_to_value += match[0];
}
const countManaged =
(this.pseudo_markdown[char].allowed_chars || '').slice(-1) === '+' ||
(this.pseudo_markdown[char].allowed_chars || '').slice(-1) === '}';
const regex =
'^(' +
(this.pseudo_markdown[char].allowed_chars || '.') +
(countManaged ? '' : '*') +
(this.pseudo_markdown[char].end ? '?' : '') +
')' +
(this.pseudo_markdown[char].end ? '(' + this.pseudo_markdown[char].end + ')' : '');
match = str_right.substr(add_to_value.length).match(new RegExp(regex, ''));
}
let completion_end_char = '';
if (match) {
match[0] = add_to_value + match[0];
match[1] = add_to_value + match[1];
completion_end_char = this.pseudo_markdown[char].after_end ? match[3] || '' : '';
}
if (!match) {
str_left = str_left + char;
result = result.concat(str_left);
} else {
if (str_left) {
result = result.concat(str_left);
}
//Generate object
const object = {
start: char,
content: this.pseudo_markdown[char].disable_recursion
? match[1]
: this.compileToJSON(match[1], 1),
end: match[2],
};
result.push(object);
str_right = completion_end_char + str_right.substr(match[0].length);
}
result = result.concat(this.compileToJSON(str_right, 1));
ret = result;
} else {
if (original_str) {
ret = original_str;
} else {
ret = [];
}
}
return ret;
}
compileToHTML(
json: any,
is_app: any = false,
event_container: any = undefined,
text_transform: any = undefined,
) {
if (!text_transform) {
text_transform = {};
}
if (!json) {
return this.pseudo_markdown['text'].object('');
}
if (json.formatted || json.prepared) json = json.formatted || json.prepared;
if (typeof json === 'string') {
json = [json];
}
if (json.type || json.start) {
json = [json];
}
let el = null;
let child_contain_view = false;
const result: any = [];
try {
json.forEach((item: any) => {
if (typeof item === 'string') {
result.push(
this.pseudo_markdown['text'].object(item, is_app, event_container, text_transform),
);
} else if (Array.isArray(item)) {
el = this.compileToHTML(item, is_app, event_container, text_transform);
child_contain_view = child_contain_view || el.child_contain_view;
result.push(el);
} else {
let type = this.pseudo_markdown[item.start];
if (item.type === 'compile' && is_app && typeof item.content === 'string') {
el = this.compileToHTML(
this.compileToJSON(item.content),
is_app,
event_container,
text_transform,
);
child_contain_view = child_contain_view || el.child_contain_view;
result.push(el);
} else {
if (item.type) {
type = this.pseudo_markdown_types[item.type];
}
if (type) {
if (!type.apps_only || is_app) {
//If text transform do it
const old_text_transform = JSON.parse(JSON.stringify(text_transform));
text_transform = JSON.parse(JSON.stringify(text_transform));
if (type.text_transform) {
Object.keys(type.text_transform).forEach(key => {
text_transform[key] = type.text_transform[key];
});
}
el = this.compileToHTML(
item.content || '',
is_app,
event_container,
text_transform,
);
if (type.view) {
child_contain_view = true;
}
child_contain_view = child_contain_view || el.child_contain_view;
result.push(
type.object(el, item, event_container, text_transform, child_contain_view),
);
text_transform = old_text_transform;
}
}
}
}
});
} catch (e) {
return this.pseudo_markdown['text'].object('An error occured while showing this message.');
}
result.child_contain_view = child_contain_view;
result.forEach((item: any) => {
if (!item.child_contain_view && child_contain_view) {
item = this.pseudo_markdown['text_block_parent'].object(item);
}
});
return result;
}
compileToSimpleHTML(
json: any,
is_app = false,
text_transform: any = undefined,
result_analysis: any = undefined,
) {
if (!text_transform) {
text_transform = {};
}
if (!json) {
return this.pseudo_markdown['text'].object('');
}
if (!result_analysis) {
result_analysis = {
has_string: false,
};
}
if (json.formatted || json.prepared) json = json.formatted || json.prepared;
if (typeof json === 'string') {
json = [json];
}
if (json.type || json.start) {
json = [json];
}
const result: any = [];
try {
json.forEach((item: any) => {
if (typeof item === 'string') {
result_analysis.has_string = true;
result.push(
this.pseudo_markdown['text'].object(item, is_app, {}, text_transform, result_analysis),
);
} else if (Array.isArray(item)) {
result.push(this.compileToSimpleHTML(item, is_app, text_transform, result_analysis));
} else {
let type = this.pseudo_markdown[item.start];
if (item.type) {
type = this.pseudo_markdown_types[item.type];
}
if (type) {
if (item.type === 'compile' && is_app && typeof item.content === 'string') {
result.push(
this.compileToSimpleHTML(
this.compileToJSON(item.content),
is_app,
text_transform,
result_analysis,
),
);
} else {
if (item.type) {
type = this.pseudo_markdown_types[item.type];
}
if (type) {
if (!type.apps_only || is_app) {
//If text transform do it
const old_text_transform = JSON.parse(JSON.stringify(text_transform));
text_transform = JSON.parse(JSON.stringify(text_transform));
if (type.text_transform) {
Object.keys(type.text_transform).forEach(key => {
text_transform[key] = type.text_transform[key];
});
}
result.push(
(type.simple_object || type.object)(
this.compileToSimpleHTML(
item.content || '',
is_app,
text_transform,
result_analysis,
),
item,
{},
),
);
text_transform = old_text_transform;
}
}
}
}
}
});
} catch (e) {
console.log(e);
return this.pseudo_markdown['text'].object('An error occured while showing this message.');
}
if (!result_analysis.has_string) {
return this.pseudo_markdown['text'].object('No text content to display.');
}
return result;
}
compileToText(json: any) {
if (!json) {
return '';
}
if (typeof json === 'string') {
json = [json];
}
if (json.original_str) {
return this.transformBackChannelsUsers(json.original_str);
}
if (json.type || json.start) {
json = [json];
}
let result = '';
try {
json.forEach((item: any) => {
if (typeof item === 'string') {
let tmp = item;
Object.keys(this.pseudo_markdown).forEach(starting_value => {
let starting_value_reg = starting_value;
const allowed_chars = this.pseudo_markdown[starting_value].allowed_chars;
if (starting_value === '*') {
starting_value_reg = '\\*';
}
if (allowed_chars) {
const reg = new RegExp(starting_value_reg, 'gm');
tmp = tmp.replace(reg + '(' + allowed_chars + ')', '\\' + starting_value + '$1');
} else {
// eslint-disable-next-line no-redeclare
const reg = new RegExp(starting_value_reg, 'gm');
tmp = tmp.replace(reg, '\\' + starting_value);
}
});
result += tmp;
} else if (Array.isArray(item)) {
result += this.compileToText(item);
} else {
result += item.start || '';
result += this.compileToText(item.content);
result += item.end || '';
}
});
} catch (e) {
return '';
}
return this.transformBackChannelsUsers(result);
}
insertAtCursor(myField: any, myValue: any) {
//IE support
if ((document as any).selection) {
myField.focus();
const sel = (document as any).selection.createRange();
sel.text = myValue;
}
//MOZILLA and others
else if (myField.selectionStart || myField.selectionStart === '0') {
const startPos = myField.selectionStart;
const endPos = myField.selectionEnd;
myField.value =
myField.value.substring(0, startPos) +
myValue +
myField.value.substring(endPos, myField.value.length);
} else {
myField.value += myValue;
}
}
//Call this function after each line break
autoCompleteBulletList(input: any, didEnter: any) {
const getCursorPos = (input: any) => {
if ('selectionStart' in input && document.activeElement === input) {
return {
start: input.selectionStart,
end: input.selectionEnd,
};
} else if (input.createTextRange) {
const sel = (document as any).selection.createRange();
if (sel.parentElement() === input) {
const rng = input.createTextRange();
rng.moveToBookmark(sel.getBookmark());
let len = 0;
for (len; rng.compareEndPoints('EndToStart', rng) > 0; rng.moveEnd('character', -1)) {
len++;
rng.setEndPoint('StartToStart', input.createTextRange());
}
const pos = { start: 0, end: len };
for (pos; rng.compareEndPoints('EndToStart', rng) > 0; rng.moveEnd('character', -1)) {
pos.start++;
pos.end++;
}
return pos;
}
}
return -1;
};
const setCaretPosition = (ctrl: any, pos: any) => {
// Modern browsers
if (ctrl.setSelectionRange) {
ctrl.focus();
ctrl.setSelectionRange(pos, pos);
// IE8 and below
} else if (ctrl.createTextRange) {
const range = ctrl.createTextRange();
range.collapse(true);
range.moveEnd('character', pos);
range.moveStart('character', pos);
range.select();
}
};
if (didEnter) {
//@ts-ignore
const cursor_position = (getCursorPos(input) || {}).start;
if (cursor_position === false || cursor_position < 0) {
return;
}
const value = input.value;
const str_before = value.substr(0, cursor_position);
const str_after = value.substr(cursor_position);
const src_line_before = str_before.split('\n').pop();
let addon = '';
let to_remove = 0;
Object.keys(this.bullets).forEach(regex => {
const match = src_line_before.match(new RegExp('^' + regex, ''));
if (match) {
if (src_line_before.length > match[0].length) {
addon = this.bullets[regex](match);
} else {
to_remove = src_line_before.length;
}
}
});
if (to_remove > 0) {
input.value = str_before.substr(0, str_before.length - to_remove) + str_after;
setCaretPosition(input, cursor_position - to_remove);
} else {
input.value = str_before + '\n' + addon + str_after;
setCaretPosition(input, cursor_position + addon.length + 1);
}
return input.value;
}
}
}
const service = new PseudoMarkdownCompiler();
export default service;
@@ -0,0 +1,194 @@
import { ChannelType } from 'app/features/channels/types/channel';
import { UserType } from 'app/features/users/types/user';
import Strings from 'app/features/global/utils/strings';
import UsersService from 'app/features/users/services/current-user-service';
import Workspaces from 'app/deprecated/workspaces/workspaces.jsx';
import RouterServices from 'app/features/router/services/router-service';
import { getUserParts } from 'app/components/member/user-parts';
import Observable from 'app/deprecated/Observable/Observable';
import UserAPIClient from '../../users/api/user-api-client';
import ChannelsReachableAPIClient from '../../channels/api/channels-reachable-api-client';
import ChannelsMineAPIClient from '../../channels/api/channels-mine-api-client';
export type GenericChannel = {
type: 'user' | 'direct' | 'workspace';
sortString: string;
filterString: string;
lastActivity?: number;
resource: UserType | ChannelType;
};
class SearchListManager extends Observable {
private workspaceChannels: GenericChannel[];
private directChannels: GenericChannel[];
private users: GenericChannel[];
public list: GenericChannel[];
constructor() {
super();
this.workspaceChannels = [];
this.directChannels = [];
this.users = [];
this.list = [];
}
public async searchAll(
search: string,
opts?: {
onlyChannel?: boolean;
userListState?: UserType[];
},
): Promise<void> {
const { workspaceId, companyId } = RouterServices.getStateFromRoute();
// Reachable
let channels: ChannelType[] = [];
// Direct Channels
let directChannels: ChannelType[] = [];
// Mine
let mineWorkspaceChannels: ChannelType[] = [];
const usersSearched: UserType[] = opts?.userListState ? opts.userListState : [];
if (companyId && workspaceId) {
channels = await ChannelsReachableAPIClient.get(companyId, workspaceId);
directChannels = await ChannelsMineAPIClient.get({ companyId, workspaceId: 'direct' });
mineWorkspaceChannels = await ChannelsMineAPIClient.get({ companyId, workspaceId });
}
// Filters
this.workspaceChannels = this.filterWorkspaceChannels({
channels,
mineWorkspaceChannels,
});
this.directChannels = opts?.onlyChannel
? []
: this.filterDirectChannels({ channels: directChannels });
this.users = this.filterUsers({ users: usersSearched });
// Concat list
this.list = [...this.workspaceChannels, ...this.directChannels, ...this.users];
this.removeDuplicate();
this.list = this.list
.filter(({ filterString }) => {
return filterString.toUpperCase().indexOf(search.toUpperCase()) > -1;
})
.sort((a, b) => (b.lastActivity || 0) - (a.lastActivity || 0));
this.notify();
}
private async searchUsers(text: string) {
return UserAPIClient.search<UserType>(Strings.removeAccents(text), {
scope: 'company',
companyId: Workspaces.currentGroupId,
});
}
private filterWorkspaceChannels({
channels,
mineWorkspaceChannels,
}: {
channels: ChannelType[];
mineWorkspaceChannels: ChannelType[];
}) {
const workspaceChannels: GenericChannel[] = channels.map(channel => {
return {
sortString: channel.name || '',
filterString: channel.name || '',
type: 'workspace',
lastActivity: channel.last_activity || 0,
resource: channel,
};
});
return workspaceChannels.filter(channel => {
if (channel.type === 'workspace') {
const resource = channel.resource as ChannelType;
const isNotAbleToSeeChannel =
!this.isChannelMember(mineWorkspaceChannels, resource) &&
resource.visibility === 'private';
return !isNotAbleToSeeChannel;
}
return undefined;
});
}
private filterDirectChannels({ channels }: { channels: ChannelType[] }) {
const directChannels: GenericChannel[] = channels.map((channel: ChannelType) => {
const { name } = getUserParts({
usersIds: channel.members || [],
});
return {
sortString: name,
filterString: name,
type: 'direct',
lastActivity: channel.last_activity || 0,
resource: channel,
};
});
return directChannels;
}
private filterUsers({ users }: { users: UserType[] }) {
const usersSearched: GenericChannel[] = users.map(user => {
return (
user && {
sortString: UsersService.getFullName(user),
filterString: UsersService.getFullName(user),
type: 'user',
resource: user,
}
);
});
return usersSearched;
}
/**
* Remove duplicates between direct channels and users
*/
private removeDuplicate() {
const existingUsersIdAsDirectChannels = this.list
.filter(
userOrChannel =>
userOrChannel.type === 'direct' &&
((userOrChannel.resource as ChannelType).members?.length || 0) <= 2,
)
.map(userOrChannel => {
const channel = userOrChannel.resource as ChannelType;
if (channel.members?.length === 1) {
return channel.members[0];
}
if (channel.members?.length === 2) {
const otherUserId = channel.members.filter(
id => id !== UsersService.getCurrentUserId(),
)[0];
return otherUserId;
}
return undefined;
});
this.list = this.list.filter(userOrChannel => {
if (userOrChannel.type === 'user') {
const user = userOrChannel.resource as UserType;
return !existingUsersIdAsDirectChannels.includes(user.id);
}
return true;
});
}
private isChannelMember(mine: ChannelType[], resource: ChannelType) {
return mine.some(channel => resource.id === channel.id && channel.user_member?.user_id);
}
}
const SearchListManagerService = new SearchListManager();
export default SearchListManagerService;
@@ -0,0 +1,25 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Shortcuts } from 'shortcuts';
const shortcuts = new Shortcuts({
target: document,
});
export type ShortcutType = {
shortcut: string;
handler?: (event: any) => any;
};
export const defaultShortcutsMap = {
SEARCH_CHANNEL: 'CmdOrCtrl+K',
};
export const addShortcut = (shortcut: ShortcutType | ShortcutType[]) => {
return shortcuts.add(shortcut);
};
export const removeShortcut = (shortcut: ShortcutType | ShortcutType[]) => {
return shortcuts.remove(shortcut);
};
export default { addShortcut, removeShortcut };
@@ -0,0 +1,3 @@
import { message } from 'antd';
export { message as ToasterService };
@@ -0,0 +1,44 @@
import Globals from './globals-twake-app-service';
import Logger from '../framework/logger-service';
import JWT from '../../auth/jwt-storage-service';
import WebSocketService, { WebSocketOptions } from './websocket-service';
class WebSocketFactory {
private logger: Logger.Logger;
private instance!: WebSocketService;
constructor() {
this.logger = Logger.getLogger('WebSocketFactory');
}
get(): WebSocketService {
if (!this.instance) {
this.instance = new WebSocketService(this.getOptions());
}
return this.instance;
}
private getOptions(): WebSocketOptions {
return {
url: Globals.environment.websocket_url,
authenticateHandler: async () => {
let token = JWT.getJWT();
if (JWT.isAccessExpired()) {
try {
token = (await JWT.renew()).value;
} catch (err) {
this.logger.error('Can not get a new JWT token for WS collection', err);
}
}
return {
token,
};
},
};
}
}
export default new WebSocketFactory();
@@ -0,0 +1,261 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import io from 'socket.io-client';
import { EventEmitter } from 'events';
import Logger from 'app/features/global/framework/logger-service';
import { WebsocketEvents, WebSocketListener, WebsocketRoomActions } from '../types/websocket-types';
import { Maybe } from 'app/features/global/types/global-types';
export type WebSocketOptions = {
url: string;
authenticateHandler: () => Promise<any>;
};
const CONNECT_TIMEOUT = 30000;
class WebSocketService extends EventEmitter {
private logger: Logger.Logger;
private lastConnection = 0;
private wsListeners: {
[path: string]: { [tag: string]: WebSocketListener };
} = {};
private socket: SocketIOClient.Socket | null = null;
private connectTimeout?: ReturnType<typeof setTimeout>;
constructor(private options: WebSocketOptions) {
super();
this.logger = Logger.getLogger('WebsocketService');
this.addEventListeners();
}
private addEventListeners(): void {
const reconnectWhenNeeded = () => {
if (!this.isConnected()) {
this.connect();
}
};
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
reconnectWhenNeeded();
}
});
document.addEventListener('focus', () => {
reconnectWhenNeeded();
});
setInterval(() => {
if (new Date().getTime() - this.lastConnection > CONNECT_TIMEOUT) {
this.lastConnection = new Date().getTime();
reconnectWhenNeeded();
}
}, CONNECT_TIMEOUT);
}
isConnected(): boolean {
return this.socket?.connected || false;
}
async connect(): Promise<boolean> {
let connected: (value: boolean) => void;
const promise = new Promise<boolean>(resolve => {
connected = resolve;
});
if (!this.options.url) {
this.logger.info('Skipping connect to empty URL');
return false;
}
this.logger.debug('Connecting to websocket', this.options.url);
if (this.socket) {
if (this.socket.connected) {
this.logger.debug('Already connected to', this.options.url);
return false;
} else {
this.socket?.close();
this.socket = null;
}
}
if (!this.options.authenticateHandler) {
this.logger.error('Cannot connect without an authentication method');
return false;
}
this.socket = io.connect(this.options.url || '', {
path: '/socket',
reconnectionDelayMax: 10000,
reconnectionDelay: 2000,
});
this.socket.on('disconnect', () => {
this.logger.debug('Disconnected from websocket, socket.io will reconnect');
});
this.socket.on('connect', async () => {
this.logger.debug('Connected to websocket, authenticating...');
if (this.connectTimeout) {
clearTimeout(this.connectTimeout);
}
this.socket
?.emit('authenticate', (await this.options.authenticateHandler()) || {})
.on('authenticated', () => {
this.logger.debug('Authenticated');
this.rejoinAll(true);
connected(true);
this.emit(WebsocketEvents.Connected, { url: this.options.url });
})
.on('unauthorized', (err: any) => {
this.logger.warn('Websocket is not authorized', err);
this.socket?.close();
this.socket = null;
//Retry and expect new jwt
this.connectTimeout = setTimeout(async () => {
const isConnected = await this.connect();
if (isConnected) {
connected(true);
}
}, 1000);
});
this.socket?.on(WebsocketEvents.JoinSuccess, (event: any) => {
this.logger.debug('Websocket join success', event.name);
event.name && this.notify(event.name, WebsocketEvents.JoinSuccess, event);
});
this.socket?.on(WebsocketEvents.JoinError, (event: any) => {
this.logger.debug('Websocket join error', event.name);
event.name && this.notify(event.name, WebsocketEvents.JoinError, event);
});
this.socket?.on(WebsocketEvents.Resource, (event: any) => {
this.logger.debug('Received resource on room', event.room, event);
event.room && this.notify(event.room, WebsocketEvents.Resource, event);
});
this.socket?.on(WebsocketEvents.Event, (event: any) => {
this.logger.debug('New Websocket event', event.name);
event.name && this.notify(event.name, WebsocketEvents.Event, event);
});
this.socket?.on('disconnect', () => {
this.emit(WebsocketEvents.Disconnected, { url: this.options.url });
});
});
return promise;
}
private rejoinAll(newlyConnected = false) {
Object.keys(this.wsListeners).forEach(key => {
Object.keys(this.wsListeners[key]).forEach(tag => {
if (this.wsListeners[key][tag]) {
newlyConnected && this.wsListeners[key][tag].callback(WebsocketEvents.Connected, {});
this.join(
key,
this.wsListeners[key][tag].token,
tag,
this.wsListeners[key][tag].callback,
);
}
});
});
}
private notify(path: string, type: WebsocketEvents, event: any) {
if (this.wsListeners[path]) {
Object.values(this.wsListeners[path]).forEach(callback => callback.callback?.(type, event));
}
}
public getSocket(): SocketIOClient.Socket | null {
return this.socket;
}
/**
* Join a room. callback will be called when a message is received on the given room
*
* @param path
* @param tag
* @param callback
*/
public join(
path: string,
token: string,
tag: string,
callback: (type: WebsocketEvents, event: any) => void,
) {
const name = path.replace(/\/$/, '');
this.logger.debug(`Join room with name='${name}' and tag='${tag}'`);
if (this.socket) {
this.socket.emit(WebsocketRoomActions.Join, { name, token });
}
this.wsListeners[name] = this.wsListeners[name] || {};
this.wsListeners[name][tag] = { token, callback };
}
/**
* Leave a room
*
* @param path
* @param tag
*/
public leave(path: string, tag: string) {
const name = path.replace(/\/$/, '');
this.wsListeners[name] = this.wsListeners[name] || {};
delete this.wsListeners[name][tag];
if (Object.keys(this.wsListeners[name]).length === 0) {
if (this.socket) {
this.logger.debug(`Leave room with name='${name}' and tag='${tag}'`);
this.socket.emit(WebsocketRoomActions.Leave, { name });
}
delete this.wsListeners[name];
}
}
/**
* Send data as {name: path, data} in the realtime:event topic.
*
* @param path
* @param data
*/
public send<T>(path: string, token: string, data: T): void {
const name = path.replace(/\/$/, '');
this.logger.debug(`Send realtime:event with name='${name}'`);
if (this.socket) {
this.socket.emit('realtime:event', { name, data, token });
}
}
public async get<Request, Response>(
route: string,
request: Request,
callback?: (response: Response) => void,
): Promise<Maybe<Response>> {
this.logger.debug(`Get ${route}`);
return new Promise<Maybe<Response>>(resolve => {
if (this.socket) {
this.socket.emit(route, { data: request }, (response: { data: Response }) => {
this.logger.trace('Got a socket ack');
const result = response.data;
callback && callback(result);
resolve(result);
});
return;
}
});
}
}
export default WebSocketService;
@@ -0,0 +1,11 @@
import { atomFamily } from 'recoil';
export const LoadingState = atomFamily<boolean, string>({
key: 'LoadingState',
default: () => false,
});
export const LoadingStateInitTrue = atomFamily<boolean, string>({
key: 'LoadingStateInitTrue',
default: () => true,
});
@@ -0,0 +1,6 @@
import { atom } from 'recoil';
export const useWebState = atom<boolean>({
key: 'useWebState',
default: true,
});
@@ -0,0 +1,29 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export type ConfigurationType = {
auth?: {
internal?: {
disable_account_creation?: any | null;
disable_email_verification?: any | null;
use?: boolean;
};
console?: {
max_unverified_days: number;
account_management_url: string;
collaborators_management_url: string;
company_subscription_url: string;
company_management_url: string;
use: boolean;
};
};
auth_mode?: string[];
elastic_search_available?: boolean;
help_url?: string;
ready?: boolean;
version?: {
current?: string;
minimal?: {
web?: string;
mobile?: string;
};
};
};
@@ -0,0 +1,3 @@
export type Maybe<T> = T | undefined;
export type Timeout = ReturnType<typeof setTimeout>;
@@ -0,0 +1,41 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export type WebSocketResource = {
room: string;
name?: string;
};
export type RealtimeResources<T> = {
resources: T[];
websockets: WebSocketResource[];
};
export type RealtimeApplicationEventAction = 'configure' | 'close_configure';
export type RealtimeEventAction = 'saved' | 'updated' | 'deleted' | 'event';
export type RealtimeBaseAction = RealtimeEventAction | RealtimeApplicationEventAction;
export interface RealtimeBaseEvent {
action: RealtimeEventAction;
data?: any;
}
export interface RealtimeResourceEvent<T> extends RealtimeBaseEvent {
resource: T;
type?: string;
}
export interface RealtimeEvent<T, U> extends RealtimeBaseEvent {
room: string;
path: string;
type: U;
resource: T;
}
export interface RealtimeApplicationEvent {
action: 'configure' | 'close_configure';
connection_id: string;
application: unknown;
form: unknown;
hidden_data: unknown;
configurator_id: string;
}
@@ -0,0 +1,28 @@
import WebSocketFactory from '../services/websocket-factory-service';
export type WebsocketRoom = {
room: string;
token: string;
};
export enum WebsocketRoomActions {
Join = 'realtime:join',
Leave = 'realtime:leave',
}
export enum WebsocketEvents {
Connected = 'connected',
Connecting = 'connecting',
Disconnected = 'disconnected',
JoinSuccess = 'realtime:join:success',
JoinError = 'realtime:join:error',
Resource = 'realtime:resource',
Event = 'realtime:event',
}
export type WebSocketListener = {
token: string;
callback: <T>(type: WebsocketEvents, event: T) => void;
};
export default WebSocketFactory;
@@ -0,0 +1,22 @@
import Observable from 'app/deprecated/CollectionsV1/observable.js';
class Autocomplete extends Observable {
constructor() {
super();
this.observableName = 'autocompleteService';
this.isOpen = false;
}
open() {
this.isOpen = true;
this.notify();
}
close() {
this.isOpen = false;
this.notify();
}
}
const autocompleteService = new Autocomplete();
export default autocompleteService;
@@ -0,0 +1,13 @@
export async function copyToClipboard(url: string): Promise<void> {
try {
await navigator.clipboard.writeText(url);
} catch (err) {
const el = document.createElement('textarea');
el.value = url;
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
}
}
@@ -0,0 +1,39 @@
import $ from 'jquery';
class DroppableManager {
constructor() {
this.drop = {};
this.draggingData = {};
this.dragging = false;
}
over(key, callback, event) {
this.drop[key] = {
callback: callback,
element: event.target,
};
}
out(key) {
this.drop[key] = undefined;
delete this.drop[key];
}
up() {
var that = this;
if (!that.draggingData.type || !that.draggingData.data || that.draggingData.data.length == 0) {
return;
}
if (!this.dragging) {
return;
}
this.drop.forEach(el => {
if (el && el.callback) {
el.callback(that.draggingData);
}
});
}
}
const instanceDroppableManager = new DroppableManager();
export default instanceDroppableManager;
+130
View File
@@ -0,0 +1,130 @@
export default class Numbers {
static unid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}
static humanFileSize(bytes: number, si: boolean) {
const thresh = si ? 1000 : 1024;
if (Math.abs(bytes) < thresh) {
return bytes + ' B';
}
const units = si
? ['kb', 'mb', 'gb', 'gb', 'pb', 'eb', 'zb', 'yb']
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
let u = -1;
do {
bytes /= thresh;
++u;
} while (Math.abs(bytes) >= thresh && u < units.length - 1);
return `${bytes.toFixed(1)}${units[u]}`;
}
static hexToBase64(str: string) {
return btoa(
String.fromCharCode.apply(
null,
str
.replace(/\r|\n/g, '')
.replace(/([\da-fA-F]{2}) ?/g, '0x$1 ')
.replace(/ +$/, '')
.split(' ')
.map(s => parseInt(s)),
),
);
}
static timeuuidToDate(time_str: string) {
if (!time_str) {
return 0;
}
const uuid_arr = time_str.split('-');
// eslint-disable-next-line no-redeclare
time_str = [uuid_arr[2].substring(1), uuid_arr[1], uuid_arr[0]].join('');
return parseInt(time_str, 16);
}
static compareTimeuuid(a?: string, b?: string) {
return Numbers.timeuuidToDate(a || '') - Numbers.timeuuidToDate(b || '');
}
static minTimeuuid(a?: string, b?: string) {
if (!a) return b || '';
if (!b) return a || '';
return (Numbers.compareTimeuuid(a, b) > 0 ? b : a) || '';
}
static maxTimeuuid(a?: string, b?: string) {
if (!a) return b || '';
if (!b) return a || '';
return (Numbers.compareTimeuuid(a, b) > 0 ? a : b) || '';
}
static convertBases(src: string, srcAlphabet: string, dstAlphabet: string) {
// orion elenzil
// 20080905
const getValueOfDigit = function (digit: string, alphabet: string) {
const pos = alphabet.indexOf(digit);
return pos;
};
const srcBase = srcAlphabet.length;
const dstBase = dstAlphabet.length;
let val = 0;
let mlt = 1;
while (src.length > 0) {
const digit = src.charAt(src.length - 1);
val += mlt * getValueOfDigit(digit, srcAlphabet);
src = src.substring(0, src.length - 1);
mlt *= srcBase;
}
let wetint = val;
let ret = '';
while (wetint >= dstBase) {
const digitVal = wetint % dstBase;
// eslint-disable-next-line no-redeclare
const digit = dstAlphabet.charAt(digitVal);
ret = digit + ret;
wetint /= dstBase;
}
// eslint-disable-next-line no-redeclare
const digit = dstAlphabet.charAt(wetint);
ret = digit + ret;
return ret;
}
}
export const formatTime = (
time: number | string,
locale?: string,
options: { keepTime?: boolean; keepSeconds?: boolean; keepDate?: boolean } = {
keepTime: true,
}
) => {
time = new Date(time).getTime();
locale = locale || navigator.language;
const now = Date.now();
const year = new Date(time).getFullYear();
const nowYear = new Date(now).getFullYear();
const day = 24 * 60 * 60 * 1000;
return new Intl.DateTimeFormat(locale, {
year: nowYear !== year || options?.keepDate ? "numeric" : undefined,
month: now - time >= day || options?.keepDate ? "short" : undefined,
day: now - time >= day || options?.keepDate ? "numeric" : undefined,
hour: now - time < day || options?.keepTime ? "numeric" : undefined,
minute: now - time < day || options?.keepTime ? "numeric" : undefined,
second: options?.keepSeconds ? "numeric" : undefined,
}).format(new Date(time));
};
@@ -0,0 +1,22 @@
import environment from 'app/environment/environment';
import Globals from 'app/features/global/services/globals-twake-app-service';
export function addApiUrlIfNeeded(url: string, asCssUrl?: boolean): string {
function _wrap(url: string): string {
return asCssUrl ? `url('${url}')` : url;
}
if (!url) {
return _wrap(url);
}
if (/^http/.test(url)) {
return _wrap(url);
}
return _wrap(`${Globals.api_root_url}/${url.replace(/^\//, '').replace(/\/+/g, '/')}`);
}
export function getAsFrontUrl(path: string): string {
return `${environment.front_root_url || ''}${path}`;
}
@@ -0,0 +1,211 @@
class AceModeList {
constructor() {
var supportedModes = {
ABAP: ['abap'],
ABC: ['abc'],
ActionScript: ['as'],
ADA: ['ada|adb'],
Apache_Conf: ['^htaccess|^htgroups|^htpasswd|^conf|htaccess|htgroups|htpasswd'],
AsciiDoc: ['asciidoc|adoc'],
Assembly_x86: ['asm|a'],
AutoHotKey: ['ahk'],
BatchFile: ['bat|cmd'],
Bro: ['bro'],
C_Cpp: ['cpp|c|cc|cxx|h|hh|hpp|ino'],
C9Search: ['c9search_results'],
Cirru: ['cirru|cr'],
Clojure: ['clj|cljs'],
Cobol: ['CBL|COB'],
coffee: ['coffee|cf|cson|^Cakefile'],
ColdFusion: ['cfm'],
CSharp: ['cs'],
Csound_Document: ['csd'],
Csound_Orchestra: ['orc'],
Csound_Score: ['sco'],
CSS: ['css'],
Curly: ['curly'],
D: ['d|di'],
Dart: ['dart'],
Diff: ['diff|patch'],
Dockerfile: ['^Dockerfile'],
Dot: ['dot'],
Drools: ['drl'],
Edifact: ['edi'],
Eiffel: ['e|ge'],
EJS: ['ejs'],
Elixir: ['ex|exs'],
Elm: ['elm'],
Erlang: ['erl|hrl'],
Forth: ['frt|fs|ldr|fth|4th'],
Fortran: ['f|f90'],
FTL: ['ftl'],
Gcode: ['gcode'],
Gherkin: ['feature'],
Gitignore: ['^.gitignore'],
Glsl: ['glsl|frag|vert'],
Gobstones: ['gbs'],
golang: ['go'],
GraphQLSchema: ['gql'],
Groovy: ['groovy'],
HAML: ['haml'],
Handlebars: ['hbs|handlebars|tpl|mustache'],
Haskell: ['hs'],
Haskell_Cabal: ['cabal'],
haXe: ['hx'],
Hjson: ['hjson'],
HTML: ['html|htm|xhtml|vue|we|wpy'],
HTML_Elixir: ['eex|html.eex'],
HTML_Ruby: ['erb|rhtml|html.erb'],
INI: ['ini|conf|cfg|prefs'],
Io: ['io'],
Jack: ['jack'],
Jade: ['jade|pug'],
Java: ['java'],
JavaScript: ['js|jsm|jsx'],
JSON: ['json'],
JSONiq: ['jq'],
JSP: ['jsp'],
JSSM: ['jssm|jssm_state'],
JSX: ['jsx'],
Julia: ['jl'],
Kotlin: ['kt|kts'],
LaTeX: ['tex|latex|ltx|bib'],
LESS: ['less'],
Liquid: ['liquid'],
Lisp: ['lisp'],
LiveScript: ['ls'],
LogiQL: ['logic|lql'],
LSL: ['lsl'],
Lua: ['lua'],
LuaPage: ['lp'],
Lucene: ['lucene'],
Makefile: ['^Makefile|^GNUmakefile|^makefile|^OCamlMakefile|make'],
Markdown: ['md|markdown'],
Mask: ['mask'],
MATLAB: ['matlab'],
Maze: ['mz'],
MEL: ['mel'],
MIXAL: ['mixal'],
MUSHCode: ['mc|mush'],
MySQL: ['mysql'],
Nix: ['nix'],
NSIS: ['nsi|nsh'],
ObjectiveC: ['m|mm'],
OCaml: ['ml|mli'],
Pascal: ['pas|p'],
Perl: ['pl|pm'],
pgSQL: ['pgsql'],
PHP: ['php|phtml|shtml|php3|php4|php5|phps|phpt|aw|ctp|module'],
Pig: ['pig'],
Powershell: ['ps1'],
Praat: ['praat|praatscript|psc|proc'],
Prolog: ['plg|prolog'],
Properties: ['properties'],
Protobuf: ['proto'],
Python: ['py'],
R: ['r'],
Razor: ['cshtml|asp'],
RDoc: ['Rd'],
Red: ['red|reds'],
RHTML: ['Rhtml'],
RST: ['rst'],
Ruby: ['rb|ru|gemspec|rake|^Guardfile|^Rakefile|^Gemfile'],
Rust: ['rs'],
SASS: ['sass'],
SCAD: ['scad'],
Scala: ['scala'],
Scheme: ['scm|sm|rkt|oak|scheme'],
SCSS: ['scss'],
SH: ['sh|bash|^.bashrc'],
SJS: ['sjs'],
Smarty: ['smarty|tpl'],
snippets: ['snippets'],
Soy_Template: ['soy'],
Space: ['space'],
SQL: ['sql'],
SQLServer: ['sqlserver'],
Stylus: ['styl|stylus'],
SVG: ['svg'],
Swift: ['swift'],
Tcl: ['tcl'],
Tex: ['tex'],
Text: ['txt'],
Textile: ['textile'],
Toml: ['toml'],
TSX: ['tsx'],
Twig: ['twig|swig'],
Typescript: ['ts|typescript|str'],
Vala: ['vala'],
VBScript: ['vbs|vb'],
Velocity: ['vm'],
Verilog: ['v|vh|sv|svh'],
VHDL: ['vhd|vhdl'],
Wollok: ['wlk|wpgm|wtest'],
XML: ['xml|rdf|rss|wsdl|xslt|atom|mathml|mml|xul|xbl|xaml'],
XQuery: ['xq'],
YAML: ['yaml|yml'],
// Add the missing mode "Django" to ext-modelist
Django: ['html'],
};
this.modes = [];
var Mode = function (name, caption, extensions) {
this.name = name;
this.caption = caption;
this.mode = 'ace/mode/' + name;
this.extensions = extensions;
var re;
if (/\^/.test(extensions)) {
re =
extensions.replace(/\|(\^)?/g, function (a, b) {
return '$|' + (b ? '^' : '^.*\\.');
}) + '$';
} else {
re = '^.*\\.(' + extensions + ')$';
}
this.extRe = new RegExp(re, 'gi');
};
Mode.prototype.supportsFile = function (filename) {
return filename.match(this.extRe);
};
var nameOverrides = {
ObjectiveC: 'Objective-C',
CSharp: 'C#',
golang: 'Go',
C_Cpp: 'C and C++',
Csound_Document: 'Csound Document',
Csound_Orchestra: 'Csound',
Csound_Score: 'Csound Score',
coffee: 'CoffeeScript',
HTML_Ruby: 'HTML (Ruby)',
HTML_Elixir: 'HTML (Elixir)',
FTL: 'FreeMarker',
};
this.modesByName = {};
for (var name in supportedModes) {
var data = supportedModes[name];
var displayName = (nameOverrides[name] || name).replace(/_/g, ' ');
var filename = name.toLowerCase();
var mode = new Mode(filename, displayName, data[0]);
this.modesByName[filename] = mode;
this.modes.push(mode);
}
}
getMode(ext) {
var mode = this.modesByName.text;
for (var i = 0; i < this.modes.length; i++) {
if (this.modes[i].supportsFile('.' + ext)) {
mode = this.modes[i];
break;
}
}
return mode.name;
}
}
const aceModeList = new AceModeList();
export default aceModeList;
+191
View File
@@ -0,0 +1,191 @@
import moment from 'moment';
import 'moment/locale/ru';
import 'moment/locale/fr';
import 'moment/locale/de';
import 'moment/locale/ja';
import 'moment/locale/es';
import Observable from 'app/deprecated/CollectionsV1/observable.js';
import UserService from 'app/features/users/services/current-user-service';
import Globals from 'app/features/global/services/globals-twake-app-service';
class DateTime extends Observable {
constructor() {
super();
if (!Globals.window.navigator) {
Globals.window.navigator = {};
}
this.observableName = 'dateTimeService';
this.locale = this.cleanLocal(
Globals.window.navigator.userLanguage || Globals.window.navigator.language || 'en',
);
}
getCurrentLanguage() {
return this.locale;
}
setCurrentLanguage(lang) {
this.locale = this.cleanLocal(lang);
moment.locale(this.locale);
this.notify();
}
cleanLocal(string) {
if (string.split('-').length > 1) {
return string.split('-')[0];
}
return string;
}
getDefaultTimeFormat() {
var h24list = [
'af',
'ar-dz',
'ar-kw',
'ar-ly',
'ar-ma',
'ar-sa',
'ar-tn',
'ar',
'az',
'be',
'bg',
'bn',
'bo',
'br',
'bs',
'ca',
'cs',
'cv',
'cy',
'da',
'de-at',
'de-ch',
'de',
'dv',
'el',
'en-au',
'en-ca',
'en-gb',
'en-ie',
'en-nz',
'eo',
'es-do',
'es',
'et',
'eu',
'fa',
'fi',
'fo',
'fr-ca',
'fr-ch',
'fr',
'fy',
'gd',
'gl',
'gom-latn',
'he',
'hi',
'hr',
'hu',
'hy-am',
'id',
'is',
'it',
'ja',
'jv',
'ka',
'kk',
'km',
'kn',
'ko',
'ky',
'lb',
'lo',
'lt',
'lv',
'me',
'mi',
'mk',
'ml',
'mr',
'ms-my',
'ms',
'my',
'nb',
'ne',
'nl-be',
'nl',
'nn',
'pa-in',
'pl',
'pt-br',
'pt',
'ro',
'ru',
'sd',
'se',
'si',
'sk',
'sl',
'sq',
'sr-cyrl',
'sr',
'ss',
'sv',
'sw',
'ta',
'te',
'tet',
'th',
'tl-ph',
'tlh',
'tr',
'tzl',
'tzm-latn',
'tzm',
'uk',
'ur',
'uz-latn',
'uz',
'vi',
'x-pseudo',
'yo',
'zh-cn',
'zh-hk',
'zh-tw',
];
if (
h24list.indexOf(
(UserService.getCurrentUser() || {}).language ||
Globals.window.navigator.language ||
Globals.window.navigator.userLanguage ||
'en',
) >= 0
) {
return 'H:mm';
}
return 'LT';
}
isDateFirstInFormat() {
var numbers = moment().format('L').split('/');
if (numbers[0] === new Date().getDate()) {
return true;
} else {
return false;
}
}
getDefaultDateFormat() {
/*var numbers = moment().format("L").split("/");
if(numbers[0] == (new Date()).getDate()){
return "DD/MM/YYYY";
}else{
return "MM/DD/YYYY";
}*/
return 'LL'; //Default format for country better but US is "May 11, 2019" instead of "11 may 2019"
}
}
var x = new DateTime();
Globals.window.dateTimeService = x;
export default x;
@@ -0,0 +1,9 @@
export function getDevice() {
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
return 'ios';
}
if (/Android/i.test(navigator.userAgent)) {
return 'android';
}
return 'other';
}
@@ -0,0 +1,14 @@
export const formatDate = (date?: number) => {
return date
? new Intl.DateTimeFormat(navigator.languages[0], {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
weekday: 'short',
hour12: false,
}).format(new Date(date))
: '';
};
@@ -0,0 +1,11 @@
export const formatSize = (size?: number) => {
if (size && size >= 0) {
let pos = 0;
while (size > 1024) {
size = size / 1024;
pos++;
}
return size.toFixed(2) + ' ' + ['B', 'KB', 'MB', 'GB', 'TB', 'PB'][pos];
}
return 'Unknown size';
};
@@ -0,0 +1,43 @@
/**
*
* This helper will make sure of two things:
* - we call request as soon as possible
* - then we wait options.timeout before calling any new request
* - we can avoid the initial instant callback with options.doInitialCallback
*/
const delayedRequests: Map<string, () => Promise<void>> = new Map();
const delayedRequestsHasTimout: Map<string, boolean> = new Map();
const requestIsInProgress: { [key: string]: boolean } = {};
export const delayRequest = async (
key: string,
request: () => Promise<void>,
options: { timeout: number; doInitialCall: boolean } = { timeout: 1000, doInitialCall: true },
) => {
if (!delayedRequestsHasTimout.has(key)) {
delayedRequestsHasTimout.set(key, true);
if (options.doInitialCall) {
requestIsInProgress[key] = true;
try {
await request();
} catch (e) {
requestIsInProgress[key] = false;
throw e;
}
requestIsInProgress[key] = false;
} else delayedRequests.set(key, request);
setTimeout(() => {
const request = delayedRequests.get(key);
delayedRequestsHasTimout.delete(key);
request &&
delayRequest(key, request, { ...options, doInitialCall: !requestIsInProgress[key] });
delayedRequests.delete(key);
}, options.timeout);
} else {
delayedRequests.set(key, request);
}
};
@@ -0,0 +1,57 @@
import React, { useState } from 'react';
import { useEffect } from 'react';
import { Snapshot, useGotoRecoilSnapshot, useRecoilCallback, useRecoilSnapshot } from 'recoil';
export const DebugObserver = () => {
const snapshot = useRecoilSnapshot();
useEffect(() => {
console.debug('The following atoms have been modified');
for (const node of snapshot.getNodes_UNSTABLE({ isModified: true })) {
console.debug(node.key, snapshot.getLoadable(node));
}
}, [snapshot]);
return null;
};
export const DebugButton = () => {
const onClick = useRecoilCallback(
({ snapshot }) =>
async () => {
console.debug('Atom values');
for (const node of snapshot.getNodes_UNSTABLE()) {
const value = await snapshot.getPromise(node);
console.debug(node.key, value);
}
},
[],
);
return <button onClick={onClick}>Dump State</button>;
};
export const TimeTravelObserver = () => {
const [snapshots, setSnapshots] = useState<Snapshot[]>([]);
const snapshot = useRecoilSnapshot();
useEffect(() => {
if (snapshots.every(s => s.getID() !== snapshot.getID())) {
setSnapshots([...snapshots, snapshot]);
}
}, [snapshot, snapshots]);
const gotoSnapshot = useGotoRecoilSnapshot();
return (
<ol>
{snapshots.map((snapshot, i) => (
<li key={i}>
Snapshot {i}
<button onClick={() => gotoSnapshot(snapshot)}>Restore</button>
</li>
))}
</ol>
);
};
+177
View File
@@ -0,0 +1,177 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { isString } from 'lodash';
export const getBase64 = (file: File): Promise<string> => {
return new Promise((result, fail) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function () {
result(`${reader.result}`);
};
reader.onerror = function (error) {
fail(error);
};
});
};
export default class Strings {
static verifyMail(email: string) {
const re =
// eslint-disable-next-line no-useless-escape
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email.toLowerCase());
}
static removeAccents(str: string) {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
static autoSpaces(element: any, separator: any, size: any, max: any) {
if (element.textAreaRef) {
element = element.textAreaRef;
}
if (!element) {
return;
}
if (!separator) {
separator = ' ';
}
if (!size) {
size = 5;
}
if (!max) {
size = (5 + 1) * 4;
}
return;
}
static convertBase(src: any, srctable: any, desttable: any) {
const srclen = srctable.length;
const destlen = desttable.length;
// first convert to base 10
let val = 0;
const numlen = src.length;
for (let i = 0; i < numlen; i++) {
val = val * srclen + srctable.indexOf(src.charAt(i));
}
if (val < 0) {
return 0;
}
// then covert to any base
let r = val % destlen;
let res = desttable.charAt(r);
let q = Math.floor(val / destlen);
while (q) {
r = q % destlen;
q = Math.floor(q / destlen);
res = desttable.charAt(r) + res;
}
return res;
}
}
export const matchQuery = (query: string, candidate: string) => {
return query
.split(' ')
.every(
word =>
Strings.removeAccents(candidate)
.toLocaleLowerCase()
.indexOf(Strings.removeAccents(word).toLocaleLowerCase()) > -1,
);
};
/**
* The goal of this score is to get closest match for a query and candidates based of
* not only number of valid words but also how close words are.
*
* Example:
* query: "a flower"
* candidates: "amazing flowers", "flower", "Flower A", "a flower", "a bus", "a"
* The result should be in this order:
* "a flower"
* "Flower A"
* "amazing flowers" ("a" and "flower" included but "a" is only 15% of amazing)
* "flower" (a not included but flower 100% match)
* "a" (flower not included but a 100% match)
* "a bus" (a 100% match but parasite words)
*
* Idea:
* 1. Any non letter nor number is a separator
* 2. For each words in query we get the percentage of match + We add this percentages multiplied by the word size relative to query
* 3. We reduce score for each parasite words by 90%
* 4. We add a bonus for full match in the query 110%
* query: "a flower", a represent 15% of the query, flower represent 85%
* candidates computed:
* "a flower" a=1*0.15 flower=1*0.85 parasite=0 => 1 => full match bonus => 1.1
* "Flower A" a=1*0.15 flower=1*0.85 parasite=0 => 1
* "amazing flower" (a is 15% of amazing) a=0.15*0.15 flower=1*0.85 parasite=0 => 0.87
* "flower" a=0 flower=1*0.85 parasite=0 => 0.85
* "a" a=1*0.15 flower=0 parasite=0 => 0.15
* "a bus" a=1*0.15 flower=0 parasite=1 => 0.15*(0.9^1parasite) => 0.14
*/
export const distanceFromQuery = (
candidates: string | string[],
query: string,
options?: { booster: number[] },
) => {
let score = 0;
let parasites = 0;
if (isString(candidates)) {
candidates = [candidates];
}
//Step 1
Strings.removeAccents(query)
.toLocaleLowerCase()
.replace(/[^a-z0-9]/gm, ' ')
.split(' ')
//Step 2
.forEach(queryWord => {
const queryWordImportance = queryWord.length / query.replace(/ /gm, '').length;
let i = 0;
for (const candidate of candidates) {
const boost = options?.booster[i] || 1;
i++;
Strings.removeAccents(candidate)
.toLocaleLowerCase()
.replace(/[^a-z0-9]/gm, ' ')
.split(' ')
.map(sanitizedField => {
if (sanitizedField?.trim()) {
const match =
(sanitizedField.length - sanitizedField.replace(queryWord, '').length) /
sanitizedField.length;
if (match === 0) {
parasites += 1;
} else {
let prefixBoost = 1;
if (sanitizedField.indexOf(queryWord) === 0) {
prefixBoost = 2;
}
score += match * queryWordImportance * boost * prefixBoost;
}
}
});
}
});
//Step 3
score *= Math.pow(0.9, parasites);
//Step 4
const candidateSanitized = Strings.removeAccents(candidates.join(' '))
.toLocaleLowerCase()
.replace(/[^a-z0-9]/gm, ' ');
if (
candidateSanitized.replace(/ /gm, '').replace(query, '').length !==
candidateSanitized.replace(/ /gm, '').length
)
score *= 1.1;
return 0 - score;
};
+124
View File
@@ -0,0 +1,124 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { capitalize } from 'lodash';
import Globals from 'app/features/global/services/globals-twake-app-service';
class WindowState {
public readonly app_name: string = 'Twake';
public prefix = '';
public suffix = '';
public allGetParameter() {
const result: { [key: string]: string } = {};
let tmp: string[] = [];
Globals.window.location.search
.substr(1)
.split('&')
.forEach(item => {
tmp = item.split('=');
result[tmp[0]] = tmp[1];
});
return result;
}
public findGetParameter(parameterName: string) {
let result = null;
let tmp = [];
Globals.window.location.search
.substr(1)
.split('&')
.forEach(function (item) {
tmp = item.split('=');
if (tmp[0] === parameterName) {
result = decodeURIComponent(tmp[1]);
if (tmp[1] === undefined) {
result = true;
}
}
});
return result;
}
public updateTitle(): string {
return (document.title = `${this.prefix}${this.app_name}${this.suffix}`);
}
public setSuffix(text?: string) {
const separator = '-';
this.suffix = text ? ` ${separator} ${capitalize(text)}` : '';
return this.updateTitle();
}
public setPrefix(notifications_count = 0) {
this.prefix = notifications_count > 0 ? `(${notifications_count}) ` : '';
return this.updateTitle();
}
public nameToUrl(str: string) {
return str
.trim()
.replace(/[ -/]+/g, '_')
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/[^@a-zA-Z0-9_]/g, '');
}
public reduceUUID4(id: string) {
if (!id) return undefined;
return id
.replace(/(.)\1{2,3}/g, '$1i')
.replace(/(.)\1{1,2}/g, '$1h')
.replace(/-/g, 'g');
}
public expandUUID4(id: string) {
if (!id) return undefined;
return (
id
.replace(/(.)i/g, '$1$1$1')
.replace(/(.)h/g, '$1$1')
.replace(/[^0-9a-g]/g, '')
.replace(/g/g, '-') || undefined
);
}
public getInfoFromUrl() {
let result: { [key: string]: any } = {};
let url: string = document.location.pathname.replace(/^\/client/, '');
if (url) {
if (url.indexOf('/private/') === 0) {
url = url.split('/').pop() || '';
const list = url.split('-');
result.channel_id = this.expandUUID4(list[1]);
result.message = list[2] ? this.expandUUID4(list[2]) : false;
if (!result.channel_id) result = {};
} else {
url = url.split('/').pop() || '';
const list = url.split('-');
const channel_id = list[2];
const workspace_id = list[1];
result.message = list[3] ? this.expandUUID4(list[3]) : false;
result.channel_id = this.expandUUID4(channel_id);
result.workspace_id = this.expandUUID4(workspace_id);
if (!result.workspace_id || !result.channel_id) result = {};
}
}
return result;
}
public reset() {
this.setPrefix();
this.setSuffix();
}
}
export default new WindowState();
@@ -0,0 +1,31 @@
import Observable from 'app/deprecated/CollectionsV1/observable.js';
import Api from 'app/features/global/framework/api-service';
class WorkspacePicker extends Observable {
constructor() {
super();
this.observableName = 'workspacePicker';
this.searchedWorkspace = null;
this.refresh = false;
this.searchInput = '';
this.searchLabel = '';
this.isSearching = false;
}
getWorkspaceByName(name) {
var data = {
name: name,
};
this.isSearching = true;
this.notify();
var that = this;
Api.post('/ajax/workspace/getByName', data, function (res) {
that.searchedWorkspace = res.data.workspace;
that.isSearching = false;
that.notify();
});
}
}
const wsPicker = new WorkspacePicker();
export default wsPicker;