first edition

main
zestack 1 year ago
commit 9a0a502de2
  1. 11
      .editorconfig
  2. 24
      .gitignore
  3. 3
      .prettierrc
  4. 3
      .vscode/extensions.json
  5. 18
      README.md
  6. 58
      demo.html
  7. 13
      index.html
  8. 3402
      package-lock.json
  9. 32
      package.json
  10. 6
      postcss.config.js
  11. 64
      src/App.vue
  12. 203
      src/backend/api.ts
  13. 73
      src/backend/index.ts
  14. 173
      src/backend/qiniu/api/index.ts
  15. 1
      src/backend/qiniu/config/index.ts
  16. 37
      src/backend/qiniu/config/region.ts
  17. 62
      src/backend/qiniu/errors/index.ts
  18. 195
      src/backend/qiniu/image/index.ts
  19. 22
      src/backend/qiniu/index.ts
  20. 73
      src/backend/qiniu/logger/index.ts
  21. 48
      src/backend/qiniu/logger/report-v3.ts
  22. 340
      src/backend/qiniu/upload/base.ts
  23. 70
      src/backend/qiniu/upload/direct.ts
  24. 134
      src/backend/qiniu/upload/hosts.ts
  25. 86
      src/backend/qiniu/upload/index.ts
  26. 314
      src/backend/qiniu/upload/resume.ts
  27. 274
      src/backend/qiniu/utils/base64.ts
  28. 218
      src/backend/qiniu/utils/compress.ts
  29. 61
      src/backend/qiniu/utils/config.ts
  30. 90
      src/backend/qiniu/utils/crc32.ts
  31. 346
      src/backend/qiniu/utils/helper.ts
  32. 8
      src/backend/qiniu/utils/index.ts
  33. 151
      src/backend/qiniu/utils/observable.ts
  34. 53
      src/backend/qiniu/utils/pool.ts
  35. 348
      src/backend/qiniu/utils/spark-md5.ts
  36. 181
      src/backend/reader.ts
  37. 96
      src/backend/report.ts
  38. 275
      src/backend/socket.ts
  39. 417
      src/backend/uploader.ts
  40. BIN
      src/frontend/assets/logo.png
  41. 1
      src/frontend/directives/index.ts
  42. 69
      src/frontend/directives/vTooltip.ts
  43. 1
      src/frontend/hooks/index.ts
  44. 91
      src/frontend/hooks/useTheme.ts
  45. 225
      src/frontend/index.ts
  46. 38
      src/frontend/store/events.ts
  47. 112
      src/frontend/store/filter.ts
  48. 204
      src/frontend/store/handlers.ts
  49. 4
      src/frontend/store/index.ts
  50. 52
      src/frontend/store/store.ts
  51. 131
      src/frontend/style.css
  52. 105
      src/frontend/utils/align.ts
  53. 3
      src/frontend/utils/index.ts
  54. 106
      src/frontend/utils/preview.ts
  55. 58
      src/frontend/utils/scroll.ts
  56. 63
      src/frontend/widgets/UiBarrier.vue
  57. 103
      src/frontend/widgets/UiContainer.vue
  58. 275
      src/frontend/widgets/UiContextMenu.vue
  59. 173
      src/frontend/widgets/UiContextMenuController.ts
  60. 159
      src/frontend/widgets/UiDialog.vue
  61. 85
      src/frontend/widgets/UiDirectoryButton.vue
  62. 199
      src/frontend/widgets/UiDirectoryNaming.vue
  63. 29
      src/frontend/widgets/UiDirectoryNamingController.ts
  64. 35
      src/frontend/widgets/UiDirectoryTree.vue
  65. 22
      src/frontend/widgets/UiDirectoryTreeItem.vue
  66. 69
      src/frontend/widgets/UiDropFeedback.vue
  67. 101
      src/frontend/widgets/UiFile.vue
  68. 157
      src/frontend/widgets/UiFileNaming.vue
  69. 25
      src/frontend/widgets/UiFileNamingController.ts
  70. 45
      src/frontend/widgets/UiFileThumbnail.vue
  71. 83
      src/frontend/widgets/UiFiles.vue
  72. 64
      src/frontend/widgets/UiMimeSelect.vue
  73. 30
      src/frontend/widgets/UiMistake.vue
  74. 65
      src/frontend/widgets/UiModeTools.vue
  75. 54
      src/frontend/widgets/UiNavbar.vue
  76. 40
      src/frontend/widgets/UiNoData.vue
  77. 21
      src/frontend/widgets/UiPaginateEllipsis.vue
  78. 32
      src/frontend/widgets/UiPaginateItem.vue
  79. 83
      src/frontend/widgets/UiPagination.vue
  80. 90
      src/frontend/widgets/UiSidebar.vue
  81. 63
      src/frontend/widgets/UiSplitter.vue
  82. 134
      src/frontend/widgets/UiTask.vue
  83. 25
      src/frontend/widgets/UiTaskAction.vue
  84. 82
      src/frontend/widgets/UiTasks.vue
  85. 40
      src/frontend/widgets/UiThemeSelect.vue
  86. 85
      src/frontend/widgets/UiToast.vue
  87. 24
      src/frontend/widgets/UiToastController.ts
  88. 39
      src/frontend/widgets/UiToaster.vue
  89. 14
      src/frontend/widgets/VBadge.vue
  90. 37
      src/frontend/widgets/VButton.vue
  91. 124
      src/frontend/widgets/VDropdown.vue
  92. 21
      src/frontend/widgets/VIcon.vue
  93. 38
      src/frontend/widgets/VIconButton.vue
  94. 516
      src/frontend/widgets/VIcons.ts
  95. 35
      src/frontend/widgets/VImage.vue
  96. 24
      src/frontend/widgets/VLoading.vue
  97. 24
      src/frontend/widgets/VProgress.vue
  98. 50
      src/frontend/widgets/VText.vue
  99. 9
      src/frontend/widgets/utils.ts
  100. 70
      src/frontend/worker.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,11 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = false
trim_trailing_whitespace = true
max_line_length = 80
tab_width = 2

24
.gitignore vendored

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,3 @@
{
"singleAttributePerLine": true
}

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

@ -0,0 +1,18 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Type Support For `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.

@ -0,0 +1,58 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<link href="/src/frontend/assets/logo.png" rel="icon" type="image/png"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app">
<button id="btn">Show File Manager</button>
</div>
<script type="module">
const apiUrl = 'https://wfs.yaojiankang.top'
const artifact = 'genesis'
import {showFileManager} from 'https://wfs.yaojiankang.top/wfs.js?t=12334'
const show = (async () => {
const response = await fetch(apiUrl + "/login", {
method: "POST",
body: new URLSearchParams({
name: 'root',
password: 'root',
artifact
}),
})
const res = await response.json();
if (!res.ok) {
throw new Error(res.msg);
}
const accessToken = res.data.token
showFileManager({
artifact,
accessToken,
apiUrl,
select: 'single',
onOpen() {
console.log("open")
},
onError(error) {
console.log('error', error)
},
onDismiss() {
console.log('dismiss')
},
onConfirm(files) {
console.log('files', files)
}
})
})
document.querySelector('#btn').addEventListener('click', show)
</script>
</body>
</html>

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<link href="/src/frontend/assets/logo.png" rel="icon" type="image/png"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app"></div>
<script src="/src/main.ts" type="module"></script>
</body>
</html>

3402
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,32 @@
{
"name": "wfs",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@vueuse/core": "^10.7.2",
"dom-align": "^1.12.4",
"hls.js": "^1.4.14",
"viewerjs": "^1.11.6",
"vue": "^3.3.8"
},
"devDependencies": {
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.7",
"@types/node": "^20.10.4",
"@vitejs/plugin-basic-ssl": "^1.0.2",
"@vitejs/plugin-vue": "^4.5.0",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.32",
"tailwindcss": "^3.4.0",
"typescript": "^5.2.2",
"vite": "^5.0.0",
"vue-tsc": "^1.8.22"
}
}

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

@ -0,0 +1,64 @@
<script lang="ts" setup>
import {onMounted, ref} from 'vue'
import {installIcons} from "./frontend/widgets/VIcons.ts";
import UiContainer from "./frontend/widgets/UiContainer.vue";
const loading = ref(false)
const apiUrl = 'https://zmf.yaojiankang.top'
const artifact = 'genesis'
const login = async () => {
loading.value = true
const response = await fetch(apiUrl + "/login", {
method: "POST",
body: new URLSearchParams({
name: 'root',
password: 'root',
artifact
}),
})
const res = await response.json();
if (!res.ok) {
throw new Error(res.msg);
}
const accessToken = res.data.token
import('./frontend').then(({showFileManager}) => {
showFileManager({
artifact,
accessToken,
apiUrl,
select: 'multiple',
onOpen() {
loading.value = false
},
onSelect(files: CloudFile[]) {
console.log(files)
},
onFail(error: string) {
console.log(error)
},
onClose() {
console.log("closed")
}
})
})
return {
artifact,
accessToken,
apiUrl,
}
}
onMounted(() => {
login()
installIcons(document.body)
})
</script>
<template>
<UiContainer v-if="0" :setup="login"/>
</template>

@ -0,0 +1,203 @@
import {$on} from '../shared'
import {useSocket} from './socket'
let apiUrl: string | undefined;
let accessToken: string | undefined;
let artifact: string | undefined;
export async function configApi(cfg: Partial<ApiConfig>) {
if (
cfg.accessToken !== accessToken ||
cfg.apiUrl !== apiUrl ||
cfg.artifact !== artifact
) {
await ajax(cfg.apiUrl + "/" + cfg.artifact, {
headers: {"Authorization": `Bearer ${cfg.accessToken}`}
})
accessToken = cfg.accessToken;
artifact = cfg.artifact;
apiUrl = cfg.apiUrl;
useSocket(cfg)
}
}
export async function ajax<T>(url: string, init: RequestInit = {}): Promise<T> {
return new Promise<T>(async (resolve, reject) => {
if (accessToken) {
const headers = new Headers(init.headers);
headers.set("Authorization", `Bearer ${accessToken}`);
init.headers = headers;
}
try {
const uri = (apiUrl ?? '') + url;
const response = await fetch(uri, init);
const res = await response.json();
if (
res != null &&
typeof res === "object" &&
!Array.isArray(res) &&
"ok" in res &&
"msg" in res &&
typeof res.ok === "boolean" &&
typeof res.msg === "string"
) {
if (res.ok) {
resolve(res.data);
} else {
reject(new Error(res.msg));
}
} else {
reject(new Error("服务器异常"));
}
} catch (err) {
// 服务器返回的有可能不是 JSON 数据
if (err instanceof SyntaxError) {
reject(new Error("服务器异常"));
} else if (err instanceof Error && err.message === "Failed to fetch") {
reject(new Error("服务器异常"));
} else {
reject(err);
}
}
});
}
export interface UploadArgs {
skip: false;
token: string;
key: string;
}
export interface UploadSkip {
skip: true;
}
/**
*
*/
export function initiateUploadApi(
fileMime: string,
fileHash: string,
signal: AbortSignal,
): Promise<UploadSkip | UploadArgs> {
const url = `/${artifact}/objects/initiate`;
const body = new URLSearchParams({fileMime, fileHash});
const init = {method: "POST", body, signal};
return ajax<UploadSkip | UploadArgs>(url, init);
}
export function completeUploadApi(
fileHash: string,
fileNames: string[],
directories: number[],
signal: AbortSignal,
) {
const targets: Map<number, Set<string>> = new Map();
for (let i = 0; i < directories.length; i++) {
const dirid = directories[i];
const dirs = targets.get(dirid) ?? new Set();
if (!fileNames[i]) return Promise.reject("参数错误");
dirs.add(fileNames[i]);
targets.set(dirid, dirs);
}
const body = new URLSearchParams({fileHash});
targets.forEach((names, dir) => {
names.forEach((name) => {
body.append("directoryId", `${dir}`);
body.append("fileName", name);
});
});
const url = `/${artifact}/objects/complete`;
return ajax<
Array<{
id: TaskId;
directoryId: number;
fileName: string;
fileHash: string;
createdAt: string;
updatedAt: string;
}>
>(url, {method: "POST", body, signal});
}
interface SimulateQiniuCallbackOptions {
key: string;
name: string;
mime: string;
md5: FileHash;
size: number;
}
export function simulateQiniuCallback(
data: SimulateQiniuCallbackOptions,
signal: AbortSignal,
) {
const url = `/${artifact}/objects/uploaded`;
const body = JSON.stringify({...data, etag: "<etag>", bucket: "<bucket>"});
return ajax<any>(url, {
method: "POST",
body,
signal,
headers: {"Content-Type": "application/json"},
});
}
// 新建文件夹
$on('dir', 'create', (data: NewDirEventData): Promise<CloudDirectory> => {
const {title, pid = 0} = data
const uri = `/${artifact}/directories`;
const body = new URLSearchParams({title});
if (pid > 0) body.set("parentId", `${pid}`);
return ajax<CloudDirectory>(uri, {method: "POST", body});
})
// 所有文件夹
$on('dir', 'all', (): Promise<CloudDirectory[]> => {
return ajax<CloudDirectory[]>(`/${artifact}/directories`);
})
// 删除文件夹
$on('dir', 'delete', (id: number): Promise<boolean> => {
const url = `/${artifact}/directories/${id}`;
return ajax<boolean>(url, {method: "DELETE"});
})
// 重命名文件夹
$on('dir', 'rename', (data: RenameDirEventData): Promise<CloudDirectory> => {
const url = `/${artifact}/directories/${data.id}`;
const body = new URLSearchParams({title: data.title});
return ajax<CloudDirectory>(url, {method: "PUT", body});
})
// 文件重命名
$on('file', 'rename', async (data: RenameFileEventData): Promise<CloudFile> => {
const url = `/${artifact}/files/${data.id}`;
const body = new URLSearchParams({name: data.name});
return await ajax(url, {method: "PUT", body});
})
// 删除文件
$on('file', 'delete', (files: number[] | number) => {
let url = `/${artifact}/files`;
if (!Array.isArray(files)) {
url += `/${files}`;
} else {
const body = new URLSearchParams();
files.forEach((file) => body!.append("files", `${file}`));
url += "?" + body
}
return ajax<void>(url, {method: "DELETE"});
})
// 文件列表(分页)
$on('file', 'list', (data: FilesEventData) => {
const url = `/${artifact}/files`;
const query = new URLSearchParams();
query.set("page", `${data.page}`)
query.set("limit", `${data.limit}`)
if (data.directoryId != null) query.set("directoryId", `${data.directoryId}`);
if (data.mime != null) query.set("mime", data.mime);
return ajax<FilesEventResult>(url + "?" + query.toString());
})

@ -0,0 +1,73 @@
import {$on} from '../shared'
import {configApi} from './api'
import {
cleanReadTasks,
getReadTasks,
pauseReadTask,
Reader,
removeReadTask,
resumeReadTask,
} from "./reader";
import {handleNetworkEvent} from "./socket";
import {
cleanUploadTasks,
getUploadTasks,
pauseUploadTask,
removeUploadTask,
resumeUploadTask,
} from "./uploader";
// 取消前端任务
async function cancel(data: TaskEventData): Promise<void> {
const {fileHash, taskId} = data;
await pauseReadTask(taskId);
await pauseUploadTask(fileHash, taskId);
}
// 恢复任务
async function resume(data: TaskEventData): Promise<void> {
const {fileHash, taskId} = data;
await resumeReadTask(taskId);
await resumeUploadTask(fileHash, taskId);
}
// 清理任务
async function cleanup() {
await cleanReadTasks();
await cleanUploadTasks();
}
// 创建任务
function create(data: TaskCreateData) {
Reader.create(data.file, data.dirid);
}
// 删除任务
async function remove(data: TaskEventData): Promise<void> {
const {fileHash, taskId} = data;
await removeReadTask(taskId);
await removeUploadTask(fileHash, taskId);
}
function allTasks(): Task[] {
return [
...getReadTasks(),
...getUploadTasks(),
]
}
// 配置接口事件
$on("cfg", "init", configApi);
// 监听前端任务事件
$on("task", "all", allTasks)
$on("task", "cancel", cancel);
$on("task", "cleanup", cleanup);
$on("task", "create", create);
$on("task", "remove", remove);
$on("task", "resume", resume);
// 网络变化事件
$on("net", "status", handleNetworkEvent);

@ -0,0 +1,173 @@
import * as utils from '../utils'
import {normalizeUploadConfig} from '../utils'
import {Config, InternalConfig, UploadInfo} from '../upload'
interface UpHosts {
data: {
up: {
acc: {
main: string[]
backup: string[]
}
}
}
}
export async function getUpHosts(accessKey: string, bucketName: string, protocol: InternalConfig['upprotocol']): Promise<UpHosts> {
const params = new URLSearchParams({ak: accessKey, bucket: bucketName})
const url = `${protocol}://api.qiniu.com/v2/query?${params}`
return utils.request(url, {method: 'GET'})
}
/**
* @param bucket
* @param key
* @param uploadInfo
*/
function getBaseUrl(bucket: string, key: string | null | undefined, uploadInfo: UploadInfo) {
const {url, id} = uploadInfo
return `${url}/buckets/${bucket}/objects/${key != null ? utils.urlSafeBase64Encode(key) : '~'}/uploads/${id}`
}
export interface InitPartsData {
/** 该文件的上传 id, 后续该文件其他各个块的上传,已上传块的废弃,已上传块的合成文件,都需要该 id */
uploadId: string
/** uploadId 的过期时间 */
expireAt: number
}
/**
* @param token
* @param bucket
* @param key
* @param uploadUrl
*/
export function initUploadParts(
token: string,
bucket: string,
key: string | null | undefined,
uploadUrl: string
): utils.Response<InitPartsData> {
const url = `${uploadUrl}/buckets/${bucket}/objects/${key != null ? utils.urlSafeBase64Encode(key) : '~'}/uploads`
return utils.request<InitPartsData>(
url,
{
method: 'POST',
headers: utils.getAuthHeaders(token)
}
)
}
export interface UploadChunkData {
etag: string
md5: string
}
/**
* @param token
* @param index chunk
* @param uploadInfo
* @param options
*/
export function uploadChunk(
token: string,
key: string | null | undefined,
index: number,
uploadInfo: UploadInfo,
options: Partial<utils.RequestOptions & { md5: string }>
): utils.Response<UploadChunkData> {
const bucket = utils.getPutPolicy(token).bucketName
const url = getBaseUrl(bucket, key, uploadInfo) + `/${index}`
const headers = utils.getHeadersForChunkUpload(token)
if (options.md5) headers['Content-MD5'] = options.md5
return utils.request<UploadChunkData>(url, {
...options,
method: 'PUT',
headers
})
}
export type UploadCompleteData = any
/**
* @param token
* @param key
* @param uploadInfo
* @param options
*/
export function uploadComplete(
token: string,
key: string | null | undefined,
uploadInfo: UploadInfo,
options: Partial<utils.RequestOptions>
): utils.Response<UploadCompleteData> {
const bucket = utils.getPutPolicy(token).bucketName
const url = getBaseUrl(bucket, key, uploadInfo)
return utils.request<UploadCompleteData>(url, {
...options,
method: 'POST',
headers: utils.getHeadersForMkFile(token)
})
}
/**
* @param token
* @param key
* @param uploadInfo
*/
export function deleteUploadedChunks(
token: string,
key: string | null | undefined,
uploadinfo: UploadInfo
): utils.Response<void> {
const bucket = utils.getPutPolicy(token).bucketName
const url = getBaseUrl(bucket, key, uploadinfo)
return utils.request(
url,
{
method: 'DELETE',
headers: utils.getAuthHeaders(token)
}
)
}
/**
* @param {string} url
* @param {FormData} data
* @param {Partial<utils.RequestOptions>} options
* @returns Promise
* @description
*/
export function direct(
url: string,
data: FormData,
options: Partial<utils.RequestOptions>
): Promise<UploadCompleteData> {
return utils.request<UploadCompleteData>(url, {
method: 'POST',
body: data,
...options
})
}
export type UploadUrlConfig = Partial<Pick<Config, 'upprotocol' | 'uphost' | 'region' | 'useCdnDomain'>>
/**
* @param {UploadUrlConfig} config
* @param {string} token
* @returns Promise
* @description url
*/
export async function getUploadUrl(_config: UploadUrlConfig, token: string): Promise<string> {
const config = normalizeUploadConfig(_config)
const protocol = config.upprotocol
if (config.uphost.length > 0) {
return `${protocol}://${config.uphost[0]}`
}
const putPolicy = utils.getPutPolicy(token)
const res = await getUpHosts(putPolicy.assessKey, putPolicy.bucketName, protocol)
const hosts = res.data.up.acc.main
return `${protocol}://${hosts[0]}`
}

@ -0,0 +1 @@
export * from './region'

@ -0,0 +1,37 @@
/** 上传区域 */
export const region = {
z0: 'z0',
z1: 'z1',
z2: 'z2',
na0: 'na0',
as0: 'as0',
cnEast2: 'cn-east-2'
} as const
/** 上传区域对应的 host */
export const regionUphostMap = {
[region.z0]: {
srcUphost: ['up.qiniup.com'],
cdnUphost: ['upload.qiniup.com']
},
[region.z1]: {
srcUphost: ['up-z1.qiniup.com'],
cdnUphost: ['upload-z1.qiniup.com']
},
[region.z2]: {
srcUphost: ['up-z2.qiniup.com'],
cdnUphost: ['upload-z2.qiniup.com']
},
[region.na0]: {
srcUphost: ['up-na0.qiniup.com'],
cdnUphost: ['upload-na0.qiniup.com']
},
[region.as0]: {
srcUphost: ['up-as0.qiniup.com'],
cdnUphost: ['upload-as0.qiniup.com']
},
[region.cnEast2]: {
srcUphost: ['up-cn-east-2.qiniup.com'],
cdnUphost: ['upload-cn-east-2.qiniup.com']
}
} as const

@ -0,0 +1,62 @@
export enum QiniuErrorName {
// 输入错误
InvalidFile = 'InvalidFile',
InvalidToken = 'InvalidToken',
InvalidMetadata = 'InvalidMetadata',
InvalidChunkSize = 'InvalidChunkSize',
InvalidCustomVars = 'InvalidCustomVars',
NotAvailableUploadHost = 'NotAvailableUploadHost',
// 缓存相关
ReadCacheFailed = 'ReadCacheFailed',
InvalidCacheData = 'InvalidCacheData',
WriteCacheFailed = 'WriteCacheFailed',
RemoveCacheFailed = 'RemoveCacheFailed',
// 图片压缩模块相关
GetCanvasContextFailed = 'GetCanvasContextFailed',
UnsupportedFileType = 'UnsupportedFileType',
// 运行环境相关
FileReaderReadFailed = 'FileReaderReadFailed',
NotAvailableXMLHttpRequest = 'NotAvailableXMLHttpRequest',
InvalidProgressEventTarget = 'InvalidProgressEventTarget',
// 请求错误
RequestError = 'RequestError'
}
export class QiniuError implements Error {
public stack: string | undefined
constructor(public name: QiniuErrorName, public message: string) {
this.stack = new Error().stack
}
}
export class QiniuRequestError extends QiniuError {
/**
* @description error QiniuRequestError
* @deprecated 使使 instanceof
*/
public isRequestError = true
/**
* @description json undefined
*/
public data?: any
constructor(public code: number, public reqId: string, message: string, data?: any) {
super(QiniuErrorName.RequestError, message)
this.data = data
}
}
/**
* @description host
*/
export class QiniuNetworkError extends QiniuRequestError {
constructor(message: string, reqId = '') {
super(0, reqId, message)
}
}

@ -0,0 +1,195 @@
import { request, urlSafeBase64Encode } from '../utils'
export interface ImageViewOptions {
mode: number
format?: string
w?: number
h?: number
q?: number
}
export interface ImageWatermark {
image: string
mode: number
fontsize?: number
dissolve?: number
dx?: number
dy?: number
gravity?: string
text?: string
font?: string
fill?: string
}
export interface ImageMogr2 {
'auto-orient'?: boolean
strip?: boolean
thumbnail?: number
crop?: number
gravity?: number
format?: number
blur?: number
quality?: number
rotate?: number
}
type Pipeline =
| (ImageWatermark & { fop: 'watermark' })
| (ImageViewOptions & { fop: 'imageView2' })
| (ImageMogr2 & { fop: 'imageMogr2' })
export interface Entry {
domain: string
key: string
}
function getImageUrl(key: string, domain: string) {
key = encodeURIComponent(key)
if (domain.slice(domain.length - 1) !== '/') {
domain += '/'
}
return domain + key
}
export function imageView2(op: ImageViewOptions, key?: string, domain?: string) {
if (!/^\d$/.test(String(op.mode))) {
throw 'mode should be number in imageView2'
}
const { mode, w, h, q, format } = op
if (!w && !h) {
throw 'param w and h is empty in imageView2'
}
let imageUrl = 'imageView2/' + encodeURIComponent(mode)
imageUrl += w ? '/w/' + encodeURIComponent(w) : ''
imageUrl += h ? '/h/' + encodeURIComponent(h) : ''
imageUrl += q ? '/q/' + encodeURIComponent(q) : ''
imageUrl += format ? '/format/' + encodeURIComponent(format) : ''
if (key && domain) {
imageUrl = getImageUrl(key, domain) + '?' + imageUrl
}
return imageUrl
}
// invoke the imageMogr2 api of Qiniu
export function imageMogr2(op: ImageMogr2, key?: string, domain?: string) {
const autoOrient = op['auto-orient']
const { thumbnail, strip, gravity, crop, quality, rotate, format, blur } = op
let imageUrl = 'imageMogr2'
imageUrl += autoOrient ? '/auto-orient' : ''
imageUrl += thumbnail ? '/thumbnail/' + encodeURIComponent(thumbnail) : ''
imageUrl += strip ? '/strip' : ''
imageUrl += gravity ? '/gravity/' + encodeURIComponent(gravity) : ''
imageUrl += quality ? '/quality/' + encodeURIComponent(quality) : ''
imageUrl += crop ? '/crop/' + encodeURIComponent(crop) : ''
imageUrl += rotate ? '/rotate/' + encodeURIComponent(rotate) : ''
imageUrl += format ? '/format/' + encodeURIComponent(format) : ''
imageUrl += blur ? '/blur/' + encodeURIComponent(blur) : ''
if (key && domain) {
imageUrl = getImageUrl(key, domain) + '?' + imageUrl
}
return imageUrl
}
// invoke the watermark api of Qiniu
export function watermark(op: ImageWatermark, key?: string, domain?: string) {
const mode = op.mode
if (!mode) {
throw "mode can't be empty in watermark"
}
let imageUrl = 'watermark/' + mode
if (mode !== 1 && mode !== 2) {
throw 'mode is wrong'
}
if (mode === 1) {
const image = op.image
if (!image) {
throw "image can't be empty in watermark"
}
imageUrl += image ? '/image/' + urlSafeBase64Encode(image) : ''
}
if (mode === 2) {
const { text, font, fontsize, fill } = op
if (!text) {
throw "text can't be empty in watermark"
}
imageUrl += text ? '/text/' + urlSafeBase64Encode(text) : ''
imageUrl += font ? '/font/' + urlSafeBase64Encode(font) : ''
imageUrl += fontsize ? '/fontsize/' + fontsize : ''
imageUrl += fill ? '/fill/' + urlSafeBase64Encode(fill) : ''
}
const { dissolve, gravity, dx, dy } = op
imageUrl += dissolve ? '/dissolve/' + encodeURIComponent(dissolve) : ''
imageUrl += gravity ? '/gravity/' + encodeURIComponent(gravity) : ''
imageUrl += dx ? '/dx/' + encodeURIComponent(dx) : ''
imageUrl += dy ? '/dy/' + encodeURIComponent(dy) : ''
if (key && domain) {
imageUrl = getImageUrl(key, domain) + '?' + imageUrl
}
return imageUrl
}
// invoke the imageInfo api of Qiniu
export function imageInfo(key: string, domain: string) {
const url = getImageUrl(key, domain) + '?imageInfo'
return request(url, { method: 'GET' })
}
// invoke the exif api of Qiniu
export function exif(key: string, domain: string) {
const url = getImageUrl(key, domain) + '?exif'
return request(url, { method: 'GET' })
}
export function pipeline(arr: Pipeline[], key?: string, domain?: string) {
const isArray = Object.prototype.toString.call(arr) === '[object Array]'
let option: Pipeline
let errOp = false
let imageUrl = ''
if (isArray) {
for (let i = 0, len = arr.length; i < len; i++) {
option = arr[i]
if (!option.fop) {
throw "fop can't be empty in pipeline"
}
switch (option.fop) {
case 'watermark':
imageUrl += watermark(option) + '|'
break
case 'imageView2':
imageUrl += imageView2(option) + '|'
break
case 'imageMogr2':
imageUrl += imageMogr2(option) + '|'
break
default:
errOp = true
break
}
if (errOp) {
throw 'fop is wrong in pipeline'
}
}
if (key && domain) {
imageUrl = getImageUrl(key, domain) + '?' + imageUrl
const length = imageUrl.length
if (imageUrl.slice(length - 1) === '|') {
imageUrl = imageUrl.slice(0, length - 1)
}
}
return imageUrl
}
throw "pipeline's first param should be array"
}

@ -0,0 +1,22 @@
export {
QiniuErrorName,
QiniuError,
QiniuRequestError,
QiniuNetworkError
} from './errors'
export {imageMogr2, watermark, imageInfo, exif, pipeline} from './image'
export {deleteUploadedChunks, getUploadUrl} from './api'
export {
upload,
type UploadProgress
} from './upload'
export {region} from './config'
export {
// compressImage,
// type CompressResult,
urlSafeBase64Encode,
urlSafeBase64Decode,
getHeadersForMkFile,
getHeadersForChunkUpload
} from './utils'

@ -0,0 +1,73 @@
import {reportV3, V3LogInfo} from './report-v3'
export type LogLevel = 'INFO' | 'WARN' | 'ERROR' | 'OFF'
export default class Logger {
private static id = 0
// 为每个类分配一个 id
// 用以区分不同的上传任务
private id = ++Logger.id
constructor(
private token: string,
private disableReport = true,
private level: LogLevel = 'OFF',
private prefix = 'UPLOAD'
) {
}
/**
* @param {V3LogInfo} data
* @param {boolean} retry 3
* @description
*/
report(data: V3LogInfo, retry?: number) {
if (this.disableReport) return
try {
reportV3(this.token, data, retry)
} catch (error) {
this.warn(error)
}
}
/**
* info
* @param {unknown[]} args
*/
info(...args: unknown[]) {
const allowLevel: LogLevel[] = ['INFO']
if (allowLevel.includes(this.level)) {
// eslint-disable-next-line no-console
console.log(this.getPrintPrefix('INFO'), ...args)
}
}
/**
* warn
* @param {unknown[]} args
*/
warn(...args: unknown[]) {
const allowLevel: LogLevel[] = ['INFO', 'WARN']
if (allowLevel.includes(this.level)) {
// eslint-disable-next-line no-console
console.warn(this.getPrintPrefix('WARN'), ...args)
}
}
/**
* error
* @param {unknown[]} args
*/
error(...args: unknown[]) {
const allowLevel: LogLevel[] = ['INFO', 'WARN', 'ERROR']
if (allowLevel.includes(this.level)) {
// eslint-disable-next-line no-console
console.error(this.getPrintPrefix('ERROR'), ...args)
}
}
private getPrintPrefix(level: LogLevel) {
return `Qiniu-JS-SDK [${level}][${this.prefix}#${this.id}]:`
}
}

@ -0,0 +1,48 @@
import { createXHR, getAuthHeaders } from '../utils'
export interface V3LogInfo {
code: number
reqId: string
host: string
remoteIp: string
port: string
duration: number
time: number
bytesSent: number
upType: 'jssdk-h5'
size: number
}
/**
* @param {string} token 使 token
* @param {V3LogInfo} data
* @param {number} retry 3
* @description v3 https://github.com/qbox/product/blob/master/kodo/uplog.md#%E7%89%88%E6%9C%AC-3。
*/
export function reportV3(token: string, data: V3LogInfo, retry = 3) {
const xhr = createXHR()
xhr.open('POST', 'https://uplog.qbox.me/log/3')
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
xhr.setRequestHeader('Authorization', getAuthHeaders(token).Authorization)
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status !== 200 && retry > 0) {
reportV3(token, data, retry - 1)
}
}
// 顺序参考:https://github.com/qbox/product/blob/master/kodo/uplog.md#%E7%89%88%E6%9C%AC-3
const stringifyData = [
data.code || '',
data.reqId || '',
data.host || '',
data.remoteIp || '',
data.port || '',
data.duration || '',
data.time || '',
data.bytesSent || '',
data.upType || '',
data.size || ''
].join(',')
xhr.send(stringifyData)
}

@ -0,0 +1,340 @@
import {QiniuError, QiniuErrorName, QiniuRequestError} from '../errors'
import Logger, {LogLevel} from '../logger'
import {region} from '../config'
import * as utils from '../utils'
import {Host, HostPool} from './hosts'
export const DEFAULT_CHUNK_SIZE = 4 // 单位 MB
// code 信息地址 https://developer.qiniu.com/kodo/3928/error-responses
export const FREEZE_CODE_LIST = [0, 502, 503, 504, 599] // 将会冻结当前 host 的 code
export const RETRY_CODE_LIST = [...FREEZE_CODE_LIST, 612] // 会进行重试的 code
/** 上传文件的资源信息配置 */
export interface Extra {
/** 文件原文件名 */
fname: string
/** 用来放置自定义变量 */
customVars?: { [key: string]: string }
/** 自定义元信息 */
metadata?: { [key: string]: string }
/** 文件类型设置 */
mimeType?: string //
}
export interface InternalConfig {
/** 是否开启 cdn 加速 */
useCdnDomain: boolean
/** 是否开启服务端校验 */
checkByServer: boolean
/** 是否对分片进行 md5校验 */
checkByMD5: boolean
/** 强制直传 */
forceDirect: boolean
/** 上传失败后重试次数 */
retryCount: number
/** 自定义上传域名 */
uphost: string[]
/** 自定义分片上传并发请求量 */
concurrentRequestLimit: number
/** 分片大小,单位为 MB */
chunkSize: number
/** 上传域名协议 */
upprotocol: 'https' | 'http'
/** 上传区域 */
region?: typeof region[keyof typeof region]
/** 是否禁止统计日志上报 */
disableStatisticsReport: boolean
/** 设置调试日志输出模式,默认 `OFF`,不输出任何日志 */
debugLogLevel?: LogLevel
}
/** 上传任务的配置信息 */
export interface Config extends Partial<Omit<InternalConfig, 'upprotocol' | 'uphost'>> {
/** 上传域名协议 */
upprotocol?: InternalConfig['upprotocol'] | 'https:' | 'http:'
/** 自定义上传域名 */
uphost?: InternalConfig['uphost'] | string
}
export interface UploadOptions {
file: File
key: string | null | undefined
token: string
config: InternalConfig
putExtra?: Partial<Extra>
}
export interface UploadInfo {
id: string
url: string
}
/** 传递给外部的上传进度信息 */
export interface UploadProgress {
total: ProgressCompose
uploadInfo?: UploadInfo
chunks?: ProgressCompose[]
}
export interface UploadHandlers {
onData: (data: UploadProgress) => void
onError: (err: QiniuError) => void
onComplete: (res: any) => void
}
export interface Progress {
total: number
loaded: number
}
export interface ProgressCompose {
size: number
loaded: number
percent: number
fromCache?: boolean
}
export type XHRHandler = (xhr: XMLHttpRequest) => void
const GB = 1024 ** 3
export default abstract class Base {
protected config: InternalConfig
protected putExtra: Extra
protected aborted = false
protected retryCount = 0
protected uploadHost?: Host
protected xhrList: XMLHttpRequest[] = []
protected file: File
protected key: string | null | undefined
protected token: string
protected assessKey: string = ''
protected bucketName: string = ''
protected uploadAt?: number
protected progress?: UploadProgress
protected onData: (data: UploadProgress) => void
protected onError: (err: QiniuError) => void
protected onComplete: (res: any) => void
constructor(
options: UploadOptions,
handlers: UploadHandlers,
protected hostPool: HostPool,
protected logger: Logger
) {
this.config = options.config
logger.info('config inited.', this.config)
this.putExtra = {
fname: '',
...options.putExtra
}
logger.info('putExtra inited.', this.putExtra)
this.key = options.key
this.file = options.file
this.token = options.token
this.onData = handlers.onData
this.onError = handlers.onError
this.onComplete = handlers.onComplete
try {
const putPolicy = utils.getPutPolicy(this.token)
this.bucketName = putPolicy.bucketName
this.assessKey = putPolicy.assessKey
} catch (error) {
logger.error('get putPolicy from token failed.', error)
this.onError(error as QiniuError)
}
}
/**
* @returns Promise [Subscriber]
* @description [Subscriber]
*/
public async putFile(): Promise<void> {
this.aborted = false
if (!this.putExtra.fname) {
this.logger.info('use file.name as fname.')
this.putExtra.fname = this.file.name
}
if (this.file.size > 10000 * GB) {
this.handleError(new QiniuError(
QiniuErrorName.InvalidFile,
'file size exceed maximum value 10000G'
))
return
}
if (this.putExtra.customVars) {
if (!utils.isCustomVarsValid(this.putExtra.customVars)) {
this.handleError(new QiniuError(
QiniuErrorName.InvalidCustomVars,
'customVars key should start with x:'
))
return
}
}
if (this.putExtra.metadata) {
if (!utils.isMetaDataValid(this.putExtra.metadata)) {
this.handleError(new QiniuError(
QiniuErrorName.InvalidMetadata,
'metadata key should start with x-qn-meta-'
))
return
}
}
try {
this.uploadAt = new Date().getTime()
await this.checkAndUpdateUploadHost()
const result = await this.run()
this.onComplete(result.data)
this.checkAndUnfreezeHost()
this.sendLog(result.reqId, 200)
return
} catch (err) {
if (this.aborted) {
this.logger.warn('upload is aborted.')
this.sendLog('', -2)
return
}
this.clear()
this.logger.error(err)
if (err instanceof QiniuRequestError) {
this.sendLog(err.reqId, err.code)
// 检查并冻结当前的 host
this.checkAndFreezeHost(err)
const notReachRetryCount = ++this.retryCount <= this.config.retryCount
const needRetry = RETRY_CODE_LIST.includes(err.code)
// 以下条件满足其中之一则会进行重新上传:
// 1. 满足 needRetry 的条件且 retryCount 不为 0
// 2. uploadId 无效时在 resume 里会清除本地数据,并且这里触发重新上传
if (needRetry && notReachRetryCount) {
this.logger.warn(`error auto retry: ${this.retryCount}/${this.config.retryCount}.`)
this.putFile()
return
}
}
this.onError(err as QiniuError)
}
}
public stop() {
this.logger.info('aborted.')
this.clear()
this.aborted = true
}
public addXhr(xhr: XMLHttpRequest) {
this.xhrList.push(xhr)
}
public getProgressInfoItem(loaded: number, size: number, fromCache?: boolean): ProgressCompose {
return {
size,
loaded,
percent: loaded / size * 100,
...(fromCache == null ? {} : {fromCache})
}
}
/**
* @returns utils.Response<any>
* @description
*/
protected abstract run(): utils.Response<any>
// 检查并更新 upload host
protected async checkAndUpdateUploadHost() {
// 从 hostPool 中获取一个可用的 host 挂载在 this
this.logger.info('get available upload host.')
const newHost = await this.hostPool.getUp(
this.assessKey,
this.bucketName,
this.config.upprotocol
)
if (newHost == null) {
throw new QiniuError(
QiniuErrorName.NotAvailableUploadHost,
'no available upload host.'
)
}
if (this.uploadHost != null && this.uploadHost.host !== newHost.host) {
this.logger.warn(`host switches from ${this.uploadHost.host} to ${newHost.host}.`)
} else {
this.logger.info(`use host ${newHost.host}.`)
}
this.uploadHost = newHost
}
// 检查并解冻当前的 host
protected checkAndUnfreezeHost() {
this.logger.info('check unfreeze host.')
if (this.uploadHost != null && this.uploadHost.isFrozen()) {
this.logger.warn(`${this.uploadHost.host} will be unfrozen.`)
this.uploadHost.unfreeze()
}
}
// 检查并更新冻结当前的 host
private checkAndFreezeHost(error: QiniuError) {
this.logger.info('check freeze host.')
if (error instanceof QiniuRequestError && this.uploadHost != null) {
if (FREEZE_CODE_LIST.includes(error.code)) {
this.logger.warn(`${this.uploadHost.host} will be temporarily frozen.`)
this.uploadHost.freeze()
}
}
}
private handleError(error: QiniuError) {
this.logger.error(error.message)
this.onError(error)
}
private clear() {
this.xhrList.forEach(xhr => {
xhr.onreadystatechange = null
xhr.abort()
})
this.xhrList = []
this.logger.info('cleanup uploading xhr.')
}
private sendLog(reqId: string, code: number) {
this.logger.report({
code,
reqId,
remoteIp: '',
upType: 'jssdk-h5',
size: this.file.size,
time: Math.floor(this.uploadAt! / 1000),
port: utils.getPortFromUrl(this.uploadHost?.getUrl()),
host: utils.getDomainFromUrl(this.uploadHost?.getUrl()),
bytesSent: this.progress ? this.progress.total.loaded : 0,
duration: Math.floor((new Date().getTime() - this.uploadAt!) / 1000)
})
}
}

@ -0,0 +1,70 @@
import { CRC32 } from '../utils/crc32'
import { direct } from '../api'
import Base from './base'
export default class Direct extends Base {
protected async run() {
this.logger.info('start run Direct.')
const formData = new FormData()
formData.append('file', this.file)
formData.append('token', this.token)
if (this.key != null) {
formData.append('key', this.key)
}
formData.append('fname', this.putExtra.fname)
if (this.config.checkByServer) {
const crcSign = await CRC32.file(this.file)
formData.append('crc32', crcSign.toString())
}
if (this.putExtra.customVars) {
this.logger.info('init customVars.')
const { customVars } = this.putExtra
Object.keys(customVars).forEach(key => formData.append(key, customVars[key].toString()))
this.logger.info('customVars inited.')
}
if (this.putExtra.metadata) {
this.logger.info('init metadata.')
const { metadata } = this.putExtra
Object.keys(metadata).forEach(key => formData.append(key, metadata[key].toString()))
}
this.logger.info('formData inited.')
const result = await direct(this.uploadHost!.getUrl(), formData, {
onProgress: data => {
this.updateDirectProgress(data.loaded, data.total)
},
onCreate: xhr => this.addXhr(xhr)
})
this.logger.info('Direct progress finish.')
this.finishDirectProgress()
return result
}
private updateDirectProgress(loaded: number, total: number) {
// 当请求未完成时可能进度会达到100,所以total + 1来防止这种情况出现
this.progress = { total: this.getProgressInfoItem(loaded, total + 1) }
this.onData(this.progress)
}
private finishDirectProgress() {
// 在某些浏览器环境下,xhr 的 progress 事件无法被触发,progress 为 null,这里 fake 下
if (!this.progress) {
this.logger.warn('progress is null.')
this.progress = { total: this.getProgressInfoItem(this.file.size, this.file.size) }
this.onData(this.progress)
return
}
const { total } = this.progress
this.progress = { total: this.getProgressInfoItem(total.loaded + 1, total.size) }
this.onData(this.progress)
}
}

@ -0,0 +1,134 @@
import {getUpHosts} from '../api'
import {InternalConfig} from './base'
/**
* @description key hostvalue
*/
const unfreezeTimeMap = new Map<string, number>()
export class Host {
constructor(public host: string, public protocol: InternalConfig['upprotocol']) {
}
/**
* @description host
*/
isFrozen() {
const currentTime = new Date().getTime()
const unfreezeTime = unfreezeTimeMap.get(this.host)
return unfreezeTime != null && unfreezeTime >= currentTime
}
/**
* @param {number} time 20s
* @description host host
*/
freeze(time = 20) {
const unfreezeTime = new Date().getTime() + (time * 1000)
unfreezeTimeMap.set(this.host, unfreezeTime)
}
/**
* @description host
*/
unfreeze() {
unfreezeTimeMap.delete(this.host)
}
/**
* @description host url
*/
getUrl() {
return `${this.protocol}://${this.host}`
}
/**
* @description
*/
getUnfreezeTime() {
return unfreezeTimeMap.get(this.host)
}
}
export class HostPool {
/**
* @description host bucket accessKey key
*/
private cachedHostsMap = new Map<string, Host[]>()
/**
* @param {string[]} initHosts
* @description initHosts host 使 initHosts
*/
constructor(private initHosts: string[] = []) {
}
/**
* @param {string} accessKey
* @param {string} bucketName
* @param {InternalConfig['upprotocol']} protocol
* @returns {Promise<Host | null>}
* @description Host
*/
public async getUp(accessKey: string, bucketName: string, protocol: InternalConfig['upprotocol']): Promise<Host | null> {
await this.refresh(accessKey, bucketName, protocol)
const cachedHostList = this.cachedHostsMap.get(`${accessKey}@${bucketName}`) || []
if (cachedHostList.length === 0) {
return null
}
const availableHostList = cachedHostList.filter(host => !host.isFrozen())
if (availableHostList.length > 0) {
return availableHostList[0]
}
// 无可用的,去取离解冻最近的 host
const priorityQueue = cachedHostList
.slice()
.sort((hostA, hostB) => (hostA.getUnfreezeTime() || 0) - (hostB.getUnfreezeTime() || 0))
return priorityQueue[0]
}
/**
* @param {string} accessKey
* @param {string} bucketName
* @param {string[]} hosts
* @param {InternalConfig['upprotocol']} protocol
* @returns {void}
* @description host
*/
private register(accessKey: string, bucketName: string, hosts: string[], protocol: InternalConfig['upprotocol']): void {
this.cachedHostsMap.set(
`${accessKey}@${bucketName}`,
hosts.map(host => new Host(host, protocol))
)
}
/**
* @param {string} accessKey
* @param {string} bucketName
* @param {InternalConfig['upprotocol']} protocol
* @returns {Promise<void>}
* @description host host
*/
private async refresh(accessKey: string, bucketName: string, protocol: InternalConfig['upprotocol']): Promise<void> {
const cachedHostList = this.cachedHostsMap.get(`${accessKey}@${bucketName}`) || []
if (cachedHostList.length > 0) return
if (this.initHosts.length > 0) {
this.register(accessKey, bucketName, this.initHosts, protocol)
return
}
const response = await getUpHosts(accessKey, bucketName, protocol)
if (response?.data != null) {
const stashHosts: string[] = [
...(response.data.up?.acc?.main || []),
...(response.data.up?.acc?.backup || [])
]
this.register(accessKey, bucketName, stashHosts, protocol)
}
}
}

@ -0,0 +1,86 @@
import Resume from './resume'
import Direct from './direct'
import Logger from '../logger'
import {UploadCompleteData} from '../api'
import {IObserver, MB, normalizeUploadConfig, Observable} from '../utils'
import {QiniuError, QiniuNetworkError, QiniuRequestError} from '../errors'
import {
Config,
Extra,
UploadHandlers,
UploadOptions,
UploadProgress
} from './base'
import {HostPool} from './hosts'
export * from './base'
export * from './resume'
export type {
UploadProgress
}
export function createUploadManager(
options: UploadOptions,
handlers: UploadHandlers,
hostPool: HostPool,
logger: Logger
) {
if (options.config && options.config.forceDirect) {
logger.info('ues forceDirect mode.')
return new Direct(options, handlers, hostPool, logger)
}
if (options.file.size > 4 * MB) {
logger.info('file size over 4M, use Resume.')
return new Resume(options, handlers, hostPool, logger)
}
logger.info('file size less or equal than 4M, use Direct.')
return new Direct(options, handlers, hostPool, logger)
}
/**
* @param file
* @param key
* @param token
* @param putExtra
* @param config
* @returns
*/
export function upload(
file: File,
key: string | null | undefined,
token: string,
putExtra?: Partial<Extra>,
config?: Config
): Observable<UploadProgress, QiniuError | QiniuRequestError | QiniuNetworkError, UploadCompleteData> {
// 为每个任务创建单独的 Logger
const logger = new Logger(token, config?.disableStatisticsReport, config?.debugLogLevel, file.name)
const options: UploadOptions = {
file,
key,
token,
putExtra,
config: normalizeUploadConfig(config, logger)
}
// 创建 host 池
const hostPool = new HostPool(options.config.uphost)
return new Observable((observer: IObserver<
UploadProgress,
QiniuError | QiniuRequestError | QiniuNetworkError,
UploadCompleteData
>) => {
const manager = createUploadManager(options, {
onData: (data: UploadProgress) => observer.next(data),
onError: (err: QiniuError) => observer.error(err),
onComplete: (res: any) => observer.complete(res)
}, hostPool, logger)
manager.putFile()
return manager.stop.bind(manager)
})
}

@ -0,0 +1,314 @@
import {
initUploadParts,
uploadChunk,
UploadChunkData,
uploadComplete
} from '../api'
import {QiniuError, QiniuErrorName, QiniuRequestError} from '../errors'
import * as utils from '../utils'
import Base, {Extra, Progress, UploadInfo} from './base'
export interface UploadedChunkStorage extends UploadChunkData {
size: number
}
export interface ChunkLoaded {
mkFileProgress: 0 | 1
chunks: number[]
}
export interface ChunkInfo {
chunk: Blob
index: number
}
export interface LocalInfo {
data: UploadedChunkStorage[]
id: string
}
export interface ChunkPart {
etag: string
partNumber: number
}
export interface UploadChunkBody extends Extra {
parts: ChunkPart[]
}
/** 是否为正整数 */
function isPositiveInteger(n: number) {
const re = /^[1-9]\d*$/
return re.test(String(n))
}
export default class Resume extends Base {
/**
* @description chunks
*/
private chunks: Blob[] = []
/**
* @description 使 chunks
*/
private usedCacheList: boolean[] = []
/**
* @description
*/
private cachedUploadedList: UploadedChunkStorage[] = []
/**
* @description
*/
private uploadedList: UploadedChunkStorage[] = []
/**
* @description
*/
private loaded?: ChunkLoaded
/**
* @description id
*/
private uploadId?: string
/**
* @returns {Promise<ResponseSuccess<any>>}
* @description Base run
*/
protected async run() {
this.logger.info('start run Resume.')
if (!this.config.chunkSize || !isPositiveInteger(this.config.chunkSize)) {
throw new QiniuError(
QiniuErrorName.InvalidChunkSize,
'chunkSize must be a positive integer'
)
}
if (this.config.chunkSize > 1024) {
throw new QiniuError(
QiniuErrorName.InvalidChunkSize,
'chunkSize maximum value is 1024'
)
}
await this.initBeforeUploadChunks()
const pool = new utils.Pool(
async (chunkInfo: ChunkInfo) => {
if (this.aborted) {
pool.abort()
throw new Error('pool is aborted')
}
await this.uploadChunk(chunkInfo)
},
this.config.concurrentRequestLimit
)
let mkFileResponse = null
const localKey = this.getLocalKey()
const uploadChunks = this.chunks.map((chunk, index) => pool.enqueue({
chunk,
index
}))
try {
await Promise.all(uploadChunks)
mkFileResponse = await this.mkFileReq()
} catch (error) {
// uploadId 无效,上传参数有误(多由于本地存储信息的 uploadId 失效)
if (error instanceof QiniuRequestError && (error.code === 612 || error.code === 400)) {
utils.removeLocalFileInfo(localKey, this.logger)
}
throw error
}
// 上传成功,清理本地缓存数据
utils.removeLocalFileInfo(localKey, this.logger)
return mkFileResponse
}
private async uploadChunk(chunkInfo: ChunkInfo) {
const {index, chunk} = chunkInfo
const cachedInfo = this.cachedUploadedList[index]
this.logger.info(`upload part ${index}, cache:`, cachedInfo)
const shouldCheckMD5 = this.config.checkByMD5
const reuseSaved = () => {
this.usedCacheList[index] = true
this.updateChunkProgress(chunk.size, index)
this.uploadedList[index] = cachedInfo
this.updateLocalCache()
}
// FIXME: 至少判断一下 size
if (cachedInfo && !shouldCheckMD5) {
reuseSaved()
return
}
const md5 = await utils.computeMd5(chunk)
this.logger.info('computed part md5.', md5)
if (cachedInfo && md5 === cachedInfo.md5) {
reuseSaved()
return
}
// 没有使用缓存设置标记为 false
this.usedCacheList[index] = false
const onProgress = (data: Progress) => {
this.updateChunkProgress(data.loaded, index)
}
const requestOptions = {
body: chunk,
md5: this.config.checkByServer ? md5 : undefined,
onProgress,
onCreate: (xhr: XMLHttpRequest) => this.addXhr(xhr)
}
this.logger.info(`part ${index} start uploading.`)
const response = await uploadChunk(
this.token,
this.key,
chunkInfo.index + 1,
this.getUploadInfo(),
requestOptions
)
this.logger.info(`part ${index} upload completed.`)
// 在某些浏览器环境下,xhr 的 progress 事件无法被触发,progress 为 null,这里在每次分片上传完成后都手动更新下 progress
onProgress({
loaded: chunk.size,
total: chunk.size
})
this.uploadedList[index] = {
etag: response.data.etag,
md5: response.data.md5,
size: chunk.size
}
this.updateLocalCache()
}
private async mkFileReq() {
const data: UploadChunkBody = {
parts: this.uploadedList.map((value, index) => ({
etag: value.etag,
// 接口要求 index 需要从 1 开始,所以需要整体 + 1
partNumber: index + 1
})),
fname: this.putExtra.fname,
...this.putExtra.mimeType && {mimeType: this.putExtra.mimeType},
...this.putExtra.customVars && {customVars: this.putExtra.customVars},
...this.putExtra.metadata && {metadata: this.putExtra.metadata}
}
this.logger.info('parts upload completed, make file.', data)
const result = await uploadComplete(
this.token,
this.key,
this.getUploadInfo(),
{
onCreate: xhr => this.addXhr(xhr),
body: JSON.stringify(data)
}
)
this.logger.info('finish Resume Progress.')
this.updateMkFileProgress(1)
return result
}
private async initBeforeUploadChunks() {
this.uploadedList = []
this.usedCacheList = []
const cachedInfo = utils.getLocalFileInfo(this.getLocalKey(), this.logger)
// 分片必须和当时使用的 uploadId 配套,所以断点续传需要把本地存储的 uploadId 拿出来
// 假如没有 cachedInfo 本地信息并重新获取 uploadId
if (!cachedInfo) {
this.logger.info('init upload parts from api.')
const res = await initUploadParts(
this.token,
this.bucketName,
this.key,
this.uploadHost!.getUrl()
)
this.logger.info(`initd upload parts of id: ${res.data.uploadId}.`)
this.uploadId = res.data.uploadId
this.cachedUploadedList = []
} else {
const infoMessage = [
'resume upload parts from local cache,',
`total ${cachedInfo.data.length} part,`,
`id is ${cachedInfo.id}.`
]
this.logger.info(infoMessage.join(' '))
this.cachedUploadedList = cachedInfo.data
this.uploadId = cachedInfo.id
}
this.chunks = utils.getChunks(this.file, this.config.chunkSize)
this.loaded = {
mkFileProgress: 0,
chunks: this.chunks.map(_ => 0)
}
this.notifyResumeProgress()
}
private getUploadInfo(): UploadInfo {
return {
id: this.uploadId!,
url: this.uploadHost!.getUrl()
}
}
private getLocalKey() {
return utils.createLocalKey(this.file.name, this.key, this.file.size)
}
private updateLocalCache() {
utils.setLocalFileInfo(this.getLocalKey(), {
id: this.uploadId!,
data: this.uploadedList
}, this.logger)
}
private updateChunkProgress(loaded: number, index: number) {
this.loaded!.chunks[index] = loaded
this.notifyResumeProgress()
}
private updateMkFileProgress(progress: 0 | 1) {
this.loaded!.mkFileProgress = progress
this.notifyResumeProgress()
}
private notifyResumeProgress() {
this.progress = {
total: this.getProgressInfoItem(
utils.sum(this.loaded!.chunks) + this.loaded!.mkFileProgress,
// FIXME: 不准确的 fileSize
this.file.size + 1 // 防止在 complete 未调用的时候进度显示 100%
),
chunks: this.chunks.map((chunk, index) => {
const fromCache = this.usedCacheList[index]
return this.getProgressInfoItem(this.loaded!.chunks[index], chunk.size, fromCache)
}),
uploadInfo: {
id: this.uploadId!,
url: this.uploadHost!.getUrl()
}
}
this.onData(this.progress!)
}
}

@ -0,0 +1,274 @@
/* eslint-disable */
// https://github.com/locutusjs/locutus/blob/master/src/php/xml/utf8_encode.js
function utf8Encode(argString: string) {
// http://kevin.vanzonneveld.net
// + original by: Webtoolkit.info (http://www.webtoolkit.info/)
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + improved by: sowberry
// + tweaked by: Jack
// + bugfixed by: Onno Marsman
// + improved by: Yves Sucaet
// + bugfixed by: Onno Marsman
// + bugfixed by: Ulrich
// + bugfixed by: Rafal Kukawski
// + improved by: kirilloid
// + bugfixed by: kirilloid
// * example 1: this.utf8Encode('Kevin van Zonneveld')
// * returns 1: 'Kevin van Zonneveld'
if (argString === null || typeof argString === 'undefined') {
return ''
}
let string = argString + '' // .replace(/\r\n/g, '\n').replace(/\r/g, '\n')
let utftext = '',
start,
end,
stringl = 0
start = end = 0
stringl = string.length
for (let n = 0; n < stringl; n++) {
let c1 = string.charCodeAt(n)
let enc = null
if (c1 < 128) {
end++
} else if (c1 > 127 && c1 < 2048) {
enc = String.fromCharCode((c1 >> 6) | 192, (c1 & 63) | 128)
} else if ((c1 & 0xf800 ^ 0xd800) > 0) {
enc = String.fromCharCode(
(c1 >> 12) | 224,
((c1 >> 6) & 63) | 128,
(c1 & 63) | 128
)
} else {
// surrogate pairs
if ((c1 & 0xfc00 ^ 0xd800) > 0) {
throw new RangeError('Unmatched trail surrogate at ' + n)
}
let c2 = string.charCodeAt(++n)
if ((c2 & 0xfc00 ^ 0xdc00) > 0) {
throw new RangeError('Unmatched lead surrogate at ' + (n - 1))
}
c1 = ((c1 & 0x3ff) << 10) + (c2 & 0x3ff) + 0x10000
enc = String.fromCharCode(
(c1 >> 18) | 240,
((c1 >> 12) & 63) | 128,
((c1 >> 6) & 63) | 128,
(c1 & 63) | 128
)
}
if (enc !== null) {
if (end > start) {
utftext += string.slice(start, end)
}
utftext += enc
start = end = n + 1
}
}
if (end > start) {
utftext += string.slice(start, stringl)
}
return utftext
}
// https://github.com/locutusjs/locutus/blob/master/src/php/xml/utf8_decode.js
function utf8Decode(strData: string) {
// eslint-disable-line camelcase
// discuss at: https://locutus.io/php/utf8_decode/
// original by: Webtoolkit.info (https://www.webtoolkit.info/)
// input by: Aman Gupta
// input by: Brett Zamir (https://brett-zamir.me)
// improved by: Kevin van Zonneveld (https://kvz.io)
// improved by: Norman "zEh" Fuchs
// bugfixed by: hitwork
// bugfixed by: Onno Marsman (https://twitter.com/onnomarsman)
// bugfixed by: Kevin van Zonneveld (https://kvz.io)
// bugfixed by: kirilloid
// bugfixed by: w35l3y (https://www.wesley.eti.br)
// example 1: utf8_decode('Kevin van Zonneveld')
// returns 1: 'Kevin van Zonneveld'
const tmpArr = []
let i = 0
let c1 = 0
let seqlen = 0
strData += ''
while (i < strData.length) {
c1 = strData.charCodeAt(i) & 0xFF
seqlen = 0
// https://en.wikipedia.org/wiki/UTF-8#Codepage_layout
if (c1 <= 0xBF) {
c1 = (c1 & 0x7F)
seqlen = 1
} else if (c1 <= 0xDF) {
c1 = (c1 & 0x1F)
seqlen = 2
} else if (c1 <= 0xEF) {
c1 = (c1 & 0x0F)
seqlen = 3
} else {
c1 = (c1 & 0x07)
seqlen = 4
}
for (let ai = 1; ai < seqlen; ++ai) {
c1 = ((c1 << 0x06) | (strData.charCodeAt(ai + i) & 0x3F))
}
if (seqlen === 4) {
c1 -= 0x10000
tmpArr.push(String.fromCharCode(0xD800 | ((c1 >> 10) & 0x3FF)))
tmpArr.push(String.fromCharCode(0xDC00 | (c1 & 0x3FF)))
} else {
tmpArr.push(String.fromCharCode(c1))
}
i += seqlen
}
return tmpArr.join('')
}
function base64Encode(data: any) {
// http://kevin.vanzonneveld.net
// + original by: Tyler Akins (http://rumkin.com)
// + improved by: Bayron Guevara
// + improved by: Thunder.m
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + bugfixed by: Pellentesque Malesuada
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// - depends on: this.utf8Encode
// * example 1: this.base64Encode('Kevin van Zonneveld')
// * returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA=='
// mozilla has this native
// - but breaks in 2.0.0.12!
// if (typeof this.window['atob'] == 'function') {
// return atob(data)
// }
let b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
let o1,
o2,
o3,
h1,
h2,
h3,
h4,
bits,
i = 0,
ac = 0,
enc = '',
tmp_arr = []
if (!data) {
return data
}
data = utf8Encode(data + '')
do {
// pack three octets into four hexets
o1 = data.charCodeAt(i++)
o2 = data.charCodeAt(i++)
o3 = data.charCodeAt(i++)
bits = (o1 << 16) | (o2 << 8) | o3
h1 = (bits >> 18) & 0x3f
h2 = (bits >> 12) & 0x3f
h3 = (bits >> 6) & 0x3f
h4 = bits & 0x3f
// use hexets to index into b64, and append result to encoded string
tmp_arr[ac++] =
b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4)
} while (i < data.length)
enc = tmp_arr.join('')
switch (data.length % 3) {
case 1:
enc = enc.slice(0, -2) + '=='
break
case 2:
enc = enc.slice(0, -1) + '='
break
}
return enc
}
function base64Decode(data: string) {
// http://kevin.vanzonneveld.net
// + original by: Tyler Akins (http://rumkin.com)
// + improved by: Thunder.m
// + input by: Aman Gupta
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + bugfixed by: Onno Marsman
// + bugfixed by: Pellentesque Malesuada
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + input by: Brett Zamir (http://brett-zamir.me)
// + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// * example 1: base64_decode('S2V2aW4gdmFuIFpvbm5ldmVsZA==')
// * returns 1: 'Kevin van Zonneveld'
// mozilla has this native
// - but breaks in 2.0.0.12!
// if (typeof this.window['atob'] == 'function') {
// return atob(data)
// }
let b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
let o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
ac = 0,
dec = '',
tmp_arr = []
if (!data) {
return data
}
data += ''
do { // unpack four hexets into three octets using index points in b64
h1 = b64.indexOf(data.charAt(i++))
h2 = b64.indexOf(data.charAt(i++))
h3 = b64.indexOf(data.charAt(i++))
h4 = b64.indexOf(data.charAt(i++))
bits = h1 << 18 | h2 << 12 | h3 << 6 | h4
o1 = bits >> 16 & 0xff
o2 = bits >> 8 & 0xff
o3 = bits & 0xff
if (h3 === 64) {
tmp_arr[ac++] = String.fromCharCode(o1)
} else if (h4 === 64) {
tmp_arr[ac++] = String.fromCharCode(o1, o2)
} else {
tmp_arr[ac++] = String.fromCharCode(o1, o2, o3)
}
} while (i < data.length)
dec = tmp_arr.join('')
return utf8Decode(dec)
}
export function urlSafeBase64Encode(v: any) {
v = base64Encode(v)
// 参考 https://tools.ietf.org/html/rfc4648#section-5
return v.replace(/\//g, '_').replace(/\+/g, '-')
}
export function urlSafeBase64Decode(v: any) {
v = v.replace(/_/g, '/').replace(/-/g, '+')
return base64Decode(v)
}

@ -0,0 +1,218 @@
// import {QiniuError, QiniuErrorName} from '../errors'
//
// import {createObjectURL} from './helper'
//
// export interface CompressOptions {
// quality?: number
// noCompressIfLarger?: boolean
// maxWidth?: number
// maxHeight?: number
// }
//
// export interface Dimension {
// width?: number
// height?: number
// }
//
// export interface CompressResult {
// dist: Blob | File
// width: number
// height: number
// }
//
// const mimeTypes = {
// PNG: 'image/png',
// JPEG: 'image/jpeg',
// WEBP: 'image/webp',
// BMP: 'image/bmp'
// } as const
//
// const maxSteps = 4
// const scaleFactor = Math.log(2)
// // @ts-ignore
// const supportMimeTypes = Object.keys(mimeTypes).map(type => mimeTypes[type])
// const defaultType = mimeTypes.JPEG
//
// type MimeKey = keyof typeof mimeTypes
//
// function isSupportedType(type: string): type is typeof mimeTypes[MimeKey] {
// return supportMimeTypes.includes(type)
// }
//
// class Compress {
// private outputType: string
// private config: CompressOptions
//
// constructor(private file: File, config: CompressOptions) {
// this.outputType = this.file.type
// this.config = {
// quality: 0.92,
// noCompressIfLarger: false,
// ...config
// }
// }
//
// async process(): Promise<CompressResult> {
// this.outputType = this.file.type
// const srcDimension: Dimension = {}
// if (!isSupportedType(this.file.type)) {
// throw new QiniuError(
// QiniuErrorName.UnsupportedFileType,
// `unsupported file type: ${this.file.type}`
// )
// }
//
// const originImage = await this.getOriginImage()
// const canvas = await this.getCanvas(originImage)
// let scale = 1
// if (this.config.maxWidth) {
// scale = Math.min(1, this.config.maxWidth / canvas.width)
// }
// if (this.config.maxHeight) {
// scale = Math.min(1, scale, this.config.maxHeight / canvas.height)
// }
// srcDimension.width = canvas.width
// srcDimension.height = canvas.height
//
// const scaleCanvas = await this.doScale(canvas, scale)
// const distBlob = this.toBlob(scaleCanvas)
// if (distBlob.size > this.file.size && this.config.noCompressIfLarger) {
// return {
// dist: this.file,
// width: srcDimension.width,
// height: srcDimension.height
// }
// }
//
// return {
// dist: distBlob,
// width: scaleCanvas.width,
// height: scaleCanvas.height
// }
// }
//
// clear(ctx: CanvasRenderingContext2D, width: number, height: number) {
// // jpeg 没有 alpha 通道,透明区间会被填充成黑色,这里把透明区间填充为白色
// if (this.outputType === defaultType) {
// ctx.fillStyle = '#fff'
// ctx.fillRect(0, 0, width, height)
// } else {
// ctx.clearRect(0, 0, width, height)
// }
// }
//
// /** 通过 file 初始化 image 对象 */
// getOriginImage(): Promise<HTMLImageElement> {
// return new Promise((resolve, reject) => {
// const url = createObjectURL(this.file)
// const img = new Image()
// img.onload = () => {
// resolve(img)
// }
// img.onerror = () => {
// reject('image load error')
// }
// img.src = url
// })
// }
//
// getCanvas(img: HTMLImageElement): Promise<HTMLCanvasElement> {
// return new Promise((resolve, reject) => {
// const canvas = document.createElement('canvas')
// const context = canvas.getContext('2d')
//
// if (!context) {
// reject(new QiniuError(
// QiniuErrorName.GetCanvasContextFailed,
// 'context is null'
// ))
// return
// }
//
// const {width, height} = img
// canvas.height = height
// canvas.width = width
//
// this.clear(context, width, height)
// context.drawImage(img, 0, 0)
// resolve(canvas)
// })
// }
//
// async doScale(source: HTMLCanvasElement, scale: number) {
// if (scale === 1) {
// return source
// }
// // 不要一次性画图,通过设定的 step 次数,渐进式的画图,这样可以增加图片的清晰度,防止一次性画图导致的像素丢失严重
// const sctx = source.getContext('2d')
// const steps = Math.min(maxSteps, Math.ceil((1 / scale) / scaleFactor))
//
// const factor = scale ** (1 / steps)
//
// const mirror = document.createElement('canvas')
// const mctx = mirror.getContext('2d')
//
// let {width, height} = source
// const originWidth = width
// const originHeight = height
// mirror.width = width
// mirror.height = height
// if (!mctx || !sctx) {
// throw new QiniuError(
// QiniuErrorName.GetCanvasContextFailed,
// "mctx or sctx can't be null"
// )
// }
//
// let src!: CanvasImageSource
// let context!: CanvasRenderingContext2D
// for (let i = 0; i < steps; i++) {
//
// let dw = width * factor | 0 // eslint-disable-line no-bitwise
// let dh = height * factor | 0 // eslint-disable-line no-bitwise
// // 到最后一步的时候 dw, dh 用目标缩放尺寸,否则会出现最后尺寸偏小的情况
// if (i === steps - 1) {
// dw = originWidth * scale
// dh = originHeight * scale
// }
//
// if (i % 2 === 0) {
// src = source
// context = mctx
// } else {
// src = mirror
// context = sctx
// }
// // 每次画前都清空,避免图像重叠
// this.clear(context, width, height)
// context.drawImage(src, 0, 0, width, height, 0, 0, dw, dh)
// width = dw
// height = dh
// }
//
// const canvas = src === source ? mirror : source
// // save data
// const data = context.getImageData(0, 0, width, height)
//
// // resize
// canvas.width = width
// canvas.height = height
//
// // store image data
// context.putImageData(data, 0, 0)
//
// return canvas
// }
//
// /** 这里把 base64 字符串转为 blob 对象 */
// toBlob(result: HTMLCanvasElement) {
// const dataURL = result.toDataURL(this.outputType, this.config.quality)
// const buffer = atob(dataURL.split(',')[1]).split('').map(char => char.charCodeAt(0))
// const blob = new Blob([new Uint8Array(buffer)], {type: this.outputType})
// return blob
// }
// }
//
// const compressImage = (file: File, options: CompressOptions) => new Compress(file, options).process()
//
// export default compressImage

@ -0,0 +1,61 @@
import Logger from '../logger'
import { regionUphostMap } from '../config'
import { Config, DEFAULT_CHUNK_SIZE, InternalConfig } from '../upload'
export function normalizeUploadConfig(config?: Partial<Config>, logger?: Logger): InternalConfig {
const { upprotocol, uphost, ...otherConfig } = { ...config }
const normalizeConfig: InternalConfig = {
uphost: [],
retryCount: 3,
checkByMD5: false,
forceDirect: false,
useCdnDomain: true,
checkByServer: false,
concurrentRequestLimit: 3,
chunkSize: DEFAULT_CHUNK_SIZE,
upprotocol: 'https',
debugLogLevel: 'OFF',
disableStatisticsReport: false,
...otherConfig
}
// 兼容原来的 http: https: 的写法
if (upprotocol) {
normalizeConfig.upprotocol = upprotocol
.replace(/:$/, '') as InternalConfig['upprotocol']
}
const hostList: string[] = []
if (logger && config?.uphost != null && config?.region != null) {
logger.warn('do not use both the uphost and region config.')
}
// 如果同时指定了 uphost 参数,添加到可用 host 列表
if (uphost) {
if (Array.isArray(uphost)) {
hostList.push(...uphost)
} else {
hostList.push(uphost)
}
// 否则如果用户传了 region,添加指定 region 的 host 到可用 host 列表
} else if (normalizeConfig?.region) {
const hostMap = regionUphostMap[normalizeConfig?.region]
if (normalizeConfig.useCdnDomain) {
hostList.push(...hostMap.cdnUphost)
} else {
hostList.push(...hostMap.srcUphost)
}
}
return {
...normalizeConfig,
uphost: hostList.filter(Boolean)
}
}

@ -0,0 +1,90 @@
/* eslint-disable no-bitwise */
import { MB } from './helper'
/**
* class
* https://github.com/Stuk/jszip/blob/d4702a70834bd953d4c2d0bc155fad795076631a/lib/crc32.js
* `>>> 0`
*/
export class CRC32 {
private crc = -1
private table = this.makeTable()
private makeTable() {
const table = new Array<number>()
for (let i = 0; i < 256; i++) {
let t = i
for (let j = 0; j < 8; j++) {
if (t & 1) {
// IEEE 标准
t = (t >>> 1) ^ 0xEDB88320
} else {
t >>>= 1
}
}
table[i] = t
}
return table
}
private append(data: Uint8Array) {
let crc = this.crc
for (let offset = 0; offset < data.byteLength; offset++) {
crc = (crc >>> 8) ^ this.table[(crc ^ data[offset]) & 0xFF]
}
this.crc = crc
}
private compute() {
return (this.crc ^ -1) >>> 0
}
private async readAsUint8Array(file: File | Blob): Promise<Uint8Array> {
if (typeof file.arrayBuffer === 'function') {
return new Uint8Array(await file.arrayBuffer())
}
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => {
if (reader.result == null) {
reject()
return
}
if (typeof reader.result === 'string') {
reject()
return
}
resolve(new Uint8Array(reader.result))
}
reader.readAsArrayBuffer(file)
})
}
async file(file: File): Promise<number> {
if (file.size <= MB) {
this.append(await this.readAsUint8Array(file))
return this.compute()
}
const count = Math.ceil(file.size / MB)
for (let index = 0; index < count; index++) {
const start = index * MB
const end = index === (count - 1) ? file.size : start + MB
// eslint-disable-next-line no-await-in-loop
const chuck = await this.readAsUint8Array(file.slice(start, end))
this.append(new Uint8Array(chuck))
}
return this.compute()
}
static file(file: File): Promise<number> {
const crc = new CRC32()
return crc.file(file)
}
}

@ -0,0 +1,346 @@
import {SparkMD5} from './spark-md5'
import {
QiniuError,
QiniuErrorName,
QiniuNetworkError,
QiniuRequestError
} from '../errors'
import {LocalInfo, Progress} from '../upload'
import Logger from '../logger'
import {urlSafeBase64Decode} from './base64'
export const MB = 1024 ** 2
// 文件分块
export function getChunks(file: File, blockSize: number): Blob[] {
let chunkByteSize = blockSize * MB // 转换为字节
// 如果 chunkByteSize 比文件大,则直接取文件的大小
if (chunkByteSize > file.size) {
chunkByteSize = file.size
} else {
// 因为最多 10000 chunk,所以如果 chunkSize 不符合则把每片 chunk 大小扩大两倍
while (file.size > chunkByteSize * 10000) {
chunkByteSize *= 2
}
}
const chunks: Blob[] = []
const count = Math.ceil(file.size / chunkByteSize)
for (let i = 0; i < count; i++) {
const chunk = file.slice(
chunkByteSize * i,
i === count - 1 ? file.size : chunkByteSize * (i + 1)
)
chunks.push(chunk)
}
return chunks
}
export function isMetaDataValid(params: { [key: string]: string }) {
return Object.keys(params).every(key => key.indexOf('x-qn-meta-') === 0)
}
export function isCustomVarsValid(params: { [key: string]: string }) {
return Object.keys(params).every(key => key.indexOf('x:') === 0)
}
export function sum(list: number[]) {
return list.reduce((data, loaded) => data + loaded, 0)
}
export function setLocalFileInfo(localKey: string, info: LocalInfo, logger: Logger) {
try {
localStorage.setItem(localKey, JSON.stringify(info))
} catch (err) {
logger.warn(new QiniuError(
QiniuErrorName.WriteCacheFailed,
`setLocalFileInfo failed: ${localKey}`
))
}
}
export function createLocalKey(name: string, key: string | null | undefined, size: number): string {
const localKey = key == null ? '_' : `_key_${key}_`
return `qiniu_js_sdk_upload_file_name_${name}${localKey}size_${size}`
}
export function removeLocalFileInfo(localKey: string, logger: Logger) {
try {
localStorage.removeItem(localKey)
} catch (err) {
logger.warn(new QiniuError(
QiniuErrorName.RemoveCacheFailed,
`removeLocalFileInfo failed. key: ${localKey}`
))
}
}
export function getLocalFileInfo(localKey: string, logger: Logger): LocalInfo | null {
let localInfoString: string | null = null
try {
localInfoString = localStorage.getItem(localKey)
} catch {
logger.warn(new QiniuError(
QiniuErrorName.ReadCacheFailed,
`getLocalFileInfo failed. key: ${localKey}`
))
}
if (localInfoString == null) {
return null
}
let localInfo: LocalInfo | null = null
try {
localInfo = JSON.parse(localInfoString)
} catch {
// 本地信息已被破坏,直接删除
removeLocalFileInfo(localKey, logger)
logger.warn(new QiniuError(
QiniuErrorName.InvalidCacheData,
`getLocalFileInfo failed to parse. key: ${localKey}`
))
}
return localInfo
}
export function getAuthHeaders(token: string) {
const auth = 'UpToken ' + token
return {Authorization: auth}
}
export function getHeadersForChunkUpload(token: string): Record<string, string> {
const header = getAuthHeaders(token)
return {
'content-type': 'application/octet-stream',
...header
}
}
export function getHeadersForMkFile(token: string) {
const header = getAuthHeaders(token)
return {
'content-type': 'application/json',
...header
}
}
export function createXHR(): XMLHttpRequest {
if (typeof XMLHttpRequest === "function") {
return new XMLHttpRequest()
}
throw new QiniuError(
QiniuErrorName.NotAvailableXMLHttpRequest,
'the current environment does not support.'
)
}
export async function computeMd5(data: Blob): Promise<string> {
const buffer = await readAsArrayBuffer(data)
const spark = new SparkMD5()
spark.append(buffer)
return spark.end()
}
export function readAsArrayBuffer(data: Blob): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
const reader = new FileReader()
// evt 类型目前存在问题 https://github.com/Microsoft/TypeScript/issues/4163
reader.onload = (evt: ProgressEvent<FileReader>) => {
if (evt.target) {
const body = evt.target.result
resolve(body as ArrayBuffer)
} else {
reject(new QiniuError(
QiniuErrorName.InvalidProgressEventTarget,
'progress event target is undefined'
))
}
}
reader.onerror = () => {
reject(new QiniuError(
QiniuErrorName.FileReaderReadFailed,
'fileReader read failed'
))
}
reader.readAsArrayBuffer(data)
})
}
export interface ResponseSuccess<T> {
data: T
reqId: string
}
export type XHRHandler = (xhr: XMLHttpRequest) => void
export interface RequestOptions {
method: string
onProgress?: (data: Progress) => void
onCreate?: XHRHandler
body?: XMLHttpRequestBodyInit | null
headers?: { [key: string]: string }
}
export type Response<T> = Promise<ResponseSuccess<T>>
export function request<T>(url: string, options: RequestOptions): Response<T> {
return new Promise((resolve, reject) => {
const xhr = createXHR()
xhr.open(options.method, url)
if (options.onCreate) {
options.onCreate(xhr)
}
if (options.headers) {
const headers = options.headers
Object.keys(headers).forEach(k => {
xhr.setRequestHeader(k, headers[k])
})
}
xhr.upload.addEventListener('progress', (evt: ProgressEvent) => {
if (evt.lengthComputable && options.onProgress) {
options.onProgress({
loaded: evt.loaded,
total: evt.total
})
}
})
xhr.onreadystatechange = () => {
const responseText = xhr.responseText
if (xhr.readyState !== 4) {
return
}
const reqId = xhr.getResponseHeader('x-reqId') || ''
if (xhr.status === 0) {
// 发生 0 基本都是网络错误,常见的比如跨域、断网、host 解析失败、系统拦截等等
reject(new QiniuNetworkError('network error.', reqId))
return
}
if (xhr.status !== 200) {
let message = `xhr request failed, code: ${xhr.status}`
if (responseText) {
message += ` response: ${responseText}`
}
let data
try {
data = JSON.parse(responseText)
} catch {
// 无需处理该错误、可能拿到非 json 格式的响应是预期的
}
reject(new QiniuRequestError(xhr.status, reqId, message, data))
return
}
try {
resolve({
data: JSON.parse(responseText),
reqId
})
} catch (err) {
reject(err)
}
}
xhr.send(options.body)
})
}
export function getPortFromUrl(url: string | undefined) {
if (url && url.match) {
let groups = url.match(/(^https?)/)
if (!groups) {
return ''
}
const type = groups[1]
groups = url.match(/^https?:\/\/([^:^/]*):(\d*)/)
if (groups) {
return groups[2]
}
if (type === 'http') {
return '80'
}
return '443'
}
return ''
}
export function getDomainFromUrl(url: string | undefined): string {
if (url && url.match) {
const groups = url.match(/^https?:\/\/([^:^/]*)/)
return groups ? groups[1] : ''
}
return ''
}
// 非标准的 PutPolicy
interface PutPolicy {
assessKey: string
bucketName: string
scope: string
}
/**
* @param token
* @throws {QiniuError}
*/
export function getPutPolicy(token: string): PutPolicy {
if (!token) throw new QiniuError(QiniuErrorName.InvalidToken, 'invalid token.')
const segments = token.split(':')
if (segments.length === 1) throw new QiniuError(QiniuErrorName.InvalidToken, 'invalid token segments.')
// token 构造的差异参考:https://github.com/qbox/product/blob/master/kodo/auths/UpToken.md#admin-uptoken-authorization
const assessKey = segments.length > 3 ? segments[1] : segments[0]
if (!assessKey) throw new QiniuError(QiniuErrorName.InvalidToken, 'missing assess key field.')
let putPolicy: PutPolicy | null = null
try {
putPolicy = JSON.parse(urlSafeBase64Decode(segments[segments.length - 1]))
} catch (error) {
throw new QiniuError(QiniuErrorName.InvalidToken, 'token parse failed.')
}
if (putPolicy == null) {
throw new QiniuError(QiniuErrorName.InvalidToken, 'putPolicy is null.')
}
if (putPolicy.scope == null) {
throw new QiniuError(QiniuErrorName.InvalidToken, 'scope field is null.')
}
const bucketName = putPolicy.scope.split(':')[0]
if (!bucketName) {
throw new QiniuError(QiniuErrorName.InvalidToken, 'resolve bucketName failed.')
}
return {assessKey, bucketName, scope: putPolicy.scope}
}
export function createObjectURL(file: File) {
// @ts-ignore
const URL = window.URL || window.webkitURL || window.mozURL
// FIXME: 需要 revokeObjectURL
return URL.createObjectURL(file)
}

@ -0,0 +1,8 @@
export * from './pool'
export * from './observable'
export * from './base64'
export * from './helper'
export * from './config'
// export {default as compressImage, type CompressResult} from './compress'

@ -0,0 +1,151 @@
/** 消费者接口 */
export interface IObserver<T, E, C> {
/** 用来接收 Observable 中的 next 类型通知 */
next: (value: T) => void
/** 用来接收 Observable 中的 error 类型通知 */
error: (err: E) => void
/** 用来接收 Observable 中的 complete 类型通知 */
complete: (res: C) => void
}
export interface NextObserver<T, E, C> {
next: (value: T) => void
error?: (err: E) => void
complete?: (res: C) => void
}
export interface ErrorObserver<T, E, C> {
next?: (value: T) => void
error: (err: E) => void
complete?: (res: C) => void
}
export interface CompletionObserver<T, E, C> {
next?: (value: T) => void
error?: (err: E) => void
complete: (res: C) => void
}
export type PartialObserver<T, E, C> = NextObserver<T, E, C> | ErrorObserver<T, E, C> | CompletionObserver<T, E, C>
export interface IUnsubscribable {
/** 取消 observer 的订阅 */
unsubscribe(): void
}
/** Subscription 的接口 */
export interface ISubscriptionLike extends IUnsubscribable {
readonly closed: boolean
}
export type TeardownLogic = () => void
export interface ISubscribable<T, E, C> {
subscribe(
observer?: PartialObserver<T, E, C> | ((value: T) => void),
error?: (error: any) => void,
complete?: () => void
): IUnsubscribable
}
/** 表示可清理的资源,比如 Observable 的执行 */
class Subscription implements ISubscriptionLike {
/** 用来标示该 Subscription 是否被取消订阅的标示位 */
public closed = false
/** 清理 subscription 持有的资源 */
private _unsubscribe: TeardownLogic | undefined
/** 取消 observer 的订阅 */
unsubscribe() {
if (this.closed) {
return
}
this.closed = true
if (this._unsubscribe) {
this._unsubscribe()
}
}
/** 添加一个 tear down 在该 Subscription 的 unsubscribe() 期间调用 */
add(teardown: TeardownLogic) {
this._unsubscribe = teardown
}
}
/**
* Observer Subscription Observer Observable API
* Observers Subscriber便 Subscription unsubscribe
*/
export class Subscriber<T, E, C> extends Subscription implements IObserver<T, E, C> {
protected isStopped = false
protected destination: Partial<IObserver<T, E, C>>
constructor(
observerOrNext?: PartialObserver<T, E, C> | ((value: T) => void) | null,
error?: ((err: E) => void) | null,
complete?: ((res: C) => void) | null
) {
super()
if (observerOrNext && typeof observerOrNext === 'object') {
this.destination = observerOrNext
} else {
this.destination = {
...observerOrNext && { next: observerOrNext },
...error && { error },
...complete && { complete }
}
}
}
unsubscribe(): void {
if (this.closed) {
return
}
this.isStopped = true
super.unsubscribe()
}
next(value: T) {
if (!this.isStopped && this.destination.next) {
this.destination.next(value)
}
}
error(err: E) {
if (!this.isStopped && this.destination.error) {
this.isStopped = true
this.destination.error(err)
}
}
complete(result: C) {
if (!this.isStopped && this.destination.complete) {
this.isStopped = true
this.destination.complete(result)
}
}
}
/** 可观察对象,当前的上传事件的集合 */
export class Observable<T, E, C> implements ISubscribable<T, E, C> {
constructor(private _subscribe: (subscriber: Subscriber<T, E, C>) => TeardownLogic) {}
subscribe(observer: PartialObserver<T, E, C>): Subscription
subscribe(next: null | undefined, error: null | undefined, complete: (res: C) => void): Subscription
subscribe(next: null | undefined, error: (error: E) => void, complete?: (res: C) => void): Subscription
subscribe(next: (value: T) => void, error: null | undefined, complete: (res: C) => void): Subscription
subscribe(
observerOrNext?: PartialObserver<T, E, C> | ((value: T) => void) | null,
error?: ((err: E) => void) | null,
complete?: ((res: C) => void) | null
): Subscription {
const sink = new Subscriber(observerOrNext, error, complete)
sink.add(this._subscribe(sink))
return sink
}
}

@ -0,0 +1,53 @@
export type RunTask<T> = (...args: T[]) => Promise<void>
export interface QueueContent<T> {
task: T
resolve: () => void
reject: (err?: any) => void
}
export class Pool<T> {
aborted = false
queue: Array<QueueContent<T>> = []
processing: Array<QueueContent<T>> = []
constructor(private runTask: RunTask<T>, private limit: number) {}
enqueue(task: T) {
return new Promise<void>((resolve, reject) => {
this.queue.push({
task,
resolve,
reject
})
this.check()
})
}
private run(item: QueueContent<T>) {
this.queue = this.queue.filter(v => v !== item)
this.processing.push(item)
this.runTask(item.task).then(
() => {
this.processing = this.processing.filter(v => v !== item)
item.resolve()
this.check()
},
err => item.reject(err)
)
}
private check() {
if (this.aborted) return
const processingNum = this.processing.length
const availableNum = this.limit - processingNum
this.queue.slice(0, availableNum).forEach(item => {
this.run(item)
})
}
abort() {
this.queue = []
this.aborted = true
}
}

@ -0,0 +1,348 @@
// https://www.npmjs.com/package/spark-md5
export interface State {
buff: string;
length: number;
hash: number[];
}
/**
* SparkMD5 OOP implementation for array buffers.
*
* Use this class to perform an incremental md5 ONLY for array buffers.
*/
export class SparkMD5 {
_buff: Uint8Array = new Uint8Array(0);
_length: number = 0;
_hash: number[] = [];
constructor() {
this.reset();
}
/**
* Appends an array buffer.
*
* @param {ArrayBuffer} arr The array to be appended
*
* @return {SparkMD5.ArrayBuffer} The instance itself
*/
append(arr: ArrayBufferLike) {
const buff = concatenateArrayBuffers(this._buff.buffer, arr);
const length = buff.length;
this._length += arr.byteLength;
let i;
for (i = 64; i <= length; i += 64) {
md5cycle(this._hash, md5blk_array(buff.subarray(i - 64, i)));
}
this._buff =
i - 64 < length
? new Uint8Array(buff.buffer.slice(i - 64))
: new Uint8Array(0);
return this;
}
/**
* Finishes the incremental computation, resetting the internal state and
* returning the result.
*
* @param {Boolean} raw True to get the raw string, false to get the hex string
*
* @return {String} The result
*/
end(raw?: boolean) {
const buff = this._buff;
const length = buff.length;
const tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for (let i = 0; i < length; i += 1) {
tail[i >> 2] |= buff[i] << (i % 4 << 3);
}
this._finish(tail, length);
let ret = hex(this._hash);
if (raw) {
ret = hexToBinaryString(ret);
}
this.reset();
return ret;
}
/**
* Resets the internal state of the computation.
*/
reset() {
this._buff = new Uint8Array(0);
this._length = 0;
this._hash = [1732584193, -271733879, -1732584194, 271733878];
}
/**
* Gets the internal state of the computation.
*
* @return {Object} The state
*/
getState(): State {
return {
buff: arrayBuffer2Utf8Str(this._buff), // Convert buffer to a string
length: this._length,
hash: this._hash.slice(),
};
}
/**
* Releases memory used by the incremental buffer and other additional
* resources. If you plan to use the instance again, use reset instead.
*/
destroy() {
// delete this._hash;
// delete this._buff;
// delete this._length;
this.reset();
}
/**
* Finish the final calculation based on the tail.
*
* @param {Array} tail The tail (will be modified)
* @param {Number} length The length of the remaining buffer
*/
_finish(tail: number[], length: number) {
let i = length;
tail[i >> 2] |= 0x80 << (i % 4 << 3);
if (i > 55) {
md5cycle(this._hash, tail);
for (i = 0; i < 16; i += 1) {
tail[i] = 0;
}
}
// Do the final computation based on the tail and length
// Beware that the final length may not fit in 32 bits so we take care of that
const tmp = this._length * 8;
const arr = tmp.toString(16).match(/(.*?)(.{0,8})$/)!;
const lo = parseInt(arr[2], 16);
const hi = parseInt(arr[1], 16) || 0;
tail[14] = lo;
tail[15] = hi;
md5cycle(this._hash, tail);
}
}
function md5cycle(x: number[], k: number[]) {
let a = x[0];
let b = x[1];
let c = x[2];
let d = x[3];
a += (((b & c) | (~b & d)) + k[0] - 680876936) | 0;
a = (((a << 7) | (a >>> 25)) + b) | 0;
d += (((a & b) | (~a & c)) + k[1] - 389564586) | 0;
d = (((d << 12) | (d >>> 20)) + a) | 0;
c += (((d & a) | (~d & b)) + k[2] + 606105819) | 0;
c = (((c << 17) | (c >>> 15)) + d) | 0;
b += (((c & d) | (~c & a)) + k[3] - 1044525330) | 0;
b = (((b << 22) | (b >>> 10)) + c) | 0;
a += (((b & c) | (~b & d)) + k[4] - 176418897) | 0;
a = (((a << 7) | (a >>> 25)) + b) | 0;
d += (((a & b) | (~a & c)) + k[5] + 1200080426) | 0;
d = (((d << 12) | (d >>> 20)) + a) | 0;
c += (((d & a) | (~d & b)) + k[6] - 1473231341) | 0;
c = (((c << 17) | (c >>> 15)) + d) | 0;
b += (((c & d) | (~c & a)) + k[7] - 45705983) | 0;
b = (((b << 22) | (b >>> 10)) + c) | 0;
a += (((b & c) | (~b & d)) + k[8] + 1770035416) | 0;
a = (((a << 7) | (a >>> 25)) + b) | 0;
d += (((a & b) | (~a & c)) + k[9] - 1958414417) | 0;
d = (((d << 12) | (d >>> 20)) + a) | 0;
c += (((d & a) | (~d & b)) + k[10] - 42063) | 0;
c = (((c << 17) | (c >>> 15)) + d) | 0;
b += (((c & d) | (~c & a)) + k[11] - 1990404162) | 0;
b = (((b << 22) | (b >>> 10)) + c) | 0;
a += (((b & c) | (~b & d)) + k[12] + 1804603682) | 0;
a = (((a << 7) | (a >>> 25)) + b) | 0;
d += (((a & b) | (~a & c)) + k[13] - 40341101) | 0;
d = (((d << 12) | (d >>> 20)) + a) | 0;
c += (((d & a) | (~d & b)) + k[14] - 1502002290) | 0;
c = (((c << 17) | (c >>> 15)) + d) | 0;
b += (((c & d) | (~c & a)) + k[15] + 1236535329) | 0;
b = (((b << 22) | (b >>> 10)) + c) | 0;
a += (((b & d) | (c & ~d)) + k[1] - 165796510) | 0;
a = (((a << 5) | (a >>> 27)) + b) | 0;
d += (((a & c) | (b & ~c)) + k[6] - 1069501632) | 0;
d = (((d << 9) | (d >>> 23)) + a) | 0;
c += (((d & b) | (a & ~b)) + k[11] + 643717713) | 0;
c = (((c << 14) | (c >>> 18)) + d) | 0;
b += (((c & a) | (d & ~a)) + k[0] - 373897302) | 0;
b = (((b << 20) | (b >>> 12)) + c) | 0;
a += (((b & d) | (c & ~d)) + k[5] - 701558691) | 0;
a = (((a << 5) | (a >>> 27)) + b) | 0;
d += (((a & c) | (b & ~c)) + k[10] + 38016083) | 0;
d = (((d << 9) | (d >>> 23)) + a) | 0;
c += (((d & b) | (a & ~b)) + k[15] - 660478335) | 0;
c = (((c << 14) | (c >>> 18)) + d) | 0;
b += (((c & a) | (d & ~a)) + k[4] - 405537848) | 0;
b = (((b << 20) | (b >>> 12)) + c) | 0;
a += (((b & d) | (c & ~d)) + k[9] + 568446438) | 0;
a = (((a << 5) | (a >>> 27)) + b) | 0;
d += (((a & c) | (b & ~c)) + k[14] - 1019803690) | 0;
d = (((d << 9) | (d >>> 23)) + a) | 0;
c += (((d & b) | (a & ~b)) + k[3] - 187363961) | 0;
c = (((c << 14) | (c >>> 18)) + d) | 0;
b += (((c & a) | (d & ~a)) + k[8] + 1163531501) | 0;
b = (((b << 20) | (b >>> 12)) + c) | 0;
a += (((b & d) | (c & ~d)) + k[13] - 1444681467) | 0;
a = (((a << 5) | (a >>> 27)) + b) | 0;
d += (((a & c) | (b & ~c)) + k[2] - 51403784) | 0;
d = (((d << 9) | (d >>> 23)) + a) | 0;
c += (((d & b) | (a & ~b)) + k[7] + 1735328473) | 0;
c = (((c << 14) | (c >>> 18)) + d) | 0;
b += (((c & a) | (d & ~a)) + k[12] - 1926607734) | 0;
b = (((b << 20) | (b >>> 12)) + c) | 0;
a += ((b ^ c ^ d) + k[5] - 378558) | 0;
a = (((a << 4) | (a >>> 28)) + b) | 0;
d += ((a ^ b ^ c) + k[8] - 2022574463) | 0;
d = (((d << 11) | (d >>> 21)) + a) | 0;
c += ((d ^ a ^ b) + k[11] + 1839030562) | 0;
c = (((c << 16) | (c >>> 16)) + d) | 0;
b += ((c ^ d ^ a) + k[14] - 35309556) | 0;
b = (((b << 23) | (b >>> 9)) + c) | 0;
a += ((b ^ c ^ d) + k[1] - 1530992060) | 0;
a = (((a << 4) | (a >>> 28)) + b) | 0;
d += ((a ^ b ^ c) + k[4] + 1272893353) | 0;
d = (((d << 11) | (d >>> 21)) + a) | 0;
c += ((d ^ a ^ b) + k[7] - 155497632) | 0;
c = (((c << 16) | (c >>> 16)) + d) | 0;
b += ((c ^ d ^ a) + k[10] - 1094730640) | 0;
b = (((b << 23) | (b >>> 9)) + c) | 0;
a += ((b ^ c ^ d) + k[13] + 681279174) | 0;
a = (((a << 4) | (a >>> 28)) + b) | 0;
d += ((a ^ b ^ c) + k[0] - 358537222) | 0;
d = (((d << 11) | (d >>> 21)) + a) | 0;
c += ((d ^ a ^ b) + k[3] - 722521979) | 0;
c = (((c << 16) | (c >>> 16)) + d) | 0;
b += ((c ^ d ^ a) + k[6] + 76029189) | 0;
b = (((b << 23) | (b >>> 9)) + c) | 0;
a += ((b ^ c ^ d) + k[9] - 640364487) | 0;
a = (((a << 4) | (a >>> 28)) + b) | 0;
d += ((a ^ b ^ c) + k[12] - 421815835) | 0;
d = (((d << 11) | (d >>> 21)) + a) | 0;
c += ((d ^ a ^ b) + k[15] + 530742520) | 0;
c = (((c << 16) | (c >>> 16)) + d) | 0;
b += ((c ^ d ^ a) + k[2] - 995338651) | 0;
b = (((b << 23) | (b >>> 9)) + c) | 0;
a += ((c ^ (b | ~d)) + k[0] - 198630844) | 0;
a = (((a << 6) | (a >>> 26)) + b) | 0;
d += ((b ^ (a | ~c)) + k[7] + 1126891415) | 0;
d = (((d << 10) | (d >>> 22)) + a) | 0;
c += ((a ^ (d | ~b)) + k[14] - 1416354905) | 0;
c = (((c << 15) | (c >>> 17)) + d) | 0;
b += ((d ^ (c | ~a)) + k[5] - 57434055) | 0;
b = (((b << 21) | (b >>> 11)) + c) | 0;
a += ((c ^ (b | ~d)) + k[12] + 1700485571) | 0;
a = (((a << 6) | (a >>> 26)) + b) | 0;
d += ((b ^ (a | ~c)) + k[3] - 1894986606) | 0;
d = (((d << 10) | (d >>> 22)) + a) | 0;
c += ((a ^ (d | ~b)) + k[10] - 1051523) | 0;
c = (((c << 15) | (c >>> 17)) + d) | 0;
b += ((d ^ (c | ~a)) + k[1] - 2054922799) | 0;
b = (((b << 21) | (b >>> 11)) + c) | 0;
a += ((c ^ (b | ~d)) + k[8] + 1873313359) | 0;
a = (((a << 6) | (a >>> 26)) + b) | 0;
d += ((b ^ (a | ~c)) + k[15] - 30611744) | 0;
d = (((d << 10) | (d >>> 22)) + a) | 0;
c += ((a ^ (d | ~b)) + k[6] - 1560198380) | 0;
c = (((c << 15) | (c >>> 17)) + d) | 0;
b += ((d ^ (c | ~a)) + k[13] + 1309151649) | 0;
b = (((b << 21) | (b >>> 11)) + c) | 0;
a += ((c ^ (b | ~d)) + k[4] - 145523070) | 0;
a = (((a << 6) | (a >>> 26)) + b) | 0;
d += ((b ^ (a | ~c)) + k[11] - 1120210379) | 0;
d = (((d << 10) | (d >>> 22)) + a) | 0;
c += ((a ^ (d | ~b)) + k[2] + 718787259) | 0;
c = (((c << 15) | (c >>> 17)) + d) | 0;
b += ((d ^ (c | ~a)) + k[9] - 343485551) | 0;
b = (((b << 21) | (b >>> 11)) + c) | 0;
x[0] = (a + x[0]) | 0;
x[1] = (b + x[1]) | 0;
x[2] = (c + x[2]) | 0;
x[3] = (d + x[3]) | 0;
}
function md5blk_array(a: Uint8Array) {
const md5blks: number[] = [];
for (let i = 0; i < 64; i += 4) {
md5blks[i >> 2] =
a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24);
}
return md5blks;
}
const hex_chr = [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"a",
"b",
"c",
"d",
"e",
"f",
];
function rhex(n: number): string {
let s = "";
for (let j = 0; j < 4; j += 1) {
s += hex_chr[(n >> (j * 8 + 4)) & 0x0f] + hex_chr[(n >> (j * 8)) & 0x0f];
}
return s;
}
function hex(x: number[]) {
const r = new Array(x.length);
for (let i = 0; i < x.length; i += 1) {
r[i] = rhex(x[i]);
}
return r.join("");
}
function arrayBuffer2Utf8Str(buff: ArrayBufferLike) {
// @ts-ignore
return String.fromCharCode.apply(null, new Uint8Array(buff));
}
function concatenateArrayBuffers(
first: ArrayBufferLike,
second: ArrayBufferLike,
): Uint8Array {
const result = new Uint8Array(first.byteLength + second.byteLength);
result.set(new Uint8Array(first));
result.set(new Uint8Array(second), first.byteLength);
return result;
}
function hexToBinaryString(hex: string) {
const bytes = [];
const length = hex.length;
for (let x = 0; x < length - 1; x += 2) {
bytes.push(parseInt(hex.substring(x, 2), 16));
}
return String.fromCharCode.apply(String, bytes);
}

@ -0,0 +1,181 @@
import {
abortion,
type AbortionContext,
coerce,
isInvalid,
isValid,
SparkMD5,
uniqueId
} from "../shared";
import {report} from "./report";
import {Uploader} from "./uploader";
const registry: Map<TaskId, Reader> = new Map();
export function getReadTasks(): Array<Task> {
return Array.from(registry.values()).map((t) => ({...t.data}));
}
export async function pauseReadTask(id: TaskId, reason?: any): Promise<void> {
return registry.get(id)?.pause(reason);
}
export async function resumeReadTask(id: TaskId): Promise<void> {
return registry.get(id)?.resume();
}
export async function removeReadTask(id: TaskId): Promise<void> {
return registry.get(id)?.destroy();
}
export async function cleanReadTasks(): Promise<void> {
for (const task of registry.values()) {
await task.cleanup();
}
}
/**
* 5
* initialreadingreadendfailedaborted
*/
export class Reader implements AbortionContext {
ctrl: AbortController;
protected constructor(
readonly file: File,
readonly data: Task,
) {
this.ctrl = abortion(this);
this.file = file;
}
get id(): TaskId {
return this.data.id;
}
get status(): TaskStatus {
return this.data.status;
}
get signal(): AbortSignal {
return this.ctrl.signal;
}
static create(file: File, dirid: number): void {
if (!file.type) {
// 有些文件是没有类型的
// TODO 需要更加明确的信息来指明是哪一个文件
void report("app", "error", "未知文件类型");
} else if (!/^(image|video|audio)\//.test(file.type)) {
// 仅支持图片、视频和音频这三种类型
// TODO 需要更加明确的信息来指明是哪一个文件
void report("app", "error", "不支持的文件类型");
} else {
const task = new Reader(file, {
id: uniqueId(),
dirid,
name: file.name,
path: URL.createObjectURL(file),
hash: "<hash>",
mime: file.type,
size: file.size,
status: "initial",
});
registry.set(task.id, task);
queueMicrotask(task.start.bind(task));
}
}
/** 暂停任务 */
pause(reason?: any): void {
if (isValid(this) && !this.signal.aborted) {
this.ctrl.abort(reason ?? new Error("已暂停"));
}
}
/** 恢复任务 */
async resume(): Promise<void> {
if (isInvalid(this)) {
if (this.signal.aborted) {
this.ctrl = abortion(this);
}
await this.start();
}
}
/** 清理任务 */
async cleanup(): Promise<void> {
if (isInvalid(this)) {
await this.destroy();
}
}
/** 直接删除 */
async destroy(): Promise<void> {
registry.delete(this.id);
if (!this.signal.aborted) {
this.ctrl.abort(new Error("删除任务"));
}
if (this.status !== "readend") {
// 如果任务已经完成,文件路径还会被继续使用,
// 所以只有不在该状态下才能够释放内存。
URL.revokeObjectURL(this.data.path);
}
await report("task", "delete", [this.id]);
}
async onabort(_: Event): Promise<void> {
if (isValid(this)) {
await this.report("aborted", {
error: this.signal.reason,
});
}
}
/** 开始解析文件 */
private async start(): Promise<void> {
await this.report("initial");
if (this.data.hash === "<hash>") {
await this.report("reading", {progress: 0});
const spark = new SparkMD5();
let read = 0;
try {
await this.file.stream().pipeTo(
new WritableStream<Uint8Array>({
write: async (chunk) => {
spark.append(chunk);
read += chunk.length;
await this.report("reading", {
progress: Math.min(read / this.file.size, 1),
});
},
}),
{signal: this.signal},
);
} catch (error) {
if (!this.signal.aborted && isValid(this)) {
await this.report("failed", {error});
}
return;
}
this.data.hash = spark.end();
}
await this.report("readend", {progress: 1});
Uploader.create(this.file, this.data, (duplicated: boolean) => {
registry.delete(this.id);
if (!duplicated) return;
return report("app", "error", "任务已经存在了,切勿重复上传哦");
});
}
private async report(
status: TaskStatus,
info?: { progress?: number; error?: any },
): Promise<void> {
const error = coerce(info?.error);
this.data.status = status;
this.data.progress = info?.progress;
this.data.error = error != null ? String(error) : undefined;
await report("task", "sync", {...this.data});
}
}

@ -0,0 +1,96 @@
import {
call,
coerce,
createReplier,
emit,
isRecvData,
isSendData,
on,
once,
uniqueId,
} from "../shared";
const ports: Array<MessagePort> = [];
const resolvers: Map<number, VoidFunction> = new Map();
let online = false
// 处理来自客户端的数据
// @ts-ignore
self.onconnect = (evt: MessageEvent): void => {
const port = evt.ports[0];
// 客户端卸载事件,无需回复
once("app:close", () => {
const index = ports.findIndex((p) => p === port);
index > -1 && ports.splice(index, 1);
})
// 接受客户端消息
port.onmessage = (evt: MessageEvent): void => {
if (isRecvData(evt.data)) {
call(resolvers.get(evt.data.id)); // 忽略来自客户端的结果
} else if (isSendData(evt.data)) {
const {scope, action, data} = evt.data
const reply = createReplier(port, evt.data)
emit(`${scope}:${action}`, data, reply);
} else {
console.warn("[wfs] unknown message event", evt)
}
};
ports.push(port);
void report('app', 'status', online ? 'online' : 'offline')
}
// WebSocket 事件
on("ws:open", () => {
online = true
void report("app", "status", "online")
});
on("ws:close", () => {
online = false
void report("app", "status", "offline")
});
on('ws:file:create', (data: CloudFile) => {
void report('file', 'create', data);
})
on('ws:file:update', (data: CloudFile) => {
void report('file', 'update', data);
})
export function report(scope: "app", action: "config", config: Partial<ApiConfig>): Promise<void>
export function report(scope: "app", action: "status", data: "online" | "offline"): Promise<void>;
export function report(scope: "app", action: "init", tasks: Array<Task>): Promise<void>;
export function report(scope: "app", action: "error", error: any): Promise<void>;
export function report(scope: "task", action: "sync", task: Task): Promise<void>;
export function report(scope: "task", action: "delete", tasks: number[]): Promise<void>;
export function report(scope: 'file', action: "create", file: CloudFile): Promise<void>
export function report(scope: 'file', action: "update", file: CloudFile): Promise<void>
export function report(scope: string, action: string, data?: any): Promise<void> {
const id = uniqueId()
if (scope === "app" && action === "error") {
data = String(coerce(data));
}
return new Promise((resolve) => {
let sends = 0; // 发送次数
let count = 0; // 接收次数
resolvers.set(id, () => {
if (++count >= sends) {
resolvers.delete(id);
resolve();
}
});
queueMicrotask(() => {
for (const port of ports) {
sends++
port.postMessage({id, scope, action, data});
}
});
});
}

@ -0,0 +1,275 @@
// 参考文档链接
// https://www.zhihu.com/tardis/zm/art/162808604?source_id=1003
// https://www.ruanyifeng.com/blog/2017/05/websocket.html
// https://web.dev/articles/websockets-basics?hl=zh-cn
import {emit} from "../shared";
const HEARTBEAT = "heartbeat";
const CLOSE = "close";
const closeTimeout = 5000; // 等待关闭超时
const reconnectInterval = 3000; // 重连间隔
const heartbeatInterval = 60000; // 心跳间隔
const heartbeatTimeout = 50000; // 心跳超时
// WebSocket 对象引用
let socket: WebSocket | undefined;
// 自动重连开关。
//
// 如果客户端网络不可用,我们就只能等到网络可用时
// 才去连接服务器。所以该变量是用来配合网络变化事件,
// 当连接不可用时能否自动重连。
let isReconnect = navigator.onLine;
// 心跳机制
type Heartbeat = {
boot: VoidFunction;
next: VoidFunction;
stop: VoidFunction;
};
let heartbeat: Heartbeat | undefined;
/**
*
*/
export function handleNetworkEvent(status: "on" | "off"): void {
// 只有在网络可用的情况下才支持自动重连
isReconnect = status === "on";
if (status === "on") {
// 理论上,切换成在线状态时,立即发送一个心跳包,
// 这样就可以检测连接是否可以;但是当网络可用时,
// 打开的连接可能还不能立即恢复,所以延迟发送。
setTimeout(() => heartbeat?.boot(), reconnectInterval);
} else {
heartbeat?.stop();
}
}
export function useSocket(cfg: Partial<ApiConfig>): void {
if (cfg.apiUrl && cfg.artifact && cfg.accessToken) {
const uri = new URL(cfg.apiUrl);
uri.protocol = uri.protocol.replace(/^http/, "ws");
uri.pathname = uri.pathname.replace(/\/+$/, "") + `/${cfg.artifact}/notifies`;
openSocket(uri.toString(), cfg.accessToken);
} else if (socket != null) {
closeSocket(socket);
}
}
function createHeartbeat(ws: WebSocket): Heartbeat {
let timeoutTimer: ReturnType<typeof setTimeout> | undefined; // 心跳回复超时器
let heartbeatTimer: ReturnType<typeof setTimeout> | undefined; // 心跳定时器
const boot = () => {
// 引用不存在或被重置
if (socket !== ws) {
closeSocket(ws);
return;
}
// 如果还存在定时器,就说明没有收到心跳回复,
// 我们就可以直接关闭连接。
if (timeoutTimer != null) {
closeSocket(ws);
socket = undefined;
return;
}
// 只有在连接可用时才能发送心跳包
if (ws.readyState === ws.OPEN) {
// 发送心跳包
ws.send(HEARTBEAT);
// 心跳回复超时检测
timeoutTimer = setTimeout(() => {
timeoutTimer = undefined;
closeSocket(ws);
}, heartbeatTimeout);
}
};
const next = () => {
// 引用不存在或被重置
if (socket !== ws) {
closeSocket(ws);
return;
}
// 取消心跳回复超时事件
if (timeoutTimer) {
clearTimeout(timeoutTimer);
timeoutTimer = undefined;
}
// 下次心跳
heartbeatTimer = setTimeout(() => {
heartbeatTimer = undefined;
boot();
}, heartbeatInterval);
};
const stop = () => {
// 取消心跳回复超时事件
if (timeoutTimer) {
clearTimeout(timeoutTimer);
timeoutTimer = undefined;
}
// 取消心跳事件
if (heartbeatTimer) {
clearTimeout(heartbeatTimer);
heartbeatTimer = undefined;
}
};
return {boot, next, stop};
}
// 关闭 WebSocket 连接
function closeSocket(ws: WebSocket, force = false): void {
console.log("closeSocket")
switch (ws.readyState) {
case WebSocket.CONNECTING:
ws.close();
return;
case WebSocket.CLOSING:
case WebSocket.CLOSED:
return;
case WebSocket.OPEN:
if (force) {
ws.close();
return;
}
// 我们尝试通知服务器关闭,
// 如果超过指定时限就强制关闭。
ws.send(CLOSE);
setTimeout(() => {
closeSocket(ws, true);
}, closeTimeout);
}
}
interface NotifyData {
scope: string;
action: string;
data: Record<string, any> | Array<any>;
}
function isNotifyData(v: any): v is NotifyData {
return (
typeof v === "object" &&
!Array.isArray(v) &&
v != null &&
"scope" in v &&
"action" in v &&
"data" in v &&
typeof v.scope === "string" &&
typeof v.action === "string" &&
typeof v.data === "object" &&
v.data != null
);
}
// 打开 WebSocket 连接
function openSocket(url: string, protocol: string): void {
if (socket != null) {
if (
socket.url === url &&
socket.protocol === protocol &&
socket.readyState < WebSocket.CLOSING
) {
// 如果关键参数一致而且连接是可用的,
// 我们就不需要重新创建连接。
return;
}
// 关闭之前的连接
closeSocket(socket);
socket = undefined;
}
// 创建新的连接
const ws = new WebSocket(url, protocol);
const hb = createHeartbeat(ws);
heartbeat = hb;
// Event: WebSocket opened
ws.onopen = (): void => {
if (socket === ws) {
emit("ws:open"); // 通知连接可用
hb.boot(); // 启动心跳检测
} else {
// 运行到这里,说明在 ws 可用之前就被弃用了,
// 我们就发送关闭事件通知服务器关闭这个连接。
closeSocket(ws);
}
};
// Event: WebSocket message received
ws.onmessage = (evt: MessageEvent): void => {
// 假定我们只接受字符串消息,服务器不会发送二进制数据
const lines = (evt.data as string).split("\n");
if (socket !== ws) {
// 代码运行到这里,就说明 ws 被弃用了,此刻若收到关闭命令,
// 我们就主动关闭客户端,相反则通知服务器关闭连接。
if (lines.includes(CLOSE)) {
ws.close();
} else {
closeSocket(ws);
}
return;
}
// 处理消息
for (const line of lines) {
if (line === CLOSE) {
// 当我们收到的消息里面包含了关闭命令时,
// 我们就主动关闭客户端并销毁连接引用。
// 同时抛弃后续数据
ws.close();
socket = undefined;
return;
}
if (line === HEARTBEAT) {
hb.next();
continue;
}
try {
const item = JSON.parse(line);
if (isNotifyData(item)) {
// 派发事件
const {scope, action, data} = item;
emit(`ws:${scope}:${action}`, data);
} else {
emit("ws:message", item);
}
} catch {
emit("ws:message", line);
}
}
};
// Event: WebSocket error
ws.onerror = (error) => {
console.error("WebSocket error:", error);
socket === ws && emit("ws:error", error);
closeSocket(ws); // Close the socket if an error occurs
};
// Event: WebSocket closed
ws.onclose = () => {
console.log("WebSocket connection is closed.");
if (socket === ws) {
emit("ws:close");
}
socket = undefined
// 停止心跳
hb.stop();
// 断线后自动重连
if (isReconnect) {
console.log("Reconnecting...");
setTimeout(function () {
openSocket(url, protocol);
}, reconnectInterval);
}
};
socket = ws;
}

@ -0,0 +1,417 @@
// import {QiniuNetworkError, QiniuRequestError, upload} from './qiniu-js';
// import {type UploadProgress} from "./qiniu-js/upload";
import {
QiniuNetworkError,
QiniuRequestError,
upload,
type UploadProgress,
} from './qiniu'
import {
abortion,
type AbortionContext,
coerce,
isInvalid,
isSameTask,
isValid,
promisify,
} from "../shared";
import type {UploadArgs} from './api'
import {
completeUploadApi,
initiateUploadApi,
simulateQiniuCallback
} from './api'
import {report} from "./report";
const registry: Map<FileHash, Uploader> = new Map();
export function getUploadTasks(): Array<Task> {
return Array.from(registry.values()).reduce<Task[]>(
(l, u) => l.concat(...u.tasks.map((t) => ({...t}))),
[],
);
}
export async function pauseUploadTask(
hash: FileHash,
id: TaskId,
): Promise<void> {
await registry.get(hash)?.pause(id);
}
export async function resumeUploadTask(
hash: FileHash,
id: TaskId,
): Promise<void> {
await registry.get(hash)?.resume(id);
}
export async function removeUploadTask(
hash: FileHash,
id: TaskId,
): Promise<void> {
await registry.get(hash)?.destroy(id);
}
export async function cleanUploadTasks(): Promise<void> {
for (const tu of registry.values()) {
await tu.cleanup();
}
}
/**
*
*/
type JobStatus =
| "initial" // 初始化状态
| "pending" // 等待上传
| "uploading" // 正在上传
| "uploaded" // 上传完成
| "errored" // 上传失败
| "aborted"; // 终止上传
const statuses: Array<JobStatus> = [
"initial", // 初始化状态
"pending", // 等待上传
"uploading", // 正在上传
"uploaded", // 上传完成
"errored", // 上传失败
"aborted", // 终止上传
];
export class Uploader implements AbortionContext {
ctrl: AbortController;
skipUpload?: boolean;
uploadToken?: string;
key?: string;
status: JobStatus;
protected constructor(
readonly file: File,
readonly hash: FileHash,
readonly tasks: Array<Task>,
) {
this.ctrl = abortion(this);
this.status = "initial";
}
get signal(): AbortSignal {
return this.ctrl.signal;
}
get isUploaded(): boolean {
return this.status === "uploaded";
}
get isValid(): boolean {
return statuses.indexOf(this.status) < statuses.indexOf("uploaded");
}
get isInvalid(): boolean {
return statuses.indexOf(this.status) > statuses.indexOf("uploaded");
}
static create(
file: File,
task: Task,
callback: (duplicated: boolean) => void,
): void {
let job = registry.get(task.hash);
if (job == null) {
job = new Uploader(file, task.hash, [task]);
registry.set(job.hash, job);
callback(false);
queueMicrotask(job.start.bind(job));
return;
}
const exists = job.tasks.some((t) => isSameTask(t, task));
if (exists) {
callback(true); // 任务重复
return;
}
job.tasks.push(task);
callback(false);
if (!job.isValid) {
void job.resume(task.id);
}
}
async pause(taskId: TaskId): Promise<void> {
const task = this.tasks.find((t) => t.id === taskId);
if (!task) {
return;
}
// 只能中止状态正常的任务
if (isValid(task)) {
task.status = "aborted";
task.error = "已暂停";
await report("task", "sync", {...task});
}
// 如果没有正常的任务,就中止文件上传
if (this.isValid && !this.signal.aborted && !this.tasks.some(isValid)) {
this.ctrl.abort();
}
}
// 恢复上传任务
async resume(taskId: TaskId): Promise<void> {
// 获取前端任务
const task = this.tasks.find((t) => t.id === taskId);
if (task == null) {
return;
}
// 恢复任务
if (isInvalid(task)) {
task.status = "readend";
task.error = undefined;
task.progress = 1;
await report("task", "sync", {...task});
}
// 确保中断器可用
if (this.signal.aborted) {
this.ctrl = abortion(this);
}
// 启动任务
if (this.isInvalid) {
await this.start();
}
}
// 移除任务
async destroy(taskId: TaskId): Promise<void> {
const index = this.tasks.findIndex((t) => t.id === taskId);
if (index > -1) {
const task = this.tasks.splice(index, 1)[0];
URL.revokeObjectURL(task.path); // 释放内存
await report("task", "delete", [taskId]);
}
// 如果没有正常的任务,就中止文件上传
if (!this.signal.aborted && !this.tasks.some(isValid)) {
this.ctrl.abort();
}
// 若没有前端任务,我们就删除文件上传任务
if (!this.tasks.length) {
registry.delete(this.hash);
}
}
async cleanup() {
let hasValidTask = false;
const deletes: Array<TaskId> = [];
for (let i = this.tasks.length - 1; i >= 0; i--) {
const task = this.tasks[i];
if (!isValid(task)) {
this.tasks.splice(i, 1); // 删除任务
URL.revokeObjectURL(task.path); // 释放内存
deletes.push(task.id);
} else {
hasValidTask = true;
}
}
// 如果没有正常的前端任务,就中止后端任务
if (!this.signal.aborted && !hasValidTask && !this.isInvalid) {
this.ctrl.abort();
}
// 如果所有的前端任务都被删除了,就同时把后端任务也删除掉。
if (!this.tasks.length) {
registry.delete(this.hash);
}
// 广播:通知前端删除任务。
await report("task", "delete", deletes);
}
async onabort(_?: Event): Promise<void> {
await this.report("aborted", {
status: "aborted",
error: this.signal.reason || "已暂停",
});
}
async report(
status: JobStatus,
info: { error?: any; progress?: number; status: TaskStatus },
): Promise<void> {
this.status = status;
let error = coerce(info.error);
if (error != null) error = String(error);
for (const task of this.tasks) {
if (isValid(task)) {
task.status = info.status;
task.error = error;
task.progress = info.progress;
await report("task", "sync", task);
}
}
}
private async start(): Promise<void> {
await this.report("initial", {status: "queuing"});
await this.getUploadOptions();
await this.startUpload();
await this.completeUpload();
}
private async getUploadOptions(): Promise<void> {
if (this.skipUpload || this.uploadToken || this.signal.aborted) {
return;
}
try {
const result = await initiateUploadApi(
this.file.type,
this.hash,
this.signal,
);
this.skipUpload = result.skip;
this.uploadToken = (result as UploadArgs).token;
this.key = (result as UploadArgs).key;
this.signal.throwIfAborted();
} catch (error) {
if (!this.signal.aborted) {
await this.report("errored", {
status: "failed",
error,
});
}
}
}
private async startUpload(): Promise<void> {
// 任务被中止或者状态错误
if (this.signal.aborted || this.isUploaded) {
return;
}
// 跳过文件上传
if (this.skipUpload) {
await this.report("uploaded", {
status: "uploaded",
progress: 1,
});
return;
}
// 没有状态正常的任务,就不需要上传
if (!this.tasks.some(isValid)) {
return;
}
const {resolve, promise} = promisify<void>();
const observable = upload(
this.file,
this.key,
this.uploadToken!,
{
fname: this.file.name, // 文件原文件名
customVars: {"x:md5": this.hash}, // 自定义变量
mimeType: this.file.type, // 文件类型设置
},
{
useCdnDomain: false,
checkByServer: true,
checkByMD5: true,
},
);
const subscription = observable.subscribe({
next: (res: UploadProgress) => {
this.report("uploading", {
status: "uploading",
progress: res.total.percent / 100,
});
},
error: async (error: any) => {
if (this.signal.aborted) {
resolve();
return;
}
// 如果文件已经存在,则模拟七牛云回调,
// 这样就可以保证文件被正确上传。
if (
error instanceof QiniuRequestError &&
error.data?.error === "file exists"
) {
try {
await simulateQiniuCallback(
{
md5: this.hash,
name: this.file.name,
mime: this.file.type,
size: this.file.size,
key: this.key!,
},
this.signal,
);
this.signal.throwIfAborted();
await this.report("uploaded", {
status: "uploaded",
progress: 1,
});
resolve();
return;
} catch (err) {
error = err;
}
}
if (!this.signal.aborted) {
if (error instanceof QiniuNetworkError) {
await this.report("errored", {
status: "failed",
error: "网络异常",
});
} else {
await this.report("errored", {
status: "failed",
error
});
}
}
resolve();
},
complete: async (_: any) => {
// 参数 _ 是七牛云返回我们服务器返回的结果
await this.report("uploaded", {
status: "uploaded",
progress: 1,
});
resolve();
},
});
const dispose = (): void => {
if (!subscription.closed) {
subscription.unsubscribe();
}
};
// 当信号被中止时取消文件上传
this.signal.addEventListener("abort", dispose);
try {
await promise;
} finally {
this.signal.removeEventListener("abort", dispose);
}
}
private async completeUpload(): Promise<void> {
if (this.signal.aborted || !this.isUploaded) {
return;
}
try {
const fileNames: Array<string> = [];
const directories: Array<number> = [];
// const removes: Array<TaskId> = [];
for (const task of this.tasks) {
fileNames.push(task.name);
directories.push(task.dirid);
// removes.push(task.id);
}
await completeUploadApi(this.hash, fileNames, directories, this.signal);
await this.report("uploaded", {status: "completed"});
} catch (error) {
if (!this.signal.aborted) {
await this.report("errored", {status: "failed", error});
}
return;
}
await this.report("uploaded", {
status: "completed",
progress: 1,
});
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

@ -0,0 +1 @@
export * from './vTooltip'

@ -0,0 +1,69 @@
import type {DirectiveBinding, ObjectDirective} from 'vue'
import {alignWithElement} from '../utils'
interface Elm extends Element {
v$tooltip?: HTMLDivElement
}
const getTooltip = (el: Elm): HTMLDivElement => {
if (el.v$tooltip != null) {
return el.v$tooltip
}
const tooltip = document.createElement('div')
tooltip.style.display = "none"
tooltip.classList.add('tooltip')
el.addEventListener("mouseenter", () => {
if (tooltip.parentNode) {
tooltip.style.display = "block"
alignWithElement(el, tooltip, "top-center", [0, -6])
}
})
el.addEventListener("mouseleave", () => {
if (tooltip.parentNode) {
tooltip.style.display = "none"
}
})
el.v$tooltip = tooltip
return tooltip
}
const stringify = (value: any) => {
if (value == null) {
return value
} else if (typeof value === 'string') {
return value.trim()
} else {
return value.toString().trim()
}
}
function destroy(elm: Elm) {
if (elm.v$tooltip) {
elm.v$tooltip.remove()
delete elm.v$tooltip
}
}
function bind(el: Elm, {value}: DirectiveBinding) {
const text = stringify(value)
if (text) {
const tooltip = getTooltip(el)
if (!tooltip.parentNode) {
el.closest('[data-ui-barrier]')?.appendChild(tooltip)
}
tooltip.innerHTML = text
} else {
destroy(el)
}
}
export const vTooltip: ObjectDirective<Elm> = {
mounted: bind,
updated: bind,
beforeUnmount: destroy,
}

@ -0,0 +1 @@
export * from './useTheme'

@ -0,0 +1,91 @@
import {
computed,
ComputedRef,
getCurrentInstance,
getCurrentScope,
inject,
InjectionKey,
onScopeDispose,
provide,
Ref,
ref,
watchEffect
} from 'vue'
export interface UseTheme {
use: Ref<'dark' | 'light' | 'system'>
dark: ComputedRef<boolean>
light: ComputedRef<boolean>
}
const key: InjectionKey<UseTheme | undefined> = Symbol("useTheme")
export function useTheme(): UseTheme {
let instance = inject(key, undefined)
if (instance != null) {
return instance
}
const use = ref<'dark' | 'light' | 'system'>('light')
const matches = ref(false)
const dark = computed(() => {
if (use.value === 'dark') return true
if (use.value === 'light') return false
return matches.value
})
const light = computed(() => {
if (use.value === 'light') return true
if (use.value === 'dark') return false
return !matches.value
})
const handler = (event: MediaQueryListEvent) => {
matches.value = event.matches
}
let mediaQuery: MediaQueryList | undefined
const cleanup = () => {
if (mediaQuery) {
if ('removeEventListener' in mediaQuery) {
mediaQuery.removeEventListener('change', handler)
} else {
// @ts-expect-error deprecated API
mediaQuery.removeListener(handler)
}
}
}
const stopWatch = watchEffect(() => {
if ('matchMedia' in window && typeof window.matchMedia === 'function') {
cleanup()
mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
if ('addEventListener' in mediaQuery) {
mediaQuery.addEventListener('change', handler)
} else {
// @ts-expect-error deprecated API
mediaQuery.addListener(handler)
}
matches.value = mediaQuery.matches
}
})
if (getCurrentScope()) {
onScopeDispose(() => {
stopWatch()
cleanup()
mediaQuery = undefined
})
}
instance = {use, dark, light}
if (getCurrentInstance()) {
provide(key, instance)
}
return instance
}

@ -0,0 +1,225 @@
import {store} from './store'
import {installIcons} from './widgets/VIcons'
import UiContainer from './widgets/UiContainer.vue'
import VDialog from './widgets/UiDialog.vue'
import {
type CSSProperties,
defineCustomElement,
getCurrentInstance,
h,
nextTick,
onBeforeMount,
onMounted,
onUnmounted,
type Prop,
reactive,
ref,
type VNode,
VueElementConstructor,
watch
} from 'vue'
import style from './style.css?inline'
let FileManager: VueElementConstructor
if (!customElements.get("file-manager")) {
FileManager = defineCustomElement({
props: {
select: {
type: String,
validator: v => v == null || v === 'single' || v === 'multiple',
} as Prop<'single' | 'multiple'>,
mime: {
type: String,
validator: v => v == null || v === 'image' || v === 'video' || v === 'all',
} as Prop<FileMime>,
apiUrl: {
type: String,
required: true,
},
artifact: {
type: String,
required: true,
},
accessToken: {
type: String,
required: true,
},
dialog: Boolean,
width: Number,
height: Number,
zIndex: Number
},
emits: [
'close',
'select',
'open',
'fail',
],
setup(props, ctx) {
const shadowRoot = ref<ShadowRoot | null>(null)
const visible = ref(false)
const size = reactive<CSSProperties>({})
watch(() => props.select, value => {
store.mode = value ? 'select' : 'manage'
store.multiple = value === 'multiple'
}, {immediate: true})
watch(() => props.mime, value => {
store.mime = value ?? 'all'
store.lockMime = value != null
}, {immediate: true})
const attachDialog = (root: ShadowRoot) => {
if (!props.dialog) return
const style = document.createElement('style')
style.textContent = `:host{position:fixed;inset:0;z-index:${props.zIndex ?? 100}`
root.insertBefore(style, root.firstChild)
}
const handleWindowResize = () => {
let {width, height} = props
if (width == null || width < 100) {
width = window.innerWidth * 0.8
}
if (height == null || height < 100) {
height = window.innerHeight * 0.8
}
size.width = `${Math.max(width, 875)}px`
size.height = `${Math.max(height, 640)}px`
}
onBeforeMount(handleWindowResize)
onMounted(() => {
const el = getCurrentInstance()?.vnode.el as HTMLElement
const root = el.parentNode as ShadowRoot
installIcons(root)
attachDialog(root)
shadowRoot.value = root
void nextTick(() => {
visible.value = true
})
window.addEventListener('resize', handleWindowResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleWindowResize)
})
const createManager = () => h(UiContainer, {
setup() {
return {
apiUrl: props.apiUrl,
accessToken: props.accessToken,
artifact: props.artifact,
}
},
onClose() {
ctx.emit('close')
},
onSelect(files: CloudFile[]) {
ctx.emit('select', files)
},
onFail(message: string) {
ctx.emit('fail', message)
}
})
return () => {
let child: VNode
if (props.dialog && shadowRoot.value) {
child = h(VDialog, {
barrier: shadowRoot.value as unknown as Element,
visible: visible.value,
class: 'shadow-2xl',
style: size,
rounded: false,
outside: 'shake',
onDismissed() {
ctx.emit('close')
},
onCompleted() {
ctx.emit('open')
}
}, {
default: createManager,
})
} else {
child = createManager()
}
return h('div', {
ref: shadowRoot,
class: ['size-full', {'fixed': props.dialog}]
}, [child])
}
},
styles: [style],
})
customElements.define("file-manager", FileManager)
} else {
FileManager = customElements.get('file-manager') as VueElementConstructor
}
export {
FileManager
}
interface ShowFileManagerOptions {
select?: 'single' | 'multiple',
mime?: FileMime
apiUrl: string
accessToken: string
artifact: string
zIndex?: number // with dialog
width?: number // with dialog
height?: number // with dialog
onClose?: VoidFunction // with dialog
onSelect?: (files: CloudFile[]) => void // with dialog
onOpen?: VoidFunction // with dialog
onFail?: (error: string) => void
}
export function showFileManager(opts: ShowFileManagerOptions) {
let elm: Element | null = new FileManager({
select: opts.select,
mime: opts.mime,
apiUrl: opts.apiUrl,
accessToken: opts.accessToken,
artifact: opts.artifact,
dialog: true,
zIndex: opts.zIndex,
width: opts.width,
height: opts.height,
})
const dismiss = () => {
if (elm != null) {
elm.remove()
elm = null
}
}
elm.addEventListener('close', () => {
opts.onClose?.()
dismiss()
})
elm.addEventListener('select', evt => {
opts.onSelect?.((evt as CustomEvent).detail as CloudFile[])
dismiss()
})
elm.addEventListener('open', () => {
opts.onOpen?.()
})
elm.addEventListener('fail', evt => {
opts.onFail?.((evt as CustomEvent).detail)
})
document.body.appendChild(elm)
return dismiss
}

@ -0,0 +1,38 @@
import {$on} from '../../shared'
import {useFile} from './handlers'
import {store} from './store'
import {toast} from "../widgets/UiToastController.ts";
// 接收上传任务的信息同步命令
$on("task", "sync", (task: Task): void => {
const old = store.tasks.find((t) => t.id === task.id);
if (old == null) {
store.tasks.push(task);
} else {
Object.assign(old, task);
}
});
// 接收上传任务的删除命令
$on("task", "delete", (deletes: Array<TaskId>): void => {
for (const id of deletes) {
const i = store.tasks.findIndex((t) => t.id === id);
i > -1 && store.tasks.splice(i, 1);
}
});
$on('file', 'create', useFile)
$on('file', 'update', useFile)
let online = false
$on('app', 'status', (status) => {
if (status === 'online' && !online) {
online = true
} else if (status === 'offline' && online) {
online = false
toast('warn', '后台不在线,上传文件无法同步显示')
}
})
export {}

@ -0,0 +1,112 @@
import {dispatch} from '../worker.ts'
import {coerce, off, on, sleep} from '../../shared'
import {getCurrentScope, nextTick, onScopeDispose, reactive, watch} from 'vue'
import {store} from './store'
export interface Filer {
loading: boolean
limit: number
page: number
total: number
error?: string
dirId: number
view:
| "error"
| "files"
| "empty"
| "loader"
}
export function useFiler(directoryId: number): Filer {
const filer = reactive<Filer>({
loading: false,
limit: 30,
page: 0,
total: 0,
dirId: directoryId,
error: undefined,
view: "loader",
})
const jump = async (to: number) => {
if (filer.total < 1 || filer.view === "error") {
filer.view = "loader"
}
filer.loading = true
filer.page = to
filer.error = undefined
const mime = store.mime === "all" ? undefined : store.mime
const {limit, page} = filer
try {
const res = await dispatch('file', "list", {
directoryId,
page,
limit,
mime
})
if (filer.page === res.page && filer.limit === res.limit) {
filer.loading = false
filer.total = res.total
filer.view = res.items?.length ? 'files' : 'empty'
store.files[directoryId] = res.items ?? [] // 这里会不会是响应式的
}
} catch (error) {
if (filer.page === page && filer.limit === limit) {
filer.view = "error"
filer.error = String(coerce(error))
filer.loading = false
}
}
}
const refresh = async (): Promise<void> => {
filer.loading = true
if (filer.page == 0) {
filer.page = 1
}
await sleep(300)
await jump(filer.page)
}
const reload = async (): Promise<void> => {
filer.loading = true
filer.view = "loader"
await sleep(300)
await jump(filer.page)
}
const reset = async (): Promise<void> => {
filer.page = 0
filer.total = 0
delete store.files[directoryId]
await jump(1)
}
watch([
() => filer.limit,
() => store.mime,
], reset)
if (getCurrentScope()) {
on(`files:${directoryId}:refresh`, refresh)
on(`files:${directoryId}:reload`, reload)
on(`files:${directoryId}:reset`, reset)
on(`files:${directoryId}:jump`, jump)
on("mime:change", reset)
void nextTick(reset)
onScopeDispose(() => {
off(`files:${directoryId}:refresh`, refresh)
off(`files:${directoryId}:reload`, reload)
off(`files:${directoryId}:reset`, reset)
off(`files:${directoryId}:jump`, jump)
off("mime:change", reset)
delete store.files[directoryId]
})
}
return filer
}

@ -0,0 +1,204 @@
import {toast} from '../widgets/UiToastController'
import {dispatch} from '../worker'
import {store} from './store'
export function getDirectoryEntry(id: number): CloudDirectory | undefined {
if (id === 0) return store.rootDirectory;
return store.directories.get(id);
}
export function hasChildDirectoryEntries(id: number) {
return id > 0 && Array.from(store.directories.values()).some((d) => d.pid === id);
}
export async function refreshDirectories() {
const dirs = await dispatch("dir", "all");
const set = (dirs: CloudDirectory[] | undefined) => {
if (dirs == null) return;
for (const {children, ...dir} of dirs) {
store.directories.set(dir.id, dir);
set(children);
}
};
store.directories.clear()
set(dirs);
if (store.activeDirectoryId > 0 && !store.directories.has(store.activeDirectoryId)) {
store.activeDirectoryId = 0;
}
}
export function reload(): Promise<void> {
return new Promise<void>((resolve) => {
store.activeDirectoryId = 0;
store.files = {};
store.directories.clear();
queueMicrotask(() => {
refreshDirectories()
.catch(err => toast("error", err))
.then(resolve)
});
});
}
export async function uploadFiles(dirid: number, files: FileList | File[] | File): Promise<void> {
if (files instanceof File) {
await dispatch('task', 'create', {dirid, file: files})
} else {
for (const file of files) {
await dispatch('task', "create", {dirid, file})
}
}
}
export function deleteFile(file: CloudFile): void {
const list = store.files[file.directoryId]
const index = list?.findIndex(f => f.id === file.id)
if (list && index != null && index > -1) {
list.splice(index, 1)
}
removeSelected(file.id)
}
export function updateFile(file: CloudFile): void {
const list = store.files[file.directoryId]
const index = list?.findIndex(f => f.id === file.id)
if (list && index != null && index > -1) {
list.splice(index, 1, file)
}
}
export function useFile(list: CloudFile | Array<CloudFile>) {
if (!Array.isArray(list)) {
list = [list]
}
for (let file of list) {
console.log(file.id, file.object.status, file.name)
const files = store.files[file.directoryId] ?? []
const index = files.findIndex(f => f.id === file.id)
if (index === -1) {
files.push(file)
} else if (files[index].object.status < file.object.status) {
files.splice(index, 1, file)
}
store.files[file.directoryId] = files
}
}
export function toggleSelect(file: CloudFile): void {
if (store.mode === 'manage' || store.multiple) {
if (!removeSelected(file.id)) {
store.selected.push({...file})
}
} else if (store.selected.length && store.selected[0].id === file.id) {
store.selected = []
} else {
store.selected = [file]
}
}
export function removeSelected(id: number): boolean {
const index = store.selected.findIndex(f => f.id === id)
if (index > -1) {
store.selected.splice(index, 1)
return true
}
return false
}
// Feature detection. The API needs to be supported
// and the app not run in an iframe.
const supportsFileSystemAccess =
"showOpenFilePicker" in window &&
(() => {
try {
return window.self === window.top;
} catch {
return false;
}
})();
export async function pickFiles(directoryId: number) {
// If the File System Access API is supported…
if (supportsFileSystemAccess) {
try {
// Show the file picker, optionally allowing multiple files.
// @ts-ignore
const handles = await self.showOpenFilePicker({
multiple: true,
excludeAcceptAllOption: true,
types: [
{
description: "图片和视频",
accept: {
"image/*": [
".apng",
".avif",
".gif",
".jpeg", ".jpg", ".jfif", ".pjpeg", ".pjp",
".png",
".webp",
".bmp",
".ico", ".cur",
".tif", ".tiff",
],
"video/*": [
".3gp",
".mpeg", ".mpg",
".mp4", ".m4v", ".m4p",
".ogv", ".ogg",
".mov", ".qt",
".webm",
".avi",
],
}
}
],
});
const files = await Promise.all(
handles.map(async (handle: FileSystemFileHandle) => {
const file = await handle.getFile();
// Add the `FileSystemFileHandle` as `.handle`.
// @ts-ignore
file.handle = handle;
return file;
})
);
await uploadFiles(directoryId, files)
} catch (err) {
// Fail silently if the user has simply canceled the dialog.
if ((err as Error).name !== 'AbortError') {
// console.error(err.name, err.message);
throw err
}
}
return;
}
// Fallback if the File System Access API is not supported.
return new Promise<void>((resolve) => {
const input = document.createElement('input');
input.style.display = 'none';
input.type = 'file';
input.accept = "image/*,video/*"
document.body.append(input);
input.multiple = true;
input.addEventListener('change', () => {
input.remove();
if (!input.files) {
return;
}
uploadFiles(directoryId, input.files)
resolve()
});
// Show the picker.
if ('showPicker' in HTMLInputElement.prototype) {
input.showPicker();
} else {
input.click();
}
});
}

@ -0,0 +1,4 @@
export * from './events'
export * from './filter'
export * from './handlers'
export * from './store'

@ -0,0 +1,52 @@
import {computed, reactive} from 'vue'
export interface Store {
showSidebar: boolean;
showTasksPanel: boolean;
isDragging: boolean;
mime: FileMime
lockMime: boolean
mode: FileMode
multiple: boolean
tasks: Array<Task>;
rootDirectory: CloudDirectory;
directories: Map<number, CloudDirectory>;
activeDirectoryId: number;
// files: Map<number, CloudFile[]>;
files: Record<number, CloudFile[]>;
selected: CloudFile[];
}
export const store = reactive<Store>({
showSidebar: true,
showTasksPanel: false,
isDragging: false,
mime: "all",
lockMime: false,
tasks: [],
mode: 'manage',
multiple: true,
directories: new Map(),
rootDirectory: rootDirectoryEntry(),
activeDirectoryId: 0,
files: {},
selected: [],
});
/** 当前文件视图的关联目录 */
export const activeDirectory = computed(() => {
if (store.activeDirectoryId === 0) {
return store.rootDirectory;
}
return store.directories.get(store.activeDirectoryId);
});
export function rootDirectoryEntry(): CloudDirectory {
return {
id: 0,
pid: -1,
title: "根目录",
createdAt: "",
sort: Number.MAX_SAFE_INTEGER,
};
}

@ -0,0 +1,131 @@
:host,
:root {
font-size: 14px;
}
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer utilities {
.flex-center {
@apply flex flex-col justify-center items-center ;
}
.shadow-pop {
@apply shadow-[0_2px_11px_rgba(0,0,0,0.1),0_3px_6px_rgba(0,0,0,0.05)];
}
.pseudo-border {
@apply before:absolute before:z-10 before:inset-0 ring-1 ring-gray-900/[0.18] before:rounded-2xl before:pointer-events-none;
}
.popup-panel {
@apply rounded-lg overflow-hidden shadow-pop bg-[#f2f1f1]/[0.9] backdrop-blur-[8px] pseudo-border;
}
.tooltip {
@apply text-xs bg-white px-1 py-0.5 fixed top-0 left-0 leading-tight transition-opacity pointer-events-none;
@apply rounded overflow-hidden shadow-pop bg-[#fff]/[0.8] backdrop-blur-[8px] pseudo-border z-[1234];
}
}
[hidden] {
display: none;
}
.spinner {
position: relative;
transform: translate3d(50%, 50%, 0);
}
@keyframes v-loading-spin {
0% {
opacity: 1;
}
100% {
opacity: 0.15;
}
}
.loading-bar {
animation: v-loading-spin 1.2s linear infinite;
border-radius: 6px;
height: 8%;
left: -10%;
position: absolute;
top: -3.9%;
width: 24%;
}
.loading-bar:nth-child(1) {
animation-delay: -1.2s;
transform: rotate(0.0001deg) translate(146%);
}
.loading-bar:nth-child(2) {
animation-delay: -1.1s;
transform: rotate(30deg) translate(146%);
}
.loading-bar:nth-child(3) {
animation-delay: -1s;
transform: rotate(60deg) translate(146%);
}
.loading-bar:nth-child(4) {
animation-delay: -0.9s;
transform: rotate(90deg) translate(146%);
}
.loading-bar:nth-child(5) {
animation-delay: -0.8s;
transform: rotate(120deg) translate(146%);
}
.loading-bar:nth-child(6) {
animation-delay: -0.7s;
transform: rotate(150deg) translate(146%);
}
.loading-bar:nth-child(7) {
animation-delay: -0.6s;
transform: rotate(180deg) translate(146%);
}
.loading-bar:nth-child(8) {
animation-delay: -0.5s;
transform: rotate(210deg) translate(146%);
}
.loading-bar:nth-child(9) {
animation-delay: -0.4s;
transform: rotate(240deg) translate(146%);
}
.loading-bar:nth-child(10) {
animation-delay: -0.3s;
transform: rotate(270deg) translate(146%);
}
.loading-bar:nth-child(11) {
animation-delay: -0.2s;
transform: rotate(300deg) translate(146%);
}
.loading-bar:nth-child(12) {
animation-delay: -0.1s;
transform: rotate(330deg) translate(146%);
}
.v-snake {
--v-snake: 102%;
animation: v-snake 100ms ease-out normal;
}
@keyframes v-snake {
50% {
transform: scale(var(--v-snake, 102%))
}
}

@ -0,0 +1,105 @@
import domAlign, {alignPoint} from "dom-align";
export type Placement =
| 'top-left'
| 'top-center'
| 'top-right'
| 'bottom-left'
| 'bottom-center'
| 'bottom-right'
| 'left-top'
| 'left-center'
| 'left-bottom'
| 'right-top'
| 'right-center'
| 'right-bottom'
// position-align
//
//
// | top-left top-center top-right |
// -------------+------------------------------------------+--------------
// left-top | | right-top
// | |
// left-center | | right-center
// | |
// left-bottom | | right-bottom
// -------------+------------------------------------------+--------------
// | bottom-left bottom-center bottom-right |
//
export const positions: Record<Placement, Placement> = {
// trigger => popup
'top-left': 'bottom-left',
'top-center': 'bottom-center',
'top-right': 'bottom-right',
'bottom-left': 'top-left',
'bottom-center': 'top-center',
'bottom-right': 'top-right',
'left-top': 'right-top',
'left-center': 'right-center',
'left-bottom': 'right-bottom',
'right-top': 'left-top',
'right-center': 'left-center',
'right-bottom': 'left-bottom',
}
export function alignWithElement(
trigger: Element,
popup: HTMLElement,
placement: Placement,
offset: [number, number]
) {
let triggerPoints: string[] = placement.split('-')
let popupPoints: string[] = positions[placement].split('-')
switch (triggerPoints[0]) {
case 'left':
case 'right':
triggerPoints = triggerPoints.reverse()
popupPoints = popupPoints.reverse()
break
}
domAlign(popup, trigger, {
points: [
popupPoints.map(c => c[0]).join(''),
triggerPoints.map(c => c[0]).join(''),
],
offset: offset,
overflow: {
adjustX: true,
adjustY: true,
},
useCssTransform: true,
ignoreShake: true,
})
}
export function alignWithPoint(
popup: HTMLElement,
clientX: number,
clientY: number,
placement: Placement,
offset: [number, number]
) {
let triggerPoints: string[] = placement.split('-')
let popupPoints: string[] = positions[placement].split('-')
switch (triggerPoints[0]) {
case 'left':
case 'right':
triggerPoints = triggerPoints.reverse()
popupPoints = popupPoints.reverse()
break
}
alignPoint(popup, {clientX, clientY}, {
points: [
popupPoints.map(c => c[0]).join(''),
triggerPoints.map(c => c[0]).join(''),
],
offset: offset,
overflow: {
adjustX: true,
adjustY: true,
},
useCssTransform: true,
ignoreShake: true,
})
}

@ -0,0 +1,3 @@
export * from './align'
export * from './preview'
export * from './scroll'

@ -0,0 +1,106 @@
import Hls from "hls.js"
import Viewer from 'viewerjs'
import viewerStyle from "viewerjs/dist/viewer.css?inline"
const viewerStyleId = "wfs-viewer-style"
let video: HTMLVideoElement | undefined
interface PreviewOptions {
path: string
mime: string
thumb: string
name: string
}
// https://juejin.cn/post/7146947008015089672
const startVideoPreview = (path: string, img: HTMLImageElement): VoidFunction => {
video = document.createElement('video');
video.controls = true
video.className = img.className
video.style.cssText = img.style.cssText
.replace("relative", "absolute")
.replace("margin-left", "left")
.replace("margin-top", "top")
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource(path);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function () {
void video?.play();
});
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = 'https://video-dev.github.io/streams/x36xhzz/x36xhzz.m3u8';
video.addEventListener('loadedmetadata', function () {
img.style.opacity = "0"
void video?.play();
});
} else {
alert("浏览器不支持该视频")
}
img.parentNode!.appendChild(video)
const onResize = () => {
if (video != null) {
video.className = img.className
video.style.cssText = img.style.cssText
.replace("relative", "absolute")
.replace("margin-left", "left")
.replace("margin-top", "top")
}
}
onResize()
window.addEventListener("resize", onResize)
return () => {
video?.pause()
video?.remove()
video = undefined
window.removeEventListener("resize", onResize)
}
}
export function showPreview(opts: PreviewOptions): void {
if (!document.querySelector(`#${viewerStyleId}`)) {
const style = document.createElement("style")
style.innerHTML = viewerStyle
style.id = viewerStyleId
document.head.appendChild(style)
}
const isVideo = opts.mime.startsWith("video/")
const img = document.createElement("img")
img.src = isVideo ? opts.thumb : opts.path
let dispose: VoidFunction | undefined
const viewer = new Viewer(img, {
title: [4, (_: any, imageData: any) => `${opts.name} (${imageData.naturalWidth} × ${imageData.naturalHeight})`],
navbar: false,
zoomable: !isVideo,
movable: !isVideo,
backdrop: true,
slideOnTouch: !isVideo,
keyboard: !isVideo,
toolbar: isVideo ? {} : {
flipHorizontal: true,
flipVertical: true,
oneToOne: true,
reset: true,
rotateLeft: true,
rotateRight: true,
zoomIn: true,
zoomOut: true,
},
viewed(event: CustomEvent) {
const el = event.detail.image as HTMLImageElement
isVideo && (dispose = startVideoPreview(opts.path, el))
},
hide() {
dispose?.()
},
})
viewer.show()
}

@ -0,0 +1,58 @@
export function getScrollParent(el?: HTMLElement, includeHidden = false) {
while (el) {
if (includeHidden ? isPotentiallyScrollable(el) : hasScrollbar(el)) return el
el = el.parentElement!
}
return document.scrollingElement as HTMLElement
}
export function getScrollParents(el?: Element | null, stopAt?: Element | null) {
const elements: HTMLElement[] = []
if (stopAt && el && !stopAt.contains(el)) return elements
while (el) {
if (hasScrollbar(el)) elements.push(el as HTMLElement)
if (el === stopAt) break
el = el.parentElement!
}
return elements
}
export function hasScrollbar(el?: Element | null) {
if (!el || el.nodeType !== Node.ELEMENT_NODE) return false
const style = window.getComputedStyle(el)
return style.overflowY === 'scroll' || (style.overflowY === 'auto' && el.scrollHeight > el.clientHeight)
}
function isPotentiallyScrollable(el?: Element | null) {
if (!el || el.nodeType !== Node.ELEMENT_NODE) return false
const style = window.getComputedStyle(el)
return ['scroll', 'auto'].includes(style.overflowY)
}
export function getScrollBarWidth() {
const outer = document.createElement('div');
outer.style.overflow = 'scroll';
outer.style.height = '200px';
outer.style.width = '100px';
outer.style.position = 'fixed'
outer.style.opacity = '0'
outer.style.pointerEvents = 'none'
document.body.appendChild(outer);
const inner = document.createElement('div');
inner.style.width = '100%';
outer.appendChild(inner);
const widthNoScroll = outer.offsetWidth;
const widthWithScroll = inner.offsetWidth;
outer.remove()
return widthNoScroll - widthWithScroll;
}

@ -0,0 +1,63 @@
<script lang="ts" setup>
import UiMistake from './UiMistake.vue'
import VLoading from './VLoading.vue'
import {coerce, sleep} from '../../shared'
import {onMounted, ref} from 'vue'
const props = defineProps<{
setup: () => Promise<void>
}>()
const emit = defineEmits<{
(ev: 'setup'): void
}>()
const rootElmRef = ref<HTMLDivElement | null>(null)
const errorMessage = ref<string>()
const isInitializing = ref(true)
const initialize = async (): Promise<void> => {
try {
errorMessage.value = undefined
await props.setup()
emit('setup')
} catch (error) {
errorMessage.value = String(coerce(error))
} finally {
isInitializing.value = false
}
}
const reinitialize = async () => {
isInitializing.value = true
await sleep(500)
await initialize()
}
onMounted(initialize)
</script>
<template>
<div
ref="rootElmRef"
class="relative size-full !min-w-[52rem] !min-h-[30rem] backdrop-blur-md rounded-lg"
data-ui-barrier
>
<div
class="relative z-0 size-full rounded-lg bg-gray-200/95 dark:bg-gray-700/95 overflow-hidden">
<v-loading
v-if="isInitializing"
class="absolute inset-0"
/>
<UiMistake
v-else-if="errorMessage"
:message="errorMessage"
class="absolute inset-0"
@refresh="reinitialize"
/>
<div v-else class="absolute inset-0">
<slot/>
</div>
</div>
</div>
</template>

@ -0,0 +1,103 @@
<script lang="ts" setup>
import {useTheme} from '../hooks'
import {reload, store, useFiler} from '../store'
import UiBarrier from './UiBarrier.vue'
import UiContextMenu from './UiContextMenu.vue'
import {hideContextMenu, toggleContextMenu} from './UiContextMenuController'
import UiDirectoryNaming from './UiDirectoryNaming.vue'
import {hideDirectoryNaming} from './UiDirectoryNamingController'
import UiDropFeedback from './UiDropFeedback.vue'
import UiFileNaming from './UiFileNaming.vue'
import {hideFileNaming} from './UiFileNamingController'
import UiFiles from './UiFiles.vue'
import UiNavbar from './UiNavbar.vue'
import UiSidebar from './UiSidebar.vue'
import UiSplitter from './UiSplitter.vue'
import UiTasks from './UiTasks.vue'
import {toast} from './UiToastController'
import UiToaster from './UiToaster.vue'
import {dispatch} from '../worker'
import {$on, coerce} from '../../shared'
import {onMounted, onUnmounted} from 'vue'
const props = defineProps<{
setup: () => Awaitable<ApiConfig>
}>()
const emit = defineEmits<{
(ev: 'close'): void
(ev: 'select', files: CloudFile[]): void
(ev: 'fail', message: string): void
}>()
const {dark, light} = useTheme()
const setup = async () => {
try {
const cfg = await props.setup()
await dispatch("cfg", "init", cfg)
await reload()
useFiler(0)
} catch (error) {
emit('fail', String(coerce(error)))
}
}
onMounted(() => {
const dispose = $on('app', 'error', (error: string): void => {
toast('error', error)
})
onUnmounted(() => {
dispose()
hideContextMenu()
hideFileNaming()
hideDirectoryNaming()
store.selected = []
})
})
</script>
<template>
<UiBarrier
:class="{
'ring-white/80': light,
'ring-white/30': dark,
'light': light,
'dark': dark,
}"
:setup="setup"
class="ring-1"
@contextmenu.prevent="toggleContextMenu"
>
<UiSplitter :max="300" :min="162" :size="256">
<template v-slot:secondary>
<UiSidebar/>
</template>
<template v-slot:primary>
<div
class="size-full flex flex-col items-stretch bg-white dark:bg-black">
<UiNavbar
class="bg-gradient-to-b from-white to-gray-100 border-b border-gray-200"
@confirm="$emit('select', store.selected.map(f => ({...f, object: {...f.object}})))"
@dismiss="$emit('close')"
/>
<UiTasks/>
<UiToaster/>
<div class="relative flex-1 overflow-hidden">
<KeepAlive>
<UiFiles
:key="store.activeDirectoryId"
:directory-id="store.activeDirectoryId"
class="absolute inset-0"
/>
</KeepAlive>
</div>
</div>
</template>
</UiSplitter>
<UiContextMenu/>
<UiDirectoryNaming/>
<UiFileNaming/>
<UiDropFeedback/>
</UiBarrier>
</template>

@ -0,0 +1,275 @@
<script lang="ts" setup>
import {pickFiles} from '../store'
import {emit, formatDate, humanSize} from '../../shared'
import {onMounted, ref, watch} from 'vue'
import {alignWithPoint, showPreview} from '../utils'
import VButton from './VButton.vue'
import {
canShare,
controller,
copyDirName,
copyFileName,
copyFilePath,
file,
handleNewDir,
hideContextMenu,
isTarget,
removeDir,
removeFile,
renameDir,
share,
} from './UiContextMenuController'
import {showFileNaming} from './UiFileNamingController'
import UiIconButton from './VIconButton.vue'
import UiText from './VText.vue'
import {getRootBarrier} from './utils'
import {onClickOutside} from "@vueuse/core";
type Status =
| 'forward' // ->
| 'completed' //
| 'reverse' // ->
| 'dismissed' //
const barrier = ref<Node | null>()
const menuElm = ref<HTMLElement | null>(null)
const status = ref<Status>("dismissed")
const visible = ref(controller.visible)
const align = (): void => {
if (menuElm.value) {
alignWithPoint(
menuElm.value,
controller.x,
controller.y,
'bottom-left',
[0, 0],
)
}
}
const handleForward = () => {
emit('contextmenu:shown')
status.value = 'forward'
}
const handleCompleted = () => {
status.value = "completed"
}
const handleReverse = () => {
status.value = "reverse"
}
const handleDismissed = () => {
status.value = "dismissed"
if (controller.visible) {
visible.value = true
align()
} else {
emit('contextmenu:hidden')
hideContextMenu()
}
}
watch(menuElm, align)
watch([
barrier,
() => controller.x,
() => controller.y,
], () => {
// ???
if (visible.value) {
visible.value = false
} else {
align()
}
})
watch(() => controller.visible, (value) => {
visible.value = value
})
//
const handleClick = (_: any): void => {
hideContextMenu()
}
onMounted(() => {
barrier.value = getRootBarrier()
})
onClickOutside(menuElm, hideContextMenu)
</script>
<template>
<Teleport v-if="barrier" :to="barrier">
<div
ref="menuElm"
class="absolute z-[100] top-0 left-0 text-sm empty:hidden"
data-role="contextmenu"
role="menu"
>
<transition
enter-from-class="duration-100 opacity-0 translate-y-4 scale-95 sm:translate-y-0"
enter-to-class="duration-100 opacity-100 translate-y-0 scale-100"
leave-from-class="duration-100 opacity-100 translate-y-0 scale-100"
leave-to-class="duration-100 opacity-0 translate-y-4 scale-95 sm:translate-y-0"
@enter="align"
@before-enter="handleForward"
@after-enter="handleCompleted"
@before-leave="handleReverse"
@after-leave="handleDismissed"
>
<div
v-if="visible"
class="transition-all origin-bottom-left min-w-36 popup-panel relative z-10 flex flex-col items-stretch gap-0.5 p-1.5"
>
<v-button
v-if="isTarget('sidebar', 'directory')"
class="hover:bg-indigo-500 hover:text-white"
icon="ArrowClockwiseSolid"
label="刷新"
@click="handleClick(emit('dirs:refresh'))"
/>
<v-button
v-if="isTarget('content')"
class="hover:bg-indigo-500 hover:text-white"
icon="ArrowClockwiseSolid"
label="刷新"
@click="handleClick(emit(`files:${controller.directoryId ?? 0}:reload`))"
/>
<div class="my-0.5 mx-2.5 border-t border-slate-300 first:hidden"/>
<v-button
v-if="isTarget('directory', 'content')"
class="hover:bg-indigo-500 hover:text-white"
icon="FileAdd"
label="上传文件"
@click="handleClick(pickFiles(controller.directoryId ?? 0))"
/>
<v-button
v-if="isTarget('sidebar')"
class="hover:bg-indigo-500 hover:text-white"
icon="FolderAdd"
label="新建文件夹"
@click="handleClick(handleNewDir(false))"
/>
<!-- 与文件夹相关的选项 -->
<template
v-if="isTarget('directory') && (controller.directoryId ?? 0) > 0">
<v-button
class="hover:bg-indigo-500 hover:text-white"
icon="FolderAdd"
label="新建子文件夹"
@click="handleClick(handleNewDir(true))"
/>
<v-button
class="hover:bg-indigo-500 hover:text-white"
icon="Rename"
label="重命名"
@click="handleClick(renameDir())"
/>
<v-button
class="hover:bg-indigo-500 hover:text-white"
icon="Copy"
label="复制名称"
@click="handleClick(copyDirName())"
/>
<div class="my-0.5 mx-2.5 border-t border-slate-300"/>
<v-button
class="hover:bg-red-500 hover:text-white"
icon="Delete"
label="删除"
@click="handleClick(removeDir())"
/>
</template>
<!-- 与文件相关的选项 -->
<div
v-if="isTarget('file', 'filename') && file"
class="w-60 flex flex-col items-stretch"
>
<div class="flex items-center gap-1 p-1.5">
<UiIconButton
icon="Eye"
primary
tooltip="预览文件"
@click="handleClick(showPreview({
name: file.name,
mime: file.mime,
path: file.object.path,
thumb: file.object.thumb,
}))"
/>
<UiIconButton
icon="Rename"
primary
tooltip="重命名文件"
@click="handleClick(showFileNaming({
dirId: file.directoryId,
id: file.id,
}))"
/>
<UiIconButton
icon="Copy"
primary
tooltip="复制文件名称"
@click="handleClick(copyFileName())"
/>
<UiIconButton
v-if="canShare()"
icon="Share"
primary
tooltip="分享文件"
@click="handleClick(share())"
/>
<div class="flex-1"/>
<UiIconButton
class="text-red-500 hover:bg-red-500 hover:text-white"
icon="Delete"
tooltip="删除文件"
@click="handleClick(removeFile())"
/>
</div>
<div class="mt-1 mb-3 mx-2 border-t border-slate-300"/>
<div class="leading-tight mx-2.5">
<div class="text-xs text-gray-500">名称</div>
<UiText
:text="file.name"
class="mt-1 break-all leading-tight"
tag="div"
/>
</div>
<div class="my-3 mx-2 border-t border-slate-300"/>
<div class="text-xs mx-2.5">
<div class="text-xs text-gray-500">详情</div>
<div class="mt-1">
<span class="inline-block w-16 text-right">大小</span>
<span>{{ humanSize(file.object.size) }}</span>
</div>
<div>
<span class="inline-block w-16 text-right">尺寸</span>
<span>{{ file.object.width }}x{{ file.object.height }}</span>
</div>
<div>
<span class="inline-block w-16 text-right">上传时间</span>
<span>{{ formatDate(file.createdAt) }}</span>
</div>
<div>
<span class="inline-block w-16 text-right">修改时间</span>
<span>{{ formatDate(file.updatedAt) }}</span>
</div>
</div>
<div class="my-3 mx-2 border-t border-slate-300"/>
<div class="text-xs whitespace-pre-wrap mx-2.5 pb-2">
<div class="flex items-center text-xs text-gray-500">链接</div>
<UiText
:text="file.object.path"
class="break-all rounded mt-1 leading-tight"
tag="div"
@dblclick="copyFilePath()"
/>
</div>
</div>
</div>
</transition>
</div>
</Teleport>
</template>

@ -0,0 +1,173 @@
import {copyText} from '../../shared'
import {computed, nextTick, reactive} from "vue";
import {deleteFile, store} from "../store";
import {dispatch} from "../worker.ts";
import {showDirectoryNaming} from "./UiDirectoryNamingController";
import {toast} from "./UiToastController";
export type ContextmenuTarget =
| "directory" // 作用在侧边栏文件夹按钮上
| "file" // 作用在右侧文件图标上
| "filename" // 作用在右侧文件标题图标上
| "sidebar" // 作用在侧边栏上
| "content"; // 作用在右侧文件容器上
export interface ContextMenuOptions {
x: number;
y: number;
directoryId?: number;
fileId?: number;
target?: ContextmenuTarget;
}
interface ContextMenuState extends ContextMenuOptions {
visible: boolean;
}
export const controller = reactive<ContextMenuState>({
x: 0,
y: 0,
directoryId: 0,
fileId: undefined,
visible: false,
});
export const file = computed((): CloudFile | undefined => {
return controller.fileId
? store.files[controller.directoryId ?? 0]?.find(f => f.id === controller.fileId)
: undefined
})
export const directory = computed(() => {
const dirId = controller.directoryId
return dirId == 0
? store.rootDirectory
: dirId != null
? store.directories.get(dirId)
: undefined
})
export function isTarget(...targets: ContextmenuTarget[]): boolean {
return controller.target != null && targets.includes(controller.target);
}
export function showContextMenu(opts: ContextMenuOptions): void {
if (opts.target && opts.directoryId != null) {
controller.visible = true;
void nextTick(() => {
controller.x = opts.x;
controller.y = opts.y;
controller.fileId = opts.fileId;
controller.directoryId = opts.directoryId;
controller.target = opts.target;
});
} else {
hideContextMenu();
}
}
export function hideContextMenu() {
if (controller.visible) {
controller.visible = false;
void nextTick(() => {
if (!controller.visible) {
controller.directoryId = undefined;
controller.fileId = undefined;
controller.target = undefined;
}
});
}
}
export function toggleContextMenu(evt: MouseEvent): void {
const elm = evt.target as HTMLElement;
if (elm.closest('[data-role="contextmenu"]')) {
return;
}
const dataset = elm.closest<HTMLElement>("[data-contextmenu]")?.dataset;
if (dataset && dataset.target != null && dataset.directoryId != null) {
showContextMenu({
x: evt.clientX,
y: evt.clientY,
fileId: dataset.fileId ? Number(dataset.fileId) : undefined,
directoryId: Number(dataset.directoryId),
target: dataset.target as ContextmenuTarget,
});
} else {
hideContextMenu();
}
}
export function handleNewDir(child?: boolean) {
showDirectoryNaming({
action: "create",
pid: child ? controller.directoryId : undefined,
});
}
// 重命名文件或文件夹
export function renameDir() {
const {directoryId} = controller
if (isTarget("directory") && directoryId) {
showDirectoryNaming({
id: directoryId,
action: "rename",
});
}
}
export function copyDirName() {
const text = directory.value?.title
text?.length && copyText(text)
}
export function copyFileName() {
const text = file.value?.name
text?.length && copyText(text)
}
export function copyFilePath() {
const text = file.value?.object.path
text?.length && copyText(text)
}
export function removeDir() {
const {directoryId} = controller
if (isTarget("directory") && directoryId) {
dispatch("dir", "delete", directoryId)
.then(() => store.directories.delete(directoryId))
.then(() => toast("success", "删除成功"))
.catch((error) => toast("error", error));
}
}
export function removeFile() {
const f = file.value;
if (isTarget("file", "filename") && f) {
dispatch("file", "delete", f.id)
.then(() => deleteFile(f))
.then(() => toast("success", "删除成功"))
.catch((error) => toast("error", error));
}
}
export function canShare() {
return typeof navigator.canShare === 'function' && navigator.canShare({
title: "MDN",
text: "Learn web development on MDN!",
url: "https://developer.mozilla.org",
})
}
export function share() {
if (file.value) {
navigator
.share({
title: file.value!.name,
text: file.value!.object.path,
url: file.value!.object.path,
})
.then(() => toast("success", "分享成功"))
.catch((error) => toast("error", error));
}
}

@ -0,0 +1,159 @@
<script lang="ts" setup>
import {getScrollParent} from '../utils'
import {nextTick, ref, watch} from 'vue'
import {onClickOutside, useEventListener} from "@vueuse/core";
type Status =
| 'forward' // ->
| 'completed' //
| 'reverse' // ->
| 'dismissed' //
defineOptions({
inheritAttrs: false,
})
const props = withDefaults(defineProps<{
barrier: Node
visible?: boolean
rounded?: boolean
outside?: 'close' | 'shake' | 'none'
zIndex?: number
}>(), {
rounded() {
return true
},
outside() {
return 'none'
},
zIndex() {
return 100
},
})
const emit = defineEmits<{
(ev: Status): void
}>()
const panel = ref<HTMLElement | null>(null)
const visible = ref(false)
const isOpen = ref(false)
const status = ref<Status>('dismissed')
let resumeScrolling: VoidFunction | undefined
const setStatus = (value: Status): void => {
status.value = value
}
watch(() => props.visible, (value) => {
if (value && !visible.value) {
visible.value = true
nextTick(() => {
isOpen.value = value
})
} else {
isOpen.value = value
}
})
watch(status, (value) => {
visible.value = value !== 'dismissed'
emit(value)
if (value === 'dismissed' && resumeScrolling) {
resumeScrolling()
}
})
watch(panel, el => {
if (el) {
const scroll = getScrollParent(el)
//
const div = document.createElement('div')
div.style.cssText = 'position:fixed;left:0;right:0'
scroll.appendChild(div)
const hasScroll = div.offsetWidth < scroll.offsetWidth
div.remove()
if (!hasScroll) {
return
}
const {overflowY, position, inset} = scroll.style
scroll.style.overflowY = 'scroll'
scroll.style.position = 'fixed'
scroll.style.inset = '0'
const resume = () => {
if (resume === resumeScrolling) {
resumeScrolling = undefined
scroll.style.inset = inset
scroll.style.overflowY = overflowY
scroll.style.position = position
}
}
resumeScrolling = resume
}
})
useEventListener(panel, 'animationend', () => {
panel.value?.classList.remove('v-snake')
})
onClickOutside(panel, () => {
if (props.outside === 'close') {
isOpen.value = false
} else if (props.outside === 'shake' && panel.value) {
const w = panel.value.clientWidth
const v = ((w + 10) / w).toFixed(4)
panel.value.style.setProperty("--v-snake", v)
panel.value.classList.add('v-snake')
}
})
</script>
<template>
<Teleport :to="barrier">
<div
v-if="visible"
:style="{zIndex}"
class="absolute inset-0 flex-center"
>
<Transition
enter-from-class="opacity-0"
enter-to-class="backdrop-blur-[2px]"
leave-from-class="backdrop-blur-[2px]"
leave-to-class="opacity-0"
@before-enter="setStatus('forward')"
@after-enter="setStatus('completed')"
@before-leave="setStatus('reverse')"
@after-leave="setStatus('dismissed')"
>
<div
v-if="isOpen"
:class="{
'backdrop-blur-sm': status === 'completed',
'rounded-lg': rounded,
}"
class="transition-all ease-out absolute z-0 inset-0 bg-black/30"
/>
</Transition>
<Transition
enter-from-class="opacity-0 scale-75"
leave-to-class="opacity-0 scale-75"
>
<div
v-if="isOpen"
ref="panel"
class="transition-all ease-out relative z-10"
role="dialog"
style="--v-snake:90%"
v-bind="$attrs"
>
<slot/>
</div>
</Transition>
</div>
</Teleport>
</template>

@ -0,0 +1,85 @@
<script lang="ts" setup>
import {hideContextMenu} from './UiContextMenuController'
import {once} from '../../shared'
import {computed, inject, type Ref} from 'vue'
import {getDirectoryEntry, hasChildDirectoryEntries, store} from '../store'
import VIcon from "./VIcon.vue";
const props = defineProps<{
dir: number
deep: number
}>()
const expandedItems = inject<Ref<number[]>>("expandedItems")!
const highlighting = inject<Ref<number>>("highlighting")!
const isExpanded = computed(() => expandedItems.value.includes(props.dir))
const isSelected = computed(() => store.activeDirectoryId === props.dir)
const hasIndicative = computed(() => hasChildDirectoryEntries(props.dir))
const info = computed(() => getDirectoryEntry(props.dir))
const handleIndicative = (): void => {
hideContextMenu()
const i = expandedItems.value.indexOf(props.dir)
if (i > -1) expandedItems.value.splice(i, 1)
else expandedItems.value.push(props.dir)
}
const handleClick = () => (store.activeDirectoryId = props.dir)
const handleContextmenu = () => {
once("contextmenu:shown", () => {
highlighting.value = props.dir
})
once("contextmenu:hidden", () => {
if (highlighting.value === props.dir) {
highlighting.value = -1
}
})
}
</script>
<template>
<button
v-if="info"
:class="{
'text-indigo-600': isSelected,
'bg-black/10': isSelected,
'bg-black/5': !isSelected && highlighting === dir,
}"
:data-directory-id="dir"
class="w-full flex items-center py-1.5 px-1 text-left hover:bg-black/5 rounded-md"
data-contextmenu="true"
data-target="directory"
@click="handleClick"
@contextmenu="handleContextmenu"
>
<span
v-if="deep > 0"
:style="{ width: `${deep}em` }"
class="block pointer-events-none"
/>
<span class="size-[1em] scale-110">
<span
v-if="hasIndicative"
class="size-full flex flex-col justify-center items-center group"
@click.stop="handleIndicative"
>
<span
:class='{ "rotate-90": isExpanded }'
class="size-0 border-[3px] border-transparent border-l-gray-600 group-hover:border-l-gray-900 border-r-0 scale-x-125 origin-center"
/>
</span>
</span>
<span class="flex pointer-events-none ml-1">
<v-icon
:name="dir === 0 ? 'FileBriefcase' : isExpanded ? 'FolderOpen' : 'Folder'"
size="1.25em"
/>
</span>
<span
class="flex-1 pointer-events-none line-clamp-1 ml-1"
v-text="info.title"
/>
</button>
</template>

@ -0,0 +1,199 @@
<script lang="ts" setup>
import {getRootBarrier} from './utils'
import {dispatch} from '../worker'
import {emit} from '../../shared'
import {computed, nextTick, onMounted, ref, watch} from 'vue'
import {store} from '../store'
import UiDialog from './UiDialog.vue'
import {controller} from './UiDirectoryNamingController'
import {toast} from './UiToastController'
const barrier = ref<Node | null>()
const hasError = ref(false)
const conformClicked = ref(false)
const isSubmitting = ref(false)
const content = ref('')
const title = computed(() => {
if (controller.action === 'rename') {
return '重命名文件夹'
} else if (controller.pid) {
return "创建子文件夹"
} else {
return "创建文件夹"
}
})
const parent = computed(() => {
return controller.pid
? store.directories.get(controller.pid)
: undefined
})
const directory = computed(() => {
return controller.id
? store.directories.get(controller.id)
: undefined
})
const handleCancel = (): void => {
if (!isSubmitting.value) {
controller.visible = false
nextTick(() => {
hasError.value = false
conformClicked.value = false
isSubmitting.value = false
content.value = ''
})
}
}
const handleConfirm = (): void => {
if (isSubmitting.value) {
return;
}
if (content.value.trim().length < 2) {
hasError.value = true
}
if (hasError.value) {
return;
}
if (!conformClicked.value) {
conformClicked.value = true
return
}
isSubmitting.value = true
requestAnimationFrame(async () => {
try {
const title = content.value.trim()
switch (controller.action) {
case "create": {
const pid = parent.value?.id ?? 0
const result = await dispatch("dir", "create", {title, pid});
store.directories.set(result.id, result);
toast("success", `成功创建文件夹 “${title}`)
if (pid && pid === controller.pid) {
emit("directory:expanded", parent.value!.id)
}
break
}
case "rename": {
const dir = store.directories.get(directory.value!.id);
if (!dir) {
toast("error", "文件夹不存在")
break
}
const res = await dispatch("dir", "rename", {id: dir.id, title});
Object.assign(dir, res);
toast("success", `成功将文件夹 “${directory.value!.title}” 重命名为 “${title}`)
break
}
}
isSubmitting.value = false
handleCancel()
} catch (err) {
isSubmitting.value = false
toast('error', err as any)
}
})
}
watch(() => controller.visible, (value) => {
if (value && controller.action === 'rename') {
content.value = directory.value?.title ?? ''
}
})
watch(content, (value) => {
conformClicked.value = false
if (value.trim().length > 1) {
hasError.value = false
}
})
onMounted(() => {
barrier.value = getRootBarrier()
})
</script>
<template>
<UiDialog
v-if="barrier"
:barrier="barrier"
:visible="controller.visible"
class="w-96 rounded-md overflow-hidden shadow-2xl"
outside="shake"
@dismissed="handleCancel"
>
<div class="bg-white p-6">
<h3
id="modal-title"
class="text-base font-semibold leading-6 text-gray-900"
v-text="title"
/>
<p class="text-xs text-gray-500 mt-1">
<template v-if="controller.action === 'rename'">
您正在修改文件夹 <b
class="text-black align-baseline text-sm">{{ directory!.title }}</b><br/>
</template>
<template v-else-if="controller.pid">
您正在为 <b
class="text-black align-baseline text-sm">{{ parent!.title }}</b>
创建子文件夹<br/>
</template>
<span :class="{ 'text-red-500': hasError }">
文件夹的名称至少 2 个字符请尽量不要超过 8 个汉字
</span>
</p>
<div class="mt-4">
<input
v-model="content"
:disabled="isSubmitting" autocomplete="off"
class="block w-full rounded-md border-0 px-4 py-2 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 disabled:bg-gray-100 disabled:cursor-progress"
maxlength="20" minlength="2"
placeholder="请输入文件夹名称" required
@blur="hasError = content.length < 2"/>
</div>
</div>
<div
class="bg-gray-50 px-6 py-3 flex justify-end gap-4 border-t border-t-slate-200 text-sm font-semibold">
<button
:disabled="isSubmitting"
class="rounded-md px-4 py-2 select-none transition-all bg-white text-gray-900 shadow-sm hover:bg-gray-50 ring-1 ring-inset ring-gray-300 disabled:bg-gray-100 disabled:hover:bg-gray-100 disabled:cursor-progress"
type="button"
@click="handleCancel"
>
取消
</button>
<button
:disabled="isSubmitting"
class="inline-flex items-center gap-2 px-4 py-2 rounded-md transition-all select-none bg-indigo-600 text-white shadow-sm hover:bg-indigo-500 disabled:bg-indigo-400 disabled:hover:bg-indigo-400 disabled:cursor-progress"
type="button"
@click="handleConfirm"
>
<svg
v-if="isSubmitting"
class="animate-spin h-3 w-3 text-white"
fill="none"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
fill="currentColor"
/>
</svg>
{{ conformClicked ? "确定提交?" : "提交" }}
</button>
</div>
</UiDialog>
</template>

@ -0,0 +1,29 @@
import { reactive } from 'vue'
interface DirectoryNamingOptions {
pid?: number
id?: number
action: 'create' | 'rename'
}
interface DirectoryNamingState extends DirectoryNamingOptions {
visible: boolean
}
export const controller = reactive<DirectoryNamingState>({
visible: false,
pid: undefined,
id: undefined,
action: 'create',
})
export function hideDirectoryNaming() {
controller.visible = false
}
export function showDirectoryNaming(opts: DirectoryNamingOptions): void {
controller.visible = true
controller.action = opts.action
controller.pid = opts.pid
controller.id = opts.id
}

@ -0,0 +1,35 @@
<script lang="ts" setup>
import {timestamp} from '../../shared';
import {computed, inject, type Ref} from 'vue'
import {store} from '../store'
import DirectoryTreeItem from './UiDirectoryTreeItem.vue'
const props = defineProps<{
pid: number
deep: number
}>()
const expandedItems = inject<Ref<number[]>>("expandedItems")
const isExpanded = computed(() => {
return expandedItems?.value.includes(props.pid)
})
const children = computed(() => {
return Array.from(store.directories.values()).filter(d => d.pid === props.pid).sort((a, b) => {
if (a.sort != b.sort) return a.sort - b.sort
return timestamp(a.createdAt) - timestamp(b.createdAt)
});
})
</script>
<template>
<ul v-if="children.length && (pid === 0 || isExpanded)" class="w-full">
<DirectoryTreeItem
v-for="item in children"
:key="item.id"
:deep="deep"
:dir="item.id"
/>
</ul>
</template>

@ -0,0 +1,22 @@
<script lang="ts" setup>
import DirectoryButton from './UiDirectoryButton.vue'
import DirectoryTree from './UiDirectoryTree.vue'
defineProps<{
dir: number
deep: number
}>()
</script>
<template>
<li class="w-full my-0.5">
<DirectoryButton
:deep="deep"
:dir="dir"
/>
<DirectoryTree
:deep="deep + 1"
:pid="dir"
/>
</li>
</template>

@ -0,0 +1,69 @@
<script lang="ts" setup>
import {store, uploadFiles} from '../store'
import VIcon from './VIcon.vue'
import {onBeforeUnmount, onMounted} from 'vue'
const handleDragOver = (event: DragEvent) => {
event.preventDefault();
// const target = event.target as HTMLElement
// store.isDragging = !!target.closest('[data-ui-barrier]');
store.isDragging = event.composedPath().some(et => (et instanceof Element) ? et.closest('[data-ui-barrier]') : false)
}
const handleMouseout = () => {
store.isDragging = false
}
const handleDrop = (ev: DragEvent): void => {
//
// https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop
ev.preventDefault();
if (store.isDragging) {
store.isDragging = false
if (store.activeDirectoryId >= 0) {
const directoryId = store.activeDirectoryId
if (ev.dataTransfer?.items) {
for (const item of ev.dataTransfer.items) {
const file = item.getAsFile()
file && uploadFiles(directoryId, file)
}
} else if (ev.dataTransfer?.files) {
uploadFiles(directoryId, ev.dataTransfer.files)
}
}
}
}
onMounted(() => {
console.log("dragover drop mouseout")
document.addEventListener('dragover', handleDragOver)
document.addEventListener('drop', handleDrop)
document.addEventListener('mouseout', handleMouseout)
})
onBeforeUnmount(() => {
document.removeEventListener('dragover', handleDragOver)
document.removeEventListener('drop', handleDrop)
document.removeEventListener('mouseout', handleMouseout)
})
</script>
<template>
<Transition enter-from-class="opacity-0" leave-to-class="opacity-0">
<div
v-if="store.isDragging"
class="absolute z-50 inset-0 overflow-hidden pointer-events-none
flex flex-col justify-center items-center transition-opacity
w-full h-full bg-black/30 backdrop-blur-sm"
data-drop-feedback
>
<div
class="p-3 rounded-md bg-gray-100/90 flex text-sm items-center gap-1">
<VIcon name="InfoSolid" size="20"/>
<span>释放鼠标开始上传文件</span>
</div>
</div>
</Transition>
</template>

@ -0,0 +1,101 @@
<script lang="ts" setup>
import VText from './VText.vue'
import {toast} from './UiToastController'
import {once} from '../../shared'
import {computed, inject, type Ref, ref} from 'vue'
import {store, toggleSelect} from '../store'
import VBadge from './VBadge.vue'
import UiFileThumbnail from './UiFileThumbnail.vue'
const props = defineProps<{
file: CloudFile
}>()
const isSelected = computed((): boolean => {
return store.selected.some(f => f.id === props.file.id)
})
const index = computed((): number => {
const index = store.selected.findIndex(f => f.id === props.file.id)
return index > -1 ? index + 1 : 0
})
const elmRef = ref<HTMLDivElement>()
const scrollIntoView = () => {
elmRef.value?.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
})
}
const handleClick = () => {
const {status} = props.file.object
if (status < 3) {
toast("warn", "文件处理中,暂时不可选")
} else if (status > 3) {
toast("error", props.file.object.error ?? '文件错误,无法选择')
} else {
scrollIntoView()
toggleSelect(props.file)
}
}
const onNameClick = () => {
scrollIntoView()
}
const highlighting = inject<Ref<number>>("highlighting")!
const handleContextmenu = () => {
once("contextmenu:shown", () => {
highlighting.value = props.file.id
})
once("contextmenu:hidden", () => {
if (highlighting.value === props.file.id) {
highlighting.value = -1
}
})
}
</script>
<template>
<div ref="elmRef" class="relative w-32 p-2 z-0">
<div class="w-28 h-28 p-1 relative rounded-lg">
<template v-if="isSelected || highlighting === file.id">
<div
:class="{
'ring-2': isSelected,
'ring-inset': isSelected,
'ring-indigo-600': isSelected,
}"
class="absolute left-0 right-0 top-0 bottom-0 bg-black bg-opacity-5 rounded-lg"
/>
<v-badge
v-if="index > 0 && isSelected && store.multiple"
:value="index"
class="bg-red-500 z-10"
/>
</template>
<div class="size-full flex-center p-0.5 select-none">
<UiFileThumbnail
:file="file"
@click="handleClick"
@contextmenu="handleContextmenu"
/>
</div>
</div>
<div
class="w-28 h-9 px-1 py-1 text-center line-clamp-2 text-ellipsis leading-4 text-xs">
<v-text
:data-directory-id="file.directoryId"
:data-file-id="file.id"
:text="file.name"
data-contextmenu="true"
data-target="file"
@click="onNameClick"
@contextmenu="handleContextmenu"
/>
</div>
</div>
</template>

@ -0,0 +1,157 @@
<script lang="ts" setup>
import {store, updateFile} from '../store'
import {getRootBarrier} from './utils'
import {dispatch} from '../worker'
import {computed, nextTick, onMounted, ref, watch} from 'vue'
import UiDialog from './UiDialog.vue'
import {controller} from './UiFileNamingController'
import {toast} from './UiToastController'
const barrier = ref<Node | null>()
const hasError = ref(false)
const conformClicked = ref(false)
const isSubmitting = ref(false)
const content = ref('')
const file = computed(() => {
return controller.dirId != null && controller.id
? store.files[controller.dirId]?.find((f: CloudFile) => f.id === controller.id)
: undefined
})
const handleCancel = (): void => {
if (!isSubmitting.value) {
controller.visible = false
nextTick(() => {
hasError.value = false
conformClicked.value = false
isSubmitting.value = false
content.value = ''
})
}
}
const handleConfirm = (): void => {
if (isSubmitting.value) {
return;
}
if (content.value.trim().length < 2) {
hasError.value = true
}
if (hasError.value) {
return;
}
if (!conformClicked.value) {
conformClicked.value = true
return
}
isSubmitting.value = true
requestAnimationFrame(async () => {
try {
const id = controller.id!
const name = content.value.trim()
const res = await dispatch("file", "rename", {id, name})
updateFile(res)
isSubmitting.value = false
handleCancel()
} catch (err) {
isSubmitting.value = false
toast('error', err as any)
}
})
}
watch(() => controller.visible, (value) => {
if (value) {
content.value = file.value?.name ?? ''
}
})
watch(content, (value) => {
conformClicked.value = false
if (value.trim().length > 1) {
hasError.value = false
}
})
onMounted(() => {
barrier.value = getRootBarrier()
})
</script>
<template>
<UiDialog
v-if="barrier"
:barrier="barrier"
:visible="controller.visible"
class="w-96 rounded-md overflow-hidden shadow-2xl"
outside="shake"
@dismissed="handleCancel"
>
<div class="bg-white p-6">
<h3 id="modal-title"
class="text-base font-semibold leading-6 text-gray-900">
文件重命名
</h3>
<p class="text-xs text-gray-500 mt-1">
您正在重命名文件 <b
class="text-black align-baseline text-sm">{{ file!.name }}</b><br/>
<span :class="{'text-red-500': hasError}">
文件夹的名称至少 2 个字符请尽量不要超过 50 个汉字
</span>
</p>
<div class="mt-4">
<input
v-model="content"
:disabled="isSubmitting"
autocomplete="off"
class="block w-full rounded-md border-0 px-4 py-2 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 disabled:bg-gray-100 disabled:cursor-progress"
maxlength="20"
minlength="2"
placeholder="请输入文件夹名称"
required
/>
</div>
</div>
<div
class="bg-gray-50 px-6 py-3 flex justify-end gap-4 border-t border-t-slate-200 text-sm font-semibold">
<button
:disabled="isSubmitting"
class="rounded-md px-4 py-2 select-none transition-all bg-white text-gray-900 shadow-sm hover:bg-gray-50 ring-1 ring-inset ring-gray-300 disabled:bg-gray-100 disabled:hover:bg-gray-100 disabled:cursor-progress"
type="button"
@click="handleCancel"
>
取消
</button>
<button
:disabled="isSubmitting"
class="inline-flex items-center gap-2 px-4 py-2 rounded-md transition-all select-none bg-indigo-600 text-white shadow-sm hover:bg-indigo-500 disabled:bg-indigo-400 disabled:hover:bg-indigo-400 disabled:cursor-progress"
type="button"
@click="handleConfirm"
>
<svg
v-if="isSubmitting"
class="animate-spin h-3 w-3 text-white"
fill="none"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
fill="currentColor"
/>
</svg>
{{ conformClicked ? "确定提交?" : "提交" }}
</button>
</div>
</UiDialog>
</template>

@ -0,0 +1,25 @@
import { reactive } from 'vue'
interface FileNamingOptions {
dirId?: number
id?: number
}
interface FileNamingState extends FileNamingOptions {
visible: boolean
}
export const controller = reactive<FileNamingState>({
visible: false,
dirId: undefined,
id: undefined,
})
export function hideFileNaming(): void {
controller.visible = false
}
export function showFileNaming(opts: Required<FileNamingOptions>): void {
Object.assign(controller, opts)
controller.visible = true
}

@ -0,0 +1,45 @@
<script lang="ts" setup>
import VIcon from './VIcon.vue'
import {computed} from 'vue'
const props = defineProps<{
file: CloudFile
}>()
const classes = computed(() => {
if (props.file.object.status !== 3) {
return ["size-full", 'p-2']
} else if (props.file.object.width < props.file.object.height) {
return ["h-full", "w-auto", "min-w-[1em]"]
} else {
return ["w-full", "h-auto", "min-h-[1em]"]
}
})
</script>
<template>
<div
:class="classes"
:data-directory-id="file.directoryId"
:data-file-id="file.id"
class="p-0.5 relative flex-center bg-white text-xs ring-2 ring-inset ring-white ring-offset-1 drop-shadow"
data-contextmenu="true"
data-target="file"
>
<template v-if="file.object.status < 3" class="flex items-center">
<v-icon class="size-full text-gray-500" name="FileSync"/>
</template>
<template v-else-if="file.object.status > 3">
<v-icon name="FileError" size="14"/>
<span class="mt-1 text-gray-500">处理失败</span>
</template>
<img
v-else
:alt="file.name"
:class="classes.join(' ')"
:loading="'lazy'"
:src="file.object.thumb"
class="pointer-events-none"
/>
</div>
</template>

@ -0,0 +1,83 @@
<script lang="ts" setup>
import {store, useFiler} from '../store'
import UiFile from './UiFile.vue'
import UiMistake from './UiMistake.vue'
import UiNoData from './UiNoData.vue'
import UiPagination from './UiPagination.vue'
import VLoading from './VLoading.vue'
import {emit, timestamp} from '../../shared'
import {computed, provide, ref} from 'vue'
const props = defineProps<{
directoryId: number
}>()
const filer = useFiler(props.directoryId)
const highlighting = ref(-1)
const maxPage = computed(() => Math.ceil(filer.total / filer.limit))
const list = computed(() => (store.files[props.directoryId] ?? [])
.sort((a, b) => timestamp(a.createdAt) - timestamp(b.createdAt))
.reverse())
provide("highlighting", highlighting)
const reload = (): void => emit(`files:${props.directoryId}:reload`)
const jump = (to: number): void => emit(`files:${props.directoryId}:jump`, to)
</script>
<template>
<VLoading
v-if="filer.view === 'loader'"
class="w-full h-full"
/>
<UiMistake
v-else-if="filer.view === 'error'"
:message="filer.error"
@refresh="reload"
/>
<UiNoData
v-else-if="filer.view === 'empty'"
:data-directory-id="directoryId"
data-contextmenu="true"
data-target="content"
subtitle="支持单次和批量上传,仅能够上传图片、视频和音频文件,严禁上传公司数据或其他违禁文件"
title="拖拽文件至此处即可上传"
@refresh="reload"
/>
<div
v-else
:data-directory-id="directoryId"
class="flex-1 flex flex-col items-stretch"
data-contextmenu="true"
data-target="content"
>
<div class="relative flex-1 overflow-y-auto">
<div
class="flex-1 flex flex-wrap items-start justify-start content-start p-4">
<UiFile
v-for="file in list"
:key="file.id"
:file="file"
/>
</div>
</div>
<div
class="border-t border-slate-200 w-full flex items-center leading-tight text-sm select-none">
<span class="py-1 px-2"> {{ filer.total }} 条记录</span>
<span
v-if="store.mode === 'select' && store.selected.length > 0"
class="border-l py-1 px-2"
>
已选中 {{ store.selected.length }}
</span>
<div class="flex-1"/>
<UiPagination
v-if="maxPage > 1"
:current="filer.page"
:total="maxPage"
class="mx-2"
@change="jump"
/>
</div>
</div>
</template>

@ -0,0 +1,64 @@
<script lang="ts" setup>
import type {IconName} from './VIcons'
import UiButton from './VButton.vue'
import {hideContextMenu} from './UiContextMenuController'
import VDropdown from './VDropdown.vue'
import {store} from '../store'
const items: Array<{
id: FileMime
icon: IconName
label: string
}> = [
{
id: "image",
icon: "Image",
label: "只看图片"
},
{
id: "video",
icon: "Video",
label: "只看视频",
},
{
id: "all",
icon: "FileMultiple",
label: "图片和视频",
}
]
</script>
<template>
<v-dropdown
:disabled="store.lockMime"
:tooltip="store.lockMime ? null : '切换要显示的文件类型'"
@open="hideContextMenu"
>
<template #default>
<svg
class="pointer-events-none size-5" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5.5 5h13a1 1 0 0 1 .5 1.5L14 12v7l-4-3v-4L5 6.5A1 1 0 0 1 5.5 5"
fill="none" stroke="currentColor"
stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"></path>
</svg>
{{ items.find(i => i!.id === store.mime)!.label }}
</template>
<template #content>
<UiButton
v-for="item in items"
:key="item.id"
:icon="item.icon"
:label="item.label"
class="flex items-center gap-2 px-3 py-1.5 rounded hover:bg-indigo-500 hover:text-white text-sm select-none"
icon-size="1.5em"
type="button"
@click="store.mime = item.id"
/>
</template>
</v-dropdown>
</template>

@ -0,0 +1,30 @@
<script lang="ts" setup>
defineProps<{
message?: string
}>()
defineEmits<{
(type: "refresh"): void
}>()
</script>
<template>
<div class="size-full flex-center">
<div class="text-sm font-semibold dark:text-white">
<slot name="message">
{{ message ?? '哦哦,我们遇到了一些问题!' }}
</slot>
</div>
<slot name="button">
<button
class="
mx-auto mt-4 px-4 py-2 rounded-lg transition-all text-sm font-semibold
bg-gray-50 ring-1 ring-inset ring-gray-400/75 focus:ring-2 focus:ring-indigo-500
shadow-sm dark:bg-transparent dark:text-white
"
@pointerup="$nextTick(() => $emit('refresh'))"
v-text="'刷新'"
/>
</slot>
</div>
</template>

@ -0,0 +1,65 @@
<script lang="ts" setup>
import {deleteFile, store} from '../store'
import VButton from './VButton.vue'
import {toast} from './UiToastController'
import {dispatch} from '../worker.ts'
import {computed, ref} from 'vue'
const emit = defineEmits<{
(ev: 'confirm'): void
}>()
const isPending = ref(false)
const hasSelected = computed(() => store.selected.length > 0)
const isManageMode = computed(() => store.mode === 'manage')
const handleConfirm = async () => {
if (!hasSelected.value) {
return
}
if (store.mode === 'select') {
emit('confirm')
return
}
isPending.value = true
const files = store.selected.slice()
const deletes = Array.from(new Set(files.map(f => f.id)))
dispatch('file', "delete", deletes)
.then(() => {
store.selected = store.selected.filter(f => deletes.includes(f.id))
files.forEach(deleteFile)
toast('success', '操作成功')
})
.catch(error => toast('error', error))
.finally(() => isPending.value = false)
}
</script>
<template>
<transition enter-from-class="opacity-0" leave-to-class="opacity-0">
<div v-if="hasSelected" class="flex items-center transition-all gap-4">
<v-button
:disabled="isPending"
class="bg-white border hover:bg-gray-50 text-sm font-semibold h-8 focus:ring-4 transition-all focus:ring-gray-100 shadow-sm"
label="取消2"
@click="store.selected = []"
/>
<v-button
:class="{
'bg-red-500': isManageMode,
'hover:bg-red-600': isManageMode,
'focus:ring-red-200': isManageMode,
'bg-indigo-500': !isManageMode,
'hover:bg-indigo-600': !isManageMode,
'focus:ring-indigo-200': !isManageMode,
}"
:disabled="isPending"
:label="isManageMode ? '删除' : '确定'"
class="text-sm font-semibold h-8 focus:ring-4 transition-all text-white shadow-sm"
@click="handleConfirm"
/>
<div class="w-[1px] h-4 bg-gray-100 mx-2 flex-shrink-0"/>
</div>
</transition>
</template>

@ -0,0 +1,54 @@
<script lang="ts" setup>
import {store} from '../store'
import VBadge from './VBadge.vue'
import VIconButton from './VIconButton.vue'
import UiMimeSelect from './UiMimeSelect.vue'
import UiModeTools from './UiModeTools.vue'
import UiThemeSelect from './UiThemeSelect.vue'
const emit = defineEmits<{
(ev: 'dismiss'): void
(ev: 'confirm'): void
}>()
const handleDismiss = () => {
store.showTasksPanel = false
emit('dismiss')
}
</script>
<template>
<div
class="relative z-50 h-12 px-2 flex-shrink-0 flex gap-2 items-center content-center">
<div class="flex-1"/>
<UiModeTools @confirm="$emit('confirm')"/>
<UiMimeSelect/>
<UiThemeSelect/>
<div/>
<div class="flex relative">
<v-icon-button
:class="{
'ring-2': store.showTasksPanel,
'ring-indigo-500': store.showTasksPanel,
}"
data-tasks-trigger
icon="TaskListSolid"
tooltip="任务列表"
@click="store.showTasksPanel = !store.showTasksPanel"
/>
<v-badge
v-if="store.tasks.length > 0"
:value="store.tasks.length"
/>
</div>
<template v-if="store.mode === 'select'">
<div class="w-[1px] h-4 bg-gray-100 mx-2 flex-shrink-0"/>
<v-icon-button
class="focus:ring-red-500 text-red-500"
icon="DismissSolid"
tooltip="关闭"
@click="handleDismiss"
/>
</template>
</div>
</template>

@ -0,0 +1,40 @@
<script lang="ts" setup>
import VIcon from './VIcon.vue'
defineProps<{
title?: string
subtitle?: string
}>()
defineEmits<{
(ev: 'refresh'): void
}>()
</script>
<template>
<div class="w-full h-full flex-center select-none">
<div class="max-w-96 text-center pointer-events-none">
<v-icon
class="text-gray-300 transition-colors"
name="MailInbox"
size="4em"
/>
<h2 v-if="$slots.title || title" class="line-clamp-1 mt-4">
<slot name="title">{{ title }}</slot>
</h2>
<p v-if="$slots.subtitle || subtitle" class="text-sm text-gray-500 mt-2">
<slot name="subtitle">{{ subtitle }}</slot>
</p>
</div>
<button
class="
flex items-center px-4 py-2 rounded-xl font-semibold mt-4 mx-auto bg-gray-100 text-sm
hover:text-indigo-600 hover:bg-indigo-100 transition-colors ring-1 ring-inset ring-slate-300/50
focus:ring-2 focus:ring-indigo-500
"
@click.stop="$emit('refresh')"
>
<span>点我刷新</span>
</button>
</div>
</template>

@ -0,0 +1,21 @@
<script lang="ts" setup>
import VIcon from './VIcon.vue'
defineProps<{
forward: boolean
}>()
</script>
<template>
<div>
<span
class="opacity-0 group-hover:opacity-100 transition-colors flex flex-col justify-center items-center absolute inset-0">
<v-icon v-if="forward" class="scale-75" name="ChevronDoubleRight"/>
<v-icon v-else class="scale-75" name="ChevronDoubleLeft"/>
</span>
<span
class="opacity-100 group-hover:opacity-0 flex flex-col justify-center items-center">
<v-icon name="MoreHorizontal"/>
</span>
</div>
</template>

@ -0,0 +1,32 @@
<script lang="ts" setup>
defineProps<{
active?: boolean
disabled?: boolean
}>()
</script>
<template>
<button
:class='{
"text-indigo-600": active,
"cursor-default": active,
"hover:bg-gray-100": !active,
"hover:text-indigo-600": active,
}'
:disabled="disabled"
class="
relative inline-flex flex-col justify-center items-center leading-tight
px-1 group
text-sm font-semibold text-gray-900
focus:outline-offset-0
hover:text-indigo-600
disabled:text-inherit disabled:bg-transparent disabled:opacity-40
min-w-[2em] min-h-[2em]
"
type="button"
@contextmenu.stop.prevent=""
>
<slot />
</button>
</template>

@ -0,0 +1,83 @@
<script lang="ts" setup>
import {type Page, paginate} from '../../shared'
import {computed} from 'vue'
import UiPaginateEllipsis from './UiPaginateEllipsis.vue'
import UiPaginateItem from './UiPaginateItem.vue'
const props = defineProps<{
total: number //
current: number //
}>()
const emit = defineEmits<{
(ev: 'change', value: number): void
}>()
const pages = computed(() => paginate(props.current, props.total))
const handleClick = (to: number) => {
if (to !== props.current) {
emit('change', to)
}
}
const handlePage = (page: Page): void => {
if (!page.ellipsis) {
handleClick(page.value as number)
} else if (page.forward) {
handleClick(Math.min(props.current + 5, props.total))
} else {
handleClick(Math.min(props.current - 5, props.total))
}
}
</script>
<template>
<nav
aria-label="Pagination"
class="isolate inline-flex -space-x-px gap-x-1.5"
>
<UiPaginateItem
:disabled="current === 1"
@click="handleClick(props.current - 1)"
>
<svg
aria-hidden="true"
class="h-5 w-5"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
clip-rule="evenodd"
d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z"
fill-rule="evenodd"
/>
</svg>
</UiPaginateItem>
<UiPaginateItem
v-for="page in pages"
:active="page.value === current"
@click="handlePage(page)"
>
<UiPaginateEllipsis v-if="page.ellipsis" :forward="page.forward"/>
<template v-else>{{ page.value }}</template>
</UiPaginateItem>
<UiPaginateItem
:disabled="current === total"
@click="handleClick(props.current + 1)"
>
<svg
aria-hidden="true"
class="size-5"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
clip-rule="evenodd"
d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
fill-rule="evenodd"
/>
</svg>
</UiPaginateItem>
</nav>
</template>

@ -0,0 +1,90 @@
<script lang="ts" setup>
import logo from '../assets/logo.png'
import {toast} from './UiToastController'
import {getScrollBarWidth} from '../utils'
import {coerce, off, on, sleep} from '../../shared'
import {onBeforeUnmount, onMounted, provide, ref} from 'vue'
import {refreshDirectories, store} from '../store'
import DirectoryButton from './UiDirectoryButton.vue'
import DirectoryTree from './UiDirectoryTree.vue'
const isLoading = ref(false)
const expandedItems = ref<number[]>([])
const highlighting = ref(-1)
const scrollWidth = ref(0)
provide("expandedItems", expandedItems)
provide("highlighting", highlighting)
const refresh = async (): Promise<void> => {
if (!isLoading.value) {
isLoading.value = true
await sleep(300)
await refreshDirectories()
.then(() => toast('success', "刷新侧边栏成功"))
.catch((err) => toast('error', `刷新侧边栏失败,原因:${coerce(err)}`))
.then(() => sleep(300))
.finally(() => isLoading.value = false)
}
}
const onContextmenuDismissed = () => (highlighting.value = -1);
const onExpanded = (id: number): void => {
while (id > 0) {
const dir = store.directories.get(id)
if (!dir) return
if (!expandedItems.value.includes(id)) {
expandedItems.value.push(id)
}
id = dir.pid
}
}
onMounted(() => {
on("contextmenu:dismissed", onContextmenuDismissed)
on("dirs:refresh", refresh)
on("directory:expanded", onExpanded)
scrollWidth.value = getScrollBarWidth()
})
onBeforeUnmount(() => {
off("contextmenu:dismissed", onContextmenuDismissed)
off("dirs:refresh", refresh)
off("directory:expanded", onExpanded)
})
</script>
<template>
<div
class="relative h-full flex flex-col items-stretch"
data-contextmenu="true"
data-directory-id="0"
data-target="sidebar"
>
<div class="flex content-center items-center px-2 h-12 gap-2 select-none">
<span
class="flex-center size-8 flex-shrink-0 pointer-events-none select-none">
<img
:src="logo"
alt="资源管理器"
class="size-8"
/>
</span>
<span class="flex-1">资源管理器</span>
<span
v-if="isLoading"
class="block size-4 border-2 border-gray-300 rounded-full border-l-black animate-spin ease-in-out"
/>
</div>
<div
:style="{marginRight: `${-scrollWidth}px`}"
class="flex-1 text-sm overflow-y-scroll p-2"
>
<div class="mb-0.5">
<DirectoryButton :deep="0" :dir="0"/>
</div>
<DirectoryTree :deep="0" :pid="0"/>
</div>
</div>
</template>

@ -0,0 +1,63 @@
<script lang="ts" setup>
import {ref} from 'vue'
const props = defineProps<{
size: number
min: number
max: number
disabled?: boolean
}>()
const isDraggingRef = ref(false)
const dragElmRef = ref<HTMLElement | null>(null)
const currentSize = ref(256)
const handleMouseMove = (evt: MouseEvent) => {
evt.preventDefault()
isDraggingRef.value = true
const onMouseMove = (e: MouseEvent): void => {
updateSize(e)
}
const onMouseUp = (): void => {
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
isDraggingRef.value = false
document.body.style.cursor = ''
}
document.body.style.cursor = 'col-resize'
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
updateSize(evt)
}
const updateSize = (event: MouseEvent): void => {
const parentRect = dragElmRef.value?.parentElement?.getBoundingClientRect()
if (!parentRect) return
let newSize = event.clientX - parentRect.left
if (props.min > 0) newSize = Math.max(newSize, props.min)
if (props.max > 0) newSize = Math.min(newSize, 356)
currentSize.value = Math.max(newSize, 0)
}
</script>
<template>
<div class="flex items-stretch h-full w-full">
<div :style="{flexBasis: `${currentSize}px`}" class="relative z-0">
<slot name="secondary"/>
</div>
<div
v-if="!disabled"
ref="dragElmRef"
:class="{'bg-green-700': isDraggingRef}"
class="relative z-20 w-1 h-full -mx-0.5 hover:bg-green-700 transition-colors cursor-col-resize"
@mousedown="handleMouseMove"
/>
<div class="relative z-10 flex-1">
<slot name="primary"/>
</div>
</div>
</template>

@ -0,0 +1,134 @@
<script lang="ts" setup>
import {dispatch} from '../worker'
import {
humanSize,
isCompleted,
isInvalid,
isValid,
sleep,
statusIndex
} from '../../shared'
import {ref, watch} from 'vue'
import {showPreview} from '../utils'
import VIcon from './VIcon.vue'
import UiTaskAction from './UiTaskAction.vue'
import {toast} from './UiToastController'
import VProgress from './VProgress.vue'
import {vTooltip} from '../directives'
const props = defineProps<{
task: Task
}>()
const isActing = ref(false)
const progress = ref(0)
const handleAction = async (action: "cancel" | "remove" | "resume") => {
const {hash: fileHash, id: taskId} = props.task
try {
isActing.value = true
await sleep(300)
await dispatch("task", action as "cancel", {fileHash, taskId})
} catch (error) {
toast('error', error as any)
}
isActing.value = false
}
const handlePreview = () => {
showPreview({
name: props.task.name,
mime: props.task.mime,
thumb: props.task.path, // todo first frame for video
path: props.task.path,
})
}
watch(props.task, (t) => {
if (isInvalid(t)) {
progress.value = 1
} else if (statusIndex(t.status) <= statusIndex("readend")) {
progress.value = (t.progress ?? 0) / 2
} else if (!isCompleted(t)) {
progress.value = ((t.progress ?? 0) + 1) / 2
}
progress.value = 0.678
}, {immediate: true})
</script>
<template>
<div class="relative w-full text-sm text-gray-500 py-2 px-3 group">
<div
:class="{'text-red-500': isInvalid(task)}"
class="flex items-center gap-2 mb-1 text-sm"
>
<span v-if="isInvalid(task)" class="size-4 flex-center">
<v-icon
v-tooltip="task.error ?? '上传失败'"
class="text-red-500"
name="ErrorCircleSolid"
size="1.5em"
/>
</span>
<span
v-else-if="isValid(task)"
class="size-4 border-2 border-gray-300 rounded-full border-l-black animate-spin ease-in-out"
/>
<span v-else>
<v-icon
class="text-green-500"
name="CheckmarkCircleSolid"
size="1.5em"
/>
</span>
<span class="size-4 flex-center">
<v-icon :name="task.mime.startsWith('image/') ? 'Image' : 'Video'"/>
</span>
<div class="line-clamp-1 flex-1 font-semibold break-all">
{{ task.name }}
</div>
<v-progress
:class="{hidden: !isValid(task)}"
:label="progress.toString()"
:value="progress"
class="w-20"
/>
<div
class="w-16 text-right text-gray-600 select-none group-hover:opacity-0">
{{ humanSize(task.size) }}
</div>
</div>
<div
class="absolute inset-y-0 right-0 flex gap-2 items-center opacity-0 group-hover:opacity-100">
<div class="flex items-center gap-0.5">
<UiTaskAction
icon="Eye"
tooltip="预览文件"
@click="handlePreview"
/>
<UiTaskAction
v-if="isValid(task)"
:disabled="isActing"
icon="SyncOff"
tooltip="取消"
@click="handleAction('cancel')"
/>
<UiTaskAction
v-if="isInvalid(task)"
:disabled="isActing"
icon="Sync"
tooltip="恢复"
@click="handleAction('resume')"
/>
<UiTaskAction
:disabled="isActing"
class="focus:text-red-600 hover:text-red-600"
icon="Delete"
tooltip="删除"
@click="handleAction('remove')"
/>
</div>
</div>
</div>
</template>

@ -0,0 +1,25 @@
<script lang="ts" setup>
import type {IconName} from './VIcons'
import VIcon from './VIcon.vue'
import {vTooltip} from '../directives'
defineProps<{
icon: IconName
tooltip: string
disabled?: boolean
}>()
</script>
<template>
<button
v-tooltip="tooltip"
:disabled="disabled"
class="
flex-shrink-0 inline-flex justify-center items-center text-gray-900 rounded text-xs size-6 opacity-65
hover:text-indigo-600 focus:text-indigo-600 hover:opacity-100 focus:opacity-100
"
type="button"
>
<v-icon :name="icon"/>
</button>
</template>

@ -0,0 +1,82 @@
<script lang="ts" setup>
import {store} from '../store'
import UiTask from './UiTask.vue'
import {dispatch} from '../worker'
import {isCompleted, isInvalid, isValid} from '../../shared'
import {computed, ref} from 'vue'
import VIcon from './VIcon.vue'
const tasks = computed(() => {
const list = store.tasks.sort((a, b) => b.id - a.id)
const completes = list.filter(isCompleted).length
const invalids = list.filter(isInvalid).length
const pends = list.filter(isValid).length
return {
list,
completes,
invalids,
pends,
length: completes + invalids + pends,
}
})
const cleaning = ref(false)
const clean = async () => {
cleaning.value = true
await dispatch('task', 'cleanup')
cleaning.value = false
}
</script>
<template>
<Transition
enter-from-class="-translate-y-1/3 opacity-0"
leave-to-class="-translate-y-1/3 opacity-0"
>
<div
v-show="store.showTasksPanel"
class="
transition-all
absolute right-0 top-12 mt-2 mr-2 z-10
max-h-[400px] w-[400px] min-w-[260px] bg-white text-sm rounded-lg overflow-hidden
shadow-pop backdrop-blur-[8px] pseudo-border flex flex-col
"
data-tasks-panel
>
<div
class="flex-shrink-0 bg-gray-50 border-b pl-2 flex items-center justify-between">
<div class="py-1">任务列表</div>
<button
v-if="tasks.completes || tasks.invalids"
:disabled="cleaning"
class="hover:bg-gray-200 h-full p-1"
@click="clean"
>
清空
</button>
</div>
<div class="flex-1 overflow-y-auto overflow-hidden">
<div
v-if="!tasks.length"
class="flex justify-center items-center gap-2 p-8 opacity-40 select-none"
>
<VIcon name="MailInbox"/>
<div>没有上传任务</div>
</div>
<template v-for="(task, index) in tasks.list" v-else :key="task.id">
<div v-if="index > 0" class="border-t border-slate-50"/>
<UiTask :task="task"/>
</template>
</div>
<div
v-if="tasks.length"
class="border-t px-2 py-1 text-center text-xs text-gray-500 select-none"
>
<span> {{ tasks.length }} </span>
<span v-if="tasks.completes">完成 {{ tasks.completes }} </span>
<span v-if="tasks.invalids">失败{{ tasks.invalids }}</span>
<span v-if="tasks.pends"> {{ tasks.pends }} 项正在进行中</span>
</div>
</div>
</Transition>
</template>

@ -0,0 +1,40 @@
<script lang="ts" setup>
import {useTheme} from '../hooks'
import {hideContextMenu} from './UiContextMenuController'
import VDropdown from './VDropdown.vue'
import VButton from "./VButton.vue";
const {light, use} = useTheme()
</script>
<template>
<v-dropdown
:icon="light ? 'SunSolid' : 'MoonSolid'"
tooltip="切换主题"
@open="hideContextMenu"
>
<template #content>
<v-button
class="flex items-center gap-2 px-3 py-1.5 rounded hover:bg-indigo-500 hover:text-white text-sm select-none"
icon="SunSolid"
icon-size="1.5em"
label="浅色"
@click="use = 'light'"
/>
<v-button
class="flex items-center gap-2 px-3 py-1.5 rounded hover:bg-indigo-500 hover:text-white text-sm select-none"
icon="MoonSolid"
icon-size="1.5em"
label="深色"
@click="use = 'dark'"
/>
<v-button
class="flex items-center gap-2 px-3 py-1.5 rounded hover:bg-indigo-500 hover:text-white text-sm select-none"
icon="DesktopSolid"
icon-size="1.5em"
label="自动"
@click="use = 'system'"
/>
</template>
</v-dropdown>
</template>

@ -0,0 +1,85 @@
<script lang="ts" setup>
import type {ToastType} from './UiToastController'
import type {IconName} from "./VIcons";
import {computed, onMounted, onUnmounted, watch} from 'vue'
import VIcon from "./VIcon.vue";
const props = defineProps<{
type?: ToastType,
message: string
duration?: number
interact?: boolean
}>()
const emit = defineEmits<{
(ev: 'dismiss'): void
(ev: 'interact', value: boolean): void
}>()
type IconInfo = {
class: string; name: IconName
}
const icon = computed((): IconInfo => {
return ({
'success': {name: 'CheckmarkCircleSolid', class: 'text-green-500'},
'error': {name: 'DismissCircleSolid', class: 'text-red-500'},
'warn': {name: 'ErrorCircleSolid', class: 'text-yellow-500'},
'info': {name: 'InfoSolid', class: 'text-indigo-500'},
} as Record<ToastType, IconInfo>)[props.type ?? 'info']
})
let timer: ReturnType<typeof setTimeout> | null = null
const stop = () => {
if (timer) {
clearTimeout(timer)
timer = null
}
}
const start = () => {
const {duration = 3000} = props
if (duration > 0) {
timer = setTimeout(() => {
emit('dismiss')
}, duration)
}
}
const handleMouseEnter = () => {
emit('interact', true)
stop()
}
const handleMouseLeave = () => {
emit('interact', false)
start()
}
watch(() => props.interact, value => {
stop()
if (!value) {
start()
}
})
onMounted(start)
onUnmounted(stop)
</script>
<template>
<div
class="
flex items-center max-w-96 cursor-pointer p-2 mb-2 rounded-lg shadow-lg
select-none backdrop-blur-sm bg-white/90 ring-1 ring-black/10
"
role="alert"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
@click.stop="$emit('dismiss')"
>
<VIcon size="1.5em" v-bind="icon"/>
<div class="ms-1 text-sm font-normal" v-html="message"/>
</div>
</template>

@ -0,0 +1,24 @@
import {coerce} from "../../shared";
import {ref} from "vue";
export type ToastType = "success" | "error" | "warn" | "info";
export interface Toast {
key: number;
type: ToastType;
message: string;
duration: number
}
export const items = ref<Array<Toast>>([]);
let nextKey = 0;
export function toast(type: ToastType, message: any, duration: number = 3000): void {
items.value.unshift({
key: ++nextKey,
type,
message: String(coerce(message)),
duration
});
}

@ -0,0 +1,39 @@
<script lang="ts" setup>
import {computed, ref} from 'vue'
import UiToast from './UiToast.vue'
import {items} from './UiToastController'
const renders = computed(() => items.value.slice(0, 4))
const interact = ref(false)
const handleDismiss = (key: number): void => {
const i = items.value.findIndex(i => i.key === key)
i > -1 && items.value.splice(i, 1)
}
</script>
<template>
<div class="absolute top-0 left-0 right-0 mt-12 z-40">
<div class="absolute left-2 top-2 flex flex-col items-center">
<TransitionGroup
appear
enter-from-class="-translate-y-full opacity-0 -mb-8"
leave-to-class="-translate-y-full opacity-0 -mb-8"
move-class="transition-all"
>
<UiToast
v-for="(item, index) in renders"
:key="item.key"
:duration="item.duration"
:interact="interact"
:message="item.message"
:style="{zIndex: renders.length - index}"
:type="item.type"
class="transition-all duration-200 relative"
@dismiss="handleDismiss(item.key)"
@interact="interact = $event"
/>
</TransitionGroup>
</div>
</div>
</template>

@ -0,0 +1,14 @@
<script lang="ts" setup>
defineProps<{
value?: string | number
}>()
</script>
<template>
<span class="
absolute top-0 right-0 inline-flex items-center justify-center rounded-full px-1 min-w-[1.325em] -translate-y-[45%] translate-x-[45%]
select-none text-xs font-medium text-white bg-indigo-500 ring-2 ring-white ring-opacity-90 drop-shadow
">
<slot>{{ value }}</slot>
</span>
</template>

@ -0,0 +1,37 @@
<script lang="ts" setup>
import type {IconName} from './VIcons'
import VIcon from "./VIcon.vue";
import {vTooltip} from '../directives'
defineProps<{
type?: 'button' | 'submit' | 'reset'
icon?: IconName
label?: string
iconSize?: number | string
disabled?: boolean
tooltip?: string | number
}>()
</script>
<template>
<button
v-tooltip="tooltip"
:disabled="disabled"
:type="type"
class="flex items-center flex-shrink-0 gap-2 py-1 px-3 hover:bg-gray-100 rounded"
>
<v-icon
v-if="icon"
:name="icon"
:size="iconSize"
class="pointer-events-none"
/>
<slot>
<span
v-if="label != null"
class="pointer-events-none"
v-html="label"
/>
</slot>
</button>
</template>

@ -0,0 +1,124 @@
<script lang="ts" setup>
import {store} from '../store'
import type {IconName} from './VIcons'
import UiButton from './VButton.vue'
import {getRootBarrier} from './utils'
import {
type ComponentPublicInstance,
computed,
onMounted,
ref,
watch
} from 'vue'
import UiIconButton from "./VIconButton.vue";
import {onClickOutside, useEventListener} from "@vueuse/core";
import type {Placement} from "../utils";
import {alignWithElement} from "../utils";
const props = defineProps<{
offset?: [number, number],
placement?: Placement
disabled?: boolean
icon?: IconName
tooltip?: any
}>()
defineOptions({
inheritAttrs: false,
})
const emit = defineEmits<{
(ev: 'open'): void
(ev: 'close'): void
}>()
defineSlots<{
default(props: { open: boolean }): any
content(): any
}>()
const barrier = ref<Node | null>()
const trigger = ref<ComponentPublicInstance | null>(null)
const panel = ref<HTMLElement | null>(null)
const visible = ref(false)
const button = computed(() => {
return trigger.value != null
? trigger.value instanceof Node ? trigger.value : trigger.value.$el
: null
})
const handlePanelClick = (evt: Event) => {
const el = evt.target as HTMLElement
if (el.tagName === 'BUTTON') {
visible.value = false
emit('close')
}
}
watch(visible, () => {
if (button.value && panel.value) {
if (visible.value) {
store.showTasksPanel = false
}
alignWithElement(
button.value,
panel.value,
props.placement ?? 'bottom-left',
props.offset ?? [0, 6]
)
}
})
useEventListener(
computed(() => trigger.value?.$el),
"click", () => {
emit('open')
visible.value = true
},
)
onClickOutside(panel, () => {
visible.value = false
emit('close')
}, {ignore: [trigger]})
onMounted(() => {
barrier.value = getRootBarrier()
})
</script>
<template>
<Component
:is="!$slots.default ? UiIconButton : UiButton"
ref="trigger"
:class="{
'ring-indigo-500': visible,
'ring-2': visible,
}"
:disabled="disabled"
:icon="icon"
:tooltip="tooltip"
class="h-8 text-sm font-semibold"
v-bind="$attrs"
>
<slot :open="visible"/>
</Component>
<Teleport v-if="barrier" :to="barrier">
<div ref="panel" class="absolute top-10 left-10 z-50">
<transition
enter-from-class="opacity-0 scale-95 -translate-y-[4px]"
leave-to-class="opacity-0 scale-95 -translate-y-[4px]"
>
<div
v-if="visible"
class="transition-all origin-top-left ease-out flex flex-col items-stretch gap-0.5 p-1.5 popup-panel"
@click="handlePanelClick"
>
<slot name="content"/>
</div>
</transition>
</div>
</Teleport>
</template>

@ -0,0 +1,21 @@
<script lang="ts" setup>
import {type IconName} from './VIcons'
defineProps<{
size?: string | number
name: IconName
}>()
</script>
<template>
<svg
:height="size ?? '1.25em'"
:width="size ?? '1.25em'"
class="inline-block"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<use :href="`#${name}`" class="pointer-events-none"/>
</svg>
</template>

@ -0,0 +1,38 @@
<script lang="ts" setup>
import type {IconName} from './VIcons'
import VIcon from "./VIcon.vue";
import {vTooltip} from '../directives'
defineProps<{
icon: IconName
iconSize?: number | string
type?: "submit" | "reset" | "button" | undefined
variant?: "square" | "circle"
disabled?: boolean
rounded?: boolean
primary?: boolean
tooltip?: string | number
}>()
defineEmits<{
(ev: 'click'): void
}>()
</script>
<template>
<button
v-tooltip="tooltip"
:class="{
'rounded-full': variant === 'circle',
'rounded': variant == null,
'hover:bg-indigo-500': primary,
'hover:text-white': primary,
}"
:disabled="disabled"
:type="type ?? 'button'"
class="inline-flex justify-center items-center w-8 h-8 font-semibold leading-tight hover:bg-gray-100 disabled:bg-transparent disabled:cursor-not-allowed disabled:opacity-75 focus:ring-2 focus:ring-indigo-500"
@click="$emit('click')"
>
<v-icon :name="icon" :size="iconSize"/>
</button>
</template>

@ -0,0 +1,516 @@
export const paths = {
'Folder': 'M7.167 3c.27 0 .535.073.765.21l.135.09l1.6 1.2H15.5a2.5 2.5 0 0 1 2.479 2.174l.016.162L18 7v7.5a2.5 2.5 0 0 1-2.336 2.495L15.5 17h-11a2.5 2.5 0 0 1-2.495-2.336L2 14.5v-9a2.5 2.5 0 0 1 2.336-2.495L4.5 3h2.667zm.99 4.034a1.5 1.5 0 0 1-.933.458l-.153.008L3 7.499V14.5a1.5 1.5 0 0 0 1.356 1.493L4.5 16h11a1.5 1.5 0 0 0 1.493-1.355L17 14.5V7a1.5 1.5 0 0 0-1.355-1.493L15.5 5.5H9.617l-1.46 1.534zM7.168 4H4.5a1.5 1.5 0 0 0-1.493 1.356L3 5.5v.999l4.071.001a.5.5 0 0 0 .302-.101l.06-.054L8.694 5.02L7.467 4.1a.5.5 0 0 0-.22-.093L7.167 4z',
'FolderSolid': 'M10.565 4.5H15.5a2.5 2.5 0 0 1 2.479 2.174l.016.162L18 7v7.5a2.5 2.5 0 0 1-2.336 2.495L15.5 17h-11a2.5 2.5 0 0 1-2.495-2.336L2 14.5v-7h5.07l.154-.008a1.5 1.5 0 0 0 .823-.353l.111-.106L10.565 4.5zM7.167 3c.27 0 .535.073.765.21l.135.09l1.318.989l-1.952 2.055l-.06.055a.5.5 0 0 1-.221.094l-.081.007H2v-1a2.5 2.5 0 0 1 2.336-2.495L4.5 3h2.667z',
'FolderAdd': 'M4.5 3A2.5 2.5 0 0 0 2 5.5v9A2.5 2.5 0 0 0 4.5 17h5.1a5.465 5.465 0 0 1-.393-1H4.5A1.5 1.5 0 0 1 3 14.5v-7h4.071a1.5 1.5 0 0 0 1.087-.466L9.619 5.5H15.5A1.5 1.5 0 0 1 17 7v2.6c.358.183.693.404 1 .657V7a2.5 2.5 0 0 0-2.5-2.5H9.667l-1.6-1.2a1.5 1.5 0 0 0-.9-.3H4.5zM3 5.5A1.5 1.5 0 0 1 4.5 4h2.667a.5.5 0 0 1 .3.1l1.227.92l-1.26 1.325a.5.5 0 0 1-.363.155H3v-1zm16 9a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-4-2a.5.5 0 0 0-1 0V14h-1.5a.5.5 0 0 0 0 1H14v1.5a.5.5 0 0 0 1 0V15h1.5a.5.5 0 0 0 0-1H15v-1.5z',
'FolderAddSolid': 'M9.386 4.29l-1.32-.99a1.5 1.5 0 0 0-.9-.3H4.5A2.5 2.5 0 0 0 2 5.5v1h5.07a.5.5 0 0 0 .363-.156L9.386 4.29zm1.179.21L8.158 7.033a1.5 1.5 0 0 1-1.087.467H2v7A2.5 2.5 0 0 0 4.5 17h5.1a5.5 5.5 0 0 1 8.4-6.743V7l-.005-.164A2.5 2.5 0 0 0 15.5 4.5h-4.935zM19 14.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-4-2a.5.5 0 0 0-1 0V14h-1.5a.5.5 0 0 0 0 1H14v1.5a.5.5 0 0 0 1 0V15h1.5a.5.5 0 0 0 0-1H15v-1.5z',
'FolderArrowRight': 'M4.5 3A2.5 2.5 0 0 0 2 5.5v9A2.5 2.5 0 0 0 4.5 17h5.1a5.465 5.465 0 0 1-.393-1H4.5A1.5 1.5 0 0 1 3 14.5v-7h4.071a1.5 1.5 0 0 0 1.087-.466L9.619 5.5H15.5A1.5 1.5 0 0 1 17 7v2.6c.358.183.693.404 1 .657V7a2.5 2.5 0 0 0-2.5-2.5H9.667l-1.6-1.2a1.5 1.5 0 0 0-.9-.3H4.5zM3 5.5A1.5 1.5 0 0 1 4.5 4h2.667a.5.5 0 0 1 .3.1l1.227.92l-1.26 1.325a.5.5 0 0 1-.363.155H3v-1zM14.5 10a4.5 4.5 0 1 1 0 9a4.5 4.5 0 0 1 0-9zm2.353 4.854l.003-.003a.499.499 0 0 0 .144-.348v-.006a.5.5 0 0 0-.146-.35l-2-2a.5.5 0 0 0-.708.707L15.293 14H12.5a.5.5 0 0 0 0 1h2.793l-1.147 1.146a.5.5 0 0 0 .708.708l2-2z',
'FolderArrowRightSolid': 'M9.386 4.29l-1.32-.99a1.5 1.5 0 0 0-.9-.3H4.5A2.5 2.5 0 0 0 2 5.5v1h5.07a.5.5 0 0 0 .363-.156L9.386 4.29zm1.179.21L8.158 7.033a1.5 1.5 0 0 1-1.087.467H2v7A2.5 2.5 0 0 0 4.5 17h5.1a5.5 5.5 0 0 1 8.4-6.743V7l-.005-.164A2.5 2.5 0 0 0 15.5 4.5h-4.935zM14.5 10a4.5 4.5 0 1 1 0 9a4.5 4.5 0 0 1 0-9zm2.353 4.854l.003-.003a.499.499 0 0 0 .144-.348v-.006a.5.5 0 0 0-.146-.35l-2-2a.5.5 0 0 0-.708.707L15.293 14H12.5a.5.5 0 0 0 0 1h2.793l-1.147 1.146a.5.5 0 0 0 .708.708l2-2z',
'FolderArrowUp': 'M4.5 3h2.667c.324 0 .64.105.9.3l1.6 1.2H15.5A2.5 2.5 0 0 1 18 7v3.257a5.503 5.503 0 0 0-1-.657V7a1.5 1.5 0 0 0-1.5-1.5H9.62L8.157 7.034A1.5 1.5 0 0 1 7.07 7.5H3v7A1.5 1.5 0 0 0 4.5 16h4.707c.099.349.23.683.393 1H4.5A2.5 2.5 0 0 1 2 14.5v-9A2.5 2.5 0 0 1 4.5 3zM3 5.5v1h4.071a.5.5 0 0 0 .363-.155l1.26-1.324L7.467 4.1a.5.5 0 0 0-.3-.1H4.5A1.5 1.5 0 0 0 3 5.5zm16 9a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-4.146-2.353l-.003-.003a.497.497 0 0 0-.348-.144h-.006a.498.498 0 0 0-.35.146l-2 2a.5.5 0 0 0 .707.708L14 13.707V16.5a.5.5 0 1 0 1 0v-2.793l1.146 1.147a.5.5 0 1 0 .707-.707l-2-2z',
'FolderArrowUpSolid': 'M8.067 3.3l1.319.99l-1.953 2.054a.5.5 0 0 1-.362.156H2v-1A2.5 2.5 0 0 1 4.5 3h2.667c.324 0 .64.105.9.3zm.091 3.733L10.565 4.5H15.5a2.5 2.5 0 0 1 2.495 2.336L18 7v3.257A5.5 5.5 0 0 0 9.6 17H4.5A2.5 2.5 0 0 1 2 14.5v-7h5.07a1.5 1.5 0 0 0 1.088-.467zM19 14.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-4.146-2.353l-.003-.003a.497.497 0 0 0-.348-.144h-.006a.498.498 0 0 0-.35.146l-2 2a.5.5 0 0 0 .707.708L14 13.707V16.5a.5.5 0 1 0 1 0v-2.793l1.146 1.147a.5.5 0 1 0 .707-.707l-2-2z',
'FolderOpen': 'M16.996 7.073V7a2.5 2.5 0 0 0-2.5-2.5H9.664l-1.6-1.2a1.5 1.5 0 0 0-.9-.3H4.5A2.5 2.5 0 0 0 2 5.5l.001 8.998a2.5 2.5 0 0 0 2.201 2.482c.085.014.172.022.26.022H15.18a1.5 1.5 0 0 0 1.472-1.214l1.358-7a1.501 1.501 0 0 0-1.014-1.715zM4.5 4h2.664a.5.5 0 0 1 .3.1l1.734 1.3a.5.5 0 0 0 .3.1h4.998a1.5 1.5 0 0 1 1.5 1.5v.002H5.824a1.5 1.5 0 0 0-1.472 1.214l-1.298 6.676A1.502 1.502 0 0 1 3 14.498L3 5.5A1.5 1.5 0 0 1 4.5 4zm.833 4.407a.5.5 0 0 1 .491-.405h10.713a.5.5 0 0 1 .491.595l-1.357 7a.5.5 0 0 1-.491.405H4.463a.5.5 0 0 1-.49-.595l1.36-7z',
'FolderOpenSolid': 'M2 5.5A2.5 2.5 0 0 1 4.5 3h2.664c.325 0 .64.105.9.3l1.6 1.2h4.832a2.5 2.5 0 0 1 2.5 2.5v.002H5.824A1.5 1.5 0 0 0 4.35 8.215l-1.577 8.09a2.493 2.493 0 0 1-.773-1.807L2 5.5zm1.773 10.907a.5.5 0 0 0 .491.595H15.18a1.5 1.5 0 0 0 1.472-1.214l1.395-7.19a.5.5 0 0 0-.491-.596H5.824a.5.5 0 0 0-.491.404l-1.56 8z',
'FolderOpenVertical': 'M4 3.5A1.5 1.5 0 0 1 5.5 2h9A1.5 1.5 0 0 1 16 3.5v3.877c0 .123-.015.245-.045.364L15 11.56V14.5a1.5 1.5 0 0 1-1.5 1.5H12v.742a1.5 1.5 0 0 1-2.04 1.4L5.6 16.46A2.5 2.5 0 0 1 4 14.128V3.5zM7.186 3L10.4 4.24A2.5 2.5 0 0 1 12 6.572V15h1.5a.5.5 0 0 0 .5-.5v-3c0-.04.005-.082.015-.121l.97-3.88A.5.5 0 0 0 15 7.376V3.5a.5.5 0 0 0-.5-.5H7.186zM5 3.958v10.17a1.5 1.5 0 0 0 .96 1.4l4.36 1.681a.5.5 0 0 0 .68-.466V6.572a1.5 1.5 0 0 0-.96-1.4L5.68 3.49a.5.5 0 0 0-.68.467z',
'FolderOpenVerticalSolid': 'M4.137 2.873C4.049 3.063 4 3.276 4 3.5v10.628a2.5 2.5 0 0 0 1.6 2.332l4.36 1.682c.355.137.72.13 1.04.015V6.568a1.5 1.5 0 0 0-.956-1.398L4.137 2.873zm.797-.763l5.472 2.128A2.5 2.5 0 0 1 12 6.568V16h1.5a1.5 1.5 0 0 0 1.5-1.5v-2.938l.955-3.821c.03-.12.045-.241.045-.364V3.5A1.5 1.5 0 0 0 14.5 2h-9c-.2 0-.391.04-.566.11z',
'FolderProhibited': 'M2 5.5A2.5 2.5 0 0 1 4.5 3h2.667c.324 0 .64.105.9.3l1.6 1.2H15.5A2.5 2.5 0 0 1 18 7v3.257a5.503 5.503 0 0 0-1-.657V7a1.5 1.5 0 0 0-1.5-1.5H9.62L8.157 7.034A1.5 1.5 0 0 1 7.07 7.5H3v7A1.5 1.5 0 0 0 4.5 16h4.707c.099.349.23.683.393 1H4.5A2.5 2.5 0 0 1 2 14.5v-9zM4.5 4A1.5 1.5 0 0 0 3 5.5v1h4.071a.5.5 0 0 0 .363-.155l1.26-1.324L7.467 4.1a.5.5 0 0 0-.3-.1H4.5zM10 14.5a4.5 4.5 0 1 1 9 0a4.5 4.5 0 0 1-9 0zm4.5-3.5a3.5 3.5 0 0 0-2.803 5.596l4.9-4.9A3.484 3.484 0 0 0 14.5 11zm2.803 1.404l-4.9 4.9a3.5 3.5 0 0 0 4.9-4.9z',
'FolderProhibitedSolid': 'M9.386 4.29l-1.32-.99a1.5 1.5 0 0 0-.9-.3H4.5A2.5 2.5 0 0 0 2 5.5v1h5.07a.5.5 0 0 0 .363-.156L9.386 4.29zm1.179.21L8.158 7.033a1.5 1.5 0 0 1-1.087.467H2v7A2.5 2.5 0 0 0 4.5 17h5.1a5.5 5.5 0 0 1 8.4-6.743V7l-.005-.164A2.5 2.5 0 0 0 15.5 4.5h-4.935zM10 14.5a4.5 4.5 0 1 1 9 0a4.5 4.5 0 0 1-9 0zm4.5-3.5a3.5 3.5 0 0 0-2.803 5.596l4.9-4.9A3.484 3.484 0 0 0 14.5 11zm0 7a3.5 3.5 0 0 0 2.803-5.596l-4.9 4.9c.585.437 1.31.696 2.097.696z',
'FolderSwap': 'M7.932 3.21A1.5 1.5 0 0 0 7.167 3H4.5l-.164.005A2.5 2.5 0 0 0 2 5.5v9l.005.164A2.5 2.5 0 0 0 4.5 17h4.377l-.436-.434A1.5 1.5 0 0 1 8.085 16H4.5l-.144-.007A1.5 1.5 0 0 1 3 14.5V7.499l4.071.001l.153-.008a1.5 1.5 0 0 0 .934-.458L9.617 5.5H15.5l.145.007A1.5 1.5 0 0 1 17 7v5.883l1 1V7l-.005-.164l-.016-.162A2.5 2.5 0 0 0 15.5 4.5H9.667l-1.6-1.2l-.135-.09zM4.5 4h2.667l.08.006a.5.5 0 0 1 .22.094l1.227.921l-1.26 1.324l-.061.054a.5.5 0 0 1-.302.101L3 6.499V5.5l.007-.144A1.5 1.5 0 0 1 4.5 4zm7.356 9.859a.5.5 0 0 0-.706-.708l-2.003 1.998a.5.5 0 0 0 0 .708l2.003 1.997a.5.5 0 1 0 .706-.708l-1.147-1.144h5.584l-1.146 1.144a.5.5 0 1 0 .706.708l2-1.996a.5.5 0 0 0 0-.708l-2-1.999a.5.5 0 1 0-.707.707l1.145 1.144H10.71l1.146-1.143z',
'FolderSwapSolid': 'M10.565 4.5H15.5a2.5 2.5 0 0 1 2.479 2.174l.016.162L18 7v6.883l-1.44-1.44a1.5 1.5 0 0 0-2.475 1.56h-1.166a1.5 1.5 0 0 0-2.475-1.56L8.44 14.44a1.5 1.5 0 0 0 0 2.125l.436.434H4.5a2.5 2.5 0 0 1-2.495-2.336L2 14.5v-7h5.07l.154-.008a1.5 1.5 0 0 0 .823-.353l.111-.106L10.565 4.5zM7.167 3c.27 0 .535.073.765.21l.135.09l1.318.989l-1.952 2.055l-.06.055a.5.5 0 0 1-.221.094l-.081.007H2v-1a2.5 2.5 0 0 1 2.336-2.495L4.5 3h2.667zm4.69 10.859a.5.5 0 0 0-.707-.708l-2.003 1.998a.5.5 0 0 0 0 .708l2.003 1.997a.5.5 0 1 0 .706-.708l-1.147-1.144h5.584l-1.146 1.144a.5.5 0 1 0 .706.708l2-1.996a.5.5 0 0 0 0-.708l-2-1.999a.5.5 0 1 0-.707.707l1.145 1.144H10.71l1.146-1.143z',
'FolderSync': 'M4.5 3A2.5 2.5 0 0 0 2 5.5v9A2.5 2.5 0 0 0 4.5 17h5.1a5.465 5.465 0 0 1-.393-1H4.5A1.5 1.5 0 0 1 3 14.5v-7h4.071a1.5 1.5 0 0 0 1.087-.466L9.619 5.5H15.5A1.5 1.5 0 0 1 17 7v2.6c.358.183.693.404 1 .657V7a2.5 2.5 0 0 0-2.5-2.5H9.667l-1.6-1.2a1.5 1.5 0 0 0-.9-.3H4.5zM3 5.5A1.5 1.5 0 0 1 4.5 4h2.667a.5.5 0 0 1 .3.1l1.227.92l-1.26 1.325a.5.5 0 0 1-.363.155H3v-1zM14.5 19a4.5 4.5 0 1 1 0-9a4.5 4.5 0 0 1 0 9zm1.5-7v.152a3.011 3.011 0 0 0-1.448-.401a2.999 2.999 0 0 0-2.173.878a.5.5 0 0 0 .707.707A2 2 0 0 1 15.468 13H15a.5.5 0 0 0 0 1h1.5a.5.5 0 0 0 .5-.5V12a.5.5 0 0 0-1 0zm-1.552 5.25a2.999 2.999 0 0 0 2.173-.879a.5.5 0 0 0-.707-.707a2 2 0 0 1-2.382.336H14a.5.5 0 0 0 0-1h-1.5a.5.5 0 0 0-.5.5V17a.5.5 0 0 0 1 0v-.152a3.011 3.011 0 0 0 1.448.402z',
'FolderSyncSolid': 'M9.386 4.29l-1.32-.99a1.5 1.5 0 0 0-.9-.3H4.5A2.5 2.5 0 0 0 2 5.5v1h5.07a.5.5 0 0 0 .363-.156L9.386 4.29zm1.179.21L8.158 7.033a1.5 1.5 0 0 1-1.087.467H2v7A2.5 2.5 0 0 0 4.5 17h5.1a5.5 5.5 0 0 1 8.4-6.743V7l-.005-.164A2.5 2.5 0 0 0 15.5 4.5h-4.935zM14.5 19a4.5 4.5 0 1 1 0-9a4.5 4.5 0 0 1 0 9zm1.5-7v.152a3.011 3.011 0 0 0-1.448-.401a2.999 2.999 0 0 0-2.173.878a.5.5 0 0 0 .707.707A2 2 0 0 1 15.468 13H15a.5.5 0 0 0 0 1h1.5a.5.5 0 0 0 .5-.5V12a.5.5 0 0 0-1 0zm-1.552 5.25a2.999 2.999 0 0 0 2.173-.879a.5.5 0 0 0-.707-.707a2 2 0 0 1-2.382.336H14a.5.5 0 0 0 0-1h-1.5a.5.5 0 0 0-.5.5V17a.5.5 0 0 0 1 0v-.152a3.011 3.011 0 0 0 1.448.402z',
'FolderZip': 'M4.5 3A2.5 2.5 0 0 0 2 5.5v9A2.5 2.5 0 0 0 4.5 17h5.1a5.465 5.465 0 0 1-.393-1H4.5A1.5 1.5 0 0 1 3 14.5v-7h4.071a1.5 1.5 0 0 0 1.087-.466L9.619 5.5H15.5A1.5 1.5 0 0 1 17 7v2.6c.358.183.693.404 1 .657V7a2.5 2.5 0 0 0-2.5-2.5H9.667l-1.6-1.2a1.5 1.5 0 0 0-.9-.3H4.5zM3 5.5A1.5 1.5 0 0 1 4.5 4h2.667a.5.5 0 0 1 .3.1l1.227.92l-1.26 1.325a.5.5 0 0 1-.363.155H3v-1zM14.5 19a4.5 4.5 0 1 1 0-9a4.5 4.5 0 0 1 0 9zm1.5-7v.152a3.011 3.011 0 0 0-1.448-.401a2.999 2.999 0 0 0-2.173.878a.5.5 0 0 0 .707.707A2 2 0 0 1 15.468 13H15a.5.5 0 0 0 0 1h1.5a.5.5 0 0 0 .5-.5V12a.5.5 0 0 0-1 0zm-1.552 5.25a2.999 2.999 0 0 0 2.173-.879a.5.5 0 0 0-.707-.707a2 2 0 0 1-2.382.336H14a.5.5 0 0 0 0-1h-1.5a.5.5 0 0 0-.5.5V17a.5.5 0 0 0 1 0v-.152a3.011 3.011 0 0 0 1.448.402z',
'FolderZipSolid': 'M12.005 4.5h-1.44L8.158 7.033l-.111.106a1.5 1.5 0 0 1-.823.353L7.07 7.5H2v7l.005.164A2.5 2.5 0 0 0 4.5 17h8.504v-1.941a.515.515 0 0 1 0-.117L13.002 14h-.498a.5.5 0 0 1 0-1h.497v-2H12.5a.5.5 0 0 1 0-1h.5V9h-.495a.5.5 0 0 1-.5-.5v-4zm2 0h-1V8h1V4.5zm1 0h.495a2.5 2.5 0 0 1 2.479 2.174l.016.162L18 7v7.5a2.5 2.5 0 0 1-2.336 2.495L15.5 17h-1.496v-1.5h.496a.5.5 0 0 0 0-1h-.497v-.955a.478.478 0 0 0 0-.09V12.5h.502a.5.5 0 1 0 0-1h-.503L14 9h.505a.5.5 0 0 0 .5-.5v-4zM7.932 3.21A1.5 1.5 0 0 0 7.167 3H4.5l-.164.005A2.5 2.5 0 0 0 2 5.5v1h5.07l.082-.007a.5.5 0 0 0 .22-.094l.061-.055L9.385 4.29L8.067 3.3l-.135-.09z',
'Checkmark': 'M3.374 10.168a.5.5 0 0 0-.748.664l4 4.5a.5.5 0 0 0 .728.022l10.5-10.5a.5.5 0 0 0-.707-.708L7.02 14.271l-3.647-4.103z',
'CheckmarkSolid': 'M7.032 13.907l-3.471-3.905a.75.75 0 0 0-1.122.996l4 4.5a.75.75 0 0 0 1.091.032l10.5-10.5a.75.75 0 0 0-1.06-1.06l-9.938 9.937z',
'CheckmarkCircle': 'M10 2a8 8 0 1 1 0 16a8 8 0 0 1 0-16zm0 1a7 7 0 1 0 0 14a7 7 0 0 0 0-14zm3.358 4.646a.5.5 0 0 1 .058.638l-.058.07l-4.004 4.004a.5.5 0 0 1-.638.058l-.07-.058l-2-2a.5.5 0 0 1 .638-.765l.07.058L9 11.298l3.651-3.652a.5.5 0 0 1 .707 0z',
'CheckmarkCircleSolid': 'M10 2a8 8 0 1 1 0 16a8 8 0 0 1 0-16zm3.358 5.646a.5.5 0 0 0-.637-.057l-.07.057L9 11.298L7.354 9.651l-.07-.058a.5.5 0 0 0-.695.696l.057.07l2 2l.07.057a.5.5 0 0 0 .568 0l.07-.058l4.004-4.004l.058-.07a.5.5 0 0 0-.058-.638z',
'CheckmarkStarburst': 'M8.46 1.897l.99.39a1.5 1.5 0 0 0 1.099 0l.99-.39a2.418 2.418 0 0 1 3.102 1.285l.424.975a1.5 1.5 0 0 0 .777.777l.975.424a2.418 2.418 0 0 1 1.285 3.103l-.39.99a1.5 1.5 0 0 0 0 1.098l.39.99a2.418 2.418 0 0 1-1.285 3.102l-.975.424a1.499 1.499 0 0 0-.777.777l-.424.975a2.418 2.418 0 0 1-3.103 1.285l-.99-.39a1.5 1.5 0 0 0-1.098 0l-.99.39a2.418 2.418 0 0 1-3.102-1.285l-.424-.975a1.5 1.5 0 0 0-.777-.777l-.975-.424a2.418 2.418 0 0 1-1.285-3.103l.39-.99a1.5 1.5 0 0 0 0-1.098l-.39-.99a2.418 2.418 0 0 1 1.285-3.102l.975-.424a1.5 1.5 0 0 0 .777-.777l.424-.975a2.418 2.418 0 0 1 3.103-1.285zm3.445.93l-.99.39a2.5 2.5 0 0 1-1.831 0l-.99-.39a1.418 1.418 0 0 0-1.819.754l-.424.975a2.5 2.5 0 0 1-1.295 1.295l-.975.424a1.418 1.418 0 0 0-.753 1.82l.389.989a2.5 2.5 0 0 1 0 1.831l-.39.99c-.279.71.054 1.514.754 1.819l.975.424a2.5 2.5 0 0 1 1.295 1.295l.424.975a1.418 1.418 0 0 0 1.82.753l.989-.39a2.5 2.5 0 0 1 1.831 0l.99.39c.71.28 1.514-.053 1.819-.753l.424-.975a2.5 2.5 0 0 1 1.295-1.295l.975-.424a1.418 1.418 0 0 0 .753-1.82l-.39-.989a2.5 2.5 0 0 1 0-1.831l.39-.99a1.418 1.418 0 0 0-.753-1.819l-.975-.424a2.5 2.5 0 0 1-1.295-1.295l-.424-.975a1.418 1.418 0 0 0-1.82-.753zm-2.927 8.944l3.648-4.104a.5.5 0 0 1 .8.592l-.053.073l-4 4.5a.5.5 0 0 1-.655.081l-.072-.06l-2-2a.5.5 0 0 1 .638-.765l.069.058l1.625 1.625l3.648-4.104l-3.648 4.104z',
'CheckmarkStarburstSolid': 'M8.46 1.897l.99.39a1.5 1.5 0 0 0 1.099 0l.99-.39a2.418 2.418 0 0 1 3.102 1.285l.424.975a1.5 1.5 0 0 0 .777.777l.975.424a2.418 2.418 0 0 1 1.285 3.103l-.39.99a1.5 1.5 0 0 0 0 1.098l.39.99a2.418 2.418 0 0 1-1.285 3.102l-.975.424a1.499 1.499 0 0 0-.777.777l-.424.975a2.418 2.418 0 0 1-3.103 1.285l-.99-.39a1.5 1.5 0 0 0-1.098 0l-.99.39a2.418 2.418 0 0 1-3.102-1.285l-.424-.975a1.5 1.5 0 0 0-.777-.777l-.975-.424a2.418 2.418 0 0 1-1.285-3.103l.39-.99a1.5 1.5 0 0 0 0-1.098l-.39-.99a2.418 2.418 0 0 1 1.285-3.102l.975-.424a1.5 1.5 0 0 0 .777-.777l.424-.975a2.418 2.418 0 0 1 3.103-1.285zm4.166 5.77l-3.648 4.104l-1.625-1.625a.5.5 0 0 0-.707.707l2 2a.5.5 0 0 0 .727-.021l4-4.5a.5.5 0 0 0-.747-.665z',
'ArrowSyncCheckmark': 'M11.414 3.635a.5.5 0 0 0 0-.707L9.293.807a.5.5 0 0 0-.707.707l.997.997a7.5 7.5 0 0 0-4.075 13.495a.5.5 0 0 0 .6-.8a6.5 6.5 0 0 1 5.29-11.554l.016-.017zM8.586 16.363l.016-.016c.408.09.831.14 1.264.15l-.006.006a.502.502 0 0 1 .074-.004a6.5 6.5 0 0 0 3.959-11.706a.5.5 0 1 1 .6-.8a7.5 7.5 0 0 1-4.075 13.495l.996.996a.5.5 0 1 1-.707.707l-2.121-2.12a.5.5 0 0 1 0-.708zm3.768-8.218a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708 0l-1.5-1.5a.5.5 0 0 1 .708-.708L9 10.792l2.646-2.647a.5.5 0 0 1 .708 0zM5 10a5 5 0 1 1 10 0a5 5 0 0 1-10 0zm5-4a4 4 0 1 0 0 8a4 4 0 0 0 0-8z',
'ArrowSyncCheckmarkSolid': 'M11.414 3.635a.5.5 0 0 0 0-.707L9.293.807a.5.5 0 0 0-.707.707l.997.997a7.5 7.5 0 0 0-4.075 13.495a.5.5 0 0 0 .6-.8a6.5 6.5 0 0 1 5.29-11.554l.016-.017zM8.586 16.363l.016-.016c.408.09.831.14 1.264.15l-.006.006a.516.516 0 0 1 .074-.004a6.5 6.5 0 0 0 3.959-11.706a.5.5 0 1 1 .6-.8a7.5 7.5 0 0 1-4.075 13.495l.996.996a.5.5 0 1 1-.707.707l-2.121-2.12a.5.5 0 0 1 0-.708zM15 9.999a5 5 0 1 1-10 0a5 5 0 0 1 10 0zm-2.646-1.854a.5.5 0 0 0-.708 0L9 10.792L7.854 9.645a.5.5 0 1 0-.708.708l1.5 1.5a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0 0-.708z',
'Image': 'M14 7.5a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0zm-1 0a.5.5 0 1 0-1 0a.5.5 0 0 0 1 0zM3 6a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6zm3-2a2 2 0 0 0-2 2v8c0 .373.102.722.28 1.02l4.669-4.588a1.5 1.5 0 0 1 2.102 0l4.67 4.588A1.99 1.99 0 0 0 16 14V6a2 2 0 0 0-2-2H6zm0 12h8c.37 0 .715-.1 1.012-.274l-4.662-4.58a.5.5 0 0 0-.7 0l-4.662 4.58A1.99 1.99 0 0 0 6 16z',
'ImageSolid': 'M12.5 8a.5.5 0 1 0 0-1a.5.5 0 0 0 0 1zM3 6a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v8c0 .65-.206 1.25-.557 1.742l-5.39-5.308a1.5 1.5 0 0 0-2.105 0l-5.39 5.308A2.986 2.986 0 0 1 3 14V6zm9.5 3a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3zm-8.235 7.448C4.755 16.796 5.354 17 6 17h8c.646 0 1.245-.204 1.735-.552l-5.384-5.3a.5.5 0 0 0-.702 0l-5.384 5.3z',
'DrawImage': 'M14 7.5a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0zm-1 0a.5.5 0 1 0-1 0a.5.5 0 0 0 1 0zM3 6a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v3.003c-.341.016-.68.092-1 .229V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8.826a.2.2 0 0 0 .34.142l.807-.796l-.002-.003l1.048-1.03l.206-.202l2.55-2.505a1.5 1.5 0 0 1 2.102 0l1.745 1.714l-.707.708l-1.739-1.709a.5.5 0 0 0-.7 0l-2.756 2.707l-1.851 1.828c-.758.748-2.043.21-2.043-.854V6zm.4 11.035c.369.184.83.335 1.217.25c.251-.056.577-.193.943-.347c.885-.373 2.003-.843 2.862-.497c.637.256.584.981.405 1.33c-.035.066-.008.16.065.177a4.6 4.6 0 0 0 1.112.088a.917.917 0 0 1 .023-.14l.375-1.498c.096-.386.296-.74.578-1.02l4.83-4.83a1.87 1.87 0 1 1 2.644 2.645l-4.83 4.829a2.197 2.197 0 0 1-1.02.578l-1.222.305c-1.121.328-2.794.222-3.313-.183c-.449-.35-.467-.887-.316-1.244c.034-.08-.026-.183-.111-.17c-.495.07-.9.25-1.3.427c-.585.26-1.156.513-1.976.411c-.711-.088-1.107-.459-1.325-.825c-.122-.204.147-.392.36-.286z',
'DrawImageSolid': 'M6 3a3 3 0 0 0-3 3v9.076a.51.51 0 0 0 .868.363l1.342-1.325l3.738-3.68a1.5 1.5 0 0 1 2.104 0l1.742 1.715l2.308-2.308A2.86 2.86 0 0 1 17 9.003V6a3 3 0 0 0-3-3H6zm8 4.5a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0zm-1 0a.5.5 0 1 1-1 0a.5.5 0 0 1 1 0zm-2.727 7.17l1.813-1.814l-1.735-1.709a.5.5 0 0 0-.702 0l-4.224 4.159c-.22.236-.008.587.296.478l.327-.117c.705-.253 1.764-.55 2.747-.154c.286.115.512.28.684.472c.154-.495.426-.947.794-1.315zm.707.707l4.83-4.83a1.87 1.87 0 1 1 2.644 2.646l-4.83 4.829a2.197 2.197 0 0 1-1.02.578l-1.221.305c-1.122.328-2.795.222-3.314-.183c-.449-.35-.467-.887-.316-1.244c.034-.08-.026-.183-.111-.17c-.495.07-.9.25-1.3.427c-.584.26-1.156.513-1.976.411c-.711-.088-1.107-.459-1.325-.825c-.122-.204.147-.392.36-.286c.368.184.829.335 1.216.25c.251-.056.577-.193.943-.347c.885-.373 2.003-.843 2.863-.497c.636.256.583.981.404 1.33c-.035.066-.008.16.065.177a4.6 4.6 0 0 0 1.112.088a.917.917 0 0 1 .023-.14l.375-1.498c.096-.386.296-.74.578-1.02z',
'ImageCopy': 'M8.498 7.497a.998.998 0 1 0 0-1.995a.998.998 0 0 0 0 1.995zM5 6a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H8a3 3 0 0 1-3-3V6zm3-2a2 2 0 0 0-2 2v6c0 .37.101.718.277 1.016L9.79 9.502a1.71 1.71 0 0 1 2.418 0l3.514 3.514C15.9 12.718 16 12.371 16 12V6a2 2 0 0 0-2-2H8zm7.016 9.723l-3.514-3.514a.71.71 0 0 0-1.004 0l-3.514 3.514C7.282 13.9 7.629 14 8 14h6c.37 0 .718-.101 1.016-.277zM12 17c.889 0 1.687-.386 2.236-1H7.5A3.5 3.5 0 0 1 4 12.5V5.764C3.386 6.314 3 7.112 3 8v4.5A4.5 4.5 0 0 0 7.5 17H12z',
'ImageCopySolid': 'M5 6a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v6c0 .648-.205 1.248-.555 1.738L12.21 9.502a1.71 1.71 0 0 0-2.418 0l-4.236 4.236A2.986 2.986 0 0 1 5 12V6zm3.498 1.497a.998.998 0 1 0 0-1.995a.998.998 0 0 0 0 1.995zm3.004 2.712l4.236 4.236c-.49.35-1.09.555-1.738.555H8a2.986 2.986 0 0 1-1.738-.555l4.236-4.236a.71.71 0 0 1 1.004 0zM14.236 16c-.55.614-1.348 1-2.236 1H7.5A4.5 4.5 0 0 1 3 12.5V8c0-.888.386-1.687 1-2.236V12.5A3.5 3.5 0 0 0 7.5 16h6.736z',
'ImageEdit': 'M13.999 7.5a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0zm-1 0a.5.5 0 1 0-1 0a.5.5 0 0 0 1 0zM3 6a3 3 0 0 1 3-3h7.999a3 3 0 0 1 3 3v3.002a2.87 2.87 0 0 0-1 .229V6a2 2 0 0 0-2-2h-8a2 2 0 0 0-2 2v7.999c0 .372.103.721.28 1.02l4.669-4.588a1.5 1.5 0 0 1 2.102 0l1.745 1.715l-.707.707l-1.738-1.709a.5.5 0 0 0-.701 0l-4.661 4.58A1.99 1.99 0 0 0 6 16h3.474c-.016.05-.03.103-.043.155l-.211.845H6a3 3 0 0 1-3-3v-8zm7.979 9.376l4.829-4.83a1.87 1.87 0 1 1 2.644 2.646l-4.829 4.828a2.197 2.197 0 0 1-1.02.578l-1.498.375a.89.89 0 0 1-1.078-1.079l.374-1.498c.097-.386.296-.739.578-1.02z',
'ImageEditSolid': 'M12.499 8a.5.5 0 1 0 0-1a.5.5 0 0 0 0 1zM3 6a3 3 0 0 1 3-3h7.999a3 3 0 0 1 3 3v3.002a2.86 2.86 0 0 0-1.898.838l-2.308 2.308l-1.741-1.714a1.5 1.5 0 0 0-2.105 0l-5.39 5.307A2.986 2.986 0 0 1 3 13.998v-8zm9.499 3a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3zm-2.227 5.669l1.813-1.814l-1.735-1.709a.5.5 0 0 0-.702 0l-5.383 5.3c.49.348 1.088.552 1.735.552h3.22l.21-.844c.141-.562.432-1.075.842-1.485zm.707.707l4.829-4.83a1.87 1.87 0 1 1 2.644 2.646l-4.829 4.828a2.197 2.197 0 0 1-1.02.578l-1.498.375a.89.89 0 0 1-1.078-1.079l.374-1.498c.097-.386.296-.739.578-1.02z',
'ImageMultiple': 'M11.5 7.5a1 1 0 1 0 0-2a1 1 0 0 0 0 2zM3 6a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6zm3-2a2 2 0 0 0-2 2v6c0 .37.101.718.277 1.016l3.309-3.309a2 2 0 0 1 2.828 0l3.31 3.309c.175-.298.276-.645.276-1.016V6a2 2 0 0 0-2-2H6zm3.707 6.414a1 1 0 0 0-1.414 0l-3.309 3.31c.298.175.645.276 1.016.276h6c.37 0 .718-.101 1.016-.277l-3.309-3.309zM8 17a2.992 2.992 0 0 1-2.236-1H12.5a3.5 3.5 0 0 0 3.5-3.5V5.764c.614.55 1 1.348 1 2.236v4.5a4.5 4.5 0 0 1-4.5 4.5H8z',
'ImageMultipleSolid': 'M6 3a3 3 0 0 0-3 3v6c0 .648.205 1.248.555 1.738l4.03-4.03a2 2 0 0 1 2.83 0l4.03 4.03c.35-.49.555-1.09.555-1.738V6a3 3 0 0 0-3-3H6zm6.5 3.5a1 1 0 1 1-2 0a1 1 0 0 1 2 0zm1.238 7.945l-4.03-4.03a1 1 0 0 0-1.415 0l-4.031 4.03c.49.35 1.09.555 1.738.555h6c.648 0 1.248-.205 1.738-.555zM5.764 16c.55.614 1.348 1 2.236 1h4.5a4.5 4.5 0 0 0 4.5-4.5V8c0-.888-.386-1.687-1-2.236V12.5a3.5 3.5 0 0 1-3.5 3.5H5.764z',
'ImageOff': 'M2.854 2.146a.5.5 0 1 0-.708.708l1.409 1.408C3.205 4.752 3 5.352 3 6v8a3 3 0 0 0 3 3h8c.648 0 1.248-.205 1.738-.555l1.408 1.409a.5.5 0 0 0 .708-.708l-15-15zm6.56 7.975a1.497 1.497 0 0 0-.465.311l-4.67 4.588A1.99 1.99 0 0 1 4 14V6c0-.37.101-.718.277-1.016l5.137 5.137zM6 16c-.37 0-.715-.1-1.012-.274l4.662-4.58a.5.5 0 0 1 .7 0l4.662 4.58A1.991 1.991 0 0 1 14 16H6zM16 6v7.879l.898.898c.067-.248.102-.508.102-.777V6a3 3 0 0 0-3-3H6c-.269 0-.53.035-.777.102L6.12 4H14a2 2 0 0 1 2 2zm-2 1.5a1.5 1.5 0 1 0-3 0a1.5 1.5 0 0 0 3 0zm-1 0a.5.5 0 1 1-1 0a.5.5 0 0 1 1 0z',
'ImageOffSolid': 'M2.854 2.146a.5.5 0 1 0-.708.708l1.409 1.408C3.205 4.752 3 5.352 3 6v8c0 .65.206 1.25.557 1.742l5.39-5.308c.14-.136.298-.24.468-.312l.632.633l-.352.352a.51.51 0 0 0-.046.04l-5.384 5.301C4.755 16.796 5.354 17 6 17h8c.597 0 1.154-.174 1.622-.475l.01-.008c.035-.022.069-.045.103-.069l-.003-.003l.003-.003l1.411 1.412a.5.5 0 0 0 .708-.708l-15-15zM13 7.5a.5.5 0 1 1-1 0a.5.5 0 0 1 1 0zM5.223 3.102l11.675 11.675c.067-.248.102-.508.102-.777V6a3 3 0 0 0-3-3H6c-.269 0-.53.035-.777.102zM14 7.5a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0z',
'ImageProhibited': 'M5.5 10a4.5 4.5 0 1 0 0-9a4.5 4.5 0 0 0 0 9zm0-1c-.786 0-1.512-.26-2.096-.697l4.9-4.899A3.5 3.5 0 0 1 5.5 9zm2.096-6.303l-4.9 4.9a3.5 3.5 0 0 1 4.9-4.9zM3 10.4c.317.162.651.294 1 .393V14c0 .373.102.722.28 1.02l4.669-4.588a1.5 1.5 0 0 1 2.102 0l4.67 4.588A1.99 1.99 0 0 0 16 14V6a2 2 0 0 0-2-2h-3.207a5.466 5.466 0 0 0-.393-1H14a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3v-3.6zm1.988 5.326A1.99 1.99 0 0 0 6 16h8c.37 0 .715-.1 1.012-.274l-4.662-4.58a.5.5 0 0 0-.7 0l-4.662 4.58zM14 7.5a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0zm-1 0a.5.5 0 1 0-1 0a.5.5 0 0 0 1 0z',
'ImageProhibitedSolid': 'M5.5 10a4.5 4.5 0 1 0 0-9a4.5 4.5 0 0 0 0 9zm0-1c-.786 0-1.512-.26-2.096-.697l4.9-4.899A3.5 3.5 0 0 1 5.5 9zM2.697 7.596a3.5 3.5 0 0 1 4.9-4.9l-4.9 4.9zM13 7.5a.5.5 0 1 1-1 0a.5.5 0 0 1 1 0zM5.5 11a5.5 5.5 0 0 0 4.9-8H14a3 3 0 0 1 3 3v8c0 .65-.206 1.25-.557 1.742l-5.39-5.308a1.5 1.5 0 0 0-2.105 0l-5.39 5.308A2.986 2.986 0 0 1 3 14v-3.6c.75.384 1.6.6 2.5.6zM14 7.5a1.5 1.5 0 1 0-3 0a1.5 1.5 0 0 0 3 0zM6 17a2.987 2.987 0 0 1-1.735-.552l5.384-5.3a.5.5 0 0 1 .702 0l5.384 5.3A2.987 2.987 0 0 1 14 17H6z',
'ImageSearch': 'M6 3a3 3 0 0 0-3 3v2.758a4.484 4.484 0 0 1 1-.502V6a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8c0 .373-.102.722-.28 1.02l-4.669-4.588a1.5 1.5 0 0 0-1.71-.278c.173.283.316.587.424.907a.5.5 0 0 1 .585.084l4.662 4.58A1.991 1.991 0 0 1 14 16h-2.879l.44.44c.163.163.281.355.354.56H14a3 3 0 0 0 3-3V6a3 3 0 0 0-3-3H6zm6.5 6a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3zm0-1a.5.5 0 1 1 0-1a.5.5 0 0 1 0 1zm-4.197 6.596a3.5 3.5 0 1 0-.707.707l2.55 2.55a.5.5 0 0 0 .708-.707l-2.55-2.55zM5.5 15a2.5 2.5 0 1 1 0-5a2.5 2.5 0 0 1 0 5z',
'ImageSearchSolid': 'M12.5 8a.5.5 0 1 0 0-1a.5.5 0 0 0 0 1zM3 6a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v8c0 .65-.206 1.25-.557 1.742l-5.39-5.308a1.5 1.5 0 0 0-1.711-.279A4.497 4.497 0 0 0 3 8.758V6zm9.5 3a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3zm-.585 8H14c.646 0 1.245-.204 1.735-.552l-5.384-5.3a.5.5 0 0 0-.586-.086c.152.451.235.935.235 1.438c0 .694-.158 1.352-.439 1.94l2 2c.163.163.281.355.354.56zm-3.612-2.404a3.5 3.5 0 1 0-.707.707l2.55 2.55a.5.5 0 0 0 .708-.707l-2.55-2.55zM5.5 15a2.5 2.5 0 1 1 0-5a2.5 2.5 0 0 1 0 5z',
'Video': 'M4.5 4A2.5 2.5 0 0 0 2 6.5v7A2.5 2.5 0 0 0 4.5 16h7a2.5 2.5 0 0 0 2.5-2.5v-1l2.4 1.8a1 1 0 0 0 1.6-.8v-7a1 1 0 0 0-1.6-.8L14 7.5v-1A2.5 2.5 0 0 0 11.5 4h-7zM14 8.75l3-2.25v7l-3-2.25v-2.5zM13 6.5v7a1.5 1.5 0 0 1-1.5 1.5h-7A1.5 1.5 0 0 1 3 13.5v-7A1.5 1.5 0 0 1 4.5 5h7A1.5 1.5 0 0 1 13 6.5z',
'VideoSolid': 'M13 6.5A2.5 2.5 0 0 0 10.5 4h-6A2.5 2.5 0 0 0 2 6.5v7A2.5 2.5 0 0 0 4.5 16h6a2.5 2.5 0 0 0 2.5-2.5v-7zm1 1.43v4.152l2.764 2.35A.75.75 0 0 0 18 13.86V6.193a.75.75 0 0 0-1.23-.575L14 7.93z',
'VideoAdd': 'M4.5 4A2.5 2.5 0 0 0 2 6.5v3.757A5.504 5.504 0 0 1 3 9.6V6.5A1.5 1.5 0 0 1 4.5 5h7A1.5 1.5 0 0 1 13 6.5v7a1.5 1.5 0 0 1-1.5 1.5h-.522a5.489 5.489 0 0 1-.185 1h.707a2.5 2.5 0 0 0 2.5-2.5v-1l2.4 1.8a1 1 0 0 0 1.6-.8v-7a1 1 0 0 0-1.6-.8L14 7.5v-1A2.5 2.5 0 0 0 11.5 4h-7zM14 8.75l3-2.25v7l-3-2.25v-2.5zm-4 5.75a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-4-2a.5.5 0 0 0-1 0V14H3.5a.5.5 0 0 0 0 1H5v1.5a.5.5 0 0 0 1 0V15h1.5a.5.5 0 0 0 0-1H6v-1.5z',
'VideoAddSolid': 'M13 6.5A2.5 2.5 0 0 0 10.5 4h-6A2.5 2.5 0 0 0 2 6.5v3.757a5.5 5.5 0 0 1 8.798 5.725A2.5 2.5 0 0 0 13 13.5v-7zm1 1.43v4.152l2.764 2.35A.75.75 0 0 0 18 13.86V6.193a.75.75 0 0 0-1.23-.575L14 7.93zm-4 6.57a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-4-2a.5.5 0 0 0-1 0V14H3.5a.5.5 0 0 0 0 1H5v1.5a.5.5 0 0 0 1 0V15h1.5a.5.5 0 0 0 0-1H6v-1.5z',
'VideoOff': 'M2.854 2.146a.5.5 0 1 0-.708.708l1.355 1.354A2.5 2.5 0 0 0 2 6.5v7A2.5 2.5 0 0 0 4.5 16h7a2.5 2.5 0 0 0 2.292-1.5l3.354 3.354a.5.5 0 0 0 .708-.708l-15-15zm10.133 11.549A1.5 1.5 0 0 1 11.5 15h-7A1.5 1.5 0 0 1 3 13.5v-7a1.5 1.5 0 0 1 1.305-1.488l8.683 8.683zM13 10.879l3.47 3.469A1 1 0 0 0 18 13.5v-7a1 1 0 0 0-1.6-.8L14 7.5v-1A2.5 2.5 0 0 0 11.5 4H6.121l1 1H11.5A1.5 1.5 0 0 1 13 6.5v4.379zm1-2.129l3-2.25v7l-3-2.25v-2.5z',
'VideoOffSolid': 'M2.854 2.146a.5.5 0 1 0-.708.708l1.355 1.354A2.5 2.5 0 0 0 2 6.5v7A2.5 2.5 0 0 0 4.5 16h6a2.5 2.5 0 0 0 2.492-2.3l4.154 4.154a.5.5 0 0 0 .708-.708l-15-15zm13.91 12.286l-1.41-1.199L14 11.88V7.93l2.77-2.314a.75.75 0 0 1 1.23.576v7.667a.75.75 0 0 1-1.236.572zM13 10.879l-6.879-6.88H10.5A2.5 2.5 0 0 1 13 6.5v4.38z',
'VideoProhibited': 'M2 6.5A2.5 2.5 0 0 1 4.5 4h7A2.5 2.5 0 0 1 14 6.5v1l2.4-1.8a1 1 0 0 1 1.6.8v3.757a5.503 5.503 0 0 0-1-.657V6.5l-3 2.25v.272a5.48 5.48 0 0 0-1 .185V6.5A1.5 1.5 0 0 0 11.5 5h-7A1.5 1.5 0 0 0 3 6.5v7A1.5 1.5 0 0 0 4.5 15h4.522c.031.343.094.678.185 1H4.5A2.5 2.5 0 0 1 2 13.5v-7zm8 8a4.5 4.5 0 1 0 9 0a4.5 4.5 0 0 0-9 0zm2.404 2.803l4.9-4.9a3.5 3.5 0 0 1-4.9 4.9zm-.707-.707a3.5 3.5 0 0 1 4.9-4.9l-4.9 4.9z',
'VideoProhibitedSolid': 'M13 6.5A2.5 2.5 0 0 0 10.5 4h-6A2.5 2.5 0 0 0 2 6.5v7A2.5 2.5 0 0 0 4.5 16h4.707A5.502 5.502 0 0 1 13 9.207V6.5zm5-.307v4.064a5.477 5.477 0 0 0-4-1.235V7.931l2.77-2.313a.75.75 0 0 1 1.23.575zM10 14.5a4.5 4.5 0 1 0 9 0a4.5 4.5 0 0 0-9 0zm1 0a3.5 3.5 0 0 1 5.596-2.803l-4.9 4.9A3.484 3.484 0 0 1 11 14.5zm3.5 3.5c-.786 0-1.512-.26-2.096-.697l4.9-4.9A3.5 3.5 0 0 1 14.5 18z',
'VideoSync': 'M4.5 4A2.5 2.5 0 0 0 2 6.5v3.757A5.504 5.504 0 0 1 3 9.6V6.5A1.5 1.5 0 0 1 4.5 5h7A1.5 1.5 0 0 1 13 6.5v7a1.5 1.5 0 0 1-1.5 1.5h-.522a5.489 5.489 0 0 1-.185 1h.707a2.5 2.5 0 0 0 2.5-2.5v-1l2.4 1.8a1 1 0 0 0 1.6-.8v-7a1 1 0 0 0-1.6-.8L14 7.5v-1A2.5 2.5 0 0 0 11.5 4h-7zM14 8.75l3-2.25v7l-3-2.25v-2.5zM1 14.5a4.5 4.5 0 1 0 9 0a4.5 4.5 0 0 0-9 0zm6.5-3a.5.5 0 0 1 .5.5v1.5a.5.5 0 0 1-.5.5H6a.5.5 0 0 1 0-1h.468a1.99 1.99 0 0 0-.933-.25a2 2 0 0 0-1.45.586a.5.5 0 0 1-.706-.707A3 3 0 0 1 7 12.152V12a.5.5 0 0 1 .5-.5zm-.876 5.532A2.999 2.999 0 0 1 4 16.848V17a.5.5 0 0 1-1 0v-1.5a.5.5 0 0 1 .5-.5H5a.5.5 0 0 1 0 1h-.468a1.99 1.99 0 0 0 .933.25a2 2 0 0 0 1.45-.586a.5.5 0 0 1 .706.707a3 3 0 0 1-.997.66z',
'VideoSyncSolid': 'M13 6.5A2.5 2.5 0 0 0 10.5 4h-6A2.5 2.5 0 0 0 2 6.5v3.757a5.5 5.5 0 0 1 8.798 5.725A2.5 2.5 0 0 0 13 13.5v-7zm1 1.43v4.152l2.764 2.35A.75.75 0 0 0 18 13.86V6.193a.75.75 0 0 0-1.23-.575L14 7.93zM1 14.5a4.5 4.5 0 1 0 9 0a4.5 4.5 0 0 0-9 0zm6.5-3a.5.5 0 0 1 .5.5v1.5a.5.5 0 0 1-.5.5H6a.5.5 0 0 1 0-1h.468a1.99 1.99 0 0 0-.933-.25a2 2 0 0 0-1.45.586a.5.5 0 0 1-.706-.707A3 3 0 0 1 7 12.152V12a.5.5 0 0 1 .5-.5zm-.876 5.532A2.999 2.999 0 0 1 4 16.848V17a.5.5 0 0 1-1 0v-1.5a.5.5 0 0 1 .5-.5H5a.5.5 0 0 1 0 1h-.468a1.99 1.99 0 0 0 .933.25a2 2 0 0 0 1.45-.586a.5.5 0 0 1 .706.707a3 3 0 0 1-.997.66z',
'File': 'M6 2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7.414a1.5 1.5 0 0 0-.44-1.06l-3.914-3.915A1.5 1.5 0 0 0 10.586 2H6zM5 4a1 1 0 0 1 1-1h4v3.5A1.5 1.5 0 0 0 11.5 8H15v8a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4zm9.793 3H11.5a.5.5 0 0 1-.5-.5V3.207L14.793 7z',
'FileSolid': 'M10 2v4.5A1.5 1.5 0 0 0 11.5 8H16v8.5a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 4 16.5v-13A1.5 1.5 0 0 1 5.5 2H10zm1 .25V6.5a.5.5 0 0 0 .5.5h4.25L11 2.25z',
'FileAdd': 'M6 2a2 2 0 0 0-2 2v5.207a5.48 5.48 0 0 1 1-.185V4a1 1 0 0 1 1-1h4v3.5A1.5 1.5 0 0 0 11.5 8H15v8a1 1 0 0 1-1 1h-3.6a5.507 5.507 0 0 1-.657 1H14a2 2 0 0 0 2-2V7.414a1.5 1.5 0 0 0-.44-1.06l-3.914-3.915A1.5 1.5 0 0 0 10.586 2H6zm8.793 5H11.5a.5.5 0 0 1-.5-.5V3.207L14.793 7zM10 14.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-4-2a.5.5 0 0 0-1 0V14H3.5a.5.5 0 0 0 0 1H5v1.5a.5.5 0 0 0 1 0V15h1.5a.5.5 0 0 0 0-1H6v-1.5z',
'FileAddSolid': 'M10 2v4.5A1.5 1.5 0 0 0 11.5 8H16v8.5a1.5 1.5 0 0 1-1.5 1.5H9.743A5.5 5.5 0 0 0 4 9.207V3.5A1.5 1.5 0 0 1 5.5 2H10zm1 .25V6.5a.5.5 0 0 0 .5.5h4.25L11 2.25zM10 14.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-4-2a.5.5 0 0 0-1 0V14H3.5a.5.5 0 0 0 0 1H5v1.5a.5.5 0 0 0 1 0V15h1.5a.5.5 0 0 0 0-1H6v-1.5z',
'FileArrowDown': 'M6 2a2 2 0 0 0-2 2v5.207a5.48 5.48 0 0 1 1-.185V4a1 1 0 0 1 1-1h4v3.5A1.5 1.5 0 0 0 11.5 8H15v8a1 1 0 0 1-1 1h-3.6a5.507 5.507 0 0 1-.657 1H14a2 2 0 0 0 2-2V7.414a1.5 1.5 0 0 0-.44-1.06l-3.914-3.915A1.5 1.5 0 0 0 10.586 2H6zm8.793 5H11.5a.5.5 0 0 1-.5-.5V3.207L14.793 7zM5.5 19a4.5 4.5 0 1 0 0-9a4.5 4.5 0 0 0 0 9zm-2.354-4.146a.5.5 0 0 1 .708-.708L5 15.293V12.5a.5.5 0 0 1 1 0v2.793l1.146-1.147a.5.5 0 0 1 .708.708l-2 2a.5.5 0 0 1-.351.146h-.006a.5.5 0 0 1-.348-.144l-.003-.003l-2-2z',
'FileArrowDownSolid': 'M10 2v4.5A1.5 1.5 0 0 0 11.5 8H16v8.5a1.5 1.5 0 0 1-1.5 1.5H9.743A5.5 5.5 0 0 0 4 9.207V3.5A1.5 1.5 0 0 1 5.5 2H10zm1 .25V6.5a.5.5 0 0 0 .5.5h4.25L11 2.25zM5.5 19a4.5 4.5 0 1 0 0-9a4.5 4.5 0 0 0 0 9zm-2.354-4.146a.5.5 0 0 1 .708-.708L5 15.293V12.5a.5.5 0 0 1 1 0v2.793l1.146-1.147a.5.5 0 0 1 .708.708l-2 2a.5.5 0 0 1-.351.146h-.006a.5.5 0 0 1-.348-.144l-.003-.003l-2-2z',
'FileArrowUp': 'M6 2a2 2 0 0 0-2 2v5.207a5.48 5.48 0 0 1 1-.185V4a1 1 0 0 1 1-1h4v3.5A1.5 1.5 0 0 0 11.5 8H15v8a1 1 0 0 1-1 1h-3.6a5.507 5.507 0 0 1-.657 1H14a2 2 0 0 0 2-2V7.414a1.5 1.5 0 0 0-.44-1.06l-3.914-3.915A1.5 1.5 0 0 0 10.586 2H6zm8.793 5H11.5a.5.5 0 0 1-.5-.5V3.207L14.793 7zM5.5 19a4.5 4.5 0 1 0 0-9a4.5 4.5 0 0 0 0 9zm2.354-4.854a.5.5 0 1 1-.708.708L6 13.707V16.5a.5.5 0 0 1-1 0v-2.793l-1.146 1.147a.5.5 0 1 1-.708-.707l2-2A.5.5 0 0 1 5.497 12h.006a.498.498 0 0 1 .348.144l.003.003l2 2z',
'FileArrowUpSolid': 'M10 2v4.5A1.5 1.5 0 0 0 11.5 8H16v8.5a1.5 1.5 0 0 1-1.5 1.5H9.743A5.5 5.5 0 0 0 4 9.207V3.5A1.5 1.5 0 0 1 5.5 2H10zm1 .25V6.5a.5.5 0 0 0 .5.5h4.25L11 2.25zM5.5 19a4.5 4.5 0 1 0 0-9a4.5 4.5 0 0 0 0 9zm2.354-4.854a.5.5 0 1 1-.708.708L6 13.707V16.5a.5.5 0 0 1-1 0v-2.793l-1.146 1.147a.5.5 0 1 1-.708-.707l2-2A.5.5 0 0 1 5.497 12h.006a.498.498 0 0 1 .348.144l.003.003l2 2z',
'FileBulletList': 'M6 10.5a.5.5 0 1 1 1 0a.5.5 0 0 1-1 0zm.5 1.5a.5.5 0 1 0 0 1a.5.5 0 0 0 0-1zM6 14.5a.5.5 0 1 1 1 0a.5.5 0 0 1-1 0zM8.5 10a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zM8 12.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm.5 1.5a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zM6 2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7.414a1.5 1.5 0 0 0-.44-1.06l-3.914-3.915A1.5 1.5 0 0 0 10.586 2H6zM5 4a1 1 0 0 1 1-1h4v3.5A1.5 1.5 0 0 0 11.5 8H15v8a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4zm9.793 3H11.5a.5.5 0 0 1-.5-.5V3.207L14.793 7z',
'FileBulletListSolid': 'M10 6.5V2H5.5A1.5 1.5 0 0 0 4 3.5v13A1.5 1.5 0 0 0 5.5 18h9a1.5 1.5 0 0 0 1.5-1.5V8h-4.5A1.5 1.5 0 0 1 10 6.5zm-4 4a.5.5 0 1 1 1 0a.5.5 0 0 1-1 0zm0 2a.5.5 0 1 1 1 0a.5.5 0 0 1-1 0zm0 2a.5.5 0 1 1 1 0a.5.5 0 0 1-1 0zm2-4a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm3-8V2.25L15.75 7H11.5a.5.5 0 0 1-.5-.5z',
'FileBulletListMultiple': 'M6.5 10a.5.5 0 1 0 0 1a.5.5 0 0 0 0-1zM6 12.5a.5.5 0 1 1 1 0a.5.5 0 0 1-1 0zm2-2a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5zm.5 1.5a.5.5 0 0 0 0 1h4a.5.5 0 0 0 0-1h-4zM6 2a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2V7.414a1.5 1.5 0 0 0-.44-1.06l-3.914-3.915A1.5 1.5 0 0 0 9.586 2H6zM5 4a1 1 0 0 1 1-1h3v3.5A1.5 1.5 0 0 0 10.5 8H14v6a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4zm5 2.5V3.207L13.793 7H10.5a.5.5 0 0 1-.5-.5zM16 8a1 1 0 0 1 1 1v5.06A3.94 3.94 0 0 1 13.06 18H7a1 1 0 0 1-1-1h7a3 3 0 0 0 3-3V8z',
'FileBulletListMultipleSolid': 'M9 6.5V2H5.5A1.5 1.5 0 0 0 4 3.5v11A1.5 1.5 0 0 0 5.5 16H13a2 2 0 0 0 2-2V8h-4.5A1.5 1.5 0 0 1 9 6.5zm-3 4a.5.5 0 1 1 1 0a.5.5 0 0 1-1 0zm.5 2.5a.5.5 0 1 1 0-1a.5.5 0 0 1 0 1zm2-2a.5.5 0 0 1 0-1h4a.5.5 0 0 1 0 1h-4zM8 12.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5zm2-6V2.25L14.75 7H10.5a.5.5 0 0 1-.5-.5zM17 9a1 1 0 0 0-1-1v6a3 3 0 0 1-3 3H6a1 1 0 0 0 1 1h6.06A3.94 3.94 0 0 0 17 14.06V9z',
'FileBulletListOff': 'M4 4.707L2.146 2.854a.5.5 0 1 1 .708-.708l15 15a.5.5 0 0 1-.708.708l-1.241-1.242A2 2 0 0 1 14 18H6a2 2 0 0 1-2-2V4.707zm11 11l-1.032-1.032A.5.5 0 0 1 13.5 15h-5a.5.5 0 0 1 0-1h4.793l-1-1H8.5a.5.5 0 0 1 0-1h2.793l-1-1H8.5a.5.5 0 0 1 0-1h.793L5 5.707V16a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-.293zM12.121 10l1 1h.379a.5.5 0 0 0 0-1h-1.379zM15 8v4.879l1 1V7.414a1.5 1.5 0 0 0-.44-1.06l-3.914-3.915A1.5 1.5 0 0 0 10.586 2H6c-.521 0-.996.2-1.352.526l.708.709A.996.996 0 0 1 6 3h4v3.5A1.5 1.5 0 0 0 11.5 8H15zm-9 2.5a.5.5 0 1 0 1 0a.5.5 0 0 0-1 0zm.5 1.5a.5.5 0 1 0 0 1a.5.5 0 0 0 0-1zM6 14.5a.5.5 0 1 1 1 0a.5.5 0 0 1-1 0zM14.793 7H11.5a.5.5 0 0 1-.5-.5V3.207L14.793 7z',
'FileBulletListOffSolid': 'M4 4.707L2.146 2.854a.5.5 0 1 1 .708-.708l15 15a.5.5 0 0 1-.708.708l-1.159-1.16A1.5 1.5 0 0 1 14.5 18h-9A1.5 1.5 0 0 1 4 16.5V4.707zM13.293 14H8.5a.5.5 0 0 0 0 1h5a.5.5 0 0 0 .468-.325L13.293 14zm-1-1l-1-1H8.5a.5.5 0 0 0 0 1h3.793zm-2-2l-1-1H8.5a.5.5 0 0 0 0 1h1.793zm3.207 0h-.379L16 13.879V8h-4.5A1.5 1.5 0 0 1 10 6.5V2H5.5c-.383 0-.733.144-.998.38l7.62 7.62H13.5a.5.5 0 0 1 0 1zM6 10.5a.5.5 0 1 0 1 0a.5.5 0 0 0-1 0zm0 2a.5.5 0 1 0 1 0a.5.5 0 0 0-1 0zm0 2a.5.5 0 1 0 1 0a.5.5 0 0 0-1 0zm5-8V2.25L15.75 7H11.5a.5.5 0 0 1-.5-.5z',
'FileCatchUp': 'M6 2a2 2 0 0 0-2 2v4.5a.5.5 0 0 0 1 0V4a1 1 0 0 1 1-1h4v3.5A1.5 1.5 0 0 0 11.5 8H15v8a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1v-3H4v3a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7.414a1.5 1.5 0 0 0-.44-1.06l-3.914-3.915A1.5 1.5 0 0 0 10.586 2H6zm8.793 5H11.5a.5.5 0 0 1-.5-.5V3.207L14.793 7zM7.462 8.308a.5.5 0 0 0-.91-.032L5.192 11H3.5a.5.5 0 0 0 0 1h2a.5.5 0 0 0 .447-.276L6.96 9.7l2.08 4.991a.5.5 0 0 0 .908.032L11.31 12H12.5a.5.5 0 0 0 0-1H11a.5.5 0 0 0-.447.276L9.54 13.3l-2.08-4.991z',
'FileCatchUpSolid': 'M10 2v4.5A1.5 1.5 0 0 0 11.5 8H16v8.5a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 4 16.5V13h1.5a1.5 1.5 0 0 0 1.342-.83l.034-.068l1.24 2.975a1.5 1.5 0 0 0 2.726.094L11.927 13h.573a1.5 1.5 0 0 0 0-3H11a1.5 1.5 0 0 0-1.342.83l-.034.068l-1.24-2.975a1.5 1.5 0 0 0-2.726-.094L4.573 10H4V3.5A1.5 1.5 0 0 1 5.5 2H10zm1 .25V6.5a.5.5 0 0 0 .5.5h4.25L11 2.25zM7.462 8.308a.5.5 0 0 0-.91-.032L5.192 11H3.5a.5.5 0 0 0 0 1h2a.5.5 0 0 0 .447-.276L6.96 9.7l2.08 4.991a.5.5 0 0 0 .908.032L11.31 12H12.5a.5.5 0 0 0 0-1H11a.5.5 0 0 0-.447.276L9.54 13.3l-2.08-4.991z',
'FileCheckmark': 'M6 2a2 2 0 0 0-2 2v5.207a5.48 5.48 0 0 1 1-.185V4a1 1 0 0 1 1-1h4v3.5A1.5 1.5 0 0 0 11.5 8H15v8a1 1 0 0 1-1 1h-3.6a5.507 5.507 0 0 1-.657 1H14a2 2 0 0 0 2-2V7.414a1.5 1.5 0 0 0-.44-1.06l-3.914-3.915A1.5 1.5 0 0 0 10.586 2H6zm8.793 5H11.5a.5.5 0 0 1-.5-.5V3.207L14.793 7zM10 14.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-2.146-1.854a.5.5 0 0 0-.708 0L4.5 15.293l-.646-.647a.5.5 0 0 0-.708.708l1 1a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0 0-.708z',
'FileCheckmarkSolid': 'M10 2v4.5A1.5 1.5 0 0 0 11.5 8H16v8.5a1.5 1.5 0 0 1-1.5 1.5H9.743A5.5 5.5 0 0 0 4 9.207V3.5A1.5 1.5 0 0 1 5.5 2H10zm1 .25V6.5a.5.5 0 0 0 .5.5h4.25L11 2.25zM10 14.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-2.146-1.854a.5.5 0 0 0-.708 0L4.5 15.293l-.646-.647a.5.5 0 0 0-.708.708l1 1a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0 0-.708z',
'FileCopy': 'M6 4a2 2 0 0 1 2-2h3.586a1.5 1.5 0 0 1 1.06.44l3.915 3.914A1.5 1.5 0 0 1 17 7.414V14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V4zm2-1a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V8h-3.5A1.5 1.5 0 0 1 11 6.5V3H8zm4 .207V6.5a.5.5 0 0 0 .5.5h3.293L12 3.207zM4 5a1 1 0 0 1 1-1v10a3 3 0 0 0 3 3h7a1 1 0 0 1-1 1H7.94A3.94 3.94 0 0 1 4 14.06V5z',
'FileCopySolid': 'M11 6.5V2H7.5A1.5 1.5 0 0 0 6 3.5v11A1.5 1.5 0 0 0 7.5 16h8a1.5 1.5 0 0 0 1.5-1.5V8h-4.5A1.5 1.5 0 0 1 11 6.5zm1 0V2.25L16.75 7H12.5a.5.5 0 0 1-.5-.5zM4 5a1 1 0 0 1 1-1v10.5A2.5 2.5 0 0 0 7.5 17H15a1 1 0 0 1-1 1H7.548A3.548 3.548 0 0 1 4 14.452V5z',
'FileDismiss': 'M6 2a2 2 0 0 0-2 2v5.207a5.48 5.48 0 0 1 1-.185V4a1 1 0 0 1 1-1h4v3.5A1.5 1.5 0 0 0 11.5 8H15v8a1 1 0 0 1-1 1h-3.6a5.507 5.507 0 0 1-.657 1H14a2 2 0 0 0 2-2V7.414a1.5 1.5 0 0 0-.44-1.06l-3.914-3.915A1.5 1.5 0 0 0 10.586 2H6zm8.793 5H11.5a.5.5 0 0 1-.5-.5V3.207L14.793 7zM8.682 17.682a4.5 4.5 0 1 0-6.364-6.364a4.5 4.5 0 0 0 6.364 6.364zm-4.95-4.95a.5.5 0 0 1 .707 0l1.06 1.06l1.062-1.06a.5.5 0 1 1 .707.707L6.207 14.5l1.06 1.06a.5.5 0 1 1-.707.708l-1.06-1.06l-1.06 1.06a.5.5 0 1 1-.708-.708l1.06-1.06l-1.06-1.06a.5.5 0 0 1 0-.708z',
'FileDismissSolid': 'M10 2v4.5A1.5 1.5 0 0 0 11.5 8H16v8.5a1.5 1.5 0 0 1-1.5 1.5H9.743A5.5 5.5 0 0 0 4 9.207V3.5A1.5 1.5 0 0 1 5.5 2H10zm1 .25V6.5a.5.5 0 0 0 .5.5h4.25L11 2.25zM2.318 17.682a4.5 4.5 0 1 0 6.364-6.364a4.5 4.5 0 0 0-6.364 6.364zm1.414-4.95a.5.5 0 0 1 .707 0l1.06 1.06l1.062-1.06a.5.5 0 1 1 .707.707L6.207 14.5l1.06 1.06a.5.5 0 1 1-.707.708l-1.06-1.06l-1.06 1.06a.5.5 0 1 1-.708-.708l1.06-1.06l-1.06-1.06a.5.5 0 0 1 0-.708z',
'FileEdit': 'M11.5 8H16v-.586a1.496 1.496 0 0 0-.057-.41L15.942 7a1.5 1.5 0 0 0-.381-.646l-3.915-3.915A1.5 1.5 0 0 0 10.586 2H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h2.221l-.013-.026A1.856 1.856 0 0 1 8.003 17H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h4v3.5A1.5 1.5 0 0 0 11.5 8zm0-1a.5.5 0 0 1-.5-.5V3.207L14.793 7H11.5zm3.31 2.548a1.87 1.87 0 1 1 2.644 2.645l-4.83 4.829a2.197 2.197 0 0 1-1.02.578l-1.498.374a.89.89 0 0 1-1.079-1.078l.375-1.498c.096-.386.296-.74.578-1.02l4.83-4.83z',
'FileEditSolid': 'M10 6.5V2H5.5A1.5 1.5 0 0 0 4 3.5v13A1.5 1.5 0 0 0 5.5 18h2.721c-.21-.39-.285-.86-.164-1.347l.375-1.498c.14-.562.43-1.075.84-1.485l4.83-4.83A2.86 2.86 0 0 1 16 8.004V8h-4.5A1.5 1.5 0 0 1 10 6.5zm1 0V2.25L15.75 7H11.5a.5.5 0 0 1-.5-.5zm6.454 3.048a1.87 1.87 0 0 0-2.645 0l-4.83 4.83a2.197 2.197 0 0 0-.577 1.02l-.375 1.498a.89.89 0 0 0 1.079 1.078l1.498-.374c.386-.097.739-.296 1.02-.578l4.83-4.83a1.87 1.87 0 0 0 0-2.644z',
'FileError': 'M6 2a2 2 0 0 0-2 2v5.207a5.48 5.48 0 0 1 1-.185V4a1 1 0 0 1 1-1h4v3.5A1.5 1.5 0 0 0 11.5 8H15v8a1 1 0 0 1-1 1h-3.6a5.507 5.507 0 0 1-.657 1H14a2 2 0 0 0 2-2V7.414a1.5 1.5 0 0 0-.44-1.06l-3.914-3.915A1.5 1.5 0 0 0 10.586 2H6zm8.793 5H11.5a.5.5 0 0 1-.5-.5V3.207L14.793 7zM10 14.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zM5.5 12a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 1 0v-2a.5.5 0 0 0-.5-.5zm0 5.125a.625.625 0 1 0 0-1.25a.625.625 0 0 0 0 1.25z',
'FileErrorSolid': 'M10 2v4.5A1.5 1.5 0 0 0 11.5 8H16v8.5a1.5 1.5 0 0 1-1.5 1.5H9.743A5.5 5.5 0 0 0 4 9.207V3.5A1.5 1.5 0 0 1 5.5 2H10zm1 .25V6.5a.5.5 0 0 0 .5.5h4.25L11 2.25zM10 14.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zM5.5 12a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 1 0v-2a.5.5 0 0 0-.5-.5zm0 5.125a.625.625 0 1 0 0-1.25a.625.625 0 0 0 0 1.25z',
'FileLink': 'M6 2a2 2 0 0 0-2 2v7h1V4a1 1 0 0 1 1-1h4v3.5A1.5 1.5 0 0 0 11.5 8H15v8a1 1 0 0 1-1 1h-2.256a4.483 4.483 0 0 1-.502 1H14a2 2 0 0 0 2-2V7.414a1.5 1.5 0 0 0-.44-1.06l-3.914-3.915A1.5 1.5 0 0 0 10.586 2H6zm8.793 5H11.5a.5.5 0 0 1-.5-.5V3.207L14.793 7zM5 12.5a.5.5 0 0 0-.5-.5l-.192.005A3.5 3.5 0 0 0 4.5 19l.09-.008A.5.5 0 0 0 4.5 18l-.164-.005A2.5 2.5 0 0 1 4.5 13l.09-.008A.5.5 0 0 0 5 12.5zm6 3A3.5 3.5 0 0 0 7.5 12l-.09.008A.5.5 0 0 0 7.5 13l.164.005A2.5 2.5 0 0 1 7.5 18l-.002.005l-.09.008a.5.5 0 0 0 .094.992V19l.192-.005A3.5 3.5 0 0 0 11 15.5zm-3.5-.498L4.5 15l-.09.008A.5.5 0 0 0 4.5 16l3 .002l.09-.008a.5.5 0 0 0-.09-.992z',
'FileLinkSolid': 'M10 2v4.5A1.5 1.5 0 0 0 11.5 8H16v8.5a1.5 1.5 0 0 1-1.5 1.5h-3.258A4.5 4.5 0 0 0 7.5 11H4V3.5A1.5 1.5 0 0 1 5.5 2H10zm1 .25V6.5a.5.5 0 0 0 .5.5h4.25L11 2.25zM5 12.5a.5.5 0 0 0-.5-.5l-.192.005A3.5 3.5 0 0 0 4.5 19l.09-.008A.5.5 0 0 0 4.5 18l-.164-.005A2.5 2.5 0 0 1 4.5 13l.09-.008A.5.5 0 0 0 5 12.5zm6 3A3.5 3.5 0 0 0 7.5 12l-.09.008A.5.5 0 0 0 7.5 13l.164.005A2.5 2.5 0 0 1 7.5 18l-.002.005l-.09.008a.5.5 0 0 0 .094.992V19l.192-.005A3.5 3.5 0 0 0 11 15.5zm-3.5-.498L4.5 15l-.09.008A.5.5 0 0 0 4.5 16l3 .002l.09-.008a.5.5 0 0 0-.09-.992z',
'FileLock': 'M6 2a2 2 0 0 0-2 2v5.401a2.98 2.98 0 0 1 1-.36V4a1 1 0 0 1 1-1h4v3.5A1.5 1.5 0 0 0 11.5 8H15v8a1 1 0 0 1-1 1h-4v1h4a2 2 0 0 0 2-2V7.414a1.5 1.5 0 0 0-.44-1.06l-3.914-3.915A1.5 1.5 0 0 0 10.586 2H6zm8.793 5H11.5a.5.5 0 0 1-.5-.5V3.207L14.793 7zM3.5 12v1H3a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1h-.5v-1a2 2 0 1 0-4 0zm1 1v-1a1 1 0 1 1 2 0v1h-2zm1 2.25a.75.75 0 1 1 0 1.5a.75.75 0 0 1 0-1.5z',
'FileLockSolid': 'M10 2v4.5A1.5 1.5 0 0 0 11.5 8H16v8.5a1.5 1.5 0 0 1-1.5 1.5H10v-4a2 2 0 0 0-1.5-1.937V12A3 3 0 0 0 4 9.401V3.5A1.5 1.5 0 0 1 5.5 2H10zm1 .25V6.5a.5.5 0 0 0 .5.5h4.25L11 2.25zM3.5 12v1H3a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1h-.5v-1a2 2 0 1 0-4 0zm1 1v-1a1 1 0 1 1 2 0v1h-2zm1 2.25a.75.75 0 1 1 0 1.5a.75.75 0 0 1 0-1.5z',
'FileMultiple': 'M4 4a2 2 0 0 1 2-2h3.586a1.5 1.5 0 0 1 1.06.44l3.915 3.914A1.5 1.5 0 0 1 15 7.414V14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4zm2-1a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V8h-3.5A1.5 1.5 0 0 1 9 6.5V3H6zm4 .207V6.5a.5.5 0 0 0 .5.5h3.293L10 3.207zM17 9a1 1 0 0 0-1-1v6a3 3 0 0 1-3 3H6a1 1 0 0 0 1 1h6.06A3.94 3.94 0 0 0 17 14.06V9z',
'FileMultipleSolid': 'M9 6.5V2H5.5A1.5 1.5 0 0 0 4 3.5v11A1.5 1.5 0 0 0 5.5 16h8a1.5 1.5 0 0 0 1.5-1.5V8h-4.5A1.5 1.5 0 0 1 9 6.5zm1 0V2.25L14.75 7H10.5a.5.5 0 0 1-.5-.5zM17 9a1 1 0 0 0-1-1v6a3 3 0 0 1-3 3H6a1 1 0 0 0 1 1h6.06A3.94 3.94 0 0 0 17 14.06V9z',
'FilePdf': 'M6.5 11a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 1 0v-.166h.333a1.167 1.167 0 0 0 0-2.334H6.5zm.833 1.334H7V12h.333a.167.167 0 0 1 0 .334zM12 11.499a.5.5 0 0 1 .5-.499h.999a.5.5 0 0 1 0 1h-.5v.335h.5a.5.5 0 1 1 0 1h-.5l.001.164a.5.5 0 0 1-1 .002L12 12.834L12 11.499zM9.498 11a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 .5.5H10a1.5 1.5 0 0 0 0-3h-.502zm.5 2v-1H10a.5.5 0 0 1 0 1h-.002zM4 4a2 2 0 0 1 2-2h4.585a1.5 1.5 0 0 1 1.061.44l3.914 3.914a1.5 1.5 0 0 1 .44 1.06v1.668a1.5 1.5 0 0 1 .998 1.414v4.003A1.5 1.5 0 0 1 16 15.913V16a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-.087A1.5 1.5 0 0 1 3 14.5v-4.003A1.5 1.5 0 0 1 4 9.082V4zm11 4h-3.5A1.5 1.5 0 0 1 10 6.5V3H6a1 1 0 0 0-1 1v4.996h10V8zM5 15.999A1 1 0 0 0 6 17h8a1 1 0 0 0 1-1.001H5zm6-12.792V6.5a.5.5 0 0 0 .5.5h3.293L11 3.207zM4.5 9.996a.5.5 0 0 0-.5.5v4.003a.5.5 0 0 0 .5.5h10.997a.5.5 0 0 0 .5-.5v-4.003a.5.5 0 0 0-.5-.5H4.501z',
'FilePdfSolid': 'M6.5 11a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 1 0v-.166h.334a1.167 1.167 0 0 0 0-2.334H6.5zm.834 1.334H7V12h.334a.167.167 0 0 1 0 .334zM12 11.499a.5.5 0 0 1 .5-.499h.998a.5.5 0 1 1 0 1h-.498v.335h.498a.5.5 0 1 1 0 1h-.498v.164a.5.5 0 1 1-1 .002L12 12.834l.002-1.335zM9.5 11a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 .5.5h.502a1.5 1.5 0 0 0 0-3h-.502zm.5 2v-1h.002a.5.5 0 0 1 0 1h-.002zm.002-6.5V2H5.5A1.5 1.5 0 0 0 4 3.5v5.582a1.5 1.5 0 0 0-1 1.414v4.003a1.5 1.5 0 0 0 1 1.414v.587A1.5 1.5 0 0 0 5.5 18h9a1.5 1.5 0 0 0 1.5-1.5v-.587a1.5 1.5 0 0 0 .998-1.414v-4.003a1.5 1.5 0 0 0-.998-1.414V8h-4.5A1.5 1.5 0 0 1 10 6.5zM4.502 9.996h10.997a.5.5 0 0 1 .5.5v4.003a.5.5 0 0 1-.5.5H4.502a.5.5 0 0 1-.5-.5v-4.003a.5.5 0 0 1 .5-.5zM11 6.5V2.25L15.75 7H11.5a.5.5 0 0 1-.5-.5z',
'FileProhibited': 'M4 4a2 2 0 0 1 2-2h4.586a1.5 1.5 0 0 1 1.06.44l3.915 3.914A1.5 1.5 0 0 1 16 7.414V16a2 2 0 0 1-2 2H9.743c.253-.307.474-.642.657-1H14a1 1 0 0 0 1-1V8h-3.5A1.5 1.5 0 0 1 10 6.5V3H6a1 1 0 0 0-1 1v5.022a5.48 5.48 0 0 0-1 .185V4zm7-.793V6.5a.5.5 0 0 0 .5.5h3.293L11 3.207zM8.682 17.682a4.5 4.5 0 1 1-6.364-6.364a4.5 4.5 0 0 1 6.364 6.364zm-5.278-.379a3.5 3.5 0 0 0 4.9-4.9l-4.9 4.9zm-.707-.707l4.9-4.9a3.5 3.5 0 0 0-4.9 4.9z',
'FileProhibitedSolid': 'M10 2v4.5A1.5 1.5 0 0 0 11.5 8H16v8.5a1.5 1.5 0 0 1-1.5 1.5H9.743A5.5 5.5 0 0 0 4 9.207V3.5A1.5 1.5 0 0 1 5.5 2H10zm1 .25V6.5a.5.5 0 0 0 .5.5h4.25L11 2.25zm-8.682 9.068a4.5 4.5 0 1 0 6.364 6.364a4.5 4.5 0 0 0-6.364-6.364zm5.657 5.657a3.5 3.5 0 0 1-4.571.328l4.9-4.9a3.5 3.5 0 0 1-.33 4.572zm-.379-5.278l-4.9 4.9a3.5 3.5 0 0 1 4.9-4.9z',
'FileQuestionMark': 'M6 2a2 2 0 0 0-2 2v5.207a5.48 5.48 0 0 1 1-.185V4a1 1 0 0 1 1-1h4v3.5A1.5 1.5 0 0 0 11.5 8H15v8a1 1 0 0 1-1 1h-3.6a5.507 5.507 0 0 1-.657 1H14a2 2 0 0 0 2-2V7.414a1.5 1.5 0 0 0-.44-1.06l-3.914-3.915A1.5 1.5 0 0 0 10.586 2H6zm8.793 5H11.5a.5.5 0 0 1-.5-.5V3.207L14.793 7zM10 14.5a4.5 4.5 0 1 0-9 0a4.5 4.5 0 0 0 9 0zm-4.5 1.88a.625.625 0 1 1 0 1.25a.625.625 0 0 1 0-1.25zm0-4.877c1.031 0 1.853.846 1.853 1.95c0 .586-.214.908-.727 1.319l-.277.214c-.246.194-.329.3-.346.448l-.011.156A.5.5 0 0 1 5 15.5c0-.57.21-.884.716-1.288l.278-.215c.288-.23.36-.342.36-.544c0-.558-.382-.95-.854-.95c-.494 0-.859.366-.853.945a.5.5 0 1 1-1 .01c-.011-1.137.805-1.955 1.853-1.955z',
'FileQuestionMarkSolid': 'M10 2v4.5A1.5 1.5 0 0 0 11.5 8H16v8.5a1.5 1.5 0 0 1-1.5 1.5H9.743A5.5 5.5 0 0 0 4 9.207V3.5A1.5 1.5 0 0 1 5.5 2H10zm1 .25V6.5a.5.5 0 0 0 .5.5h4.25L11 2.25zM10 14.5a4.5 4.5 0 1 0-9 0a4.5 4.5 0 0 0 9 0zm-4.5 1.88a.625.625 0 1 1 0 1.25a.625.625 0 0 1 0-1.25zm0-4.877c1.031 0 1.853.846 1.853 1.95c0 .586-.214.908-.727 1.319l-.277.214c-.246.194-.329.3-.346.448l-.011.156A.5.5 0 0 1 5 15.5c0-.57.21-.884.716-1.288l.278-.215c.288-.23.36-.342.36-.544c0-.558-.382-.95-.854-.95c-.494 0-.859.366-.853.945a.5.5 0 1 1-1 .01c-.011-1.137.805-1.955 1.853-1.955z',
'FileSearch': 'M10 12c0 .924-.314 1.775-.84 2.453l3.691 3.692a.5.5 0 1 1-.707.707L8.453 15.16A4 4 0 1 1 10 12zm-4 3a3 3 0 1 0 0-6a3 3 0 0 0 0 6zM5.5 3a.5.5 0 0 0-.5.5v3.6c-.348.07-.683.177-1 .316V3.5A1.5 1.5 0 0 1 5.5 2h5.086a1.5 1.5 0 0 1 1.06.44l3.915 3.914A1.5 1.5 0 0 1 16 7.414V16.5a1.5 1.5 0 0 1-1.5 1.5h-.587a1.494 1.494 0 0 0-.354-.563L13.12 17H14.5a.5.5 0 0 0 .5-.5V8h-3.5A1.5 1.5 0 0 1 10 6.5V3H5.5zm5.5.207V6.5a.5.5 0 0 0 .5.5h3.293L11 3.207z',
'FileSearchSolid': 'M5 2h5v4.5A1.5 1.5 0 0 0 11.5 8H16v9a1 1 0 0 1-1 1h-1.087a1.494 1.494 0 0 0-.354-.563l-3.125-3.124A5 5 0 0 0 4 7.416V3a1 1 0 0 1 1-1zm6 0l5 5h-4.5a.5.5 0 0 1-.5-.5V2zm-1 10c0 .924-.314 1.775-.84 2.453l3.691 3.692a.5.5 0 1 1-.707.707L8.453 15.16A4 4 0 1 1 10 12zm-4 3a3 3 0 1 0 0-6a3 3 0 0 0 0 6z',
'FileSync': 'M6 2a2 2 0 0 0-2 2v5.207a5.48 5.48 0 0 1 1-.185V4a1 1 0 0 1 1-1h4v3.5A1.5 1.5 0 0 0 11.5 8H15v8a1 1 0 0 1-1 1h-3.6a5.507 5.507 0 0 1-.657 1H14a2 2 0 0 0 2-2V7.414a1.5 1.5 0 0 0-.44-1.06l-3.914-3.915A1.5 1.5 0 0 0 10.586 2H6zm8.793 5H11.5a.5.5 0 0 1-.5-.5V3.207L14.793 7zM1 14.5a4.5 4.5 0 1 0 9 0a4.5 4.5 0 0 0-9 0zm6.5-3a.5.5 0 0 1 .5.5v1.5a.5.5 0 0 1-.5.5H6a.5.5 0 0 1 0-1h.468a1.99 1.99 0 0 0-.933-.25a2 2 0 0 0-1.45.586a.5.5 0 0 1-.706-.707A3 3 0 0 1 7 12.152V12a.5.5 0 0 1 .5-.5zm-.876 5.532A2.999 2.999 0 0 1 4 16.848V17a.5.5 0 0 1-1 0v-1.5a.5.5 0 0 1 .5-.5H5a.5.5 0 0 1 0 1h-.468a1.99 1.99 0 0 0 .933.25a2 2 0 0 0 1.45-.586a.5.5 0 0 1 .706.707a3 3 0 0 1-.997.66z',
'FileSyncSolid': 'M10 2v4.5A1.5 1.5 0 0 0 11.5 8H16v8.5a1.5 1.5 0 0 1-1.5 1.5H9.743A5.5 5.5 0 0 0 4 9.207V3.5A1.5 1.5 0 0 1 5.5 2H10zm1 .25V6.5a.5.5 0 0 0 .5.5h4.25L11 2.25zM1 14.5a4.5 4.5 0 1 0 9 0a4.5 4.5 0 0 0-9 0zm6.5-3a.5.5 0 0 1 .5.5v1.5a.5.5 0 0 1-.5.5H6a.5.5 0 0 1 0-1h.468a1.99 1.99 0 0 0-.933-.25a2 2 0 0 0-1.45.586a.5.5 0 0 1-.706-.707A3 3 0 0 1 7 12.152V12a.5.5 0 0 1 .5-.5zm-.876 5.532A2.999 2.999 0 0 1 4 16.848V17a.5.5 0 0 1-1 0v-1.5a.5.5 0 0 1 .5-.5H5a.5.5 0 0 1 0 1h-.468a1.99 1.99 0 0 0 .933.25a2 2 0 0 0 1.45-.586a.5.5 0 0 1 .706.707a3 3 0 0 1-.997.66z',
'FileText': 'M6.5 10a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1h-7zm0 2a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1h-7zm0 2a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1h-7zM4 4a2 2 0 0 1 2-2h4.586a1.5 1.5 0 0 1 1.06.44l3.915 3.914A1.5 1.5 0 0 1 16 7.414V16a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4zm2-1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V8h-3.5A1.5 1.5 0 0 1 10 6.5V3H6zm5.5 4h3.293L11 3.207V6.5a.5.5 0 0 0 .5.5z',
'FileTextSolid': 'M10 6.5V2H5.5A1.5 1.5 0 0 0 4 3.5v13A1.5 1.5 0 0 0 5.5 18h9a1.5 1.5 0 0 0 1.5-1.5V8h-4.5A1.5 1.5 0 0 1 10 6.5zM6.5 10h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zm0 2h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zm0 2h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zM11 6.5V2.25L15.75 7H11.5a.5.5 0 0 1-.5-.5z',
'FileJs': 'M4 4a2 2 0 0 1 2-2h4.586a1.5 1.5 0 0 1 1.06.44l3.915 3.914A1.5 1.5 0 0 1 16 7.414V16a2 2 0 0 1-2 2H8.5c.219-.29.375-.63.45-1H14a1 1 0 0 0 1-1V8h-3.5A1.5 1.5 0 0 1 10 6.5V3H6a1 1 0 0 0-1 1v7.5c-.081.061-.16.127-.232.198A1.504 1.504 0 0 0 4 11.085V4zm7.5 3h3.293L11 3.207V6.5a.5.5 0 0 0 .5.5zm-8 5a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0V16a.5.5 0 0 0-1 0v.5a1.5 1.5 0 0 0 3 0v-4a.5.5 0 0 0-.5-.5zM5 13.5a1.5 1.5 0 0 1 3 0a.5.5 0 0 1-1 0a.5.5 0 0 0-1 0v.382a.5.5 0 0 0 .276.447l.895.447A1.5 1.5 0 0 1 8 16.118v.382a1.5 1.5 0 0 1-3 0a.5.5 0 0 1 1 0a.5.5 0 0 0 1 0v-.382a.5.5 0 0 0-.276-.447l-.895-.447A1.5 1.5 0 0 1 5 13.882V13.5z',
'FileJsSolid': 'M10 6.5V2H5.5A1.5 1.5 0 0 0 4 3.5v7.585c.32.113.589.331.768.613A2.5 2.5 0 0 1 9 13.5c0 .444-.193.843-.5 1.118c.319.425.5.949.5 1.5v.382c0 .563-.186 1.082-.5 1.5h6a1.5 1.5 0 0 0 1.5-1.5V8h-4.5A1.5 1.5 0 0 1 10 6.5zm1 0V2.25L15.75 7H11.5a.5.5 0 0 1-.5-.5zM3.5 12a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0V16a.5.5 0 0 0-1 0v.5a1.5 1.5 0 0 0 3 0v-4a.5.5 0 0 0-.5-.5zM5 13.5a1.5 1.5 0 0 1 3 0a.5.5 0 0 1-1 0a.5.5 0 0 0-1 0v.382a.5.5 0 0 0 .276.447l.895.447A1.5 1.5 0 0 1 8 16.118v.382a1.5 1.5 0 0 1-3 0a.5.5 0 0 1 1 0a.5.5 0 0 0 1 0v-.382a.5.5 0 0 0-.276-.447l-.895-.447A1.5 1.5 0 0 1 5 13.882V13.5z',
'FileCss': 'M4 4a2 2 0 0 1 2-2h4.586a1.5 1.5 0 0 1 1.06.44l3.915 3.914A1.5 1.5 0 0 1 16 7.414V16a2 2 0 0 1-2 2h-.5c.219-.29.375-.63.45-1H14a1 1 0 0 0 1-1V8h-3.5A1.5 1.5 0 0 1 10 6.5V3H6a1 1 0 0 0-1 1v7.764a2.997 2.997 0 0 0-1-.593V4zm7.5 3h3.293L11 3.207V6.5a.5.5 0 0 0 .5.5zM3 12a2 2 0 0 0-2 2v2a2 2 0 1 0 4 0a.5.5 0 0 0-1 0a1 1 0 1 1-2 0v-2a1 1 0 1 1 2 0a.5.5 0 0 0 1 0a2 2 0 0 0-2-2zm8.5 0a1.5 1.5 0 0 0-1.5 1.5v.382a1.5 1.5 0 0 0 .83 1.342l.894.447a.5.5 0 0 1 .276.447v.382a.5.5 0 0 1-1 0a.5.5 0 0 0-1 0a1.5 1.5 0 0 0 3 0v-.382a1.5 1.5 0 0 0-.83-1.342l-.894-.447a.5.5 0 0 1-.276-.447V13.5a.5.5 0 0 1 1 0a.5.5 0 0 0 1 0a1.5 1.5 0 0 0-1.5-1.5zM6 13.5a1.5 1.5 0 0 1 3 0a.5.5 0 0 1-1 0a.5.5 0 0 0-1 0v.382a.5.5 0 0 0 .276.447l.895.447A1.5 1.5 0 0 1 9 16.118v.382a1.5 1.5 0 0 1-3 0a.5.5 0 0 1 1 0a.5.5 0 0 0 1 0v-.382a.5.5 0 0 0-.276-.447l-.895-.447A1.5 1.5 0 0 1 6 13.882V13.5z',
'FileCssSolid': 'M10 6.5V2H5.5A1.5 1.5 0 0 0 4 3.5v7.67c.552.196 1.03.548 1.38 1.004A2.498 2.498 0 0 1 9.5 12a2.5 2.5 0 0 1 4.5 1.5c0 .444-.193.843-.5 1.118c.319.425.5.949.5 1.5v.382a2.49 2.49 0 0 1-.5 1.5h1a1.5 1.5 0 0 0 1.5-1.5V8h-4.5A1.5 1.5 0 0 1 10 6.5zm1 0V2.25L15.75 7H11.5a.5.5 0 0 1-.5-.5zM3 12a2 2 0 0 0-2 2v2a2 2 0 1 0 4 0a.5.5 0 0 0-1 0a1 1 0 1 1-2 0v-2a1 1 0 1 1 2 0a.5.5 0 0 0 1 0a2 2 0 0 0-2-2zm8.5 0a1.5 1.5 0 0 0-1.5 1.5v.382a1.5 1.5 0 0 0 .83 1.342l.894.447a.5.5 0 0 1 .276.447v.382a.5.5 0 0 1-1 0a.5.5 0 0 0-1 0a1.5 1.5 0 0 0 3 0v-.382a1.5 1.5 0 0 0-.83-1.342l-.894-.447a.5.5 0 0 1-.276-.447V13.5a.5.5 0 0 1 1 0a.5.5 0 0 0 1 0a1.5 1.5 0 0 0-1.5-1.5zM6 13.5a1.5 1.5 0 0 1 3 0a.5.5 0 0 1-1 0a.5.5 0 0 0-1 0v.382a.5.5 0 0 0 .276.447l.895.447A1.5 1.5 0 0 1 9 16.118v.382a1.5 1.5 0 0 1-3 0a.5.5 0 0 1 1 0a.5.5 0 0 0 1 0v-.382a.5.5 0 0 0-.276-.447l-.895-.447A1.5 1.5 0 0 1 6 13.882V13.5z',
'ChevronDoubleRight': 'M8.646 4.147a.5.5 0 0 1 .707-.001l5.484 5.465a.55.55 0 0 1 0 .779l-5.484 5.465a.5.5 0 0 1-.706-.708L13.812 10L8.647 4.854a.5.5 0 0 1-.001-.707zm-4 0a.5.5 0 0 1 .707-.001l5.484 5.465a.55.55 0 0 1 0 .779l-5.484 5.465a.5.5 0 0 1-.706-.708L9.812 10L4.647 4.854a.5.5 0 0 1-.001-.707z',
'ChevronDoubleRightSolid': 'M8.733 4.207a.75.75 0 0 1 1.06.026l5.001 5.25a.75.75 0 0 1 0 1.035l-5 5.25a.75.75 0 1 1-1.087-1.034L13.215 10L8.707 5.267a.75.75 0 0 1 .026-1.06zm-4 0a.75.75 0 0 1 1.06.026l5.001 5.25a.75.75 0 0 1 0 1.035l-5 5.25a.75.75 0 1 1-1.087-1.034L9.216 10l-4.51-4.734a.75.75 0 0 1 .027-1.06z',
'ChevronDoubleLeft': 'M11.353 15.854a.5.5 0 0 1-.707.001L5.162 10.39a.55.55 0 0 1 0-.78l5.484-5.464a.5.5 0 1 1 .706.708L6.188 10l5.164 5.147a.5.5 0 0 1 .001.707zm4 0a.5.5 0 0 1-.708.001L9.161 10.39a.55.55 0 0 1 0-.78l5.484-5.464a.5.5 0 1 1 .706.708L10.187 10l5.164 5.147a.5.5 0 0 1 .001.707z',
'ChevronDoubleLeftSolid': 'M11.269 15.794a.75.75 0 0 1-1.06-.026l-5.002-5.25a.75.75 0 0 1 0-1.035l5.001-5.25a.75.75 0 1 1 1.086 1.034l-4.508 4.734l4.508 4.733a.75.75 0 0 1-.025 1.06zm4 .001a.75.75 0 0 1-1.06-.026l-5.001-5.25a.75.75 0 0 1 0-1.035l5.001-5.25a.75.75 0 1 1 1.086 1.034l-4.508 4.733l4.508 4.734a.75.75 0 0 1-.025 1.06z',
'MoreHorizontal': 'M6.25 10a1.25 1.25 0 1 1-2.5 0a1.25 1.25 0 0 1 2.5 0zm5 0a1.25 1.25 0 1 1-2.5 0a1.25 1.25 0 0 1 2.5 0zM15 11.25a1.25 1.25 0 1 0 0-2.5a1.25 1.25 0 0 0 0 2.5z',
'MoreHorizontalSolid': 'M6.75 10a1.75 1.75 0 1 1-3.5 0a1.75 1.75 0 0 1 3.5 0zm5 0a1.75 1.75 0 1 1-3.5 0a1.75 1.75 0 0 1 3.5 0zM15 11.75a1.75 1.75 0 1 0 0-3.5a1.75 1.75 0 0 0 0 3.5z',
'MoreVertical': 'M10 6a1.25 1.25 0 1 1 0-2.5A1.25 1.25 0 0 1 10 6zm0 5.25a1.25 1.25 0 1 1 0-2.5a1.25 1.25 0 0 1 0 2.5zm-1.25 4a1.25 1.25 0 1 0 2.5 0a1.25 1.25 0 0 0-2.5 0z',
'MoreVerticalSolid': 'M10 6.5A1.75 1.75 0 1 1 10 3a1.75 1.75 0 0 1 0 3.5zM10 17a1.75 1.75 0 1 1 0-3.5a1.75 1.75 0 0 1 0 3.5zm-1.75-7a1.75 1.75 0 1 0 3.5 0a1.75 1.75 0 0 0-3.5 0z',
'Delete': 'M11.5 4a1.5 1.5 0 0 0-3 0h-1a2.5 2.5 0 0 1 5 0H17a.5.5 0 0 1 0 1h-.554L15.15 16.23A2 2 0 0 1 13.163 18H6.837a2 2 0 0 1-1.987-1.77L3.553 5H3a.5.5 0 0 1-.492-.41L2.5 4.5A.5.5 0 0 1 3 4h8.5zm3.938 1H4.561l1.282 11.115a1 1 0 0 0 .994.885h6.326a1 1 0 0 0 .993-.885L15.438 5zM8.5 7.5c.245 0 .45.155.492.359L9 7.938v6.125c0 .241-.224.437-.5.437c-.245 0-.45-.155-.492-.359L8 14.062V7.939c0-.242.224-.438.5-.438zm3 0c.245 0 .45.155.492.359l.008.079v6.125c0 .241-.224.437-.5.437c-.245 0-.45-.155-.492-.359L11 14.062V7.939c0-.242.224-.438.5-.438z',
'DeleteSolid': 'M10 1.25a2.75 2.75 0 0 1 2.739 2.5H17a.75.75 0 0 1 .102 1.493L17 5.25h-.583L15.15 16.23A2 2 0 0 1 13.163 18H6.837a2 2 0 0 1-1.987-1.77L3.582 5.25H3a.75.75 0 0 1-.743-.648L2.25 4.5a.75.75 0 0 1 .648-.743L3 3.75h4.261A2.75 2.75 0 0 1 10 1.25zM8.5 7.5c-.245 0-.45.155-.492.359L8 7.938v6.125l.008.078c.042.204.247.359.492.359s.45-.155.492-.359L9 14.062V7.939l-.008-.08C8.95 7.656 8.745 7.5 8.5 7.5zm3 0c-.245 0-.45.155-.492.359L11 7.938v6.125l.008.078c.042.204.247.359.492.359s.45-.155.492-.359l.008-.079V7.939l-.008-.08c-.042-.203-.247-.358-.492-.358zM10 2.75c-.605 0-1.11.43-1.225 1h2.45c-.116-.57-.62-1-1.225-1z',
'DeleteDismiss': 'M11.5 4a1.5 1.5 0 0 0-3 0h3zm-4 0a2.5 2.5 0 0 1 5 0H17a.5.5 0 0 1 0 1h-.554l-.484 4.196a5.484 5.484 0 0 0-.987-.176L15.438 5H4.561l1.282 11.115a1 1 0 0 0 .994.885H9.6c.183.358.404.693.657 1h-3.42a2 2 0 0 1-1.987-1.77L3.553 5H3a.5.5 0 0 1-.492-.41L2.5 4.5A.5.5 0 0 1 3 4h4.5zM19 14.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-2.646-1.146a.5.5 0 0 0-.708-.708L14.5 13.793l-1.146-1.147a.5.5 0 0 0-.708.708l1.147 1.146l-1.147 1.146a.5.5 0 0 0 .708.708l1.146-1.147l1.146 1.147a.5.5 0 0 0 .708-.708L15.207 14.5l1.147-1.146z',
'DeleteDismissSolid': 'M10 1.25a2.75 2.75 0 0 1 2.739 2.5H17a.75.75 0 0 1 .102 1.493L17 5.25h-.583l-.455 3.946A5.5 5.5 0 0 0 10.258 18H6.836a2 2 0 0 1-1.987-1.77L3.582 5.25H3a.75.75 0 0 1-.743-.648L2.25 4.5a.75.75 0 0 1 .648-.743L3 3.75h4.261A2.75 2.75 0 0 1 10 1.25zm0 1.5c-.605 0-1.11.43-1.225 1h2.45c-.116-.57-.62-1-1.225-1zm9 11.75a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-2.646-1.146a.5.5 0 0 0-.708-.708L14.5 13.793l-1.146-1.147a.5.5 0 0 0-.708.708l1.147 1.146l-1.147 1.146a.5.5 0 0 0 .708.708l1.146-1.147l1.146 1.147a.5.5 0 0 0 .708-.708L15.207 14.5l1.147-1.146z',
'DeleteOff': 'M3 4h.293L2.146 2.854a.5.5 0 1 1 .708-.708L8.26 7.553l9.594 9.593a.5.5 0 0 1-.708.708l-1.958-1.958l-.038.333A2 2 0 0 1 13.163 18H6.837a2 2 0 0 1-1.987-1.77L3.553 5H3a.5.5 0 0 1-.492-.41L2.5 4.5A.5.5 0 0 1 3 4zm11.286 10.993L12 12.707v1.355c0 .242-.224.438-.5.438c-.245 0-.45-.155-.492-.359L11 14.062v-2.355l-2-2v4.355c0 .242-.224.438-.5.438c-.245 0-.45-.155-.492-.359L8 14.062V8.707L4.596 5.303l1.247 10.812a1 1 0 0 0 .994.885h6.326a1 1 0 0 0 .993-.885l.13-1.122zm1.195-1.633l-.903-.903l.86-7.457H7.121l-1-1H7.5a2.5 2.5 0 0 1 5 0H17a.5.5 0 0 1 0 1h-.554l-.965 8.36zM11.5 4a1.5 1.5 0 0 0-3 0h3zm.5 5.879l-1-1v-.941c0-.242.224-.438.5-.438c.245 0 .45.155.492.359l.008.079v1.94z',
'DeleteOffSolid': 'M15.188 15.896l-.038.333A2 2 0 0 1 13.163 18H6.837a2 2 0 0 1-1.987-1.77L3.582 5.25H3a.75.75 0 0 1-.743-.648L2.25 4.5a.75.75 0 0 1 .648-.743L3 3.75h.043l-.897-.896a.5.5 0 1 1 .708-.708L8.26 7.553l9.594 9.593a.5.5 0 0 1-.708.708l-1.958-1.958zM8 8.707v5.355l.008.08c.042.203.247.358.492.358s.45-.155.492-.359L9 14.062V9.707l-1-1zm3 3v2.355l.008.08c.042.203.247.358.492.358s.45-.155.492-.359l.008-.079v-1.355l-1-1zm0-2.828L5.871 3.75h1.39a2.75 2.75 0 0 1 5.478 0H17a.75.75 0 0 1 .102 1.493L17 5.25h-.583l-.936 8.11L12 9.879V7.938l-.008-.08c-.042-.203-.247-.358-.492-.358s-.45.155-.492.359L11 7.938v.94zM10 2.75c-.605 0-1.11.43-1.225 1h2.45c-.116-.57-.62-1-1.225-1z',
'DeleteArrowBack': 'M11.5 4a1.5 1.5 0 0 0-3 0h3zm-4 0a2.5 2.5 0 0 1 5 0H17a.5.5 0 0 1 0 1h-.554l-.484 4.196a5.484 5.484 0 0 0-.987-.176L15.438 5H4.561l1.282 11.115a1 1 0 0 0 .994.885H9.6c.183.358.404.693.657 1h-3.42a2 2 0 0 1-1.987-1.77L3.553 5H3a.5.5 0 0 1-.492-.41L2.5 4.5A.5.5 0 0 1 3 4h4.5zm7 15a4.5 4.5 0 1 0 0-9a4.5 4.5 0 0 0 0 9zm-.896-6.396l-.897.896h1.543A2.75 2.75 0 0 1 17 16.25v.25a.5.5 0 0 1-1 0v-.25a1.75 1.75 0 0 0-1.75-1.75h-1.543l.897.896a.5.5 0 0 1-.708.708l-1.752-1.753a.499.499 0 0 1 .002-.705l1.75-1.75a.5.5 0 0 1 .708.708z',
'DeleteArrowBackSolid': 'M10 1.25a2.75 2.75 0 0 1 2.739 2.5H17a.75.75 0 0 1 .102 1.493L17 5.25h-.583l-.455 3.946A5.5 5.5 0 0 0 10.258 18H6.836a2 2 0 0 1-1.987-1.77L3.582 5.25H3a.75.75 0 0 1-.743-.648L2.25 4.5a.75.75 0 0 1 .648-.743L3 3.75h4.261A2.75 2.75 0 0 1 10 1.25zm0 1.5c-.605 0-1.11.43-1.225 1h2.45c-.116-.57-.62-1-1.225-1zM14.5 19a4.5 4.5 0 1 0 0-9a4.5 4.5 0 0 0 0 9zm-.896-6.396l-.897.896h1.543A2.75 2.75 0 0 1 17 16.25v.25a.5.5 0 0 1-1 0v-.25a1.75 1.75 0 0 0-1.75-1.75h-1.543l.897.896a.5.5 0 0 1-.708.708l-1.752-1.753a.499.499 0 0 1 .002-.705l1.75-1.75a.5.5 0 0 1 .708.708z',
'DeleteLines': 'M7.5 4a2.5 2.5 0 0 1 5 0H17a.5.5 0 0 1 0 1h-.554l-.923 8h-1.007l.922-8H4.561l1.282 11.115a1 1 0 0 0 .994.885h5.248c.066.186.168.356.297.5c-.13.144-.23.314-.297.5H6.837a2 2 0 0 1-1.987-1.77L3.553 5H3a.5.5 0 0 1-.492-.41L2.5 4.5A.5.5 0 0 1 3 4h4.5zm4 0a1.5 1.5 0 0 0-3 0h3zm2 12a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zm0-2a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zm-.5 4.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z',
'DeleteLinesSolid': 'M10 1.25a2.75 2.75 0 0 1 2.739 2.5H17a.75.75 0 0 1 .102 1.493L17 5.25h-.583L15.523 13H13.5a1.5 1.5 0 0 0-1.118 2.5a1.494 1.494 0 0 0-.382 1c0 .384.144.735.382 1c-.13.144-.23.314-.297.5H6.837a2 2 0 0 1-1.987-1.77L3.582 5.25H3a.75.75 0 0 1-.743-.648L2.25 4.5a.75.75 0 0 1 .648-.743L3 3.75h4.261A2.75 2.75 0 0 1 10 1.25zm0 1.5c-.605 0-1.11.43-1.225 1h2.45c-.116-.57-.62-1-1.225-1zm3 11.75a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z',
'Eye': 'M3.26 11.602C3.942 8.327 6.793 6 10 6c3.206 0 6.057 2.327 6.74 5.602a.5.5 0 0 0 .98-.204C16.943 7.673 13.693 5 10 5c-3.693 0-6.943 2.673-7.72 6.398a.5.5 0 0 0 .98.204zM10 8a3.5 3.5 0 1 0 0 7a3.5 3.5 0 0 0 0-7zm-2.5 3.5a2.5 2.5 0 1 1 5 0a2.5 2.5 0 0 1-5 0z',
'EyeSolid': 'M3.26 11.602C3.942 8.327 6.793 6 10 6c3.206 0 6.057 2.327 6.74 5.602a.5.5 0 0 0 .98-.204C16.943 7.673 13.693 5 10 5c-3.693 0-6.943 2.673-7.72 6.398a.5.5 0 0 0 .98.204zM9.99 8a3.5 3.5 0 1 1 0 7a3.5 3.5 0 0 1 0-7z',
'EyeOff': 'M2.854 2.146a.5.5 0 1 0-.708.708l3.5 3.498a8.097 8.097 0 0 0-3.366 5.046a.5.5 0 1 0 .98.204a7.09 7.09 0 0 1 3.107-4.528L7.953 8.66a3.5 3.5 0 1 0 4.886 4.886l4.307 4.308a.5.5 0 0 0 .708-.708l-15-15zm9.265 10.68A2.5 2.5 0 1 1 8.673 9.38l3.446 3.447zm-1.995-4.824l3.374 3.374a3.5 3.5 0 0 0-3.374-3.374zM10 6c-.57 0-1.129.074-1.666.213l-.803-.803A7.648 7.648 0 0 1 10 5c3.693 0 6.942 2.673 7.72 6.398a.5.5 0 0 1-.98.204C16.058 8.327 13.207 6 10 6z',
'EyeOffSolid': 'M2.854 2.146a.5.5 0 1 0-.708.708l3.5 3.498a8.097 8.097 0 0 0-3.366 5.046a.5.5 0 1 0 .979.204a7.09 7.09 0 0 1 3.108-4.528L7.95 8.656a3.5 3.5 0 1 0 4.884 4.884l4.313 4.314a.5.5 0 0 0 .708-.708l-15-15zm7.27 5.857l3.363 3.363a3.5 3.5 0 0 0-3.363-3.363zM7.53 5.41l.803.803A6.632 6.632 0 0 1 10 6c3.206 0 6.057 2.327 6.74 5.602a.5.5 0 1 0 .98-.204C16.943 7.673 13.693 5 10 5c-.855 0-1.687.143-2.469.41z',
'EyeTracking': 'M3 4.5A1.5 1.5 0 0 1 4.5 3h3a.5.5 0 0 0 0-1h-3A2.5 2.5 0 0 0 2 4.5v3a.5.5 0 0 0 1 0v-3zm0 11A1.5 1.5 0 0 0 4.5 17h3a.5.5 0 0 1 0 1h-3A2.5 2.5 0 0 1 2 15.5v-3a.5.5 0 0 1 1 0v3zM15.5 3A1.5 1.5 0 0 1 17 4.5v3a.5.5 0 0 0 1 0v-3A2.5 2.5 0 0 0 15.5 2h-3a.5.5 0 0 0 0 1h3zM17 15.5a1.5 1.5 0 0 1-1.5 1.5h-3a.5.5 0 0 0 0 1h3a2.5 2.5 0 0 0 2.5-2.5v-3a.5.5 0 0 0-1 0v3zm-10-4a3 3 0 1 1 6 0a3 3 0 0 1-6 0zm3-2a2 2 0 1 0 0 4a2 2 0 0 0 0-4zm-5.052.223v.001a.5.5 0 0 1-.895-.448L4.5 9.5a24.558 24.558 0 0 1-.447-.225l.001-.001l.002-.004l.005-.01a2.106 2.106 0 0 1 .082-.145a5.14 5.14 0 0 1 .249-.377A6.49 6.49 0 0 1 5.425 7.62C6.375 6.805 7.863 6 10 6s3.624.805 4.575 1.62c.473.406.812.812 1.034 1.119a5.13 5.13 0 0 1 .33.521l.005.01l.002.004l.001.002l-.447.224l.447-.224a.5.5 0 0 1-.893.45v-.002l-.002-.001l-.009-.018a4.133 4.133 0 0 0-.245-.381a5.487 5.487 0 0 0-.873-.944C13.125 7.695 11.863 7 10 7s-3.125.695-3.924 1.38a5.49 5.49 0 0 0-.874.944a4.14 4.14 0 0 0-.245.381l-.01.018z',
'EyeTrackingSolid': 'M4.5 3A1.5 1.5 0 0 0 3 4.5v3a.5.5 0 0 1-1 0v-3A2.5 2.5 0 0 1 4.5 2h3a.5.5 0 0 1 0 1h-3zm0 14A1.5 1.5 0 0 1 3 15.5v-3a.5.5 0 0 0-1 0v3A2.5 2.5 0 0 0 4.5 18h3a.5.5 0 0 0 0-1h-3zM17 4.5A1.5 1.5 0 0 0 15.5 3h-3a.5.5 0 0 1 0-1h3A2.5 2.5 0 0 1 18 4.5v3a.5.5 0 0 1-1 0v-3zM15.5 17a1.5 1.5 0 0 0 1.5-1.5v-3a.5.5 0 0 1 1 0v3a2.5 2.5 0 0 1-2.5 2.5h-3a.5.5 0 0 1 0-1h3zM7 11.5a3 3 0 1 1 6 0a3 3 0 0 1-6 0zM4.948 9.723v.001a.5.5 0 0 1-.895-.448L4.5 9.5a24.558 24.558 0 0 1-.447-.225l.001-.001l.002-.004l.005-.01a2.106 2.106 0 0 1 .082-.145a5.14 5.14 0 0 1 .249-.377A6.49 6.49 0 0 1 5.425 7.62C6.375 6.805 7.863 6 10 6s3.624.805 4.575 1.62c.473.406.812.812 1.034 1.119a5.13 5.13 0 0 1 .33.521l.005.01l.002.004l.001.002l-.447.224l.447-.224a.5.5 0 0 1-.893.45v-.002l-.002-.001l-.009-.018a4.133 4.133 0 0 0-.245-.381a5.487 5.487 0 0 0-.873-.944C13.125 7.695 11.863 7 10 7s-3.125.695-3.924 1.38a5.49 5.49 0 0 0-.874.944a4.14 4.14 0 0 0-.245.381l-.01.018z',
'EyeTrackingOff': 'M2.414 3.121C2.152 3.517 2 3.991 2 4.5v3a.5.5 0 0 0 1 0v-3c0-.232.052-.45.146-.647l3.141 3.141A6.592 6.592 0 0 0 4.392 8.74a5.14 5.14 0 0 0-.33.521l-.006.01l-.002.004v.001s-.001.001.446.225l-.447-.224a.5.5 0 0 0 .894.448v-.001l.01-.018l.045-.078a4.14 4.14 0 0 1 .2-.303A5.582 5.582 0 0 1 7.02 7.726l1.293 1.293a3 3 0 1 0 4.168 4.168l3.667 3.667A1.494 1.494 0 0 1 15.5 17h-3a.5.5 0 0 0 0 1h3c.51 0 .983-.152 1.379-.414l.267.268a.5.5 0 0 0 .708-.707l-.268-.268l-.732-.732l-3.938-3.938L9.29 8.584L8.007 7.3l-.78-.78l-3.374-3.374l-.732-.732l-.267-.268a.5.5 0 1 0-.708.708l.268.267zm9.34 9.34A2 2 0 1 1 9.04 9.746l2.715 2.715zm6.221 3.393c.016-.116.025-.234.025-.354v-3a.5.5 0 0 0-1 0v2.379l.975.975zM9.17 7.048C9.432 7.017 9.709 7 10 7c1.863 0 3.126.695 3.925 1.38c.402.344.688.688.873.944a4.133 4.133 0 0 1 .245.381l.01.018v.002a.5.5 0 0 0 .894-.449v-.002l-.003-.004l-.005-.01a5.13 5.13 0 0 0-.33-.522a6.491 6.491 0 0 0-1.034-1.118C13.626 6.805 12.137 6 10 6a7.68 7.68 0 0 0-1.695.183l.865.865zm6.777 2.228l-.058.03l-.387.193l.445-.223zM5.121 3H7.5a.5.5 0 0 0 0-1h-3c-.12 0-.238.008-.354.025L5.121 3zM4.5 17A1.5 1.5 0 0 1 3 15.5v-3a.5.5 0 0 0-1 0v3A2.5 2.5 0 0 0 4.5 18h3a.5.5 0 0 0 0-1h-3zm11-14A1.5 1.5 0 0 1 17 4.5v3a.5.5 0 0 0 1 0v-3A2.5 2.5 0 0 0 15.5 2h-3a.5.5 0 0 0 0 1h3z',
'EyeTrackingOffSolid': 'M2.414 3.121C2.152 3.517 2 3.991 2 4.5v3a.5.5 0 0 0 1 0v-3c0-.232.052-.45.146-.647l3.141 3.141A6.592 6.592 0 0 0 4.392 8.74a5.14 5.14 0 0 0-.33.521l-.006.01l-.002.004v.001s-.001.001.446.225l-.447-.224a.5.5 0 0 0 .894.448v-.001l.01-.018l.045-.078a4.14 4.14 0 0 1 .2-.303A5.582 5.582 0 0 1 7.02 7.726l1.293 1.293a3 3 0 1 0 4.168 4.168l3.667 3.667A1.494 1.494 0 0 1 15.5 17h-3a.5.5 0 0 0 0 1h3c.51 0 .983-.152 1.379-.414l.267.268a.5.5 0 0 0 .708-.707l-.268-.268l-.732-.732l-3.938-3.938L9.29 8.584L8.007 7.3l-.78-.78l-3.374-3.374l-.732-.732l-.267-.268a.5.5 0 1 0-.708.708l.268.267zm15.561 12.733c.016-.116.025-.234.025-.354v-3a.5.5 0 0 0-1 0v2.379l.975.975zM9.17 7.048C9.432 7.017 9.709 7 10 7c1.863 0 3.126.695 3.925 1.38c.402.344.688.688.873.944a4.133 4.133 0 0 1 .245.381l.01.018v.002a.5.5 0 0 0 .894-.449v-.002l-.003-.004l-.005-.01a5.13 5.13 0 0 0-.33-.522a6.491 6.491 0 0 0-1.034-1.118C13.626 6.805 12.137 6 10 6a7.68 7.68 0 0 0-1.695.183l.865.865zm6.777 2.228l-.058.03l-.387.193l.445-.223zM5.121 3H7.5a.5.5 0 0 0 0-1h-3c-.12 0-.238.008-.354.025L5.121 3zM3 15.5A1.5 1.5 0 0 0 4.5 17h3a.5.5 0 0 1 0 1h-3A2.5 2.5 0 0 1 2 15.5v-3a.5.5 0 0 1 1 0v3zm14-11A1.5 1.5 0 0 0 15.5 3h-3a.5.5 0 0 1 0-1h3A2.5 2.5 0 0 1 18 4.5v3a.5.5 0 0 1-1 0v-3z',
'Share': 'M13.33 12.838l4.497-4.423l.057-.065a.587.587 0 0 0-.057-.767L13.33 3.162l-.062-.053c-.36-.27-.89-.01-.89.469v2.13l-.225.015c-3.563.282-5.65 2.537-6.148 6.627c-.064.525.538.854.928.506c1.431-1.278 2.91-2.072 4.445-2.39c.246-.051.493-.09.742-.117l.258-.023v2.096l.005.082c.06.453.609.666.947.334zM12.226 6.72l1.152-.077V4.61l3.446 3.388l-3.446 3.39V9.231l-1.356.122h-.008c-1.703.183-3.31.865-4.827 2.002c.298-1.339.807-2.346 1.476-3.067c.83-.895 1.99-1.443 3.563-1.569zM5.5 4A2.5 2.5 0 0 0 3 6.5v8A2.5 2.5 0 0 0 5.5 17h8a2.5 2.5 0 0 0 2.5-2.5v-1a.5.5 0 0 0-1 0v1a1.5 1.5 0 0 1-1.5 1.5h-8A1.5 1.5 0 0 1 4 14.5v-8A1.5 1.5 0 0 1 5.5 5h3a.5.5 0 0 0 0-1h-3z',
'ShareSolid': 'M12.378 5.708v-2.13c0-.48.53-.738.89-.47l.062.054l4.497 4.42c.21.207.229.539.057.768l-.057.065l-4.497 4.423c-.338.332-.887.119-.947-.334l-.005-.082v-2.096l-.258.023c-1.8.193-3.526 1.024-5.187 2.507c-.39.348-.992.02-.928-.506c.498-4.09 2.585-6.345 6.148-6.627l.225-.015zM5.5 4A2.5 2.5 0 0 0 3 6.5v8A2.5 2.5 0 0 0 5.5 17h8a2.5 2.5 0 0 0 2.5-2.5v-1a.5.5 0 0 0-1 0v1a1.5 1.5 0 0 1-1.5 1.5h-8A1.5 1.5 0 0 1 4 14.5v-8A1.5 1.5 0 0 1 5.5 5h3a.5.5 0 0 0 0-1h-3z',
'Alert': 'M9.998 2c3.149 0 5.744 2.335 5.984 5.355l.014.223l.004.224l-.001 3.596l.925 2.222c.023.054.04.11.053.167l.016.086l.008.132a1 1 0 0 1-.749.963l-.116.027l-.135.01l-3.501-.001l-.005.161a2.5 2.5 0 0 1-4.99 0l-.005-.161H3.999a.998.998 0 0 1-.26-.034l-.124-.042a1 1 0 0 1-.603-1.052l.021-.128l.043-.128l.923-2.219L4 7.793l.004-.225C4.127 4.451 6.771 2 9.998 2zM11.5 15.004h-3l.007.141a1.5 1.5 0 0 0 1.349 1.348L10 16.5a1.5 1.5 0 0 0 1.493-1.355l.007-.141zM9.998 3c-2.623 0-4.77 1.924-4.98 4.385l-.014.212L5 7.802V11.5l-.038.192l-.963 2.313l11.958.002l.045-.002l-.964-2.313L15 11.5V7.812l-.004-.204C14.891 5.035 12.695 3 9.998 3z',
'AlertSolid': 'M12.45 16.002a2.5 2.5 0 0 1-4.9 0h4.9zM9.998 2c3.149 0 5.744 2.335 5.984 5.355l.013.223l.005.224l-.001 3.606l.954 2.587l.025.085l.016.086l.005.089c0 .315-.196.59-.522.707l-.114.033l-.114.01H3.751a.75.75 0 0 1-.259-.047c-.287-.105-.476-.372-.482-.716l.004-.117l.034-.13l.95-2.584L4 7.793l.004-.225C4.127 4.451 6.771 2 9.998 2z',
'AlertOff': 'M4.004 7.568a5.62 5.62 0 0 1 .58-2.277L2.146 2.854a.5.5 0 1 1 .708-.708l15 15a.5.5 0 0 1-.708.708l-2.849-2.85H12.5l-.005.161a2.5 2.5 0 0 1-4.99 0l-.005-.161H3.999a.998.998 0 0 1-.26-.034l-.124-.042a1 1 0 0 1-.603-1.052l.021-.128l.043-.128l.923-2.219L4 7.793l.004-.225zm9.295 6.438l-7.96-7.96c-.171.42-.282.87-.322 1.339l-.013.212L5 7.802V11.5l-.038.192l-.963 2.313l9.3.001zm-1.8.998h-3l.008.141a1.5 1.5 0 0 0 1.349 1.348L10 16.5a1.5 1.5 0 0 0 1.493-1.355l.007-.141zm3.54-3.312l.874 2.1l.852.852a.977.977 0 0 0 .236-.64l-.008-.13l-.016-.087a.996.996 0 0 0-.053-.167L16 11.398L16 7.802l-.005-.224l-.013-.223C15.742 4.335 13.147 2 9.998 2c-1.64 0-3.128.633-4.213 1.664l.707.707A5.1 5.1 0 0 1 9.998 3c2.697 0 4.893 2.035 4.998 4.608l.004.204V11.5l.038.192z',
'AlertOffSolid': 'M4.004 7.568a5.62 5.62 0 0 1 .58-2.277L2.146 2.854a.5.5 0 1 1 .708-.708l15 15a.5.5 0 0 1-.708.708l-2.849-2.85H3.752a.75.75 0 0 1-.259-.046c-.287-.105-.476-.372-.482-.716l.004-.117l.034-.13l.95-2.584L4 7.793l.004-.225zM17 14.255a.72.72 0 0 1-.163.46L5.786 3.663A6.095 6.095 0 0 1 9.997 2c3.149 0 5.744 2.335 5.984 5.355l.013.223l.005.224l-.001 3.606l.954 2.587l.025.085l.016.086l.005.089zm-4.55 1.747a2.5 2.5 0 0 1-4.899 0h4.9z',
'AlertOn': 'M1.796 2.098a.5.5 0 1 0-.6.8L3.198 4.4a.5.5 0 1 0 .6-.8L1.796 2.098zM1 7a.5.5 0 0 0 0 1h1.5a.5.5 0 0 0 0-1H1zm8.998-5c3.149 0 5.744 2.334 5.984 5.355l.014.222l.004.225l-.001 3.596l.925 2.222a1 1 0 0 1 .053.167l.016.086l.008.131a1 1 0 0 1-.749.963l-.116.027l-.135.01H12.5l-.005.16a2.5 2.5 0 0 1-4.99 0l-.005-.16H3.999c-.088 0-.175-.011-.26-.034l-.124-.043a1 1 0 0 1-.603-1.052l.021-.127l.043-.128l.923-2.22L4 7.793l.004-.224C4.127 4.45 6.771 2 9.998 2zM11.5 15.004h-3l.007.141a1.5 1.5 0 0 0 1.349 1.348L10 16.5a1.5 1.5 0 0 0 1.493-1.356l.007-.14zM9.998 3c-2.623 0-4.77 1.923-4.98 4.385l-.014.212L5 7.802V11.5l-.038.192l-.963 2.312l11.958.002l.045-.002l-.964-2.312L15 11.5V7.812l-.004-.204C14.891 5.034 12.695 3 9.998 3zm8.906-.802a.5.5 0 0 0-.7-.1L16.202 3.6a.5.5 0 0 0 .6.8l2.002-1.502a.5.5 0 0 0 .1-.7zM19.5 7.5A.5.5 0 0 0 19 7h-1.5a.5.5 0 0 0 0 1H19a.5.5 0 0 0 .5-.5z',
'AlertOnSolid': 'M1.796 2.098a.5.5 0 1 0-.6.8L3.198 4.4a.5.5 0 1 0 .6-.8L1.796 2.098zM1 7a.5.5 0 0 0 0 1h1.5a.5.5 0 0 0 0-1H1zM12.45 16a2.501 2.501 0 0 1-4.9 0h4.9zM9.998 2c3.149 0 5.744 2.334 5.984 5.355l.014.222l.004.225l-.001 3.606l.954 2.587l.025.084l.016.087l.005.088c0 .315-.196.59-.522.707l-.113.033l-.115.01H3.751a.75.75 0 0 1-.259-.046c-.287-.106-.476-.372-.482-.716l.004-.118l.034-.13l.951-2.583L4 7.792l.004-.224C4.127 4.45 6.771 2 9.998 2zm8.906.198a.5.5 0 0 0-.7-.1L16.202 3.6a.5.5 0 0 0 .6.8l2.002-1.502a.5.5 0 0 0 .1-.7zM19.5 7.5A.5.5 0 0 0 19 7h-1.5a.5.5 0 0 0 0 1H19a.5.5 0 0 0 .5-.5z',
'AlertSnooze': 'M5 11.5V8.055A.505.505 0 0 0 5.003 8a5 5 0 0 1 6.36-4.813a.5.5 0 1 0 .272-.962A6 6 0 0 0 4.004 7.94A.504.504 0 0 0 4 7.998V11.4l-.923 2.216A1 1 0 0 0 4 15h3.5a2.5 2.5 0 0 0 5 0H16a1 1 0 0 0 .923-1.384L16 11.4V9.998a.5.5 0 0 0-1 0V11.5a.5.5 0 0 0 .039.192L16 14H4l.962-2.308A.5.5 0 0 0 5 11.5zM8.5 15h3a1.5 1.5 0 0 1-3 0zM14 2h3.5a.5.5 0 0 1 .452.714l-.043.073L14.96 7h2.54a.5.5 0 0 1 .09.992L17.5 8H14a.5.5 0 0 1-.452-.714l.042-.073L16.54 3H14a.5.5 0 0 1-.09-.992L14 2zM9.5 6h2.5a.5.5 0 0 1 .432.753l-.048.067L10.57 9H12a.5.5 0 0 1 .09.992L12 10h-2.5a.5.5 0 0 1-.432-.753l.048-.067L10.933 7H9.501a.5.5 0 0 1-.09-.992L9.501 6z',
'AlertSnoozeOff': 'M9.998 2c.891 0 1.738.187 2.5.524A1.5 1.5 0 0 0 13.998 4h.627l-1.286 1.826A1.475 1.475 0 0 0 11.999 5H9.454l-.18.016l-.044.008a1.5 1.5 0 0 0-.33 2.852l-.578.694l-.094.131l-.02.034C7.63 9.705 8.305 11 9.498 11h2.546l.179-.016l.044-.008a1.5 1.5 0 0 0 1.088-2.117c.191.09.407.141.643.141H16v2.408l.953 2.587l.026.085l.015.086l.005.089c0 .315-.195.59-.522.707l-.113.033l-.115.01H3.752a.75.75 0 0 1-.26-.047c-.287-.105-.475-.372-.482-.716l.004-.117l.034-.13l.951-2.584L4 7.793l.005-.225C4.127 4.451 6.77 2 9.998 2zm2.452 14.002a2.501 2.501 0 0 1-4.9 0h4.9zM13.998 2h3.5a.5.5 0 0 1 .452.714l-.042.073L14.958 7h2.54a.5.5 0 0 1 .09.992l-.09.008h-3.5a.5.5 0 0 1-.452-.714l.042-.073L16.538 3h-2.54a.5.5 0 0 1-.09-.992l.09-.008zM9.499 6h2.5a.5.5 0 0 1 .432.753l-.048.067L10.567 9h1.432a.5.5 0 0 1 .09.992l-.09.008h-2.5a.5.5 0 0 1-.432-.753l.048-.067L10.93 7H9.5a.5.5 0 0 1-.09-.992L9.5 6z',
'AlertUrgent': 'M13.264 2.078a.5.5 0 1 0-.523.852c2.258 1.384 4.12 3.414 4.26 7.09A.5.5 0 0 0 18 9.982c-.157-4.099-2.278-6.398-4.736-7.904zm-1.178 2.65a.5.5 0 0 1 .694-.134c1.607 1.085 2.715 2.638 2.888 4.424c.016.16.024.323.024.487a.5.5 0 0 1-1 0c0-.132-.007-.262-.02-.39c-.136-1.418-1.024-2.728-2.452-3.693a.5.5 0 0 1-.134-.694zm-7.006.71a5.158 5.158 0 0 0-2.614 6.811l1.223 2.749l.09 2.32a.75.75 0 0 0 1.054.656l9.727-4.33a.75.75 0 0 0 .218-1.223l-1.664-1.619l-1.224-2.749a5.158 5.158 0 0 0-6.81-2.614zm-1.7 6.404a4.158 4.158 0 0 1 7.596-3.382l1.302 2.925l1.538 1.495l-9.052 4.03l-.083-2.143l-1.302-2.925zm7.298 6.034a1.49 1.49 0 0 1-1.848-.54l2.685-1.194a1.49 1.49 0 0 1-.837 1.734z',
'AlertUrgentSolid': 'M2.466 12.25a5.158 5.158 0 0 1 9.424-4.197l1.224 2.749l1.664 1.619a.75.75 0 0 1-.218 1.222l-9.727 4.331a.75.75 0 0 1-1.054-.656l-.09-2.32l-1.223-2.749zm6.364 5.087a1.49 1.49 0 0 0 2.685-1.195L8.83 17.337zm3.256-12.609a.5.5 0 0 1 .694-.134c1.607 1.085 2.715 2.638 2.888 4.424c.016.16.024.323.024.487a.5.5 0 1 1-1 0a4.04 4.04 0 0 0-.02-.39c-.136-1.418-1.024-2.728-2.452-3.693a.5.5 0 0 1-.134-.694zm.49-2.485a.5.5 0 0 1 .688-.165c2.458 1.506 4.58 3.805 4.736 7.904a.5.5 0 0 1-1 .038C16.86 6.344 15 4.314 12.741 2.93a.5.5 0 0 1-.165-.687z',
'ArrowClockwise': 'M3.066 9.05a7 7 0 0 1 12.557-3.22l.126.17H12.5a.5.5 0 1 0 0 1h4a.5.5 0 0 0 .5-.5V2.502a.5.5 0 0 0-1 0v2.207a8 8 0 1 0 1.986 4.775a.5.5 0 0 0-.998.064A7 7 0 1 1 3.066 9.05z',
'ArrowClockwiseSolid': 'M10.628 2.025a8 8 0 1 0 7.367 7.714a.75.75 0 1 0-1.5.045a6.5 6.5 0 1 1-1.573-4.029l.204.248h-2.379l-.101.008a.75.75 0 0 0 0 1.486l.101.007h4l.102-.007a.75.75 0 0 0 .641-.641l.007-.102v-4l-.007-.102a.75.75 0 0 0-.641-.641l-.102-.007l-.102.007a.75.75 0 0 0-.641.641l-.007.102l.001 1.953a7.977 7.977 0 0 0-5.37-2.682z',
'ArrowClockwiseDashes': 'M8.132 2.22a8.02 8.02 0 0 1 3.736 0a.5.5 0 0 1-.233.972a7.02 7.02 0 0 0-3.27 0a.5.5 0 1 1-.233-.973zM6.507 3.342a.5.5 0 0 1-.165.687A7.039 7.039 0 0 0 4.03 6.342a.5.5 0 0 1-.852-.523A8.039 8.039 0 0 1 5.82 3.18a.5.5 0 0 1 .688.164zm7.674-.165a.5.5 0 1 0-.523.852A7.04 7.04 0 0 1 15.745 6H12.5a.5.5 0 0 0 0 1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 0-1 0v2.208a8.035 8.035 0 0 0-1.82-1.53zM2.822 7.762a.5.5 0 0 1 .37.603a7.02 7.02 0 0 0 0 3.27a.5.5 0 0 1-.973.233a8.02 8.02 0 0 1 0-3.736a.5.5 0 0 1 .603-.37zM18 10v-.5a.5.5 0 0 0-1 0v.5a7.02 7.02 0 0 1-.192 1.635a.5.5 0 1 0 .973.233c.143-.6.219-1.225.219-1.868zM3.343 13.493a.5.5 0 0 1 .687.165a7.038 7.038 0 0 0 2.312 2.312a.5.5 0 1 1-.523.852a8.038 8.038 0 0 1-2.64-2.641a.5.5 0 0 1 .164-.688zm13.479.688a.5.5 0 0 0-.852-.523a7.037 7.037 0 0 1-2.313 2.312a.5.5 0 0 0 .524.852a8.037 8.037 0 0 0 2.64-2.641zm-9.06 2.997a.5.5 0 0 1 .603-.37a7.02 7.02 0 0 0 3.27 0a.5.5 0 1 1 .233.973a8.02 8.02 0 0 1-3.736 0a.5.5 0 0 1-.37-.603z',
'ArrowClockwiseDashesSolid': 'M8.44 2.152a8.035 8.035 0 0 1 3.12 0a.75.75 0 0 1-.29 1.472a6.536 6.536 0 0 0-2.54 0a.75.75 0 0 1-.29-1.472zm4.965 1.402a.75.75 0 0 1 1.04-.206A8.04 8.04 0 0 1 16 4.708V2.75a.75.75 0 0 1 1.5 0v4a.75.75 0 0 1-.75.75h-4a.75.75 0 0 1 0-1.5h2.374a6.541 6.541 0 0 0-1.513-1.406a.75.75 0 0 1-.206-1.04zm-7.016 1.04a.75.75 0 0 0-.834-1.246a8.04 8.04 0 0 0-2.207 2.207a.75.75 0 0 0 1.246.834A6.54 6.54 0 0 1 6.39 4.594zM3.034 7.85a.75.75 0 0 1 .59.882a6.535 6.535 0 0 0 0 2.538a.75.75 0 0 1-1.472.291a8.035 8.035 0 0 1 0-3.12a.75.75 0 0 1 .882-.59zM18 10v-.25a.75.75 0 0 0-1.5 0V10c0 .435-.043.86-.124 1.27a.75.75 0 1 0 1.472.29c.1-.505.152-1.027.152-1.56zM3.554 13.405a.75.75 0 0 1 1.04.206a6.54 6.54 0 0 0 1.795 1.795a.75.75 0 0 1-.834 1.246a8.042 8.042 0 0 1-2.207-2.207a.75.75 0 0 1 .206-1.04zm13.098 1.04a.75.75 0 0 0-1.246-.834a6.54 6.54 0 0 1-1.795 1.795a.75.75 0 0 0 .834 1.246a8.043 8.043 0 0 0 2.207-2.207zM7.85 16.966a.75.75 0 0 1 .882-.59a6.535 6.535 0 0 0 2.538 0a.75.75 0 1 1 .291 1.472a8.033 8.033 0 0 1-3.12 0a.75.75 0 0 1-.59-.881z',
'ArrowHookDownLeft': 'M6 4.5a.5.5 0 0 1 .5-.5H11c1.636 0 2.9.618 3.749 1.574C15.59 6.521 16 7.768 16 9c0 1.232-.41 2.48-1.251 3.426C13.899 13.382 12.636 14 11 14H5.707l2.647 2.646a.5.5 0 0 1-.708.708l-3.5-3.5a.5.5 0 0 1 0-.708l3.5-3.5a.5.5 0 1 1 .708.708L5.707 13H11c1.364 0 2.35-.507 3.001-1.238C14.66 11.02 15 10.018 15 9s-.34-2.02-.999-2.762C13.351 5.507 12.364 5 11 5H6.5a.5.5 0 0 1-.5-.5z',
'ArrowHookDownLeftSolid': 'M6 4.75A.75.75 0 0 1 6.75 4h4.5c1.586 0 2.696.621 3.53 1.588C15.6 6.54 16 7.784 16 9c0 1.216-.3 2.46-1.12 3.412c-.834.967-2.044 1.588-3.63 1.588H6.56l2.22 2.22a.75.75 0 1 1-1.06 1.06l-3.5-3.5a.75.75 0 0 1 .02-1.08l3.5-3.25a.75.75 0 0 1 1.02 1.1l-2.1 1.95h4.59c1.164 0 1.86-.441 2.4-1.068c.554-.642.85-1.523.85-2.432s-.296-1.79-.85-2.432c-.54-.627-1.236-1.068-2.4-1.068h-4.5A.75.75 0 0 1 6 4.75z',
'ArrowHookDownRight': 'M4 9a5 5 0 0 1 5-5h4.5a.5.5 0 0 1 0 1H9a4 4 0 1 0 0 8h5.293l-2.7-2.7a.5.5 0 1 1 .708-.706l3.539 3.539a.5.5 0 0 1 .125.497a.499.499 0 0 1-.135.247l-3.533 3.533a.5.5 0 0 1-.707-.707L14.293 14H9a5 5 0 0 1-5-5z',
'ArrowHookDownRightSolid': 'M9 14c.06 0-.06.002 0 0c.023.002.227 0 .25 0h4.393l-2.268 2.268a.75.75 0 1 0 1.061 1.06l3.353-3.352a.749.749 0 0 0 .212-.639a.747.747 0 0 0-.215-.444L12.54 9.646a.75.75 0 1 0-1.06 1.061L13.27 12.5H9a3.5 3.5 0 1 1 0-7h4.25a.75.75 0 0 0 0-1.5H9a5 5 0 0 0 0 10z',
'Bookmark': 'M4 4.5A2.5 2.5 0 0 1 6.5 2h7A2.5 2.5 0 0 1 16 4.5v13a.5.5 0 0 1-.794.404L10 14.118l-5.206 3.786A.5.5 0 0 1 4 17.5v-13zM6.5 3A1.5 1.5 0 0 0 5 4.5v12.018l4.706-3.422a.5.5 0 0 1 .588 0L15 16.518V4.5A1.5 1.5 0 0 0 13.5 3h-7z',
'BookmarkSolid': 'M4 4.5A2.5 2.5 0 0 1 6.5 2h7A2.5 2.5 0 0 1 16 4.5v13a.5.5 0 0 1-.794.404L10 14.118l-5.206 3.786A.5.5 0 0 1 4 17.5v-13z',
'BookmarkAdd': 'M19 5.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-4-2a.5.5 0 0 0-1 0V5h-1.5a.5.5 0 0 0 0 1H14v1.5a.5.5 0 0 0 1 0V6h1.5a.5.5 0 0 0 0-1H15V3.5zm0 13.018v-5.54a5.489 5.489 0 0 0 1-.185V17.5a.5.5 0 0 1-.794.404L10 14.118l-5.206 3.786A.5.5 0 0 1 4 17.5v-13A2.5 2.5 0 0 1 6.5 2h3.757A5.504 5.504 0 0 0 9.6 3H6.5A1.5 1.5 0 0 0 5 4.5v12.018l4.706-3.422a.5.5 0 0 1 .588 0L15 16.518z',
'BookmarkAddSolid': 'M19 5.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-4-2a.5.5 0 0 0-1 0V5h-1.5a.5.5 0 0 0 0 1H14v1.5a.5.5 0 0 0 1 0V6h1.5a.5.5 0 0 0 0-1H15V3.5zm-.5 7.5c.52 0 1.023-.072 1.5-.207V17.5a.5.5 0 0 1-.794.404L10 14.118l-5.206 3.786A.5.5 0 0 1 4 17.5v-13A2.5 2.5 0 0 1 6.5 2h3.757a5.5 5.5 0 0 0 4.243 9z',
'BookmarkMultiple': 'M6.268 3A2 2 0 0 1 8 2h4.5A3.5 3.5 0 0 1 16 5.5v10a.5.5 0 0 1-.777.416L15 15.768V5.5A2.5 2.5 0 0 0 12.5 3H6.268zM6 4a2 2 0 0 0-2 2v11.5a.5.5 0 0 0 .777.416L9 15.101l4.223 2.815A.5.5 0 0 0 14 17.5V6a2 2 0 0 0-2-2H6zM5 6a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v10.566l-3.723-2.482a.5.5 0 0 0-.554 0L5 16.566V6z',
'BookmarkMultipleSolid': 'M6.268 3A2 2 0 0 1 8 2h4.5A3.5 3.5 0 0 1 16 5.5v10a.5.5 0 0 1-.777.416L15 15.768V5.5A2.5 2.5 0 0 0 12.5 3H6.268zM6 4a2 2 0 0 0-2 2v11.5a.5.5 0 0 0 .777.416L9 15.101l4.223 2.815A.5.5 0 0 0 14 17.5V6a2 2 0 0 0-2-2H6z',
'BookmarkSearch': 'M15.596 7.303a3.5 3.5 0 1 1 .707-.707l2.55 2.55a.5.5 0 0 1-.707.708l-2.55-2.55zM16 4.5a2.5 2.5 0 1 0-5 0a2.5 2.5 0 0 0 5 0zm0 4.621V17.5a.5.5 0 0 1-.794.404L10 14.118l-5.206 3.786A.5.5 0 0 1 4 17.5v-13A2.5 2.5 0 0 1 6.5 2h3.258a4.484 4.484 0 0 0-.502 1H6.5A1.5 1.5 0 0 0 5 4.5v12.018l4.706-3.422a.5.5 0 0 1 .588 0L15 16.518V8.744c.15-.053.297-.114.44-.183l.56.56z',
'BookmarkSearchSearch': 'M15.596 7.303a3.5 3.5 0 1 1 .707-.707l2.55 2.55a.5.5 0 0 1-.707.708l-2.55-2.55zM16 4.5a2.5 2.5 0 1 0-5 0a2.5 2.5 0 0 0 5 0zm0 4.621V17.5a.5.5 0 0 1-.794.404L10 14.118l-5.206 3.786A.5.5 0 0 1 4 17.5v-13A2.5 2.5 0 0 1 6.5 2h3.258a4.5 4.5 0 0 0 5.682 6.561l.56.56z',
'Clipboard': 'M7.085 3A1.5 1.5 0 0 1 8.5 2h3a1.5 1.5 0 0 1 1.415 1H14.5A1.5 1.5 0 0 1 16 4.5v12a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 4 16.5v-12A1.5 1.5 0 0 1 5.5 3h1.585zM8.5 3a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1h-3zM7.085 4H5.5a.5.5 0 0 0-.5.5v12a.5.5 0 0 0 .5.5h9a.5.5 0 0 0 .5-.5v-12a.5.5 0 0 0-.5-.5h-1.585A1.5 1.5 0 0 1 11.5 5h-3a1.5 1.5 0 0 1-1.415-1z',
'ClipboardSolid': 'M7.085 3A1.5 1.5 0 0 1 8.5 2h3a1.5 1.5 0 0 1 1.415 1H14.5A1.5 1.5 0 0 1 16 4.5v12a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 4 16.5v-12A1.5 1.5 0 0 1 5.5 3h1.585zM8.5 3a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1h-3z',
'ClipboardCheckmark': 'M7.085 3A1.5 1.5 0 0 1 8.5 2h3a1.5 1.5 0 0 1 1.415 1H14.5A1.5 1.5 0 0 1 16 4.5v4.707a5.48 5.48 0 0 0-1-.185V4.5a.5.5 0 0 0-.5-.5h-1.585A1.5 1.5 0 0 1 11.5 5h-3a1.5 1.5 0 0 1-1.415-1H5.5a.5.5 0 0 0-.5.5v12a.5.5 0 0 0 .5.5h4.1c.183.358.404.693.657 1H5.5A1.5 1.5 0 0 1 4 16.5v-12A1.5 1.5 0 0 1 5.5 3h1.585zM8.5 3a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1h-3zM19 14.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-2.146-1.854a.5.5 0 0 0-.708 0L13.5 15.293l-.646-.647a.5.5 0 0 0-.708.708l1 1a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0 0-.708z',
'ClipboardCheckmarkSolid': 'M7.085 3A1.5 1.5 0 0 1 8.5 2h3a1.5 1.5 0 0 1 1.415 1H14.5A1.5 1.5 0 0 1 16 4.5v4.707A5.5 5.5 0 0 0 10.257 18H5.5A1.5 1.5 0 0 1 4 16.5v-12A1.5 1.5 0 0 1 5.5 3h1.585zM8.5 3a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1h-3zM19 14.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-2.146-1.854a.5.5 0 0 0-.708 0L13.5 15.293l-.646-.647a.5.5 0 0 0-.708.708l1 1a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0 0-.708z',
'ClipboardError': 'M7.085 3A1.5 1.5 0 0 1 8.5 2h3a1.5 1.5 0 0 1 1.415 1H14.5A1.5 1.5 0 0 1 16 4.5v4.707a5.48 5.48 0 0 0-1-.185V4.5a.5.5 0 0 0-.5-.5h-1.585A1.5 1.5 0 0 1 11.5 5h-3a1.5 1.5 0 0 1-1.415-1H5.5a.5.5 0 0 0-.5.5v12a.5.5 0 0 0 .5.5h4.1c.183.358.404.693.657 1H5.5A1.5 1.5 0 0 1 4 16.5v-12A1.5 1.5 0 0 1 5.5 3h1.585zM8.5 3a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1h-3zM19 14.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zM14.5 12a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 1 0v-2a.5.5 0 0 0-.5-.5zm0 5.125a.625.625 0 1 0 0-1.25a.625.625 0 0 0 0 1.25z',
'ClipboardErrorSolid': 'M7.085 3A1.5 1.5 0 0 1 8.5 2h3a1.5 1.5 0 0 1 1.415 1H14.5A1.5 1.5 0 0 1 16 4.5v4.707A5.5 5.5 0 0 0 10.257 18H5.5A1.5 1.5 0 0 1 4 16.5v-12A1.5 1.5 0 0 1 5.5 3h1.585zM8.5 3a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1h-3zM19 14.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zM14.5 12a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 1 0v-2a.5.5 0 0 0-.5-.5zm0 5.125a.625.625 0 1 0 0-1.25a.625.625 0 0 0 0 1.25z',
'ClipboardText': 'M6.5 8a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1h-7zM6 11.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm.5 2.5a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zm2-12a1.5 1.5 0 0 0-1.415 1H5.5A1.5 1.5 0 0 0 4 4.5v12A1.5 1.5 0 0 0 5.5 18h9a1.5 1.5 0 0 0 1.5-1.5v-12A1.5 1.5 0 0 0 14.5 3h-1.585A1.5 1.5 0 0 0 11.5 2h-3zm3 1a.5.5 0 0 1 0 1h-3a.5.5 0 0 1 0-1h3zm-6 1h1.585A1.5 1.5 0 0 0 8.5 5h3a1.5 1.5 0 0 0 1.415-1H14.5a.5.5 0 0 1 .5.5v12a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5v-12a.5.5 0 0 1 .5-.5z',
'ClipboardTextSolid': 'M8.5 2a1.5 1.5 0 0 0-1.415 1H5.5A1.5 1.5 0 0 0 4 4.5v12A1.5 1.5 0 0 0 5.5 18h9a1.5 1.5 0 0 0 1.5-1.5v-12A1.5 1.5 0 0 0 14.5 3h-1.585A1.5 1.5 0 0 0 11.5 2h-3zm3 1a.5.5 0 0 1 0 1h-3a.5.5 0 0 1 0-1h3zm-5 5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zm0 3h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1 0-1zM6 14.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z',
'Clock': 'M10 2a8 8 0 1 1 0 16a8 8 0 0 1 0-16zm0 1a7 7 0 1 0 0 14a7 7 0 0 0 0-14zm-.5 2a.5.5 0 0 1 .492.41L10 5.5V10h2.5a.5.5 0 0 1 .09.992L12.5 11h-3a.5.5 0 0 1-.492-.41L9 10.5v-5a.5.5 0 0 1 .5-.5z',
'ClockFill': 'M10 2a8 8 0 1 1 0 16a8 8 0 0 1 0-16zm-.5 3a.5.5 0 0 0-.5.5v5l.008.09A.5.5 0 0 0 9.5 11h3l.09-.008A.5.5 0 0 0 12.5 10H10V5.5l-.008-.09A.5.5 0 0 0 9.5 5z',
'ClockAlarm': 'M10 6.5a.5.5 0 0 0-1 0v4a.5.5 0 0 0 .5.5h3a.5.5 0 1 0 0-1H10V6.5zM3.353 7.8A3.19 3.19 0 0 1 2 5.187C2 3.431 3.414 2 5.166 2c1.077 0 2.026.542 2.597 1.365A6.992 6.992 0 0 1 10 3c.78 0 1.529.127 2.23.362A3.164 3.164 0 0 1 14.83 2A3.172 3.172 0 0 1 18 5.175c0 1.08-.538 2.033-1.359 2.607c.233.697.359 1.443.359 2.218a6.973 6.973 0 0 1-1.71 4.584l1.564 1.562a.5.5 0 0 1-.708.707l-1.562-1.562A6.973 6.973 0 0 1 10 17a6.973 6.973 0 0 1-4.584-1.71l-1.562 1.564a.5.5 0 1 1-.708-.707l1.563-1.563A6.973 6.973 0 0 1 3 10c0-.769.124-1.508.353-2.2zM3 5.187c0 .662.291 1.255.75 1.656a7.03 7.03 0 0 1 3.062-3.077A2.152 2.152 0 0 0 5.166 3A2.176 2.176 0 0 0 3 5.187zm13.242 1.64c.464-.399.758-.99.758-1.652A2.172 2.172 0 0 0 14.83 3c-.66 0-1.251.295-1.65.763a7.03 7.03 0 0 1 3.06 3.065zM4 10a6 6 0 1 0 12 0a6 6 0 0 0-12 0z',
'ClockAlarmSolid': 'M7.763 3.365A3.156 3.156 0 0 0 5.166 2C3.414 2 2 3.43 2 5.187A3.19 3.19 0 0 0 3.353 7.8A6.993 6.993 0 0 0 3 10c0 1.753.644 3.356 1.71 4.584l-1.564 1.563a.5.5 0 0 0 .708.707l1.562-1.563A6.973 6.973 0 0 0 10 17a6.973 6.973 0 0 0 4.584-1.71l1.562 1.563a.5.5 0 0 0 .708-.707l-1.563-1.562A6.973 6.973 0 0 0 17 10c0-.775-.126-1.521-.359-2.218A3.174 3.174 0 0 0 18 5.175A3.172 3.172 0 0 0 14.83 2c-1.078 0-2.03.54-2.602 1.362A6.992 6.992 0 0 0 10 3c-.782 0-1.534.128-2.237.365zM5.166 3c.657 0 1.248.296 1.646.766a7.03 7.03 0 0 0-3.061 3.077A2.19 2.19 0 0 1 3 5.187C3 3.975 3.973 3 5.166 3zm8.015.763c.399-.468.99-.763 1.65-.763C16.028 3 17 3.973 17 5.175c0 .661-.294 1.253-.758 1.653a7.03 7.03 0 0 0-3.06-3.065zM9.5 6a.5.5 0 0 1 .5.5V10h2.5a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5v-4a.5.5 0 0 1 .5-.5z',
'Cloud': 'M10 4c2.817 0 4.415 1.923 4.647 4.246h.07c1.814 0 3.283 1.512 3.283 3.377C18 13.488 16.53 15 14.718 15H5.282C3.469 15 2 13.488 2 11.623C2 9.82 3.373 8.347 5.102 8.251l.251-.005C5.587 5.908 7.183 4 10 4zm0 1C7.886 5 6.551 6.316 6.348 8.345a1 1 0 0 1-.995.901h-.07C4.027 9.246 3 10.304 3 11.623C3 12.943 4.028 14 5.282 14h9.436C15.972 14 17 12.942 17 11.623c0-1.32-1.028-2.377-2.282-2.377h-.071a1 1 0 0 1-.995-.9C13.45 6.325 12.109 5 10 5z',
'CloudSolid': 'M10 4c2.817 0 4.415 1.923 4.647 4.246h.07c1.814 0 3.283 1.512 3.283 3.377C18 13.488 16.53 15 14.718 15H5.282C3.469 15 2 13.488 2 11.623c0-1.865 1.47-3.377 3.282-3.377h.071C5.587 5.908 7.183 4 10 4z',
'CloudCheckmark': 'M10 2c2.817 0 4.415 1.923 4.647 4.246h.07C16.532 6.246 18 7.758 18 9.623c0 .095-.004.19-.011.283a5.782 5.782 0 0 0-1.114-1.062c-.31-.933-1.163-1.598-2.157-1.598h-.071a1 1 0 0 1-.995-.9C13.45 4.325 12.109 3 10 3C7.886 3 6.551 4.316 6.348 6.345a1 1 0 0 1-.995.901h-.07C4.027 7.246 3 8.304 3 9.623C3 10.943 4.028 12 5.282 12h2.666a5.733 5.733 0 0 0-.177 1H5.282C3.469 13 2 11.488 2 9.623C2 7.82 3.373 6.347 5.102 6.251l.251-.005C5.587 3.908 7.183 2 10 2zm8 11.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-2.854-1.854L12.5 14.293l-.646-.647a.5.5 0 0 0-.708.708l1 1a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708z',
'CloudCheckmarkSolid': 'M10 2c2.817 0 4.415 1.923 4.647 4.246h.07C16.532 6.246 18 7.758 18 9.623c0 .095-.004.19-.011.283A5.75 5.75 0 0 0 7.772 13h-2.49C3.469 13 2 11.488 2 9.623c0-1.865 1.47-3.377 3.282-3.377h.071C5.587 3.908 7.183 2 10 2zm8 11.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-2.146-1.854a.5.5 0 0 0-.708 0L12.5 14.293l-.646-.647a.5.5 0 0 0-.708.708l1 1a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0 0-.708z',
'CloudAdd': 'M10 2c2.817 0 4.415 1.923 4.647 4.246h.07C16.532 6.246 18 7.758 18 9.623c0 .095-.004.19-.011.283a5.782 5.782 0 0 0-1.114-1.062c-.31-.933-1.163-1.598-2.157-1.598h-.071a1 1 0 0 1-.995-.9C13.45 4.325 12.109 3 10 3C7.886 3 6.551 4.316 6.348 6.345a1 1 0 0 1-.995.901h-.07C4.027 7.246 3 8.304 3 9.623C3 10.943 4.028 12 5.282 12h2.666a5.733 5.733 0 0 0-.177 1H5.282C3.469 13 2 11.488 2 9.623C2 7.82 3.373 6.347 5.102 6.251l.251-.005C5.587 3.908 7.183 2 10 2zm3.5 16a4.5 4.5 0 1 0 0-9a4.5 4.5 0 0 0 0 9zm0-7a.5.5 0 0 1 .5.5V13h1.5a.5.5 0 0 1 0 1H14v1.5a.5.5 0 0 1-1 0V14h-1.5a.5.5 0 0 1 0-1H13v-1.5a.5.5 0 0 1 .5-.5z',
'CloudAddSolid': 'M10 2c2.817 0 4.415 1.923 4.647 4.246h.07C16.532 6.246 18 7.758 18 9.623c0 .095-.004.19-.011.283A5.75 5.75 0 0 0 7.772 13h-2.49C3.469 13 2 11.488 2 9.623c0-1.865 1.47-3.377 3.282-3.377h.071C5.587 3.908 7.183 2 10 2zm3.5 16a4.5 4.5 0 1 0 0-9a4.5 4.5 0 0 0 0 9zm0-7a.5.5 0 0 1 .5.5V13h1.5a.5.5 0 0 1 0 1H14v1.5a.5.5 0 0 1-1 0V14h-1.5a.5.5 0 0 1 0-1H13v-1.5a.5.5 0 0 1 .5-.5z',
'CloudDismiss': 'M10 2c2.817 0 4.415 1.923 4.647 4.246h.07C16.532 6.246 18 7.758 18 9.623c0 .095-.004.19-.011.283a5.782 5.782 0 0 0-1.114-1.062c-.31-.933-1.163-1.598-2.157-1.598h-.071a1 1 0 0 1-.995-.9C13.45 4.325 12.109 3 10 3C7.886 3 6.551 4.316 6.348 6.345a1 1 0 0 1-.995.901h-.07C4.027 7.246 3 8.304 3 9.623C3 10.943 4.028 12 5.282 12h2.666a5.733 5.733 0 0 0-.177 1H5.282C3.469 13 2 11.488 2 9.623C2 7.82 3.373 6.347 5.102 6.251l.251-.005C5.587 3.908 7.183 2 10 2zm8 11.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-3.793 0l1.147-1.146a.5.5 0 0 0-.708-.708L13.5 12.793l-1.146-1.147a.5.5 0 0 0-.708.708l1.147 1.146l-1.147 1.146a.5.5 0 0 0 .708.708l1.146-1.147l1.146 1.147a.5.5 0 0 0 .708-.708L14.207 13.5z',
'CloudDismissSolid': 'M10 2c2.817 0 4.415 1.923 4.647 4.246h.07C16.532 6.246 18 7.758 18 9.623c0 .095-.004.19-.011.283A5.75 5.75 0 0 0 7.772 13h-2.49C3.469 13 2 11.488 2 9.623c0-1.865 1.47-3.377 3.282-3.377h.071C5.587 3.908 7.183 2 10 2zm8 11.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0zm-2.646-1.146a.5.5 0 0 0-.708-.708L13.5 12.793l-1.146-1.147a.5.5 0 0 0-.708.708l1.147 1.146l-1.147 1.146a.5.5 0 0 0 .708.708l1.146-1.147l1.146 1.147a.5.5 0 0 0 .708-.708L14.207 13.5l1.147-1.146z',
'CloudEdit': 'M14.647 8.246C14.415 5.923 12.817 4 10 4S5.587 5.908 5.353 8.246l-.251.005C3.373 8.347 2 9.821 2 11.623C2 13.488 3.47 15 5.282 15h3.193c.11-.361.283-.7.51-1H5.282C4.028 14 3 12.942 3 11.623c0-1.32 1.028-2.377 2.282-2.377h.071a1 1 0 0 0 .995-.9C6.551 6.315 7.886 5 10 5c2.108 0 3.45 1.325 3.652 3.346c.025.25.14.471.313.632l.137-.137c.252-.253.54-.448.847-.587a3.242 3.242 0 0 0-.231-.008h-.071zm.162 1.302l-4.83 4.83a2.197 2.197 0 0 0-.577 1.02l-.375 1.498a.89.89 0 0 0 1.079 1.078l1.498-.374c.386-.097.739-.296 1.02-.578l4.83-4.83a1.87 1.87 0 0 0-2.645-2.644z',
'CloudEditSolid': 'M14.647 8.246C14.415 5.923 12.817 4 10 4S5.587 5.908 5.353 8.246h-.07C3.468 8.246 2 9.758 2 11.623C2 13.488 3.47 15 5.282 15h3.193c.152-.501.426-.958.798-1.33l4.829-4.83c.252-.252.54-.447.847-.586a3.242 3.242 0 0 0-.231-.008h-.071zm.162 1.302l-4.83 4.83a2.197 2.197 0 0 0-.577 1.02l-.375 1.498a.89.89 0 0 0 1.079 1.078l1.498-.374c.386-.097.739-.296 1.02-.578l4.83-4.83a1.87 1.87 0 0 0-2.645-2.644z',
'CloudOff': 'M2.854 2.146a.5.5 0 1 0-.708.708l3.67 3.668a5.326 5.326 0 0 0-.463 1.724l-.251.005C3.373 8.347 2 9.821 2 11.623C2 13.488 3.47 15 5.282 15h9.01l2.854 2.854a.5.5 0 0 0 .708-.708l-15-15zM13.293 14h-8.01c-1.255 0-2.283-1.058-2.283-2.377c0-1.32 1.028-2.377 2.282-2.377h.071a1 1 0 0 0 .995-.9c.038-.38.116-.735.23-1.06L13.293 14zM17 11.623c0 .898-.477 1.675-1.176 2.08l.724.724A3.4 3.4 0 0 0 18 11.623c0-1.865-1.47-3.377-3.282-3.377h-.071C14.415 5.923 12.817 4 10 4c-1.209 0-2.193.352-2.941.938l.715.715C8.36 5.233 9.112 5 10 5c2.108 0 3.45 1.325 3.652 3.346a1 1 0 0 0 .995.9h.071c1.254 0 2.282 1.058 2.282 2.377z',
'CloudOffSolid': 'M2.854 2.146a.5.5 0 1 0-.708.708l3.67 3.668a5.326 5.326 0 0 0-.463 1.724h-.07C3.468 8.246 2 9.758 2 11.623C2 13.488 3.47 15 5.282 15h9.01l2.854 2.854a.5.5 0 0 0 .708-.708l-15-15zM18 11.623a3.4 3.4 0 0 1-1.452 2.804l-9.49-9.49C7.808 4.353 8.792 4 10 4c2.817 0 4.415 1.923 4.647 4.246h.07c1.814 0 3.283 1.512 3.283 3.377z',
'CloudSync': 'M10 2c2.817 0 4.415 1.923 4.647 4.246h.07C16.532 6.246 18 7.758 18 9.623c0 .095-.004.19-.011.283a5.782 5.782 0 0 0-1.114-1.062c-.31-.933-1.163-1.598-2.157-1.598h-.071a1 1 0 0 1-.995-.9C13.45 4.325 12.109 3 10 3C7.886 3 6.551 4.316 6.348 6.345a1 1 0 0 1-.995.901h-.07C4.027 7.246 3 8.304 3 9.623C3 10.943 4.028 12 5.282 12h2.666a5.733 5.733 0 0 0-.177 1H5.282C3.469 13 2 11.488 2 9.623C2 7.82 3.373 6.347 5.102 6.251l.251-.005C5.587 3.908 7.183 2 10 2zM9 13.5a4.5 4.5 0 1 0 9 0a4.5 4.5 0 0 0-9 0zm6.5-3a.5.5 0 0 1 .5.5v1.5a.5.5 0 0 1-.5.5H14a.5.5 0 0 1 0-1h.468a1.999 1.999 0 0 0-2.383.336a.5.5 0 0 1-.706-.707A3.001 3.001 0 0 1 15 11.152V11a.5.5 0 0 1 .5-.5zm-.876 5.532A2.999 2.999 0 0 1 12 15.848V16a.5.5 0 0 1-1 0v-1.5a.5.5 0 0 1 .5-.5H13a.5.5 0 0 1 0 1h-.468a1.999 1.999 0 0 0 2.383-.336a.5.5 0 0 1 .706.707c-.285.285-.624.51-.997.66z',
'CloudSyncSolid': 'M10 2c2.817 0 4.415 1.923 4.647 4.246h.07C16.532 6.246 18 7.758 18 9.623c0 .095-.004.19-.011.283A5.75 5.75 0 0 0 7.772 13h-2.49C3.469 13 2 11.488 2 9.623c0-1.865 1.47-3.377 3.282-3.377h.071C5.587 3.908 7.183 2 10 2zM9 13.5a4.5 4.5 0 1 0 9 0a4.5 4.5 0 0 0-9 0zm6.5-3a.5.5 0 0 1 .5.5v1.5a.5.5 0 0 1-.5.5H14a.5.5 0 0 1 0-1h.468a1.999 1.999 0 0 0-2.383.336a.5.5 0 0 1-.706-.707A3.001 3.001 0 0 1 15 11.152V11a.5.5 0 0 1 .5-.5zm-.876 5.532A2.999 2.999 0 0 1 12 15.848V16a.5.5 0 0 1-1 0v-1.5a.5.5 0 0 1 .5-.5H13a.5.5 0 0 1 0 1h-.468a1.999 1.999 0 0 0 2.383-.336a.5.5 0 0 1 .706.707c-.285.285-.624.51-.997.66z',
'Copy': 'M8 2a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8zM7 4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V4zM4 6a2 2 0 0 1 1-1.732V14.5A2.5 2.5 0 0 0 7.5 17h6.232A2 2 0 0 1 12 18H7.5A3.5 3.5 0 0 1 4 14.5V6z',
'CopySolid': 'M6 4a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V4zM4 6a2 2 0 0 1 1-1.732V14.5A2.5 2.5 0 0 0 7.5 17h6.232A2 2 0 0 1 12 18H7.5A3.5 3.5 0 0 1 4 14.5V6z',
'Rename': 'M8.5 2a.5.5 0 0 0 0 1h1v14h-1a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1h-1V3h1a.5.5 0 0 0 0-1h-3zm-4 2h4v1h-4A1.5 1.5 0 0 0 3 6.5v7A1.5 1.5 0 0 0 4.5 15h4v1h-4A2.5 2.5 0 0 1 2 13.5v-7A2.5 2.5 0 0 1 4.5 4zm11 11h-4v1h4a2.5 2.5 0 0 0 2.5-2.5v-7A2.5 2.5 0 0 0 15.5 4h-4v1h4A1.5 1.5 0 0 1 17 6.5v7a1.5 1.5 0 0 1-1.5 1.5z',
'RenameSolid': 'M8.5 2a.5.5 0 0 0 0 1h1v14h-1a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1h-1V3h1a.5.5 0 0 0 0-1h-3zm-4 2h4v12h-4A2.5 2.5 0 0 1 2 13.5v-7A2.5 2.5 0 0 1 4.5 4zm11 12h-4V4h4A2.5 2.5 0 0 1 18 6.5v7a2.5 2.5 0 0 1-2.5 2.5z',
'Desktop': 'M4 2a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h3v2H5.5a.5.5 0 0 0 0 1h9a.5.5 0 0 0 0-1H13v-2h3a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H4zm8 13v2H8v-2h4zM3 4a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4z',
'DesktopSolid': 'M3.5 2A1.5 1.5 0 0 0 2 3.5v10A1.5 1.5 0 0 0 3.5 15H7v2H5.5a.5.5 0 0 0 0 1h9a.5.5 0 0 0 0-1H13v-2h3.5a1.5 1.5 0 0 0 1.5-1.5v-10A1.5 1.5 0 0 0 16.5 2h-13zM12 15v2H8v-2h4z',
'Dismiss': 'M4.089 4.216l.057-.07a.5.5 0 0 1 .638-.057l.07.057L10 9.293l5.146-5.147a.5.5 0 0 1 .638-.057l.07.057a.5.5 0 0 1 .057.638l-.057.07L10.707 10l5.147 5.146a.5.5 0 0 1 .057.638l-.057.07a.5.5 0 0 1-.638.057l-.07-.057L10 10.707l-5.146 5.147a.5.5 0 0 1-.638.057l-.07-.057a.5.5 0 0 1-.057-.638l.057-.07L9.293 10L4.146 4.854a.5.5 0 0 1-.057-.638l.057-.07l-.057.07z',
'DismissSolid': 'M3.897 4.054l.073-.084a.75.75 0 0 1 .976-.073l.084.073L10 8.939l4.97-4.97a.75.75 0 0 1 .976-.072l.084.073a.75.75 0 0 1 .073.976l-.073.084L11.061 10l4.97 4.97a.75.75 0 0 1 .072.976l-.073.084a.75.75 0 0 1-.976.073l-.084-.073L10 11.061l-4.97 4.97a.75.75 0 0 1-.976.072l-.084-.073a.75.75 0 0 1-.073-.976l.073-.084L8.939 10l-4.97-4.97a.75.75 0 0 1-.072-.976l.073-.084l-.073.084z',
'DismissCircle': 'M10 2a8 8 0 1 1 0 16a8 8 0 0 1 0-16zm0 1a7 7 0 1 0 0 14a7 7 0 0 0 0-14zM7.81 7.114l.069.058L10 9.292l2.121-2.12a.5.5 0 0 1 .638-.058l.07.058a.5.5 0 0 1 .057.637l-.058.07L10.708 10l2.12 2.121a.5.5 0 0 1 .058.638l-.058.07a.5.5 0 0 1-.637.057l-.07-.058L10 10.708l-2.121 2.12a.5.5 0 0 1-.638.058l-.07-.058a.5.5 0 0 1-.057-.637l.058-.07L9.292 10l-2.12-2.121a.5.5 0 0 1-.058-.638l.058-.07a.5.5 0 0 1 .637-.057z',
'DismissCircleSolid': 'M10 2a8 8 0 1 1 0 16a8 8 0 0 1 0-16zM7.81 7.114a.5.5 0 0 0-.638.058l-.058.069a.5.5 0 0 0 .058.638L9.292 10l-2.12 2.121l-.058.07a.5.5 0 0 0 .058.637l.069.058a.5.5 0 0 0 .638-.058L10 10.708l2.121 2.12l.07.058a.5.5 0 0 0 .637-.058l.058-.069a.5.5 0 0 0-.058-.638L10.708 10l2.12-2.121l.058-.07a.5.5 0 0 0-.058-.637l-.069-.058a.5.5 0 0 0-.638.058L10 9.292l-2.121-2.12l-.07-.058z',
'DismissSquare': 'M7.146 7.146a.5.5 0 0 1 .708 0L10 9.293l2.146-2.147a.5.5 0 0 1 .708.708L10.707 10l2.147 2.146a.5.5 0 0 1-.708.708L10 10.707l-2.146 2.147a.5.5 0 0 1-.708-.708L9.293 10L7.146 7.854a.5.5 0 0 1 0-.708zM3 6a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6zm3-2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H6z',
'DismissSquareSolid': 'M3 6a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6zm4.146 1.146a.5.5 0 0 0 0 .708L9.293 10l-2.147 2.146a.5.5 0 0 0 .708.708L10 10.707l2.146 2.147a.5.5 0 0 0 .708-.708L10.707 10l2.147-2.146a.5.5 0 0 0-.708-.708L10 9.293L7.854 7.146a.5.5 0 0 0-.708 0z',
'Edit': 'M13.245 2.817a2.783 2.783 0 0 1 4.066 3.796l-.13.14l-9.606 9.606a2.001 2.001 0 0 1-.723.462l-.165.053l-4.055 1.106a.5.5 0 0 1-.63-.535l.016-.08l1.106-4.054c.076-.28.212-.54.398-.76l.117-.128l9.606-9.606zm-.86 2.275L4.346 13.13a1 1 0 0 0-.215.321l-.042.123l-.877 3.21l3.212-.875a1 1 0 0 0 .239-.1l.107-.072l.098-.085l8.038-8.04l-2.521-2.52zm4.089-1.568a1.783 1.783 0 0 0-2.402-.11l-.12.11l-.86.86l2.52 2.522l.862-.86a1.783 1.783 0 0 0 .11-2.402l-.11-.12z',
'EditSolid': 'M11.677 4.384l3.936 3.936l-8.038 8.039a2.001 2.001 0 0 1-.723.462l-.165.053l-4.055 1.106a.5.5 0 0 1-.63-.535l.016-.08l1.106-4.054c.076-.28.212-.54.398-.76l.117-.128l8.038-8.04zm1.568-1.567a2.783 2.783 0 0 1 4.066 3.796l-.13.14l-.861.86l-3.936-3.936l.861-.86z',
'EditOff': 'M2.854 2.146a.5.5 0 1 0-.707.708l5.53 5.53l-4.038 4.04l-.117.127a2 2 0 0 0-.398.76l-1.106 4.055l-.015.08a.5.5 0 0 0 .63.534l4.054-1.106l.165-.053a2 2 0 0 0 .723-.462l4.038-4.039l5.534 5.534a.5.5 0 0 0 .707-.708l-15-15zm8.052 9.467l-4.038 4.039l-.098.086l-.107.072a1 1 0 0 1-.24.1l-3.21.875l.876-3.21l.042-.124a1 1 0 0 1 .215-.32l4.039-4.039l2.521 2.521zm4-4L12.32 10.2l.708.707l4.153-4.153l.13-.14a2.783 2.783 0 0 0-4.066-3.796L9.092 6.971l.707.707l2.586-2.586l2.52 2.521zm1.568-4.089l.11.12c.584.7.547 1.744-.11 2.402l-.861.86l-2.521-2.52l.86-.862l.12-.11a1.783 1.783 0 0 1 2.402.11z',
'EditOffSolid': 'M2.854 2.146a.5.5 0 1 0-.707.708l5.53 5.53l-4.038 4.04l-.117.127a2 2 0 0 0-.398.76l-1.106 4.055l-.015.08a.5.5 0 0 0 .63.534l4.054-1.106l.165-.053a2 2 0 0 0 .723-.462l4.038-4.039l5.534 5.534a.5.5 0 0 0 .707-.708l-15-15zM15.613 8.32l-2.586 2.586L9.092 6.97l2.585-2.586l3.936 3.936zm-2.368-5.503a2.783 2.783 0 0 1 4.066 3.797l-.13.14l-.861.86l-3.936-3.937l.861-.86z',
'ErrorCircle': 'M10 2a8 8 0 1 1 0 16a8 8 0 0 1 0-16zm0 1a7 7 0 1 0 0 14a7 7 0 0 0 0-14zm0 9.5a.75.75 0 1 1 0 1.5a.75.75 0 0 1 0-1.5zM10 6a.5.5 0 0 1 .492.41l.008.09V11a.5.5 0 0 1-.992.09L9.5 11V6.5A.5.5 0 0 1 10 6z',
'ErrorCircleSolid': 'M10 2a8 8 0 1 1 0 16a8 8 0 0 1 0-16zm0 10.5a.75.75 0 1 0 0 1.5a.75.75 0 0 0 0-1.5zM10 6a.5.5 0 0 0-.492.41L9.5 6.5V11l.008.09a.5.5 0 0 0 .984 0L10.5 11V6.5l-.008-.09A.5.5 0 0 0 10 6z',
'Info': 'M10.492 8.91A.5.5 0 0 0 9.5 9v4.502l.008.09a.5.5 0 0 0 .992-.09V9l-.008-.09zm.307-2.16a.75.75 0 1 0-1.5 0a.75.75 0 0 0 1.5 0zM18 10a8 8 0 1 0-16 0a8 8 0 0 0 16 0zM3 10a7 7 0 1 1 14 0a7 7 0 0 1-14 0z',
'InfoSolid': 'M18 10a8 8 0 1 0-16 0a8 8 0 0 0 16 0zM9.508 8.91a.5.5 0 0 1 .984 0L10.5 9v4.502l-.008.09a.5.5 0 0 1-.984 0l-.008-.09V9l.008-.09zM9.25 6.75a.75.75 0 1 1 1.5 0a.75.75 0 0 1-1.5 0z',
'Link': 'M8 6a.5.5 0 0 1 .09.992L8 7H6a3 3 0 0 0-.197 5.994L6 13h2a.5.5 0 0 1 .09.992L8 14H6a4 4 0 0 1-.22-7.994L6 6h2zm6 0a4 4 0 0 1 .22 7.994L14 14h-2a.5.5 0 0 1-.09-.992L12 13h2a3 3 0 0 0 .197-5.994L14 7h-2a.5.5 0 0 1-.09-.992L12 6h2zM6 9.5h8a.5.5 0 0 1 .09.992L14 10.5H6a.5.5 0 0 1-.09-.992L6 9.5h8h-8z',
'LinkSolid': 'M14 6a4 4 0 0 1 .2 7.995L14 14h-2a.75.75 0 0 1-.102-1.493L12 12.5h2a2.5 2.5 0 0 0 .164-4.995L14 7.5h-2a.75.75 0 0 1-.102-1.493L12 6h2zM8 6a.75.75 0 0 1 .102 1.493L8 7.5H6a2.5 2.5 0 0 0-.164 4.995L6 12.5h2a.75.75 0 0 1 .102 1.493L8 14H6a4 4 0 0 1-.2-7.995L6 6h2zM6.25 9.25h7.5a.75.75 0 0 1 .102 1.493l-.102.007h-7.5a.75.75 0 0 1-.102-1.493l.102-.007h7.5h-7.5z',
'MailInbox': 'M6 3a3 3 0 0 0-3 3v8a3 3 0 0 0 3 3h8a3 3 0 0 0 3-3V6a3 3 0 0 0-3-3H6zm10 7h-3.5a.5.5 0 0 0-.5.5v.011l-.004.06a2.57 2.57 0 0 1-.256.955a1.694 1.694 0 0 1-.572.667c-.26.174-.63.307-1.168.307c-.538 0-.907-.133-1.168-.307a1.694 1.694 0 0 1-.572-.667A2.572 2.572 0 0 1 8 10.511V10.5A.5.5 0 0 0 7.5 10H4V6a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v4zM4 11h3.05c.047.264.137.616.315.974c.186.371.473.758.912 1.051c.443.295 1.01.475 1.723.475c.713 0 1.28-.18 1.723-.475c.44-.293.726-.68.912-1.051c.178-.358.268-.71.315-.974H16v3a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-3z',
'MailInboxSolid': 'M3 6a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6zm1 4h3.5a.5.5 0 0 1 .5.5v.011l.004.06a2.572 2.572 0 0 0 .256.955c.126.254.308.492.572.667c.26.174.63.307 1.168.307c.537 0 .907-.133 1.168-.307c.264-.175.446-.413.572-.667a2.57 2.57 0 0 0 .26-1.015V10.498a.5.5 0 0 1 .5-.498H16V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v4zm4 .5v-.002z',
'Navigation': 'M2 4.5a.5.5 0 0 1 .5-.5h15a.5.5 0 0 1 0 1h-15a.5.5 0 0 1-.5-.5zm0 5a.5.5 0 0 1 .5-.5h15a.5.5 0 0 1 0 1h-15a.5.5 0 0 1-.5-.5zm.5 4.5a.5.5 0 0 0 0 1h15a.5.5 0 0 0 0-1h-15z',
'NavigationSolid': 'M2 4.75A.75.75 0 0 1 2.75 4h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 4.75zm0 5A.75.75 0 0 1 2.75 9h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 9.75zM2.75 14a.75.75 0 0 0 0 1.5h14.5a.75.75 0 0 0 0-1.5H2.75z',
'Open': 'M6 4a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-2.5a.5.5 0 0 1 1 0V14a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3h2.5a.5.5 0 0 1 0 1H6zm5-.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 .5.5v5a.5.5 0 0 1-1 0V4.707l-4.146 4.147a.5.5 0 0 1-.708-.708L15.293 4H11.5a.5.5 0 0 1-.5-.5z',
'OpenSolid': 'M6.25 4.5A1.75 1.75 0 0 0 4.5 6.25v7.5c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 0 0 1.75-1.75v-2a.75.75 0 0 1 1.5 0v2A3.25 3.25 0 0 1 13.75 17h-7.5A3.25 3.25 0 0 1 3 13.75v-7.5A3.25 3.25 0 0 1 6.25 3h2a.75.75 0 0 1 0 1.5h-2zm4.25-.75a.75.75 0 0 1 .75-.75h5a.75.75 0 0 1 .75.75v5a.75.75 0 0 1-1.5 0V5.56l-3.72 3.72a.75.75 0 1 1-1.06-1.06l3.72-3.72h-3.19a.75.75 0 0 1-.75-.75z',
'Question': 'M10 3C7.794 3 6 4.794 6 7a.5.5 0 0 0 1 0c0-1.654 1.346-3 3-3s3 1.346 3 3c0 1.249-.692 1.863-1.575 2.62l-.032.027C10.534 10.384 9.5 11.27 9.5 13v.5a.5.5 0 0 0 1 0V13c0-1.249.692-1.863 1.575-2.62l.032-.027C12.966 9.615 14 8.731 14 7c0-2.206-1.794-4-4-4zm0 14a.75.75 0 1 0 0-1.5a.75.75 0 0 0 0 1.5z',
'QuestionSolid': 'M10 3C7.796 3 6 4.796 6 7a.75.75 0 0 0 1.5 0c0-1.376 1.124-2.5 2.5-2.5s2.5 1.124 2.5 2.5c0 .597-.156.975-.368 1.27c-.232.325-.547.58-.969.92l-.01.008c-.4.323-.893.724-1.27 1.288c-.391.588-.633 1.313-.633 2.264v.5a.75.75 0 0 0 1.5 0v-.5c0-.674.164-1.105.382-1.432c.233-.349.552-.62.964-.953l.068-.055c.374-.302.834-.672 1.188-1.167C13.75 8.588 14 7.903 14 7c0-2.204-1.796-4-4-4zm0 14a1 1 0 1 0 0-2a1 1 0 0 0 0 2z',
'QuestionCircle': 'M10 2a8 8 0 1 1 0 16a8 8 0 0 1 0-16zm0 1a7 7 0 1 0 0 14a7 7 0 0 0 0-14zm0 10.5a.75.75 0 1 1 0 1.5a.75.75 0 0 1 0-1.5zm0-8a2.5 2.5 0 0 1 1.651 4.377l-.154.125l-.219.163l-.087.072a1.968 1.968 0 0 0-.156.149c-.339.36-.535.856-.535 1.614a.5.5 0 0 1-1 0c0-1.012.293-1.752.805-2.298a3.11 3.11 0 0 1 .356-.323l.247-.185l.118-.1A1.5 1.5 0 1 0 8.5 8a.5.5 0 0 1-1 .001A2.5 2.5 0 0 1 10 5.5z',
'QuestionCircleSolid': 'M10 2a8 8 0 1 1 0 16a8 8 0 0 1 0-16zm0 11.5a.75.75 0 1 0 0 1.5a.75.75 0 0 0 0-1.5zm0-8A2.5 2.5 0 0 0 7.5 8a.5.5 0 0 0 1 0a1.5 1.5 0 1 1 2.632.984l-.106.11l-.118.1l-.247.185a3.11 3.11 0 0 0-.356.323C9.793 10.248 9.5 10.988 9.5 12a.5.5 0 0 0 1 0c0-.758.196-1.254.535-1.614l.075-.076l.08-.073l.088-.072l.219-.163l.154-.125A2.5 2.5 0 0 0 10 5.5z',
'Warning': 'M10 7a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0v-4A.5.5 0 0 1 10 7zm0 7.5a.75.75 0 1 0 0-1.5a.75.75 0 0 0 0 1.5zM8.686 2.852a1.5 1.5 0 0 1 2.628 0l6.56 11.925A1.5 1.5 0 0 1 16.558 17H3.44a1.5 1.5 0 0 1-1.314-2.223L8.686 2.852zm1.752.482a.5.5 0 0 0-.876 0L3.003 15.26a.5.5 0 0 0 .438.741H16.56a.5.5 0 0 0 .438-.74L10.438 3.333z',
'WarningSolid': 'M8.686 2.852L2.127 14.777A1.5 1.5 0 0 0 3.441 17H16.56a1.5 1.5 0 0 0 1.314-2.223L11.314 2.852a1.5 1.5 0 0 0-2.628 0zM10 6.75a.75.75 0 0 1 .75.75v4a.75.75 0 0 1-1.5 0v-4a.75.75 0 0 1 .75-.75zm.75 7a.75.75 0 1 1-1.5 0a.75.75 0 0 1 1.5 0z',
'Moon': 'M15.493 13.497a6.981 6.981 0 0 1-11.483.892c2.831-1.087 4.558-2.42 5.593-4.397c1.048-2 1.337-4.16.76-6.909a6.981 6.981 0 0 1 5.13 10.414zM5.457 16.918A7.981 7.981 0 1 0 9.88 2.035a.599.599 0 0 0-.614.74c.688 2.819.434 4.876-.55 6.753c-.934 1.784-2.544 3.031-5.55 4.107a.599.599 0 0 0-.292.903a7.952 7.952 0 0 0 2.582 2.38z',
'MoonSolid': 'M16.36 13.997a7.981 7.981 0 0 1-13.485.541a.599.599 0 0 1 .292-.903c3.006-1.076 4.616-2.323 5.55-4.107c.984-1.877 1.238-3.934.55-6.753a.599.599 0 0 1 .614-.74a7.981 7.981 0 0 1 6.478 11.962z',
'Sun': 'M10 2a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0v-1A.5.5 0 0 1 10 2zm0 12a4 4 0 1 0 0-8a4 4 0 0 0 0 8zm0-1a3 3 0 1 1 0-6a3 3 0 0 1 0 6zm7.5-2.5a.5.5 0 0 0 0-1h-1a.5.5 0 0 0 0 1h1zM10 16a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0v-1a.5.5 0 0 1 .5-.5zm-6.5-5.5a.5.5 0 0 0 0-1H2.463a.5.5 0 0 0 0 1H3.5zm.646-6.354a.5.5 0 0 1 .708 0l1 1a.5.5 0 1 1-.708.708l-1-1a.5.5 0 0 1 0-.708zm.708 11.708a.5.5 0 0 1-.708-.708l1-1a.5.5 0 0 1 .708.708l-1 1zm11-11.708a.5.5 0 0 0-.708 0l-1 1a.5.5 0 0 0 .708.708l1-1a.5.5 0 0 0 0-.708zm-.708 11.708a.5.5 0 0 0 .708-.708l-1-1a.5.5 0 0 0-.708.708l1 1z',
'SunSolid': 'M10 2a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0v-1A.5.5 0 0 1 10 2zm4 8a4 4 0 1 1-8 0a4 4 0 0 1 8 0zm3.5.5a.5.5 0 0 0 0-1h-1a.5.5 0 0 0 0 1h1zM10 16a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0v-1a.5.5 0 0 1 .5-.5zm-6.5-5.5a.5.5 0 0 0 0-1H2.463a.5.5 0 0 0 0 1H3.5zm.646-6.354a.5.5 0 0 1 .708 0l1 1a.5.5 0 1 1-.708.708l-1-1a.5.5 0 0 1 0-.708zm.708 11.708a.5.5 0 0 1-.708-.708l1-1a.5.5 0 0 1 .708.708l-1 1zm11-11.708a.5.5 0 0 0-.708 0l-1 1a.5.5 0 0 0 .708.708l1-1a.5.5 0 0 0 0-.708zm-.708 11.708a.5.5 0 0 0 .708-.708l-1-1a.5.5 0 0 0-.708.708l1 1z',
'TaskList': 'M5.854 4.354a.5.5 0 1 0-.708-.708L3.5 5.293l-.646-.647a.5.5 0 1 0-.708.708l1 1a.5.5 0 0 0 .708 0l2-2zM8.5 5a.5.5 0 0 0 0 1h9a.5.5 0 0 0 0-1h-9zm0 5a.5.5 0 0 0 0 1h9a.5.5 0 0 0 0-1h-9zM8 15.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zM5.854 9.854a.5.5 0 1 0-.708-.708L3.5 10.793l-.646-.647a.5.5 0 0 0-.708.708l1 1a.5.5 0 0 0 .708 0l2-2zm0 4.292a.5.5 0 0 1 0 .708l-2 2a.5.5 0 0 1-.708 0l-1-1a.5.5 0 0 1 .708-.708l.646.647l1.646-1.647a.5.5 0 0 1 .708 0z',
'TaskListSolid': 'M5.854 4.354a.5.5 0 1 0-.708-.708L3.5 5.293l-.646-.647a.5.5 0 1 0-.708.708l1 1a.5.5 0 0 0 .708 0l2-2zM8.75 4.5a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5zm0 5a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5zM8 15.25a.75.75 0 0 1 .75-.75h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1-.75-.75zM5.854 9.854a.5.5 0 1 0-.708-.708L3.5 10.793l-.646-.647a.5.5 0 0 0-.708.708l1 1a.5.5 0 0 0 .708 0l2-2zm0 4.292a.5.5 0 0 1 0 .708l-2 2a.5.5 0 0 1-.708 0l-1-1a.5.5 0 0 1 .708-.708l.646.647l1.646-1.647a.5.5 0 0 1 .708 0z',
'FileBriefcase': 'M6 2a2 2 0 0 0-2 2v5h1V4a1 1 0 0 1 1-1h4v3.5A1.5 1.5 0 0 0 11.5 8H15v8a1 1 0 0 1-1 1h-2v1h2a2 2 0 0 0 2-2V7.414a1.5 1.5 0 0 0-.44-1.06l-3.914-3.915A1.5 1.5 0 0 0 10.586 2H6zm8.793 5H11.5a.5.5 0 0 1-.5-.5V3.207L14.793 7zM4 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 .5.5V12h1a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h1v-1.5zm3 .5H5v1h2v-1zm-4 2a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1H3z',
'FileBriefcaseSolid': 'M10 2v4.5A1.5 1.5 0 0 0 11.5 8H16v8.5a1.5 1.5 0 0 1-1.5 1.5H12v-4.5A2.5 2.5 0 0 0 9.5 11H9v-1a1 1 0 0 0-1-1H4V3.5A1.5 1.5 0 0 1 5.5 2H10zm1 .25V6.5a.5.5 0 0 0 .5.5h4.25L11 2.25zM4 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 .5.5V12h1a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h1v-1.5zm3 .5H5v1h2v-1zm-4 2a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1H3z',
'Sync': 'M11.414 3.635a.5.5 0 0 0 0-.707L9.293.807a.5.5 0 0 0-.707.707l.997.997a7.5 7.5 0 0 0-4.075 13.495a.5.5 0 0 0 .6-.8A6.5 6.5 0 0 1 10.066 3.5c.024 0 .05-.002.073-.005L8.586 5.049a.5.5 0 0 0 .707.707l2.121-2.121zM8.586 16.363a.5.5 0 0 0 0 .707l2.121 2.121a.5.5 0 0 0 .707-.707l-.997-.997a7.5 7.5 0 0 0 4.075-13.495a.5.5 0 1 0-.6.8a6.5 6.5 0 0 1-3.959 11.706a.502.502 0 0 0-.073.005l1.554-1.554a.5.5 0 1 0-.707-.707l-2.121 2.121z',
'SyncSolid': 'M9.885 3.75a6.25 6.25 0 0 0-3.628 11.256a.75.75 0 0 1-.9 1.2a7.75 7.75 0 0 1 3.99-13.93l-.584-.586A.75.75 0 0 1 9.823.63l2.122 2.121a.75.75 0 0 1 0 1.06L9.823 5.934a.75.75 0 0 1-1.06-1.06L9.885 3.75zm.23 12.498a6.25 6.25 0 0 0 3.628-11.256a.75.75 0 0 1 .9-1.2a7.75 7.75 0 0 1-3.99 13.93l.584.585a.75.75 0 1 1-1.06 1.061l-2.122-2.121a.75.75 0 0 1 0-1.06l2.122-2.122a.75.75 0 1 1 1.06 1.06l-1.122 1.123z',
'SyncOff': 'M11.414 3.635a.5.5 0 0 0 0-.707L9.293.807a.5.5 0 0 0-.707.707l.997.997a7.48 7.48 0 0 0-3.72 1.23l.724.724a6.49 6.49 0 0 1 3.48-.966c.024 0 .05-.001.073-.004L8.586 5.049a.5.5 0 0 0 .707.707l2.121-2.121zm-7.06 1.426a7.5 7.5 0 0 0 1.154 10.945a.5.5 0 0 0 .6-.8A6.5 6.5 0 0 1 5.063 5.77l9.165 9.165A6.479 6.479 0 0 1 9.934 16.5a.502.502 0 0 0-.074.004l1.554-1.554a.5.5 0 1 0-.707-.707l-2.121 2.121a.5.5 0 0 0 0 .707l2.121 2.121a.5.5 0 0 0 .707-.707l-.997-.997a7.471 7.471 0 0 0 4.521-1.843l2.208 2.209a.5.5 0 0 0 .708-.708l-15-15a.5.5 0 1 0-.708.708L4.355 5.06zm10.95-.365a7.503 7.503 0 0 1 .954 9.44l-.724-.724a6.503 6.503 0 0 0-1.641-8.62a.5.5 0 1 1 .6-.8c.282.212.553.447.81.704z',
'SyncOffSolid': 'M9.885 3.75a6.236 6.236 0 0 0-3.116.897L5.683 3.56a7.725 7.725 0 0 1 3.665-1.285l-.585-.586A.75.75 0 0 1 9.823.63l2.122 2.121a.75.75 0 0 1 0 1.06L9.823 5.934a.75.75 0 0 1-1.06-1.06L9.885 3.75zM4.178 4.884a7.75 7.75 0 0 0 1.18 11.322a.75.75 0 1 0 .9-1.2a6.25 6.25 0 0 1-1.016-9.059l8.81 8.811a6.225 6.225 0 0 1-3.937 1.49l1.122-1.123a.75.75 0 0 0-1.06-1.06l-2.122 2.121a.75.75 0 0 0 0 1.06l2.122 2.122a.75.75 0 1 0 1.06-1.06l-.585-.586a7.718 7.718 0 0 0 4.463-1.9l2.031 2.03a.5.5 0 0 0 .708-.707l-15-15a.5.5 0 1 0-.708.708l2.032 2.03zm11.174 8.346l1.086 1.086a7.753 7.753 0 0 0-1.796-10.524a.75.75 0 0 0-.9 1.2a6.253 6.253 0 0 1 1.61 8.237z',
'SyncCircle': 'M10 3a7 7 0 1 1 0 14a7 7 0 0 1 0-14zm8 7a8 8 0 1 0-16 0a8 8 0 0 0 16 0zm-8-2.5A2.5 2.5 0 0 1 12.292 9H11.5a.5.5 0 1 0 0 1h2a.5.5 0 0 0 .5-.5v-2a.5.5 0 0 0-1 0v.696a3.498 3.498 0 0 0-5.609-.53a.5.5 0 0 0 .746.667A2.493 2.493 0 0 1 10 7.5zm-3 4.304v.696a.5.5 0 0 1-1 0v-2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-.792a2.5 2.5 0 0 0 4.156.666a.5.5 0 0 1 .745.668A3.498 3.498 0 0 1 7 11.804z',
'SyncCircleSolid': 'M10 18a8 8 0 1 1 0-16a8 8 0 0 1 0 16zm3.5-8a.5.5 0 0 0 .5-.5v-2a.5.5 0 0 0-1 0v.696a3.498 3.498 0 0 0-5.609-.53a.5.5 0 1 0 .746.667A2.5 2.5 0 0 1 12.293 9H11.5a.5.5 0 1 0 0 1h2zm-7.5.5v2a.5.5 0 0 0 1 0v-.696a3.498 3.498 0 0 0 5.609.53a.5.5 0 0 0-.745-.668A2.5 2.5 0 0 1 7.708 11H8.5a.5.5 0 0 0 0-1h-2a.5.5 0 0 0-.5.5z',
}
export type IconName =
| 'Folder'
// | 'FolderSolid'
| 'FolderAdd'
// | 'FolderAddSolid'
// | 'FolderArrowRight'
// | 'FolderArrowRightSolid'
// | 'FolderArrowUp'
// | 'FolderArrowUpSolid'
| 'FolderOpen'
// | 'FolderOpenSolid'
// | 'FolderOpenVertical'
// | 'FolderOpenVerticalSolid'
// | 'FolderProhibited'
// | 'FolderProhibitedSolid'
// | 'FolderSwap'
// | 'FolderSwapSolid'
// | 'FolderSync'
// | 'FolderSyncSolid'
// | 'FolderZip'
// | 'FolderZipSolid'
// | 'Checkmark'
// | 'CheckmarkSolid'
// | 'CheckmarkCircle'
| 'CheckmarkCircleSolid'
// | 'CheckmarkStarburst'
// | 'CheckmarkStarburstSolid'
// | 'ArrowSyncCheckmark'
// | 'ArrowSyncCheckmarkSolid'
| 'Image'
// | 'ImageSolid'
// | 'DrawImage'
// | 'DrawImageSolid'
// | 'ImageCopy'
// | 'ImageCopySolid'
// | 'ImageEdit'
// | 'ImageEditSolid'
// | 'ImageMultiple'
// | 'ImageMultipleSolid'
// | 'ImageOff'
// | 'ImageOffSolid'
// | 'ImageProhibited'
// | 'ImageProhibitedSolid'
// | 'ImageSearch'
// | 'ImageSearchSolid'
| 'Video'
// | 'VideoSolid'
// | 'VideoAdd'
// | 'VideoAddSolid'
// | 'VideoOff'
// | 'VideoOffSolid'
// | 'VideoProhibited'
// | 'VideoProhibitedSolid'
// | 'VideoSync'
// | 'VideoSyncSolid'
// | 'File'
// | 'FileSolid'
| 'FileAdd'
// | 'FileAddSolid'
// | 'FileArrowDown'
// | 'FileArrowDownSolid'
// | 'FileArrowUp'
// | 'FileArrowUpSolid'
// | 'FileBulletList'
// | 'FileBulletListSolid'
// | 'FileBulletListMultiple'
// | 'FileBulletListMultipleSolid'
// | 'FileBulletListOff'
// | 'FileBulletListOffSolid'
// | 'FileCatchUp'
// | 'FileCatchUpSolid'
// | 'FileCheckmark'
// | 'FileCheckmarkSolid'
// | 'FileCopy'
// | 'FileCopySolid'
// | 'FileDismiss'
// | 'FileDismissSolid'
// | 'FileEdit'
// | 'FileEditSolid'
| 'FileError'
// | 'FileErrorSolid'
// | 'FileLink'
// | 'FileLinkSolid'
// | 'FileLock'
// | 'FileLockSolid'
| 'FileMultiple'
// | 'FileMultipleSolid'
// | 'FilePdf'
// | 'FilePdfSolid'
| 'FileProhibited'
// | 'FileProhibitedSolid'
// | 'FileQuestionMark'
// | 'FileQuestionMarkSolid'
// | 'FileSearch'
// | 'FileSearchSolid'
| 'FileSync'
// | 'FileSyncSolid'
// | 'FileText'
// | 'FileTextSolid'
// | 'FileJs'
// | 'FileJsSolid'
// | 'FileCss'
// | 'FileCssSolid'
| 'FileBriefcase'
| 'FileBriefcaseSolid'
| 'ChevronDoubleRight'
// | 'ChevronDoubleRightSolid'
| 'ChevronDoubleLeft'
// | 'ChevronDoubleLeftSolid'
| 'MoreHorizontal'
// | 'MoreHorizontalSolid'
// | 'MoreVertical'
// | 'MoreVerticalSolid'
| 'Delete'
// | 'DeleteSolid'
// | 'DeleteDismiss'
// | 'DeleteDismissSolid'
// | 'DeleteOff'
// | 'DeleteOffSolid'
// | 'DeleteArrowBack'
// | 'DeleteArrowBackSolid'
// | 'DeleteLines'
// | 'DeleteLinesSolid'
| 'Eye'
// | 'EyeSolid'
// | 'EyeOff'
// | 'EyeOffSolid'
// | 'EyeTracking'
// | 'EyeTrackingSolid'
// | 'EyeTrackingOff'
// | 'EyeTrackingOffSolid'
| 'Share'
// | 'ShareSolid'
// | 'Alert'
// | 'AlertSolid'
// | 'AlertOff'
// | 'AlertOffSolid'
// | 'AlertOn'
// | 'AlertOnSolid'
// | 'AlertSnooze'
// | 'AlertSnoozeOff'
// | 'AlertUrgent'
// | 'AlertUrgentSolid'
// | 'ArrowClockwise'
| 'ArrowClockwiseSolid'
// | 'ArrowClockwiseDashes'
// | 'ArrowClockwiseDashesSolid'
// | 'ArrowHookDownLeft'
// | 'ArrowHookDownLeftSolid'
// | 'ArrowHookDownRight'
// | 'ArrowHookDownRightSolid'
// | 'Bookmark'
// | 'BookmarkSolid'
// | 'BookmarkAdd'
// | 'BookmarkAddSolid'
// | 'BookmarkMultiple'
// | 'BookmarkMultipleSolid'
// | 'BookmarkSearch'
// | 'BookmarkSearchSearch'
// | 'Clipboard'
// | 'ClipboardSolid'
// | 'ClipboardCheckmark'
// | 'ClipboardCheckmarkSolid'
// | 'ClipboardError'
// | 'ClipboardErrorSolid'
// | 'ClipboardText'
// | 'ClipboardTextSolid'
// | 'Clock'
// | 'ClockFill'
// | 'ClockAlarm'
// | 'ClockAlarmSolid'
// | 'Cloud'
// | 'CloudSolid'
// | 'CloudCheckmark'
// | 'CloudCheckmarkSolid'
// | 'CloudAdd'
// | 'CloudAddSolid'
// | 'CloudDismiss'
// | 'CloudDismissSolid'
// | 'CloudEdit'
// | 'CloudEditSolid'
// | 'CloudOff'
// | 'CloudOffSolid'
// | 'CloudSync'
// | 'CloudSyncSolid'
| 'Copy'
// | 'CopySolid'
| 'Rename'
// | 'RenameSolid'
// | 'Desktop'
| 'DesktopSolid'
// | 'Dismiss'
| 'DismissSolid'
// | 'DismissCircle'
| 'DismissCircleSolid'
// | 'DismissSquare'
// | 'DismissSquareSolid'
// | 'Edit'
// | 'EditSolid'
// | 'EditOff'
// | 'EditOffSolid'
// | 'ErrorCircle'
| 'ErrorCircleSolid'
// | 'Info'
| 'InfoSolid'
// | 'Link'
// | 'LinkSolid'
| 'MailInbox'
// | 'MailInboxSolid'
// | 'Navigation'
// | 'NavigationSolid'
// | 'Open'
// | 'OpenSolid'
// | 'Question'
// | 'QuestionSolid'
// | 'QuestionCircle'
// | 'QuestionCircleSolid'
// | 'Warning'
| 'WarningSolid'
// | 'Moon'
| 'MoonSolid'
// | 'Sun'
| 'SunSolid'
// | 'TaskList'
| 'TaskListSolid'
| 'Sync'
| 'SyncSolid'
| 'SyncOff'
| 'SyncOffSolid'
| 'SyncCircle'
| 'SyncCircleSolid'
export function installIcons(mount: ParentNode): void {
if (mount.querySelector("#wfs-icon-symbols")) {
return;
}
const symbols = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
${Object.entries(paths).map(
([key, path]) => `
<symbol id="${key}" viewBox="0 0 20 20">
<path d="${path}" fill="currentColor" />
</symbol>`,
).join('')}
</svg>`;
const temp = document.createElement("div");
temp.innerHTML = symbols;
const svgElm = temp.firstElementChild! as SVGElement;
temp.innerHTML = "";
svgElm.style.cssText = "display:none";
svgElm.id = "wfs-icon-symbols";
mount.insertBefore(svgElm, mount.firstChild);
}

@ -0,0 +1,35 @@
<script lang="ts">
import {defineComponent, h, Prop, reactive} from "vue";
import {useImage, UseImageOptions} from "@vueuse/core";
export default defineComponent({
props: {
src: {
type: String,
required: true,
} as Prop<UseImageOptions['src']>,
srcset: String as Prop<UseImageOptions['srcset']>,
sizes: String as Prop<UseImageOptions['sizes']>,
alt: String as Prop<UseImageOptions['alt']>,
class: String as Prop<UseImageOptions['class']>,
loading: String as Prop<UseImageOptions['loading']>,
crossorigin: String as Prop<UseImageOptions['crossorigin']>,
referrerPolicy: String as Prop<UseImageOptions['referrerPolicy']>
},
setup(props, {slots}) {
const data = reactive(useImage(props as UseImageOptions))
return () => {
if (data.isLoading && slots.loading) {
return slots.loading(data)
} else if (data.error && slots.error) {
return slots.error(data.error)
} else if (slots.default) {
return slots.default(data)
} else {
return h('img', props)
}
}
},
})
</script>

@ -0,0 +1,24 @@
<script lang="ts" setup>
defineProps<{ text?: string }>()
const bars = Array(12).fill(0).map((_, i) => i)
</script>
<template>
<div class="flex-center">
<div class="
flex items-center py-2 px-3 bg-gray-300/80 backdrop-blur-md rounded-lg pointer-events-none
dark:bg-gray-700/80 dark:text-white ring-1 ring-white/10
">
<div class="size-4 me-2 relative spinner">
<div
v-for="bar in bars"
:key="bar"
class="loading-bar bg-black dark:bg-white"
/>
</div>
<span class="select-none text-sm">
{{ text ?? '正在加载...' }}
</span>
</div>
</div>
</template>

@ -0,0 +1,24 @@
<script lang="ts" setup>
defineProps<{
value?: number
disabled?: boolean;
label?: string;
}>()
</script>
<template>
<div>
<div class="relative h-0.5 w-full bg-gray-300">
<div
v-if="value != null"
:style="{ width: `${Math.min(Math.round(value! * 100), 100)}%` }"
class="h-full overflow-hidden bg-indigo-500"
>
<div
v-if="value != null && value < 1 && !disabled"
class="animate-marquee h-full w-full bg-white opacity-50"
/>
</div>
</div>
</div>
</template>

@ -0,0 +1,50 @@
<script lang="ts" setup>
import {ref, watch} from 'vue'
const props = defineProps<{
text: string | number
tag?: string
highlight?: boolean
}>()
const emit = defineEmits<{
(ev: 'dblclick', event: Event): void
}>()
const el = ref<HTMLElement | null>(null)
const select = (node: Node) => {
const range = new Range()
range.selectNode(node)
const s = document.getSelection()!
s.empty()
s.addRange(range)
}
const dblclick = (evt: Event) => {
select(evt.target as Node)
emit('dblclick', evt)
}
watch(() => props.highlight, value => {
if (value && el.value) {
select(el.value)
} else if (!value && el.value) {
const range = new Range()
range.selectNode(el.value)
const s = document.getSelection()!
s.removeRange(range)
}
})
</script>
<template>
<component
:is="tag ?? 'span'"
ref="el"
class="selection:bg-indigo-500 break-all selection:text-white"
@dblclick="dblclick"
>
{{ text }}
</component>
</template>

@ -0,0 +1,9 @@
import {getCurrentInstance} from 'vue'
export function getRootBarrier(): Node | undefined {
let el = getCurrentInstance()!.vnode.el as Node
while (el.nodeType === Node.TEXT_NODE || el.nodeType === Node.COMMENT_NODE) {
el = el.parentNode!
}
return (el as Element).closest('[data-ui-barrier]') as Node
}

@ -0,0 +1,70 @@
import BackendWorker from "../backend?sharedworker&inline";
import {createReplier, emit, isRecvData, isSendData, uniqueId} from "../shared";
type Resolver = (value: any) => void;
type Rejecter = (value: any) => void;
const resolvers: Map<number, [Resolver, Rejecter]> = new Map();
const worker = new BackendWorker();
// 接收来自后端的消息
worker.port.onmessage = (evt) => {
if (isSendData(evt.data)) {
const reply = createReplier(worker.port, evt.data);
const {scope, action, data} = evt.data;
// console.log(scope, action, data)
emit(`${scope}:${action}`, data, reply);
} else if (!isRecvData(evt.data)) {
console.warn("[wfs] unknown message event:", evt);
} else if (resolvers.has(evt.data.id)) {
const {id, error, data} = evt.data;
const [resolve, reject] = resolvers.get(id)!;
if (error != null) {
reject(error);
} else {
resolve(data);
}
}
};
// 监听页面卸载,通知后端删除关联
window.addEventListener("beforeunload", () => {
void dispatch("app", "close");
});
window.addEventListener("online", () => {
return dispatch("net", "status", "on");
});
window.addEventListener("offline", () => {
return dispatch("net", "status", "off");
});
export function dispatch(scope: "cfg", action: "init", config: ApiConfig): Promise<void>;
export function dispatch(scope: "app", action: "close"): Promise<void>;
export function dispatch(scope: "net", action: "status", data: "on" | "off"): Promise<void>;
export function dispatch(scope: "dir", action: "all"): Promise<Array<CloudDirectory>>;
export function dispatch(scope: "dir", action: "create", data: NewDirEventData): Promise<CloudDirectory>;
export function dispatch(scope: "dir", action: "delete", dir: number): Promise<boolean>;
export function dispatch(scope: "dir", action: "rename", data: RenameDirEventData): Promise<CloudDirectory>;
export function dispatch(scope: "file", action: "rename", data: RenameFileEventData): Promise<CloudFile>;
export function dispatch(scope: "file", action: "delete", files: number[] | number): Promise<boolean>;
export function dispatch(scope: "file", action: "list", data: FilesEventData): Promise<FilesEventResult>
export function dispatch(scope: "task", action: "cleanup"): Promise<void>;
export function dispatch(scope: "task", action: "cancel", data: TaskEventData): Promise<void>;
export function dispatch(scope: "task", action: "create", data: TaskCreateData): Promise<void>;
export function dispatch(scope: "task", action: "remove", data: TaskEventData): Promise<void>;
export function dispatch(scope: "task", action: "resume", data: TaskEventData): Promise<void>;
export function dispatch<T = any>(scope: string, action: string, data?: any): Promise<T> {
const id = uniqueId();
return new Promise<T>((resolve, reject) => {
resolvers.set(id, [resolve, reject]);
worker.port.postMessage({id, scope, action, data} as SendData);
});
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save