chore: init web project

This commit is contained in:
Steven
2022-09-12 09:53:15 +08:00
parent e82c821396
commit 5a96e67c6d
66 changed files with 5101 additions and 0 deletions

100
web/src/helpers/api.ts Normal file
View File

@@ -0,0 +1,100 @@
import axios from "axios";
type ResponseObject<T> = {
data: T;
error?: string;
message?: string;
};
export function getSystemStatus() {
return axios.get<ResponseObject<SystemStatus>>("/api/status");
}
export function signin(email: string, password: string) {
return axios.post<ResponseObject<User>>("/api/auth/signin", {
email,
password,
});
}
export function signup(email: string, password: string) {
return axios.post<ResponseObject<User>>("/api/auth/signup", {
email,
password,
name: email,
});
}
export function signout() {
return axios.post("/api/auth/logout");
}
export function createUser(userCreate: UserCreate) {
return axios.post<ResponseObject<User>>("/api/user", userCreate);
}
export function getMyselfUser() {
return axios.get<ResponseObject<User>>("/api/user/me");
}
export function getUserList() {
return axios.get<ResponseObject<User[]>>("/api/user");
}
export function getUserById(id: number) {
return axios.get<ResponseObject<User>>(`/api/user/${id}`);
}
export function patchUser(userPatch: UserPatch) {
return axios.patch<ResponseObject<User>>(`/api/user/${userPatch.id}`, userPatch);
}
export function deleteUser(userDelete: UserDelete) {
return axios.delete(`/api/user/${userDelete.id}`);
}
export function getWorkspaceList(find?: WorkspaceFind) {
const queryList = [];
if (find?.creatorId) {
queryList.push(`creatorId=${find.creatorId}`);
}
if (find?.memberId) {
queryList.push(`memberId=${find.memberId}`);
}
return axios.get<ResponseObject<Workspace[]>>(`/api/workspace?${queryList.join("&")}`);
}
export function createWorkspace(create: WorkspaceCreate) {
return axios.post<ResponseObject<Workspace>>("/api/workspace", create);
}
export function patchWorkspace(patch: WorkspacePatch) {
return axios.patch<ResponseObject<Workspace>>(`/api/workspace/${patch.id}`, patch);
}
export function deleteWorkspaceById(workspaceId: WorkspaceId) {
return axios.delete(`/api/workspace/${workspaceId}`);
}
export function getShortcutList(shortcutFind?: ShortcutFind) {
const queryList = [];
if (shortcutFind?.creatorId) {
queryList.push(`creatorId=${shortcutFind.creatorId}`);
}
if (shortcutFind?.workspaceId) {
queryList.push(`workspaceId=${shortcutFind.workspaceId}`);
}
return axios.get<ResponseObject<Shortcut[]>>(`/api/shortcut?${queryList.join("&")}`);
}
export function createShortcut(shortcutCreate: ShortcutCreate) {
return axios.post<ResponseObject<Shortcut>>("/api/shortcut", shortcutCreate);
}
export function patchShortcut(shortcutPatch: ShortcutPatch) {
return axios.patch<ResponseObject<Shortcut>>(`/api/shortcut/${shortcutPatch.id}`, shortcutPatch);
}
export function deleteShortcutById(shortcutId: ShortcutId) {
return axios.delete(`/api/shortcut/${shortcutId}`);
}

View File

@@ -0,0 +1,8 @@
// UNKNOWN_ID is the symbol for unknown id
export const UNKNOWN_ID = -1;
// default animation duration
export const ANIMATION_DURATION = 200;
// millisecond in a day
export const DAILY_TIMESTAMP = 3600 * 24 * 1000;

View File

@@ -0,0 +1,15 @@
(() => {
if (!String.prototype.replaceAll) {
String.prototype.replaceAll = function (str: any, newStr: any) {
// If a regex pattern
if (Object.prototype.toString.call(str).toLowerCase() === "[object regexp]") {
return this.replace(str, newStr);
}
// If a string
return this.replace(new RegExp(str, "g"), newStr);
};
}
})();
export default null;

View File

@@ -0,0 +1,59 @@
/**
* Define storage data type
*/
interface StorageData {
placeholder: string;
}
type StorageKey = keyof StorageData;
/**
* storage helper
*/
export function get(keys: StorageKey[]): Partial<StorageData> {
const data: Partial<StorageData> = {};
for (const key of keys) {
try {
const stringifyValue = localStorage.getItem(key);
if (stringifyValue !== null) {
const val = JSON.parse(stringifyValue);
data[key] = val;
}
} catch (error: any) {
console.error("Get storage failed in ", key, error);
}
}
return data;
}
export function set(data: Partial<StorageData>) {
for (const key in data) {
try {
const stringifyValue = JSON.stringify(data[key as StorageKey]);
localStorage.setItem(key, stringifyValue);
} catch (error: any) {
console.error("Save storage failed in ", key, error);
}
}
}
export function remove(keys: StorageKey[]) {
for (const key of keys) {
try {
localStorage.removeItem(key);
} catch (error: any) {
console.error("Remove storage failed in ", key, error);
}
}
}
export function emitStorageChangedEvent() {
const iframeEl = document.createElement("iframe");
iframeEl.style.display = "none";
document.body.appendChild(iframeEl);
iframeEl.contentWindow?.localStorage.setItem("t", Date.now().toString());
iframeEl.remove();
}

58
web/src/helpers/utils.ts Normal file
View File

@@ -0,0 +1,58 @@
export function getNowTimeStamp(): number {
return Date.now();
}
export function getOSVersion(): "Windows" | "MacOS" | "Linux" | "Unknown" {
const appVersion = navigator.userAgent;
let detectedOS: "Windows" | "MacOS" | "Linux" | "Unknown" = "Unknown";
if (appVersion.indexOf("Win") != -1) {
detectedOS = "Windows";
} else if (appVersion.indexOf("Mac") != -1) {
detectedOS = "MacOS";
} else if (appVersion.indexOf("Linux") != -1) {
detectedOS = "Linux";
}
return detectedOS;
}
export function debounce(fn: FunctionType, delay: number) {
let timer: number | null = null;
return () => {
if (timer) {
clearTimeout(timer);
timer = setTimeout(fn, delay);
} else {
timer = setTimeout(fn, delay);
}
};
}
export function throttle(fn: FunctionType, delay: number) {
let valid = true;
return () => {
if (!valid) {
return false;
}
valid = false;
setTimeout(() => {
fn();
valid = true;
}, delay);
};
}
export async function copyTextToClipboard(text: string) {
if (navigator.clipboard && navigator.clipboard.writeText) {
try {
await navigator.clipboard.writeText(text);
} catch (error: unknown) {
console.warn("Copy to clipboard failed.", error);
}
} else {
console.warn("Copy to clipboard failed, methods not supports.");
}
}

View File

@@ -0,0 +1,52 @@
// Validator
// * use for validating form data
const chineseReg = /[\u3000\u3400-\u4DBF\u4E00-\u9FFF]/;
export interface ValidatorConfig {
// min length
minLength: number;
// max length
maxLength: number;
// no space
noSpace: boolean;
// no chinese
noChinese: boolean;
}
export function validate(text: string, config: Partial<ValidatorConfig>): { result: boolean; reason?: string } {
if (config.minLength !== undefined) {
if (text.length < config.minLength) {
return {
result: false,
reason: "Too short",
};
}
}
if (config.maxLength !== undefined) {
if (text.length > config.maxLength) {
return {
result: false,
reason: "Too long",
};
}
}
if (config.noSpace && text.includes(" ")) {
return {
result: false,
reason: "Don't allow space",
};
}
if (config.noChinese && chineseReg.test(text)) {
return {
result: false,
reason: "Don't allow chinese",
};
}
return {
result: true,
};
}