ajout app
2
Voltaserve/ui/.dockerignore
Normal file
@ -0,0 +1,2 @@
|
||||
/dist
|
||||
node_modules
|
10
Voltaserve/ui/.editorconfig
Normal file
@ -0,0 +1,10 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{js,json,yml}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
2
Voltaserve/ui/.env
Normal file
@ -0,0 +1,2 @@
|
||||
API_URL=http://127.0.0.1:5000
|
||||
IDP_URL=http://127.0.0.1:7000
|
4
Voltaserve/ui/.gitattributes
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/.yarn/** linguist-vendored
|
||||
/.yarn/releases/* binary
|
||||
/.yarn/plugins/**/* binary
|
||||
/.pnp.* binary linguist-generated
|
8
Voltaserve/ui/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
node_modules
|
||||
/dist
|
||||
/dist-ssr
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
1
Voltaserve/ui/.prettierignore
Normal file
@ -0,0 +1 @@
|
||||
pnpm-lock.yaml
|
21
Voltaserve/ui/.prettierrc.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"plugins": ["@trivago/prettier-plugin-sort-imports"],
|
||||
"singleQuote": true,
|
||||
"semi": false,
|
||||
"quoteProps": "preserve",
|
||||
"importOrder": [
|
||||
"^react$",
|
||||
"^react-dom/(.*)$",
|
||||
"^react-router-dom$",
|
||||
"^react-redux$",
|
||||
"^@chakra-ui/(.*)$",
|
||||
"^@koupr/ui$",
|
||||
"^swr$",
|
||||
"^formik$",
|
||||
"^yup$",
|
||||
"<THIRD_PARTY_MODULES>",
|
||||
"^@/(.*)$",
|
||||
"^[./](.*)$"
|
||||
],
|
||||
"importOrderSeparation": false
|
||||
}
|
3
Voltaserve/ui/.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
|
||||
}
|
24
Voltaserve/ui/Dockerfile
Normal file
@ -0,0 +1,24 @@
|
||||
FROM registry.suse.com/bci/nodejs:18
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY src ./src
|
||||
COPY public ./public
|
||||
COPY index.html .
|
||||
COPY postcss.config.cjs .
|
||||
COPY tailwind.config.cjs .
|
||||
COPY vite.config.ts .
|
||||
COPY .env .
|
||||
COPY package.json .
|
||||
COPY pnpm-lock.yaml .
|
||||
COPY tsconfig.json .
|
||||
COPY tsconfig.node.json .
|
||||
|
||||
RUN npm install -g corepack
|
||||
RUN corepack enable
|
||||
|
||||
RUN pnpm install
|
||||
|
||||
ENTRYPOINT ["pnpm", "run", "dev"]
|
||||
|
||||
EXPOSE 3000
|
19
Voltaserve/ui/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Voltaserve UI
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```shell
|
||||
bun i
|
||||
```
|
||||
|
||||
Run for development:
|
||||
|
||||
```shell
|
||||
bun run dev
|
||||
```
|
||||
|
||||
Build Docker Image:
|
||||
|
||||
```shell
|
||||
docker build -t voltaserve/ui .
|
||||
```
|
BIN
Voltaserve/ui/bun.lockb
Normal file
23
Voltaserve/ui/eslint.config.cjs
Normal file
@ -0,0 +1,23 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
files: ['./src/**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
'prettier',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'react-hooks/rules-of-hooks': 'off',
|
||||
'react-refresh/only-export-components': 'off',
|
||||
},
|
||||
}
|
13
Voltaserve/ui/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Voltaserve</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
72
Voltaserve/ui/package.json
Normal file
@ -0,0 +1,72 @@
|
||||
{
|
||||
"name": "voltaserve-ui",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
"build": "tsc && vite build",
|
||||
"tsc": "tsc",
|
||||
"preview": "vite preview",
|
||||
"format": "prettier --write .",
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/anatomy": "2.2.2",
|
||||
"@chakra-ui/react": "2.8.2",
|
||||
"@chakra-ui/system": "2.6.2",
|
||||
"@chakra-ui/theme-tools": "2.1.2",
|
||||
"@dnd-kit/core": "6.1.0",
|
||||
"@dnd-kit/modifiers": "7.0.0",
|
||||
"@dnd-kit/sortable": "8.0.0",
|
||||
"@emotion/css": "11.11.2",
|
||||
"@emotion/react": "11.11.4",
|
||||
"@emotion/styled": "11.11.5",
|
||||
"@reduxjs/toolkit": "2.2.3",
|
||||
"chakra-react-select": "4.7.6",
|
||||
"classnames": "2.5.1",
|
||||
"dateformat": "5.0.3",
|
||||
"formik": "2.4.5",
|
||||
"framer-motion": "11.0.28",
|
||||
"hashids": "2.3.0",
|
||||
"javascript-time-ago": "2.5.9",
|
||||
"js-base64": "3.7.7",
|
||||
"polished": "4.3.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-dropzone": "14.2.3",
|
||||
"react-helmet-async": "2.0.4",
|
||||
"react-redux": "9.1.0",
|
||||
"react-router-dom": "6.22.3",
|
||||
"redux": "5.0.1",
|
||||
"swr": "2.2.5",
|
||||
"uuid": "9.0.1",
|
||||
"yup": "1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "4.3.0",
|
||||
"@types/cors": "2.8.17",
|
||||
"@types/dateformat": "5.0.2",
|
||||
"@types/node": "20.12.7",
|
||||
"@types/react": "18.2.77",
|
||||
"@types/react-dom": "18.2.25",
|
||||
"@types/uuid": "9.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "7.6.0",
|
||||
"@typescript-eslint/parser": "7.6.0",
|
||||
"@vitejs/plugin-react": "4.2.1",
|
||||
"autoprefixer": "10.4.19",
|
||||
"eslint": "9.0.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-config-react-app": "7.0.1",
|
||||
"eslint-plugin-react-hooks": "4.6.0",
|
||||
"eslint-plugin-react-refresh": "0.4.6",
|
||||
"postcss": "8.4.38",
|
||||
"prettier": "3.2.5",
|
||||
"tailwindcss": "3.4.3",
|
||||
"typescript": "5.4.5",
|
||||
"vite": "5.2.8",
|
||||
"vite-plugin-svgr": "4.2.0",
|
||||
"vite-tsconfig-paths": "4.3.2"
|
||||
}
|
||||
}
|
6
Voltaserve/ui/postcss.config.cjs
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
12
Voltaserve/ui/public/favicon-dark.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,-1133.42,-1258.86)">
|
||||
<g id="Favicon---Dark" serif:id="Favicon - Dark" transform="matrix(1.39604,0,0,1.4625,-222.346,-2617.74)">
|
||||
<rect x="971.146" y="2650.66" width="183.375" height="175.042" style="fill:none;"/>
|
||||
<g transform="matrix(0.197589,0,0,0.188611,1105.64,2748.03)">
|
||||
<path d="M0,-104.412C-0.158,-107.349 -0.612,-110.275 -1.449,-113.098C-2.362,-116.179 -3.535,-119.184 -4.931,-122.079C-4.985,-122.193 -5.041,-122.308 -5.097,-122.421C-5.228,-122.687 -5.363,-122.951 -5.498,-123.214C-7.231,-126.603 -9.219,-129.534 -12.043,-132.104C-16.201,-135.889 -21.338,-139.039 -26.458,-141.341C-34.216,-144.829 -42.774,-145.322 -51.171,-145.322C-61.669,-145.322 -155.843,-146.222 -166.559,-146.325L-166.554,-146.334C-166.554,-146.334 -120.073,-426.226 -119.996,-426.693C-118.955,-432.957 -118.006,-439.397 -117.982,-445.808C-117.972,-449.063 -118.199,-452.328 -118.796,-455.531C-119.355,-458.539 -119.806,-461.522 -121.02,-464.374C-121.165,-464.715 -121.318,-465.052 -121.481,-465.384C-123.422,-469.333 -126.557,-472.738 -130.13,-475.276C-137.152,-480.265 -148.817,-480.186 -156.233,-476.057C-162.988,-472.295 -168.569,-465.351 -172.734,-458.982C-182.516,-444.022 -192.515,-429.18 -201.853,-413.933C-205.017,-408.766 -208.051,-403.524 -211.146,-398.313C-213.341,-394.616 -215.537,-390.918 -217.732,-387.22L-225.213,-374.622C-227.974,-369.974 -230.734,-365.327 -233.494,-360.679C-236.489,-355.635 -239.484,-350.591 -242.479,-345.547C-245.679,-340.159 -248.878,-334.772 -252.077,-329.385C-255.449,-323.706 -258.821,-318.028 -262.193,-312.35C-265.706,-306.434 -269.219,-300.518 -272.732,-294.602C-276.355,-288.5 -279.979,-282.399 -283.602,-276.298C-287.304,-270.064 -291.006,-263.829 -294.708,-257.596L-305.956,-238.654C-309.721,-232.313 -313.487,-225.972 -317.252,-219.63C-321.003,-213.315 -324.753,-206.999 -328.504,-200.683C-332.208,-194.446 -335.912,-188.208 -339.616,-181.971C-343.243,-175.864 -346.869,-169.757 -350.495,-163.65C-354.013,-157.727 -357.53,-151.804 -361.047,-145.881C-364.424,-140.194 -367.801,-134.507 -371.179,-128.82C-374.384,-123.422 -377.589,-118.024 -380.795,-112.626C-383.798,-107.57 -386.8,-102.513 -389.803,-97.456C-392.572,-92.794 -395.34,-88.132 -398.108,-83.47C-400.612,-79.255 -403.115,-75.04 -405.618,-70.825C-408.959,-65.198 -412.148,-59.527 -415.561,-53.93C-418.976,-48.328 -421.629,-42.412 -424.564,-36.579C-430.102,-25.576 -433.981,-12.468 -433.308,-0C-433.149,2.937 -432.695,5.862 -431.859,8.686C-430.945,11.767 -429.772,14.772 -428.377,17.667C-428.322,17.781 -428.267,17.895 -428.211,18.009C-428.081,18.275 -427.944,18.538 -427.81,18.802C-426.076,22.191 -424.089,25.121 -421.264,27.692C-417.107,31.476 -411.969,34.627 -406.85,36.929C-399.092,40.416 -390.533,40.909 -382.137,40.909C-371.639,40.909 -277.465,41.81 -266.749,41.912L-266.754,41.922C-266.754,41.922 -313.234,321.814 -313.312,322.28C-314.352,328.545 -315.302,334.984 -315.325,341.396C-315.336,344.651 -315.108,347.915 -314.512,351.118C-313.952,354.127 -313.502,357.109 -312.288,359.962C-312.143,360.303 -311.99,360.64 -311.826,360.972C-309.886,364.921 -306.751,368.325 -303.178,370.863C-296.156,375.853 -284.491,375.774 -277.074,371.644C-270.32,367.883 -264.738,360.94 -260.574,354.569C-250.792,339.609 -240.792,324.768 -231.455,309.522C-228.291,304.355 -225.257,299.111 -222.163,293.901C-219.967,290.203 -217.771,286.506 -215.575,282.808C-213.081,278.609 -210.588,274.409 -208.095,270.21C-205.334,265.563 -202.574,260.914 -199.813,256.267C-196.818,251.223 -193.823,246.179 -190.828,241.134C-187.629,235.747 -184.43,230.359 -181.23,224.972C-177.858,219.294 -174.486,213.616 -171.115,207.938C-167.602,202.022 -164.089,196.106 -160.575,190.19C-156.952,184.088 -153.329,177.986 -149.706,171.886C-146.004,165.651 -142.302,159.417 -138.6,153.184C-134.851,146.869 -131.102,140.556 -127.352,134.241C-123.586,127.9 -119.82,121.559 -116.055,115.218C-112.305,108.902 -108.555,102.587 -104.804,96.271C-101.1,90.033 -97.396,83.796 -93.691,77.558C-90.065,71.451 -86.438,65.345 -82.813,59.238C-79.295,53.315 -75.777,47.392 -72.261,41.469C-68.884,35.781 -65.506,30.095 -62.129,24.407C-58.924,19.01 -55.718,13.611 -52.513,8.214C-49.51,3.157 -46.507,-1.899 -43.505,-6.956L-35.199,-20.942C-32.696,-25.157 -30.193,-29.372 -27.689,-33.588C-24.349,-39.215 -21.159,-44.886 -17.747,-50.482C-14.332,-56.084 -11.679,-62.001 -8.743,-67.833C-3.206,-78.837 0.674,-91.944 0,-104.412" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.8 KiB |
22
Voltaserve/ui/public/favicon.svg
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<style>
|
||||
path {
|
||||
fill: black;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path {
|
||||
fill: white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<g transform="matrix(1,0,0,1,-1488.08,-1268.57)">
|
||||
<g id="Favicon" transform="matrix(1.39604,0,0,1.4625,132.314,-2608.02)">
|
||||
<rect x="971.146" y="2650.66" width="183.375" height="175.042" style="fill:none;"/>
|
||||
<g transform="matrix(0.197589,0,0,0.188611,1105.64,2748.03)">
|
||||
<path d="M0,-104.412C-0.158,-107.349 -0.612,-110.275 -1.449,-113.098C-2.362,-116.179 -3.535,-119.184 -4.931,-122.079C-4.985,-122.193 -5.041,-122.308 -5.097,-122.421C-5.228,-122.687 -5.363,-122.951 -5.498,-123.214C-7.231,-126.603 -9.219,-129.534 -12.043,-132.104C-16.201,-135.889 -21.338,-139.039 -26.458,-141.341C-34.216,-144.829 -42.774,-145.322 -51.171,-145.322C-61.669,-145.322 -155.843,-146.222 -166.559,-146.325L-166.554,-146.334C-166.554,-146.334 -120.073,-426.226 -119.996,-426.693C-118.955,-432.957 -118.006,-439.397 -117.982,-445.808C-117.972,-449.063 -118.199,-452.328 -118.796,-455.531C-119.355,-458.539 -119.806,-461.522 -121.02,-464.374C-121.165,-464.715 -121.318,-465.052 -121.481,-465.384C-123.422,-469.333 -126.557,-472.738 -130.13,-475.276C-137.152,-480.265 -148.817,-480.186 -156.233,-476.057C-162.988,-472.295 -168.569,-465.351 -172.734,-458.982C-182.516,-444.022 -192.515,-429.18 -201.853,-413.933C-205.017,-408.766 -208.051,-403.524 -211.146,-398.313C-213.341,-394.616 -215.537,-390.918 -217.732,-387.22L-225.213,-374.622C-227.974,-369.974 -230.734,-365.327 -233.494,-360.679C-236.489,-355.635 -239.484,-350.591 -242.479,-345.547C-245.679,-340.159 -248.878,-334.772 -252.077,-329.385C-255.449,-323.706 -258.821,-318.028 -262.193,-312.35C-265.706,-306.434 -269.219,-300.518 -272.732,-294.602C-276.355,-288.5 -279.979,-282.399 -283.602,-276.298C-287.304,-270.064 -291.006,-263.829 -294.708,-257.596L-305.956,-238.654C-309.721,-232.313 -313.487,-225.972 -317.252,-219.63C-321.003,-213.315 -324.753,-206.999 -328.504,-200.683C-332.208,-194.446 -335.912,-188.208 -339.616,-181.971C-343.243,-175.864 -346.869,-169.757 -350.495,-163.65C-354.013,-157.727 -357.53,-151.804 -361.047,-145.881C-364.424,-140.194 -367.801,-134.507 -371.179,-128.82C-374.384,-123.422 -377.589,-118.024 -380.795,-112.626C-383.798,-107.57 -386.8,-102.513 -389.803,-97.456C-392.572,-92.794 -395.34,-88.132 -398.108,-83.47C-400.612,-79.255 -403.115,-75.04 -405.618,-70.825C-408.959,-65.198 -412.148,-59.527 -415.561,-53.93C-418.976,-48.328 -421.629,-42.412 -424.564,-36.579C-430.102,-25.576 -433.981,-12.468 -433.308,-0C-433.149,2.937 -432.695,5.862 -431.859,8.686C-430.945,11.767 -429.772,14.772 -428.377,17.667C-428.322,17.781 -428.267,17.895 -428.211,18.009C-428.081,18.275 -427.944,18.538 -427.81,18.802C-426.076,22.191 -424.089,25.121 -421.264,27.692C-417.107,31.476 -411.969,34.627 -406.85,36.929C-399.092,40.416 -390.533,40.909 -382.137,40.909C-371.639,40.909 -277.465,41.81 -266.749,41.912L-266.754,41.922C-266.754,41.922 -313.234,321.814 -313.312,322.28C-314.352,328.545 -315.302,334.984 -315.325,341.396C-315.336,344.651 -315.108,347.915 -314.512,351.118C-313.952,354.127 -313.502,357.109 -312.288,359.962C-312.143,360.303 -311.99,360.64 -311.826,360.972C-309.886,364.921 -306.751,368.325 -303.178,370.863C-296.156,375.853 -284.491,375.774 -277.074,371.644C-270.32,367.883 -264.738,360.94 -260.574,354.569C-250.792,339.609 -240.792,324.768 -231.455,309.522C-228.291,304.355 -225.257,299.111 -222.163,293.901C-219.967,290.203 -217.771,286.506 -215.575,282.808C-213.081,278.609 -210.588,274.409 -208.095,270.21C-205.334,265.563 -202.574,260.914 -199.813,256.267C-196.818,251.223 -193.823,246.179 -190.828,241.134C-187.629,235.747 -184.43,230.359 -181.23,224.972C-177.858,219.294 -174.486,213.616 -171.115,207.938C-167.602,202.022 -164.089,196.106 -160.575,190.19C-156.952,184.088 -153.329,177.986 -149.706,171.886C-146.004,165.651 -142.302,159.417 -138.6,153.184C-134.851,146.869 -131.102,140.556 -127.352,134.241C-123.586,127.9 -119.82,121.559 -116.055,115.218C-112.305,108.902 -108.555,102.587 -104.804,96.271C-101.1,90.033 -97.396,83.796 -93.691,77.558C-90.065,71.451 -86.438,65.345 -82.813,59.238C-79.295,53.315 -75.777,47.392 -72.261,41.469C-68.884,35.781 -65.506,30.095 -62.129,24.407C-58.924,19.01 -55.718,13.611 -52.513,8.214C-49.51,3.157 -46.507,-1.899 -43.505,-6.956L-35.199,-20.942C-32.696,-25.157 -30.193,-29.372 -27.689,-33.588C-24.349,-39.215 -21.159,-44.886 -17.747,-50.482C-14.332,-56.084 -11.679,-62.001 -8.743,-67.833C-3.206,-78.837 0.674,-91.944 0,-104.412" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.0 KiB |
450
Voltaserve/ui/src/client/api/file.ts
Normal file
@ -0,0 +1,450 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import useSWR from 'swr'
|
||||
import { apiFetcher } from '@/client/fetcher'
|
||||
import { User } from '@/client/idp/user'
|
||||
import { getConfig } from '@/config/config'
|
||||
import { encodeQuery } from '@/helpers/query'
|
||||
import { getAccessTokenOrRedirect } from '@/infra/token'
|
||||
import { Group } from './group'
|
||||
import { PermissionType } from './permission'
|
||||
import { Download, Snapshot, Thumbnail } from './snapshot'
|
||||
|
||||
export enum FileType {
|
||||
File = 'file',
|
||||
Folder = 'folder',
|
||||
}
|
||||
|
||||
export enum SortBy {
|
||||
Name = 'name',
|
||||
Kind = 'kind',
|
||||
Size = 'size',
|
||||
DateCreated = 'date_created',
|
||||
DateModified = 'date_modified',
|
||||
}
|
||||
|
||||
export enum SortOrder {
|
||||
Asc = 'asc',
|
||||
Desc = 'desc',
|
||||
}
|
||||
|
||||
export enum SnapshotStatus {
|
||||
New = 'new',
|
||||
Processing = 'processing',
|
||||
Ready = 'ready',
|
||||
Error = 'error',
|
||||
}
|
||||
|
||||
export type File = {
|
||||
id: string
|
||||
workspaceId: string
|
||||
name: string
|
||||
type: FileType
|
||||
parentId: string
|
||||
version: number
|
||||
original?: Download
|
||||
preview?: Download
|
||||
thumbnail?: Thumbnail
|
||||
status: SnapshotStatus
|
||||
snapshots: Snapshot[]
|
||||
permission: PermissionType
|
||||
isShared: boolean
|
||||
createTime: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
export type CreateFolderOptions = {
|
||||
workspaceId: string
|
||||
name: string
|
||||
parentId: string
|
||||
}
|
||||
|
||||
export type List = {
|
||||
data: File[]
|
||||
totalPages: number
|
||||
totalElements: number
|
||||
page: number
|
||||
size: number
|
||||
query?: Query
|
||||
}
|
||||
|
||||
export type UserPermission = {
|
||||
id: string
|
||||
user: User
|
||||
permission: string
|
||||
}
|
||||
|
||||
export type GroupPermission = {
|
||||
id: string
|
||||
group: Group
|
||||
permission: string
|
||||
}
|
||||
|
||||
export type Query = {
|
||||
text: string
|
||||
type?: string
|
||||
createTimeAfter?: number
|
||||
createTimeBefore?: number
|
||||
updateTimeAfter?: number
|
||||
updateTimeBefore?: number
|
||||
}
|
||||
|
||||
export type ListOptions = {
|
||||
size?: number
|
||||
page?: number
|
||||
type?: FileType
|
||||
sortBy?: SortBy
|
||||
sortOrder?: SortOrder
|
||||
query?: Query
|
||||
}
|
||||
|
||||
export type MoveOptions = {
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
export type CopyOptions = {
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
export type BatchDeleteOptions = {
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
export type BatchGetOptions = {
|
||||
ids: string[]
|
||||
}
|
||||
|
||||
export type RenameOptions = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export type GrantUserPermissionOptions = {
|
||||
ids: string[]
|
||||
userId: string
|
||||
permission: string
|
||||
}
|
||||
|
||||
export type RevokeUserPermissionOptions = {
|
||||
ids: string[]
|
||||
userId: string
|
||||
}
|
||||
|
||||
export type GrantGroupPermissionOptions = {
|
||||
ids: string[]
|
||||
groupId: string
|
||||
permission: string
|
||||
}
|
||||
|
||||
export type RevokeGroupPermissionOptions = {
|
||||
ids: string[]
|
||||
groupId: string
|
||||
}
|
||||
|
||||
export type UploadOptions = {
|
||||
workspaceId: string
|
||||
parentId?: string
|
||||
name?: string
|
||||
file: Blob
|
||||
request: XMLHttpRequest
|
||||
onProgress?: (value: number) => void
|
||||
}
|
||||
|
||||
export default class FileAPI {
|
||||
static async upload({
|
||||
workspaceId,
|
||||
parentId,
|
||||
name,
|
||||
request,
|
||||
file,
|
||||
onProgress,
|
||||
}: UploadOptions): Promise<File> {
|
||||
const params = new URLSearchParams({ workspace_id: workspaceId })
|
||||
if (parentId) {
|
||||
params.append('parent_id', parentId)
|
||||
}
|
||||
if (name) {
|
||||
params.append('name', name)
|
||||
}
|
||||
return this.doUpload(
|
||||
`${getConfig().apiURL}/files?${params}`,
|
||||
'POST',
|
||||
request,
|
||||
file,
|
||||
onProgress,
|
||||
)
|
||||
}
|
||||
|
||||
static async patch(
|
||||
id: string,
|
||||
request: XMLHttpRequest,
|
||||
file: Blob,
|
||||
onProgress?: (value: number) => void,
|
||||
): Promise<File> {
|
||||
return this.doUpload(
|
||||
`${getConfig().apiURL}/files/${id}`,
|
||||
'PATCH',
|
||||
request,
|
||||
file,
|
||||
onProgress,
|
||||
)
|
||||
}
|
||||
|
||||
private static async doUpload(
|
||||
url: string,
|
||||
method: string,
|
||||
request: XMLHttpRequest,
|
||||
file: Blob,
|
||||
onProgress?: (value: number) => void,
|
||||
) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return new Promise<File>((resolve, reject) => {
|
||||
request.open(method, url)
|
||||
request.setRequestHeader(
|
||||
'Authorization',
|
||||
`Bearer ${getAccessTokenOrRedirect()}`,
|
||||
)
|
||||
request.onloadend = () => {
|
||||
if (request.status <= 299) {
|
||||
try {
|
||||
resolve(JSON.parse(request.responseText))
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
reject(JSON.parse(request.responseText))
|
||||
} catch {
|
||||
reject(request.responseText)
|
||||
}
|
||||
}
|
||||
}
|
||||
request.upload.onprogress = (e) => {
|
||||
onProgress?.((e.loaded / e.total) * 100)
|
||||
}
|
||||
request.send(formData)
|
||||
})
|
||||
}
|
||||
|
||||
static async createFolder(options: CreateFolderOptions) {
|
||||
return apiFetcher({
|
||||
url: '/files/create_folder',
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
}) as Promise<File>
|
||||
}
|
||||
|
||||
static async list(id: string, options: ListOptions) {
|
||||
return apiFetcher({
|
||||
url: `/files/${id}/list?${this.paramsFromListOptions(options)}`,
|
||||
method: 'GET',
|
||||
}) as Promise<List>
|
||||
}
|
||||
|
||||
static useList(
|
||||
id: string | undefined,
|
||||
options: ListOptions,
|
||||
swrOptions?: any,
|
||||
) {
|
||||
const url = `/files/${id}/list?${this.paramsFromListOptions(options)}`
|
||||
return useSWR<List | undefined>(
|
||||
id ? url : null,
|
||||
() => apiFetcher({ url, method: 'GET' }),
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
|
||||
static async getPath(id: string) {
|
||||
return apiFetcher({
|
||||
url: `/files/${id}/get_path`,
|
||||
method: 'GET',
|
||||
}) as Promise<File[]>
|
||||
}
|
||||
|
||||
static useGetPath(id: string | null | undefined, swrOptions?: any) {
|
||||
const url = `/files/${id}/get_path`
|
||||
return useSWR<File[]>(
|
||||
id ? url : null,
|
||||
() => apiFetcher({ url, method: 'GET' }) as Promise<File[]>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
|
||||
static async getIds(id: string) {
|
||||
return apiFetcher({
|
||||
url: `/files/${id}/get_ids`,
|
||||
method: 'GET',
|
||||
}) as Promise<string[]>
|
||||
}
|
||||
|
||||
static async rename(id: string, options: RenameOptions) {
|
||||
return apiFetcher({
|
||||
url: `/files/${id}/rename`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
}) as Promise<File>
|
||||
}
|
||||
|
||||
static async delete(id: string) {
|
||||
return apiFetcher({
|
||||
url: `/files/${id}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
static async batchDelete(options: BatchDeleteOptions) {
|
||||
return apiFetcher({
|
||||
url: `/files/batch_delete`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
})
|
||||
}
|
||||
|
||||
static async move(id: string, options: MoveOptions) {
|
||||
return apiFetcher({
|
||||
url: `/files/${id}/move`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
})
|
||||
}
|
||||
|
||||
static async copy(id: string, options: CopyOptions) {
|
||||
return apiFetcher({
|
||||
url: `/files/${id}/copy`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
})
|
||||
}
|
||||
|
||||
static useGetById(id: string | null | undefined, swrOptions?: any) {
|
||||
const url = `/files/${id}`
|
||||
return useSWR(
|
||||
id ? url : null,
|
||||
() => apiFetcher({ url, method: 'GET' }) as Promise<File>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
|
||||
static async getById(id: string) {
|
||||
return apiFetcher({
|
||||
url: `/files/${id}`,
|
||||
method: 'GET',
|
||||
}) as Promise<File>
|
||||
}
|
||||
|
||||
static async batchGet(options: BatchGetOptions) {
|
||||
return apiFetcher({
|
||||
url: `/files/batch_get`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
}) as Promise<File[]>
|
||||
}
|
||||
|
||||
static async getItemCount(id: string) {
|
||||
return apiFetcher({
|
||||
url: `/files/${id}/get_item_count`,
|
||||
method: 'GET',
|
||||
}) as Promise<number>
|
||||
}
|
||||
|
||||
static useGetItemCount(id: string | null | undefined, swrOptions?: any) {
|
||||
const url = `/files/${id}/get_item_count`
|
||||
return useSWR<number>(
|
||||
id ? url : null,
|
||||
() => apiFetcher({ url, method: 'GET' }) as Promise<number>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
|
||||
static async grantUserPermission(options: GrantUserPermissionOptions) {
|
||||
return apiFetcher({
|
||||
url: `/files/grant_user_permission`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
})
|
||||
}
|
||||
|
||||
static async revokeUserPermission(options: RevokeUserPermissionOptions) {
|
||||
return apiFetcher({
|
||||
url: `/files/revoke_user_permission`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
})
|
||||
}
|
||||
|
||||
static async grantGroupPermission(options: GrantGroupPermissionOptions) {
|
||||
return apiFetcher({
|
||||
url: `/files/grant_group_permission`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
})
|
||||
}
|
||||
|
||||
static async revokeGroupPermission(options: RevokeGroupPermissionOptions) {
|
||||
return apiFetcher({
|
||||
url: `/files/revoke_group_permission`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
})
|
||||
}
|
||||
|
||||
static async getUserPermissions(id: string) {
|
||||
return apiFetcher({
|
||||
url: `/files/${id}/get_user_permissions`,
|
||||
method: 'GET',
|
||||
}) as Promise<UserPermission[]>
|
||||
}
|
||||
|
||||
static useGetUserPermissions(
|
||||
id: string | null | undefined,
|
||||
swrOptions?: any,
|
||||
) {
|
||||
const url = `/files/${id}/get_user_permissions`
|
||||
return useSWR<UserPermission[]>(
|
||||
id ? url : null,
|
||||
() => apiFetcher({ url, method: 'GET' }) as Promise<UserPermission[]>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
|
||||
static async getGroupPermissions(id: string) {
|
||||
return apiFetcher<GroupPermission[]>({
|
||||
url: `/files/${id}/get_group_permissions`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
static useGetGroupPermissions(
|
||||
id: string | null | undefined,
|
||||
swrOptions?: any,
|
||||
) {
|
||||
const url = `/files/${id}/get_group_permissions`
|
||||
return useSWR<GroupPermission[]>(
|
||||
id ? url : null,
|
||||
() => apiFetcher({ url, method: 'GET' }) as Promise<GroupPermission[]>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
|
||||
static paramsFromListOptions(options?: ListOptions): URLSearchParams {
|
||||
const params: any = {}
|
||||
if (options?.page) {
|
||||
params.page = options.page.toString()
|
||||
}
|
||||
if (options?.size) {
|
||||
params.size = options.size.toString()
|
||||
}
|
||||
if (options?.sortBy) {
|
||||
params.sort_by = options.sortBy.toString()
|
||||
}
|
||||
if (options?.sortOrder) {
|
||||
params.sort_order = options.sortOrder.toString()
|
||||
}
|
||||
if (options?.type) {
|
||||
params.type = options.type
|
||||
}
|
||||
if (options?.query) {
|
||||
params.query = encodeQuery(JSON.stringify(options.query))
|
||||
}
|
||||
return new URLSearchParams(params)
|
||||
}
|
||||
}
|
164
Voltaserve/ui/src/client/api/group.ts
Normal file
@ -0,0 +1,164 @@
|
||||
import useSWR from 'swr'
|
||||
import { apiFetcher } from '@/client/fetcher'
|
||||
import { Organization } from './organization'
|
||||
|
||||
export enum SortBy {
|
||||
Name = 'name',
|
||||
DateCreated = 'date_created',
|
||||
DateModified = 'date_modified',
|
||||
}
|
||||
|
||||
export enum SortOrder {
|
||||
Asc = 'asc',
|
||||
Desc = 'desc',
|
||||
}
|
||||
|
||||
export type Group = {
|
||||
id: string
|
||||
name: string
|
||||
organization: Organization
|
||||
permission: string
|
||||
createTime: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
export type List = {
|
||||
data: Group[]
|
||||
totalPages: number
|
||||
totalElements: number
|
||||
page: number
|
||||
size: number
|
||||
}
|
||||
|
||||
export type ListOptions = {
|
||||
query?: string
|
||||
organizationId?: string
|
||||
size?: number
|
||||
page?: number
|
||||
sortBy?: SortBy
|
||||
sortOrder?: SortOrder
|
||||
}
|
||||
|
||||
export type CreateOptions = {
|
||||
name: string
|
||||
image?: string
|
||||
organizationId: string
|
||||
}
|
||||
|
||||
export type UpdateNameOptions = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export type AddMemberOptions = {
|
||||
userId: string
|
||||
}
|
||||
|
||||
export type RemoveMemberOptions = {
|
||||
userId: string
|
||||
}
|
||||
|
||||
export default class GroupAPI {
|
||||
static create(options: CreateOptions) {
|
||||
return apiFetcher({
|
||||
url: `/groups`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
}) as Promise<Group>
|
||||
}
|
||||
|
||||
static updateName(id: string, options: UpdateNameOptions) {
|
||||
return apiFetcher({
|
||||
url: `/groups/${id}/update_name`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
}) as Promise<Group>
|
||||
}
|
||||
|
||||
static async updateImage(id: string, file: any) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return apiFetcher<Group>({
|
||||
url: `/groups/${id}/update_image`,
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
}
|
||||
|
||||
static async getById(id: string) {
|
||||
return apiFetcher({
|
||||
url: `/groups/${id}`,
|
||||
method: 'GET',
|
||||
}) as Promise<Group>
|
||||
}
|
||||
|
||||
static useGetById(id: string | null | undefined, swrOptions?: any) {
|
||||
const url = `/groups/${id}`
|
||||
return useSWR<Group>(
|
||||
id ? url : null,
|
||||
() => apiFetcher({ url, method: 'GET' }) as Promise<Group>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
|
||||
static async list(options?: ListOptions) {
|
||||
return apiFetcher<List>({
|
||||
url: `/groups?${this.paramsFromListOptions(options)}`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
static useList(options?: ListOptions, swrOptions?: any) {
|
||||
const url = `/groups?${this.paramsFromListOptions(options)}`
|
||||
return useSWR<List>(
|
||||
url,
|
||||
() => apiFetcher({ url, method: 'GET' }) as Promise<List>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
|
||||
static delete(id: string) {
|
||||
return apiFetcher({
|
||||
url: `/groups/${id}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
static addMember(id: string, options: AddMemberOptions) {
|
||||
return apiFetcher({
|
||||
url: `/groups/${id}/add_member`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
})
|
||||
}
|
||||
|
||||
static removeMember(id: string, options: RemoveMemberOptions) {
|
||||
return apiFetcher({
|
||||
url: `/groups/${id}/remove_member`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
})
|
||||
}
|
||||
|
||||
static paramsFromListOptions(options?: ListOptions): URLSearchParams {
|
||||
const params: any = {}
|
||||
if (options?.query) {
|
||||
params.query = encodeURIComponent(options.query.toString())
|
||||
}
|
||||
if (options?.organizationId) {
|
||||
params.organization_id = options.organizationId.toString()
|
||||
}
|
||||
if (options?.page) {
|
||||
params.page = options.page.toString()
|
||||
}
|
||||
if (options?.size) {
|
||||
params.size = options.size.toString()
|
||||
}
|
||||
if (options?.sortBy) {
|
||||
params.sort_by = options.sortBy.toString()
|
||||
}
|
||||
if (options?.sortOrder) {
|
||||
params.sort_order = options.sortOrder.toString()
|
||||
}
|
||||
return new URLSearchParams(params)
|
||||
}
|
||||
}
|
129
Voltaserve/ui/src/client/api/invitation.ts
Normal file
@ -0,0 +1,129 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import useSWR from 'swr'
|
||||
import { apiFetcher } from '@/client/fetcher'
|
||||
import { User } from '@/client/idp/user'
|
||||
import { Organization } from './organization'
|
||||
|
||||
export enum SortBy {
|
||||
Email = 'email',
|
||||
DateCreated = 'date_created',
|
||||
DateModified = 'date_modified',
|
||||
}
|
||||
|
||||
export enum SortOrder {
|
||||
Asc = 'asc',
|
||||
Desc = 'desc',
|
||||
}
|
||||
|
||||
export type InvitationStatus = 'pending' | 'accepted' | 'declined'
|
||||
|
||||
export type Invitation = {
|
||||
id: string
|
||||
owner: User
|
||||
email: string[]
|
||||
organization: Organization
|
||||
status: InvitationStatus
|
||||
createTime: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
export type List = {
|
||||
data: Invitation[]
|
||||
totalPages: number
|
||||
totalElements: number
|
||||
page: number
|
||||
size: number
|
||||
}
|
||||
|
||||
export type CreateOptions = {
|
||||
organizationId: string
|
||||
emails: string[]
|
||||
}
|
||||
|
||||
export type ListOptions = {
|
||||
organizationId?: string
|
||||
size?: number
|
||||
page?: number
|
||||
sortBy?: SortBy
|
||||
sortOrder?: SortOrder
|
||||
}
|
||||
|
||||
export default class InvitationAPI {
|
||||
static async create(options: CreateOptions) {
|
||||
return apiFetcher({
|
||||
url: `/invitations`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
})
|
||||
}
|
||||
|
||||
static useGetIncoming(options?: ListOptions, swrOptions?: any) {
|
||||
const url = `/invitations/get_incoming?${this.paramsFromListOptions(
|
||||
options,
|
||||
)}`
|
||||
return useSWR<List>(
|
||||
url,
|
||||
() => apiFetcher({ url, method: 'GET' }) as Promise<List>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
|
||||
static useGetOutgoing(options?: ListOptions, swrOptions?: any) {
|
||||
const url = `/invitations/get_outgoing?${this.paramsFromListOptions(
|
||||
options,
|
||||
)}`
|
||||
return useSWR<List>(
|
||||
options?.organizationId ? url : null,
|
||||
() => apiFetcher({ url, method: 'GET' }) as Promise<List>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
|
||||
static async delete(id: string) {
|
||||
return apiFetcher({
|
||||
url: `/invitations/${id}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
static async resend(id: string) {
|
||||
return apiFetcher({
|
||||
url: `/invitations/${id}/resend`,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
static async accept(id: string) {
|
||||
return apiFetcher({
|
||||
url: `/invitations/${id}/accept`,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
static async decline(id: string) {
|
||||
return apiFetcher({
|
||||
url: `/invitations/${id}/decline`,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
static paramsFromListOptions(options?: ListOptions): URLSearchParams {
|
||||
const params: any = {}
|
||||
if (options?.organizationId) {
|
||||
params.organization_id = options.organizationId.toString()
|
||||
}
|
||||
if (options?.page) {
|
||||
params.page = options.page.toString()
|
||||
}
|
||||
if (options?.size) {
|
||||
params.size = options.size.toString()
|
||||
}
|
||||
if (options?.sortBy) {
|
||||
params.sort_by = options.sortBy.toString()
|
||||
}
|
||||
if (options?.sortOrder) {
|
||||
params.sort_order = options.sortOrder.toString()
|
||||
}
|
||||
return new URLSearchParams(params)
|
||||
}
|
||||
}
|
22
Voltaserve/ui/src/client/api/notification.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import useSWR from 'swr'
|
||||
import { apiFetcher } from '@/client/fetcher'
|
||||
import { Invitation } from './invitation'
|
||||
|
||||
export type NotificationType = 'new_invitation'
|
||||
|
||||
export type Notification = {
|
||||
type: NotificationType
|
||||
body: Invitation | any
|
||||
}
|
||||
|
||||
export default class NotificationAPI {
|
||||
static useGetAll(swrOptions?: any) {
|
||||
const url = `/notifications`
|
||||
return useSWR<Notification[]>(
|
||||
url,
|
||||
() => apiFetcher({ url, method: 'GET' }) as Promise<Notification[]>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
}
|
154
Voltaserve/ui/src/client/api/organization.ts
Normal file
@ -0,0 +1,154 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import useSWR from 'swr'
|
||||
import { apiFetcher } from '@/client/fetcher'
|
||||
import { PermissionType } from './permission'
|
||||
|
||||
export enum SortBy {
|
||||
Name = 'name',
|
||||
DateCreated = 'date_created',
|
||||
DateModified = 'date_modified',
|
||||
}
|
||||
|
||||
export enum SortOrder {
|
||||
Asc = 'asc',
|
||||
Desc = 'desc',
|
||||
}
|
||||
|
||||
export type Organization = {
|
||||
id: string
|
||||
name: string
|
||||
permission: PermissionType
|
||||
createTime: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
export type List = {
|
||||
data: Organization[]
|
||||
totalPages: number
|
||||
totalElements: number
|
||||
page: number
|
||||
size: number
|
||||
}
|
||||
|
||||
export type ListOptions = {
|
||||
query?: string
|
||||
size?: number
|
||||
page?: number
|
||||
sortBy?: SortBy
|
||||
sortOrder?: SortOrder
|
||||
}
|
||||
|
||||
export type CreateOptions = {
|
||||
name: string
|
||||
image?: string
|
||||
}
|
||||
|
||||
export type UpdateNameOptions = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export type RemoveMemberOptions = {
|
||||
userId: string
|
||||
}
|
||||
|
||||
export default class OrganizationAPI {
|
||||
static async getById(id: string) {
|
||||
return apiFetcher({
|
||||
url: `/organizations/${id}`,
|
||||
method: 'GET',
|
||||
}) as Promise<Organization>
|
||||
}
|
||||
|
||||
static useGetById(id: string | null | undefined, swrOptions?: any) {
|
||||
const url = `/organizations/${id}`
|
||||
return useSWR<Organization>(
|
||||
id ? url : null,
|
||||
() => apiFetcher({ url, method: 'GET' }) as Promise<Organization>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
|
||||
static async list(options?: ListOptions) {
|
||||
return apiFetcher({
|
||||
url: `/organizations?${this.paramsFromListOptions(options)}`,
|
||||
method: 'GET',
|
||||
}) as Promise<List>
|
||||
}
|
||||
|
||||
static useList(options?: ListOptions, swrOptions?: any) {
|
||||
const url = `/organizations?${this.paramsFromListOptions(options)}`
|
||||
return useSWR<List>(
|
||||
url,
|
||||
() => apiFetcher({ url, method: 'GET' }) as Promise<List>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
|
||||
static async create(options: CreateOptions) {
|
||||
return apiFetcher({
|
||||
url: `/organizations`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
}) as Promise<Organization>
|
||||
}
|
||||
|
||||
static async updateName(id: string, options: UpdateNameOptions) {
|
||||
return apiFetcher({
|
||||
url: `/organizations/${id}/update_name`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
}) as Promise<Organization>
|
||||
}
|
||||
|
||||
static async updateImage(id: string, file: any) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return apiFetcher({
|
||||
url: `/organizations/${id}/update_image`,
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
}) as Promise<Organization>
|
||||
}
|
||||
|
||||
static async delete(id: string) {
|
||||
return apiFetcher({
|
||||
url: `/organizations/${id}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
static async leave(id: string) {
|
||||
return apiFetcher({
|
||||
url: `/organizations/${id}/leave`,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
static async removeMember(id: string, options: RemoveMemberOptions) {
|
||||
return apiFetcher({
|
||||
url: `/organizations/${id}/remove_member`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
})
|
||||
}
|
||||
|
||||
static paramsFromListOptions(options?: ListOptions): URLSearchParams {
|
||||
const params: any = {}
|
||||
if (options?.query) {
|
||||
params.query = encodeURIComponent(options.query.toString())
|
||||
}
|
||||
if (options?.page) {
|
||||
params.page = options.page.toString()
|
||||
}
|
||||
if (options?.size) {
|
||||
params.size = options.size.toString()
|
||||
}
|
||||
if (options?.sortBy) {
|
||||
params.sort_by = options.sortBy.toString()
|
||||
}
|
||||
if (options?.sortOrder) {
|
||||
params.sort_order = options.sortOrder.toString()
|
||||
}
|
||||
return new URLSearchParams(params)
|
||||
}
|
||||
}
|
52
Voltaserve/ui/src/client/api/permission.ts
Normal file
@ -0,0 +1,52 @@
|
||||
export const VIEWER_PERMISSION = 'viewer'
|
||||
export const EDITOR_PERMISSION = 'editor'
|
||||
export const OWNER_PERMISSION = 'owner'
|
||||
|
||||
export type PermissionType = 'viewer' | 'editor' | 'owner'
|
||||
|
||||
export function geViewerPermission(permission: string): boolean {
|
||||
return (
|
||||
getPermissionWeight(permission) >= getPermissionWeight(VIEWER_PERMISSION)
|
||||
)
|
||||
}
|
||||
|
||||
export function geEditorPermission(permission: string) {
|
||||
return (
|
||||
getPermissionWeight(permission) >= getPermissionWeight(EDITOR_PERMISSION)
|
||||
)
|
||||
}
|
||||
|
||||
export function geOwnerPermission(permission: string) {
|
||||
return (
|
||||
getPermissionWeight(permission) >= getPermissionWeight(OWNER_PERMISSION)
|
||||
)
|
||||
}
|
||||
|
||||
export function ltViewerPermission(permission: string): boolean {
|
||||
return (
|
||||
getPermissionWeight(permission) < getPermissionWeight(VIEWER_PERMISSION)
|
||||
)
|
||||
}
|
||||
|
||||
export function ltEditorPermission(permission: string) {
|
||||
return (
|
||||
getPermissionWeight(permission) < getPermissionWeight(EDITOR_PERMISSION)
|
||||
)
|
||||
}
|
||||
|
||||
export function ltOwnerPermission(permission: string) {
|
||||
return getPermissionWeight(permission) < getPermissionWeight(OWNER_PERMISSION)
|
||||
}
|
||||
|
||||
export function getPermissionWeight(permission: string) {
|
||||
switch (permission) {
|
||||
case VIEWER_PERMISSION:
|
||||
return 1
|
||||
case EDITOR_PERMISSION:
|
||||
return 2
|
||||
case OWNER_PERMISSION:
|
||||
return 3
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
24
Voltaserve/ui/src/client/api/snapshot.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export type Snapshot = {
|
||||
version: number
|
||||
original: Download
|
||||
preview?: Download
|
||||
text?: Download
|
||||
thumbnail?: Thumbnail
|
||||
}
|
||||
|
||||
export type Download = {
|
||||
extension: string
|
||||
size: number
|
||||
image?: ImageProps
|
||||
}
|
||||
|
||||
export type ImageProps = {
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export type Thumbnail = {
|
||||
base64: string
|
||||
width: number
|
||||
height: number
|
||||
}
|
44
Voltaserve/ui/src/client/api/storage.ts
Normal file
@ -0,0 +1,44 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import useSWR from 'swr'
|
||||
import { apiFetcher } from '@/client/fetcher'
|
||||
|
||||
export type StorageUsage = {
|
||||
bytes: number
|
||||
maxBytes: number
|
||||
percentage: number
|
||||
}
|
||||
|
||||
export default class StorageAPI {
|
||||
static useGetAccountUsage(swrOptions?: any) {
|
||||
const url = `/storage/get_account_usage`
|
||||
return useSWR<StorageUsage>(
|
||||
url,
|
||||
() => apiFetcher({ url, method: 'GET' }) as Promise<StorageUsage>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
|
||||
static useGetWorkspaceUsage(id: string | null | undefined, swrOptions?: any) {
|
||||
const url = id
|
||||
? `/storage/get_workspace_usage?${new URLSearchParams({
|
||||
id,
|
||||
})}`
|
||||
: null
|
||||
return useSWR<StorageUsage>(
|
||||
url,
|
||||
() => apiFetcher({ url: url!, method: 'GET' }) as Promise<StorageUsage>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
|
||||
static useGetFileUsage(id: string | null | undefined, swrOptions?: any) {
|
||||
const url = `/storage/get_file_usage?${new URLSearchParams({
|
||||
id: id!,
|
||||
})}`
|
||||
return useSWR<StorageUsage>(
|
||||
id ? url : null,
|
||||
() => apiFetcher({ url, method: 'GET' }) as Promise<StorageUsage>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
}
|
86
Voltaserve/ui/src/client/api/user.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import useSWR from 'swr'
|
||||
import { apiFetcher } from '@/client/fetcher'
|
||||
|
||||
export enum SortBy {
|
||||
Email = 'email',
|
||||
FullName = 'full_name',
|
||||
}
|
||||
|
||||
export enum SortOrder {
|
||||
Asc = 'asc',
|
||||
Desc = 'desc',
|
||||
}
|
||||
|
||||
export type User = {
|
||||
id: string
|
||||
username: string
|
||||
email: string
|
||||
fullName: string
|
||||
picture?: string
|
||||
}
|
||||
|
||||
export type List = {
|
||||
data: User[]
|
||||
totalPages: number
|
||||
totalElements: number
|
||||
page: number
|
||||
size: number
|
||||
}
|
||||
|
||||
export type ListOptions = {
|
||||
query?: string
|
||||
organizationId?: string
|
||||
groupId?: string
|
||||
nonGroupMembersOnly?: boolean
|
||||
size?: number
|
||||
page?: number
|
||||
sortBy?: SortBy
|
||||
sortOrder?: SortOrder
|
||||
}
|
||||
|
||||
export default class UserAPI {
|
||||
static async list(options?: ListOptions) {
|
||||
return apiFetcher({
|
||||
url: `/users?${this.paramsFromListOptions(options)}`,
|
||||
method: 'GET',
|
||||
}) as Promise<List>
|
||||
}
|
||||
|
||||
static useList(options?: ListOptions, swrOptions?: any) {
|
||||
const url = `/users?${this.paramsFromListOptions(options)}`
|
||||
return useSWR<List>(
|
||||
url,
|
||||
() => apiFetcher({ url, method: 'GET' }) as Promise<List>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
|
||||
static paramsFromListOptions(options?: ListOptions): URLSearchParams {
|
||||
const params: any = {}
|
||||
if (options?.query) {
|
||||
params.query = encodeURIComponent(options.query.toString())
|
||||
}
|
||||
if (options?.organizationId) {
|
||||
params.organization_id = options.organizationId.toString()
|
||||
}
|
||||
if (options?.groupId) {
|
||||
params.group_id = options.groupId.toString()
|
||||
}
|
||||
if (options?.nonGroupMembersOnly) {
|
||||
params.non_group_members_only = options.nonGroupMembersOnly.toString()
|
||||
}
|
||||
if (options?.page) {
|
||||
params.page = options.page.toString()
|
||||
}
|
||||
if (options?.size) {
|
||||
params.size = options.size.toString()
|
||||
}
|
||||
if (options?.sortBy) {
|
||||
params.sort_by = options.sortBy.toString()
|
||||
}
|
||||
if (options?.sortOrder) {
|
||||
params.sort_order = options.sortOrder.toString()
|
||||
}
|
||||
return new URLSearchParams(params)
|
||||
}
|
||||
}
|
156
Voltaserve/ui/src/client/api/workspace.ts
Normal file
@ -0,0 +1,156 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import useSWR from 'swr'
|
||||
import { apiFetcher } from '@/client/fetcher'
|
||||
import { Organization } from './organization'
|
||||
import { PermissionType } from './permission'
|
||||
|
||||
export enum SortBy {
|
||||
Name = 'name',
|
||||
DateCreated = 'date_created',
|
||||
DateModified = 'date_modified',
|
||||
}
|
||||
|
||||
export enum SortOrder {
|
||||
Asc = 'asc',
|
||||
Desc = 'desc',
|
||||
}
|
||||
|
||||
export type Workspace = {
|
||||
id: string
|
||||
name: string
|
||||
permission: PermissionType
|
||||
storageCapacity: number
|
||||
rootId: string
|
||||
organization: Organization
|
||||
createTime: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
export type List = {
|
||||
data: Workspace[]
|
||||
totalPages: number
|
||||
totalElements: number
|
||||
page: number
|
||||
size: number
|
||||
}
|
||||
|
||||
export interface CreateOptions {
|
||||
name: string
|
||||
image?: string
|
||||
organizationId: string
|
||||
storageCapacity: number
|
||||
}
|
||||
|
||||
export type ListOptions = {
|
||||
query?: string
|
||||
size?: number
|
||||
page?: number
|
||||
sortBy?: SortBy
|
||||
sortOrder?: SortOrder
|
||||
}
|
||||
|
||||
export interface UpdateNameOptions {
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface StorageCapacityOptions {
|
||||
storageCapacity: number
|
||||
}
|
||||
|
||||
export default class WorkspaceAPI {
|
||||
static async getById(id: string) {
|
||||
return apiFetcher({
|
||||
url: `/workspaces/${id}`,
|
||||
method: 'GET',
|
||||
}) as Promise<Workspace>
|
||||
}
|
||||
|
||||
static useGetById(id: string | null | undefined, swrOptions?: any) {
|
||||
const url = `/workspaces/${id}`
|
||||
return useSWR<Workspace>(
|
||||
id ? url : null,
|
||||
() => apiFetcher({ url, method: 'GET' }) as Promise<Workspace>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
|
||||
static useList(options?: ListOptions, swrOptions?: any) {
|
||||
const url = `/workspaces?${this.paramsFromListOptions(options)}`
|
||||
return useSWR<List>(
|
||||
url,
|
||||
() => apiFetcher({ url, method: 'GET' }) as Promise<List>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
|
||||
static async list(options?: ListOptions) {
|
||||
return apiFetcher({
|
||||
url: `/workspaces?${this.paramsFromListOptions(options)}`,
|
||||
method: 'GET',
|
||||
}) as Promise<List>
|
||||
}
|
||||
|
||||
static async create(options: CreateOptions) {
|
||||
return apiFetcher({
|
||||
url: '/workspaces',
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
}) as Promise<Workspace>
|
||||
}
|
||||
|
||||
static async updateName(id: string, options: UpdateNameOptions) {
|
||||
return apiFetcher({
|
||||
url: `/workspaces/${id}/update_name`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
}) as Promise<Workspace>
|
||||
}
|
||||
|
||||
static async updateStorageCapacity(
|
||||
id: string,
|
||||
options: StorageCapacityOptions,
|
||||
) {
|
||||
return apiFetcher({
|
||||
url: `/workspaces/${id}/update_storage_capacity`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
}) as Promise<Workspace>
|
||||
}
|
||||
|
||||
static async updateImage(id: string, file: any) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return apiFetcher({
|
||||
url: `/workspaces/${id}/update_image`,
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
}) as Promise<Workspace>
|
||||
}
|
||||
|
||||
static async delete(id: string) {
|
||||
return apiFetcher({
|
||||
url: `/workspaces/${id}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
static paramsFromListOptions(options?: ListOptions): URLSearchParams {
|
||||
const params: any = {}
|
||||
if (options?.query) {
|
||||
params.query = encodeURIComponent(options.query.toString())
|
||||
}
|
||||
if (options?.page) {
|
||||
params.page = options.page.toString()
|
||||
}
|
||||
if (options?.size) {
|
||||
params.size = options.size.toString()
|
||||
}
|
||||
if (options?.sortBy) {
|
||||
params.sort_by = options.sortBy.toString()
|
||||
}
|
||||
if (options?.sortOrder) {
|
||||
params.sort_order = options.sortOrder.toString()
|
||||
}
|
||||
return new URLSearchParams(params)
|
||||
}
|
||||
}
|
15
Voltaserve/ui/src/client/error.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export type ErrorResponse = {
|
||||
code: string
|
||||
status: number
|
||||
message: string
|
||||
userMessage: string
|
||||
moreInfo: string
|
||||
}
|
||||
|
||||
export function errorToString(value: any): string {
|
||||
if (value.code && value.message && value.userMessage && value.moreInfo) {
|
||||
const error = value as ErrorResponse
|
||||
return error.userMessage
|
||||
}
|
||||
return value.toString()
|
||||
}
|
94
Voltaserve/ui/src/client/fetcher.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { getConfig } from '@/config/config'
|
||||
import { getAccessToken, getAccessTokenOrRedirect } from '@/infra/token'
|
||||
import store from '@/store/configure-store'
|
||||
import { errorOccurred } from '@/store/ui/error'
|
||||
import { errorToString } from './error'
|
||||
|
||||
export type FetcherOptions = {
|
||||
url: string
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'HEAD'
|
||||
body?: BodyInit | null
|
||||
contentType?: string
|
||||
redirect?: boolean
|
||||
authenticate?: boolean
|
||||
}
|
||||
|
||||
export function apiFetcher<T>(options: FetcherOptions) {
|
||||
return fetcher<T>({ ...options, url: `${getConfig().apiURL}${options.url}` })
|
||||
}
|
||||
|
||||
export function idpFetcher<T>(options: FetcherOptions) {
|
||||
return fetcher<T>({
|
||||
...options,
|
||||
url: `${getConfig().idpURL}${options.url}`,
|
||||
})
|
||||
}
|
||||
|
||||
export async function fetcher<T>({
|
||||
url,
|
||||
method,
|
||||
body,
|
||||
contentType,
|
||||
redirect,
|
||||
authenticate = true,
|
||||
}: FetcherOptions): Promise<T | undefined> {
|
||||
const headers: HeadersInit = {}
|
||||
if (!contentType) {
|
||||
headers['Content-Type'] = 'application/json'
|
||||
}
|
||||
if (authenticate) {
|
||||
headers['Authorization'] = `Bearer ${
|
||||
redirect ? getAccessTokenOrRedirect() : getAccessToken()
|
||||
}`
|
||||
}
|
||||
const response = await baseFetcher(
|
||||
url,
|
||||
{
|
||||
method,
|
||||
body,
|
||||
headers,
|
||||
credentials: authenticate ? 'include' : undefined,
|
||||
},
|
||||
redirect,
|
||||
)
|
||||
try {
|
||||
if (response) {
|
||||
return (await response.json()) as T
|
||||
}
|
||||
} catch {
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
|
||||
export async function baseFetcher(
|
||||
url: string,
|
||||
init?: RequestInit,
|
||||
redirect = true,
|
||||
) {
|
||||
try {
|
||||
const response = await fetch(url, init)
|
||||
return handleResponse(response, redirect)
|
||||
} catch (error) {
|
||||
const message = 'Unexpected error occurred.'
|
||||
store.dispatch(errorOccurred(message))
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleResponse(response: Response, redirect = true) {
|
||||
if (response.status <= 299) {
|
||||
return response
|
||||
} else {
|
||||
if (response.status === 401 && redirect) {
|
||||
window.location.href = '/sign-in'
|
||||
}
|
||||
let message
|
||||
try {
|
||||
message = errorToString(await response.json())
|
||||
} catch {
|
||||
message = 'Oops! something went wrong.'
|
||||
}
|
||||
store.dispatch(errorOccurred(message))
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
58
Voltaserve/ui/src/client/idp/account.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { idpFetcher } from '@/client/fetcher'
|
||||
import { User } from './user'
|
||||
|
||||
export type CreateOptions = {
|
||||
email: string
|
||||
password: string
|
||||
fullName: string
|
||||
picture?: string
|
||||
}
|
||||
|
||||
export type SendResetPasswordEmailOptions = {
|
||||
email: string
|
||||
}
|
||||
|
||||
export type ResetPasswordOptions = {
|
||||
token: string
|
||||
newPassword: string
|
||||
}
|
||||
|
||||
export type ConfirmEmailOptions = {
|
||||
token: string
|
||||
}
|
||||
|
||||
export default class AccountAPI {
|
||||
static async create(options: CreateOptions) {
|
||||
return idpFetcher({
|
||||
url: `/accounts`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
redirect: false,
|
||||
authenticate: false,
|
||||
}) as Promise<User>
|
||||
}
|
||||
|
||||
static async sendResetPasswordEmail(options: SendResetPasswordEmailOptions) {
|
||||
return idpFetcher({
|
||||
url: `/accounts/send_reset_password_email`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
})
|
||||
}
|
||||
|
||||
static async resetPassword(options: ResetPasswordOptions) {
|
||||
return idpFetcher({
|
||||
url: `/accounts/reset_password`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
})
|
||||
}
|
||||
|
||||
static async confirmEmail(options: ConfirmEmailOptions) {
|
||||
return idpFetcher({
|
||||
url: `/accounts/confirm_email`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
})
|
||||
}
|
||||
}
|
66
Voltaserve/ui/src/client/idp/token.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { baseFetcher } from '@/client/fetcher'
|
||||
import { getConfig } from '@/config/config'
|
||||
|
||||
export type GrantType = 'password' | 'refresh_token'
|
||||
|
||||
export type Token = {
|
||||
access_token: string
|
||||
expires_in: number
|
||||
token_type: string
|
||||
refresh_token: string
|
||||
}
|
||||
|
||||
export type ExchangeOptions = {
|
||||
grant_type: GrantType
|
||||
username?: string
|
||||
password?: string
|
||||
refresh_token?: string
|
||||
locale?: string
|
||||
}
|
||||
|
||||
export default class TokenAPI {
|
||||
static async exchange(options: ExchangeOptions) {
|
||||
const formBody = []
|
||||
formBody.push(`grant_type=${options.grant_type}`)
|
||||
if (options.grant_type === 'password') {
|
||||
if (options.username && options.password) {
|
||||
formBody.push(`username=${encodeURIComponent(options.username)}`)
|
||||
formBody.push(`password=${encodeURIComponent(options.password)}`)
|
||||
} else {
|
||||
throw new Error('Username or password missing!')
|
||||
}
|
||||
}
|
||||
if (options.grant_type === 'refresh_token') {
|
||||
if (options.refresh_token) {
|
||||
formBody.push(
|
||||
`refresh_token=${encodeURIComponent(options.refresh_token)}`,
|
||||
)
|
||||
} else {
|
||||
throw new Error('Refresh token missing!')
|
||||
}
|
||||
}
|
||||
if (options.locale) {
|
||||
formBody.push(`&locale=${encodeURIComponent(options.locale)}`)
|
||||
}
|
||||
const response = await baseFetcher(
|
||||
`${getConfig().idpURL}/token`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: formBody.join('&'),
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
},
|
||||
false,
|
||||
)
|
||||
try {
|
||||
if (response) {
|
||||
return (await response.json()) as Token
|
||||
} else {
|
||||
throw new Error('No response')
|
||||
}
|
||||
} catch {
|
||||
throw new Error('Failed to parse token')
|
||||
}
|
||||
}
|
||||
}
|
104
Voltaserve/ui/src/client/idp/user.ts
Normal file
@ -0,0 +1,104 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import useSWR from 'swr'
|
||||
import { idpFetcher } from '@/client/fetcher'
|
||||
|
||||
export type User = {
|
||||
id: string
|
||||
username: string
|
||||
email: string
|
||||
fullName: string
|
||||
picture?: string
|
||||
pendingEmail?: string
|
||||
}
|
||||
|
||||
export type UpdateFullNameOptions = {
|
||||
fullName: string
|
||||
}
|
||||
|
||||
export type UpdateEmailRequestOptions = {
|
||||
email: string
|
||||
}
|
||||
|
||||
export type UpdateEmailConfirmationOptions = {
|
||||
token: string
|
||||
}
|
||||
|
||||
export type UpdatePasswordOptions = {
|
||||
currentPassword: string
|
||||
newPassword: string
|
||||
}
|
||||
|
||||
export type DeleteOptions = {
|
||||
password: string
|
||||
}
|
||||
|
||||
export default class UserAPI {
|
||||
static useGet(swrOptions?: any) {
|
||||
const url = `/user`
|
||||
return useSWR<User>(
|
||||
url,
|
||||
() => idpFetcher({ url, method: 'GET' }) as Promise<User>,
|
||||
swrOptions,
|
||||
)
|
||||
}
|
||||
|
||||
static async updateFullName(options: UpdateFullNameOptions) {
|
||||
return idpFetcher({
|
||||
url: `/user/update_full_name`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
}) as Promise<User>
|
||||
}
|
||||
|
||||
static async updateEmailRequest(options: UpdateEmailRequestOptions) {
|
||||
return idpFetcher({
|
||||
url: `/user/update_email_request`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
}) as Promise<User>
|
||||
}
|
||||
|
||||
static async updateEmailConfirmation(
|
||||
options: UpdateEmailConfirmationOptions,
|
||||
) {
|
||||
return idpFetcher({
|
||||
url: `/user/update_email_confirmation`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
}) as Promise<User>
|
||||
}
|
||||
|
||||
static async updatePassword(options: UpdatePasswordOptions) {
|
||||
return idpFetcher({
|
||||
url: `/user/update_password`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(options),
|
||||
}) as Promise<User>
|
||||
}
|
||||
|
||||
static async delete(options: DeleteOptions) {
|
||||
return idpFetcher({
|
||||
url: `/user`,
|
||||
method: 'DELETE',
|
||||
body: JSON.stringify(options),
|
||||
})
|
||||
}
|
||||
|
||||
static async updatePicture(file: File) {
|
||||
const body = new FormData()
|
||||
body.append('file', file)
|
||||
return idpFetcher({
|
||||
url: `/user/update_picture`,
|
||||
method: 'POST',
|
||||
body,
|
||||
contentType: 'multipart/form-data',
|
||||
}) as Promise<User>
|
||||
}
|
||||
|
||||
static async deletePicture() {
|
||||
return idpFetcher({
|
||||
url: `/user/delete_picture`,
|
||||
method: 'POST',
|
||||
}) as Promise<User>
|
||||
}
|
||||
}
|
7
Voltaserve/ui/src/client/options.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export const REFRESH_INTERVAL = 5000
|
||||
|
||||
export function swrConfig() {
|
||||
return {
|
||||
refreshInterval: REFRESH_INTERVAL,
|
||||
}
|
||||
}
|
171
Voltaserve/ui/src/components/account/account-change-password.tsx
Normal file
@ -0,0 +1,171 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
Input,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
} from '@chakra-ui/react'
|
||||
import { useSWRConfig } from 'swr'
|
||||
import {
|
||||
Field,
|
||||
FieldAttributes,
|
||||
FieldProps,
|
||||
Form,
|
||||
Formik,
|
||||
FormikHelpers,
|
||||
} from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import cx from 'classnames'
|
||||
import UserAPI, { User } from '@/client/idp/user'
|
||||
|
||||
export type AccountChangePasswordProps = {
|
||||
open: boolean
|
||||
user: User
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
type FormValues = {
|
||||
currentPassword: string
|
||||
newPassword: string
|
||||
}
|
||||
|
||||
const AccountChangePassword = ({
|
||||
open,
|
||||
onClose,
|
||||
}: AccountChangePasswordProps) => {
|
||||
const { mutate } = useSWRConfig()
|
||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||
const initialValues: FormValues = { currentPassword: '', newPassword: '' }
|
||||
const formSchema = Yup.object().shape({
|
||||
currentPassword: Yup.string().required('Current password is required'),
|
||||
newPassword: Yup.string().required('New password is required'),
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setIsModalOpen(open)
|
||||
}, [open])
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (
|
||||
{ currentPassword, newPassword }: FormValues,
|
||||
{ setSubmitting }: FormikHelpers<FormValues>,
|
||||
) => {
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const result = await UserAPI.updatePassword({
|
||||
currentPassword,
|
||||
newPassword,
|
||||
})
|
||||
mutate(`/user`, result)
|
||||
setSubmitting(false)
|
||||
onClose?.()
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
},
|
||||
[onClose, mutate],
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => onClose?.()}
|
||||
closeOnOverlayClick={false}
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Change Password</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={formSchema}
|
||||
validateOnBlur={false}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({ errors, touched, isSubmitting }) => (
|
||||
<Form>
|
||||
<ModalBody>
|
||||
<div className={cx('flex', 'flex-col', 'gap-1.5')}>
|
||||
<Field name="currentPassword">
|
||||
{({ field }: FieldAttributes<FieldProps>) => (
|
||||
<FormControl
|
||||
isInvalid={
|
||||
errors.currentPassword && touched.currentPassword
|
||||
? true
|
||||
: false
|
||||
}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
type="password"
|
||||
placeholder="Current password"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<FormErrorMessage>
|
||||
{errors.currentPassword}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="newPassword">
|
||||
{({ field }: FieldAttributes<FieldProps>) => (
|
||||
<FormControl
|
||||
isInvalid={
|
||||
errors.newPassword && touched.newPassword
|
||||
? true
|
||||
: false
|
||||
}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
type="password"
|
||||
placeholder="New password"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<FormErrorMessage>
|
||||
{errors.newPassword}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div
|
||||
className={cx('flex', 'flex-row', 'items-center', 'gap-1')}
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
colorScheme="blue"
|
||||
disabled={isSubmitting}
|
||||
onClick={() => onClose?.()}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="solid"
|
||||
colorScheme="blue"
|
||||
isLoading={isSubmitting}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountChangePassword
|
142
Voltaserve/ui/src/components/account/account-delete.tsx
Normal file
@ -0,0 +1,142 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
Input,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
} from '@chakra-ui/react'
|
||||
import {
|
||||
Field,
|
||||
FieldAttributes,
|
||||
FieldProps,
|
||||
Form,
|
||||
Formik,
|
||||
FormikHelpers,
|
||||
} from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import cx from 'classnames'
|
||||
import UserAPI from '@/client/idp/user'
|
||||
|
||||
export type AccountDeleteProps = {
|
||||
open: boolean
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
type FormValues = {
|
||||
password: string
|
||||
}
|
||||
|
||||
const AccountDelete = ({ open, onClose }: AccountDeleteProps) => {
|
||||
const navigate = useNavigate()
|
||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||
const formSchema = Yup.object().shape({
|
||||
password: Yup.string().required('Password is required'),
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setIsModalOpen(open)
|
||||
}, [open])
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (
|
||||
{ password }: FormValues,
|
||||
{ setSubmitting }: FormikHelpers<FormValues>,
|
||||
) => {
|
||||
setSubmitting(true)
|
||||
try {
|
||||
await UserAPI.delete({ password })
|
||||
navigate('/sign-in')
|
||||
onClose?.()
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
},
|
||||
[navigate, onClose],
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => onClose?.()}
|
||||
closeOnOverlayClick={false}
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Delete Account</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<Formik
|
||||
initialValues={{ password: '' }}
|
||||
validationSchema={formSchema}
|
||||
validateOnBlur={false}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({ errors, touched, isSubmitting }) => (
|
||||
<Form>
|
||||
<ModalBody>
|
||||
<div className={cx('flex', 'flex-col', 'items-start', 'gap-1')}>
|
||||
<span>
|
||||
Are you sure you would like to delete your account
|
||||
permanently?
|
||||
</span>
|
||||
<span className={cx('font-semibold')}>
|
||||
Type your password to confirm:
|
||||
</span>
|
||||
<Field name="password">
|
||||
{({ field }: FieldAttributes<FieldProps>) => (
|
||||
<FormControl
|
||||
isInvalid={
|
||||
errors.password && touched.password ? true : false
|
||||
}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<FormErrorMessage>{errors.password}</FormErrorMessage>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div
|
||||
className={cx('flex', 'flex-row', 'items-center', 'gap-1')}
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
colorScheme="blue"
|
||||
disabled={isSubmitting}
|
||||
onClick={() => onClose?.()}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="solid"
|
||||
colorScheme="red"
|
||||
isLoading={isSubmitting}
|
||||
>
|
||||
Delete Permanently
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountDelete
|
139
Voltaserve/ui/src/components/account/account-edit-email.tsx
Normal file
@ -0,0 +1,139 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
Input,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
} from '@chakra-ui/react'
|
||||
import { useSWRConfig } from 'swr'
|
||||
import {
|
||||
Field,
|
||||
FieldAttributes,
|
||||
FieldProps,
|
||||
Form,
|
||||
Formik,
|
||||
FormikHelpers,
|
||||
} from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import cx from 'classnames'
|
||||
import UserAPI, { User } from '@/client/idp/user'
|
||||
|
||||
export type AccountEditEmailProps = {
|
||||
open: boolean
|
||||
user: User
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
type FormValues = {
|
||||
email: string
|
||||
}
|
||||
|
||||
const AccountEditEmail = ({ open, user, onClose }: AccountEditEmailProps) => {
|
||||
const { mutate } = useSWRConfig()
|
||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||
const formSchema = Yup.object().shape({
|
||||
email: Yup.string()
|
||||
.required('Email is required')
|
||||
.email('Must be a valid email')
|
||||
.max(255),
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setIsModalOpen(open)
|
||||
}, [open])
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (
|
||||
{ email }: FormValues,
|
||||
{ setSubmitting }: FormikHelpers<FormValues>,
|
||||
) => {
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const result = await UserAPI.updateEmailRequest({
|
||||
email,
|
||||
})
|
||||
mutate(`/user`, result)
|
||||
setSubmitting(false)
|
||||
onClose?.()
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
},
|
||||
[onClose, mutate],
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => onClose?.()}
|
||||
closeOnOverlayClick={false}
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Edit Email</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<Formik
|
||||
enableReinitialize={true}
|
||||
initialValues={{ email: user?.pendingEmail || user?.email }}
|
||||
validationSchema={formSchema}
|
||||
validateOnBlur={false}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({ errors, touched, isSubmitting }) => (
|
||||
<Form>
|
||||
<ModalBody>
|
||||
<Field name="email">
|
||||
{({ field }: FieldAttributes<FieldProps>) => (
|
||||
<FormControl
|
||||
isInvalid={errors.email && touched.email ? true : false}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="Email"
|
||||
disabled={isSubmitting}
|
||||
autoFocus
|
||||
/>
|
||||
<FormErrorMessage>{errors.email}</FormErrorMessage>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div
|
||||
className={cx('flex', 'flex-row', 'items-center', 'gap-1')}
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
colorScheme="blue"
|
||||
disabled={isSubmitting}
|
||||
onClick={() => onClose?.()}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="solid"
|
||||
colorScheme="blue"
|
||||
isLoading={isSubmitting}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountEditEmail
|
142
Voltaserve/ui/src/components/account/account-edit-full-name.tsx
Normal file
@ -0,0 +1,142 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
Input,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
} from '@chakra-ui/react'
|
||||
import { useSWRConfig } from 'swr'
|
||||
import {
|
||||
Field,
|
||||
FieldAttributes,
|
||||
FieldProps,
|
||||
Form,
|
||||
Formik,
|
||||
FormikHelpers,
|
||||
} from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import cx from 'classnames'
|
||||
import UserAPI, { User } from '@/client/idp/user'
|
||||
|
||||
export type AccountEditFullNameProps = {
|
||||
open: boolean
|
||||
user: User
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
type FormValues = {
|
||||
fullName: string
|
||||
}
|
||||
|
||||
const AccountEditFullName = ({
|
||||
open,
|
||||
user,
|
||||
onClose,
|
||||
}: AccountEditFullNameProps) => {
|
||||
const { mutate } = useSWRConfig()
|
||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||
const formSchema = Yup.object().shape({
|
||||
fullName: Yup.string().required('Full name is required').max(255),
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setIsModalOpen(open)
|
||||
}, [open])
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (
|
||||
{ fullName }: FormValues,
|
||||
{ setSubmitting }: FormikHelpers<FormValues>,
|
||||
) => {
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const result = await UserAPI.updateFullName({
|
||||
fullName,
|
||||
})
|
||||
mutate(`/user`, result)
|
||||
setSubmitting(false)
|
||||
onClose?.()
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
},
|
||||
[onClose, mutate],
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => onClose?.()}
|
||||
closeOnOverlayClick={false}
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Edit Full Name</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<Formik
|
||||
enableReinitialize={true}
|
||||
initialValues={{ fullName: user.fullName }}
|
||||
validationSchema={formSchema}
|
||||
validateOnBlur={false}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({ errors, touched, isSubmitting }) => (
|
||||
<Form>
|
||||
<ModalBody>
|
||||
<Field name="fullName">
|
||||
{({ field }: FieldAttributes<FieldProps>) => (
|
||||
<FormControl
|
||||
isInvalid={
|
||||
errors.fullName && touched.fullName ? true : false
|
||||
}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="Full name"
|
||||
disabled={isSubmitting}
|
||||
autoFocus
|
||||
/>
|
||||
<FormErrorMessage>{errors.fullName}</FormErrorMessage>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div
|
||||
className={cx('flex', 'flex-row', 'items-center', 'gap-1')}
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
colorScheme="blue"
|
||||
disabled={isSubmitting}
|
||||
onClick={() => onClose?.()}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="solid"
|
||||
colorScheme="blue"
|
||||
isLoading={isSubmitting}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountEditFullName
|
@ -0,0 +1,121 @@
|
||||
import { ChangeEvent, useCallback, useRef, useState } from 'react'
|
||||
import { IconButton, Image } from '@chakra-ui/react'
|
||||
import cx from 'classnames'
|
||||
import { IconEdit } from '@/lib'
|
||||
|
||||
export type EditPictureUploadProps = {
|
||||
name: string
|
||||
initialValue?: string
|
||||
disabled: boolean
|
||||
onChange: (event: any) => void
|
||||
}
|
||||
|
||||
const EditPictureUpload = ({
|
||||
name,
|
||||
initialValue,
|
||||
onChange,
|
||||
disabled,
|
||||
}: EditPictureUploadProps) => {
|
||||
const [src, setSrc] = useState<string>()
|
||||
const hiddenInput = useRef<HTMLInputElement>(null)
|
||||
|
||||
const handleFileChange = useCallback(
|
||||
(changeEvent: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!changeEvent.target.files || changeEvent.target.files.length === 0) {
|
||||
return
|
||||
}
|
||||
const file = changeEvent.target.files.item(0)
|
||||
if (!file) {
|
||||
return
|
||||
}
|
||||
const reader = new FileReader()
|
||||
reader.onload = (readerEvent: ProgressEvent<FileReader>) => {
|
||||
if (
|
||||
readerEvent.target?.result &&
|
||||
typeof readerEvent.target.result === 'string'
|
||||
) {
|
||||
setSrc(readerEvent.target.result)
|
||||
}
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
onChange?.(changeEvent)
|
||||
},
|
||||
[onChange],
|
||||
)
|
||||
|
||||
const handleEdit = useCallback(() => {
|
||||
if (!disabled && hiddenInput.current) {
|
||||
hiddenInput.current.click()
|
||||
}
|
||||
}, [disabled, hiddenInput])
|
||||
|
||||
return (
|
||||
<div className={cx('flex', 'flex-col', 'items-center', 'gap-1')}>
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'rounded',
|
||||
'border',
|
||||
'border-dashed',
|
||||
'border-blue-600',
|
||||
'dark:border-blue-200',
|
||||
)}
|
||||
>
|
||||
{src || initialValue ? (
|
||||
<div className={cx('relative', 'w-[400px]', 'h-[160px]')}>
|
||||
<Image
|
||||
src={src || initialValue || ''}
|
||||
className={cx(
|
||||
'rounded',
|
||||
'w-[400px]',
|
||||
'h-[160px]',
|
||||
'object-cover',
|
||||
)}
|
||||
alt="Account picture"
|
||||
/>
|
||||
<IconButton
|
||||
icon={<IconEdit />}
|
||||
variant="solid-gray"
|
||||
className={cx(
|
||||
'top-[10px]',
|
||||
'right-[5px]',
|
||||
'absolute',
|
||||
'z-[1000]',
|
||||
)}
|
||||
aria-label=""
|
||||
disabled={disabled}
|
||||
onClick={handleEdit}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'cursor-pointer',
|
||||
'w-[400px]',
|
||||
'h-[160px]',
|
||||
)}
|
||||
onClick={handleEdit}
|
||||
>
|
||||
<span className={cx('text-blue-600', 'dark:text-blue-200')}>
|
||||
Browse
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<input
|
||||
ref={hiddenInput}
|
||||
className={cx('hidden')}
|
||||
type="file"
|
||||
name={name}
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditPictureUpload
|
186
Voltaserve/ui/src/components/account/edit-picture/index.tsx
Normal file
@ -0,0 +1,186 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
} from '@chakra-ui/react'
|
||||
import { useSWRConfig } from 'swr'
|
||||
import {
|
||||
Field,
|
||||
FieldAttributes,
|
||||
FieldProps,
|
||||
Form,
|
||||
Formik,
|
||||
FormikHelpers,
|
||||
} from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import cx from 'classnames'
|
||||
import UserAPI, { User } from '@/client/idp/user'
|
||||
import EditPictureUpload from './edit-picture-upload'
|
||||
|
||||
export type AccountEditPictureProps = {
|
||||
open: boolean
|
||||
user: User
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
type FormValues = {
|
||||
picture: any
|
||||
}
|
||||
|
||||
const AccountEditPicture = ({
|
||||
open,
|
||||
user,
|
||||
onClose,
|
||||
}: AccountEditPictureProps) => {
|
||||
const { mutate } = useSWRConfig()
|
||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||
const [deletionInProgress, setDeletionInProgress] = useState(false)
|
||||
const formSchema = Yup.object().shape({
|
||||
picture: Yup.mixed()
|
||||
.required()
|
||||
.test(
|
||||
'fileSize',
|
||||
'Image is too big, should be less than 3 MB',
|
||||
(value: any) => value === null || (value && value.size <= 3000000),
|
||||
)
|
||||
.test(
|
||||
'fileType',
|
||||
'Unsupported file format',
|
||||
(value: any) =>
|
||||
value === null ||
|
||||
(value &&
|
||||
['image/jpg', 'image/jpeg', 'image/gif', 'image/png'].includes(
|
||||
value.type,
|
||||
)),
|
||||
),
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setIsModalOpen(open)
|
||||
}, [open])
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (
|
||||
{ picture }: FormValues,
|
||||
{ setSubmitting }: FormikHelpers<FormValues>,
|
||||
) => {
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const result = await UserAPI.updatePicture(picture)
|
||||
mutate(`/user`, result)
|
||||
setSubmitting(false)
|
||||
onClose?.()
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
},
|
||||
[onClose, mutate],
|
||||
)
|
||||
|
||||
const handleDelete = useCallback(async () => {
|
||||
try {
|
||||
setDeletionInProgress(true)
|
||||
const result = await UserAPI.deletePicture()
|
||||
mutate(`/user`, result)
|
||||
onClose?.()
|
||||
} finally {
|
||||
setDeletionInProgress(false)
|
||||
}
|
||||
}, [onClose, mutate])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => onClose?.()}
|
||||
closeOnOverlayClick={false}
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Edit Picture</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<Formik
|
||||
enableReinitialize={true}
|
||||
initialValues={{
|
||||
picture: user.picture,
|
||||
}}
|
||||
validationSchema={formSchema}
|
||||
validateOnBlur={false}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({ errors, touched, isSubmitting, setFieldValue, values }) => (
|
||||
<Form>
|
||||
<ModalBody>
|
||||
<div
|
||||
className={cx('flex', 'flex-col', 'items-center', 'gap-1')}
|
||||
>
|
||||
<Field name="picture">
|
||||
{({ field }: FieldAttributes<FieldProps>) => (
|
||||
<FormControl
|
||||
isInvalid={
|
||||
errors.picture && touched.picture ? true : false
|
||||
}
|
||||
>
|
||||
<EditPictureUpload
|
||||
{...field}
|
||||
initialValue={user.picture}
|
||||
disabled={isSubmitting}
|
||||
onChange={(e) =>
|
||||
setFieldValue('picture', e.target.files[0])
|
||||
}
|
||||
/>
|
||||
<FormErrorMessage>{errors.picture}</FormErrorMessage>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div
|
||||
className={cx('flex', 'flex-row', 'items-center', 'gap-1')}
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
colorScheme="blue"
|
||||
disabled={isSubmitting}
|
||||
onClick={() => onClose?.()}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
colorScheme="red"
|
||||
isLoading={deletionInProgress}
|
||||
disabled={!user.picture}
|
||||
onClick={handleDelete}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="solid"
|
||||
colorScheme="blue"
|
||||
disabled={isSubmitting || values.picture === user.picture}
|
||||
isLoading={isSubmitting}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountEditPicture
|
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 1287 403" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,-1633.87,0)">
|
||||
<g id="Brand---Glossy---Dark" serif:id="Brand - Glossy - Dark" transform="matrix(0.810414,0,0,0.725996,3307.23,378.509)">
|
||||
<rect x="-2064.82" y="-521.364" width="1588.08" height="555.099" style="fill:none;"/>
|
||||
<g transform="matrix(2.97062,0,0,3.31604,-3727.67,-5203.26)">
|
||||
<path d="M706.268,1523.46C704.78,1523.46 703.628,1522.66 702.812,1521.08L687.548,1488.9C687.164,1488.08 687.128,1487.28 687.44,1486.48C687.752,1485.69 688.364,1485.08 689.276,1484.65C690.092,1484.22 690.92,1484.14 691.76,1484.43C692.6,1484.72 693.236,1485.27 693.668,1486.09L707.924,1516.83L704.468,1516.83L718.58,1486.09C719.012,1485.27 719.672,1484.72 720.56,1484.43C721.448,1484.14 722.348,1484.22 723.26,1484.65C724.124,1485.03 724.7,1485.63 724.988,1486.45C725.276,1487.26 725.228,1488.08 724.844,1488.9L709.58,1521.08C708.86,1522.66 707.756,1523.46 706.268,1523.46Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M748.82,1523.82C744.932,1523.82 741.5,1522.96 738.524,1521.26C735.548,1519.56 733.208,1517.2 731.504,1514.2C729.8,1511.2 728.948,1507.76 728.948,1503.87C728.948,1499.94 729.8,1496.47 731.504,1493.47C733.208,1490.47 735.548,1488.12 738.524,1486.41C741.5,1484.71 744.932,1483.86 748.82,1483.86C752.66,1483.86 756.068,1484.71 759.044,1486.41C762.02,1488.12 764.36,1490.47 766.064,1493.47C767.768,1496.47 768.62,1499.94 768.62,1503.87C768.62,1507.76 767.78,1511.2 766.1,1514.2C764.42,1517.2 762.092,1519.56 759.116,1521.26C756.14,1522.96 752.708,1523.82 748.82,1523.82ZM748.82,1517.34C751.316,1517.34 753.524,1516.76 755.444,1515.61C757.364,1514.46 758.864,1512.87 759.944,1510.86C761.024,1508.84 761.564,1506.51 761.564,1503.87C761.564,1501.23 761.024,1498.89 759.944,1496.85C758.864,1494.81 757.364,1493.22 755.444,1492.06C753.524,1490.91 751.316,1490.34 748.82,1490.34C746.324,1490.34 744.116,1490.91 742.196,1492.06C740.276,1493.22 738.764,1494.81 737.66,1496.85C736.556,1498.89 736.004,1501.23 736.004,1503.87C736.004,1506.51 736.556,1508.84 737.66,1510.86C738.764,1512.87 740.276,1514.46 742.196,1515.61C744.116,1516.76 746.324,1517.34 748.82,1517.34Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M788.924,1523.53C786.812,1523.53 784.94,1522.95 783.308,1521.8C781.676,1520.65 780.404,1519.08 779.492,1517.08C778.58,1515.09 778.124,1512.8 778.124,1510.21L778.124,1470.9C778.124,1469.84 778.46,1468.98 779.132,1468.3C779.804,1467.63 780.668,1467.3 781.724,1467.3C782.78,1467.3 783.644,1467.63 784.316,1468.3C784.988,1468.98 785.324,1469.84 785.324,1470.9L785.324,1510.21C785.324,1511.98 785.66,1513.45 786.332,1514.6C787.004,1515.75 787.868,1516.33 788.924,1516.33L790.724,1516.33C791.684,1516.33 792.464,1516.66 793.064,1517.34C793.664,1518.01 793.964,1518.87 793.964,1519.93C793.964,1520.98 793.508,1521.85 792.596,1522.52C791.684,1523.19 790.508,1523.53 789.068,1523.53L788.924,1523.53Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M815.492,1523.53C813.092,1523.53 810.932,1522.92 809.012,1521.69C807.092,1520.47 805.58,1518.8 804.476,1516.69C803.372,1514.58 802.82,1512.2 802.82,1509.56L802.82,1475.22C802.82,1474.16 803.156,1473.3 803.828,1472.62C804.5,1471.95 805.364,1471.62 806.42,1471.62C807.476,1471.62 808.34,1471.95 809.012,1472.62C809.684,1473.3 810.02,1474.16 810.02,1475.22L810.02,1509.56C810.02,1511.48 810.548,1513.09 811.604,1514.38C812.66,1515.68 813.956,1516.33 815.492,1516.33L817.94,1516.33C818.804,1516.33 819.536,1516.66 820.136,1517.34C820.736,1518.01 821.036,1518.87 821.036,1519.93C821.036,1520.98 820.64,1521.85 819.848,1522.52C819.056,1523.19 818.036,1523.53 816.788,1523.53L815.492,1523.53ZM799.508,1491.78C798.5,1491.78 797.684,1491.49 797.06,1490.91C796.436,1490.34 796.124,1489.62 796.124,1488.75C796.124,1487.79 796.436,1487.02 797.06,1486.45C797.684,1485.87 798.5,1485.58 799.508,1485.58L816.14,1485.58C817.148,1485.58 817.964,1485.87 818.588,1486.45C819.212,1487.02 819.524,1487.79 819.524,1488.75C819.524,1489.62 819.212,1490.34 818.588,1490.91C817.964,1491.49 817.148,1491.78 816.14,1491.78L799.508,1491.78Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M846.452,1523.82C842.948,1523.82 839.804,1522.94 837.02,1521.19C834.236,1519.44 832.04,1517.06 830.432,1514.06C828.824,1511.06 828.02,1507.66 828.02,1503.87C828.02,1500.08 828.896,1496.67 830.648,1493.65C832.4,1490.62 834.776,1488.24 837.776,1486.48C840.776,1484.73 844.148,1483.86 847.892,1483.86C851.636,1483.86 854.996,1484.73 857.972,1486.48C860.948,1488.24 863.312,1490.62 865.064,1493.65C866.816,1496.67 867.692,1500.08 867.692,1503.87L864.884,1503.87C864.884,1507.66 864.08,1511.06 862.472,1514.06C860.864,1517.06 858.668,1519.44 855.884,1521.19C853.1,1522.94 849.956,1523.82 846.452,1523.82ZM847.892,1517.34C850.34,1517.34 852.524,1516.75 854.444,1515.57C856.364,1514.4 857.876,1512.79 858.98,1510.75C860.084,1508.71 860.636,1506.42 860.636,1503.87C860.636,1501.28 860.084,1498.96 858.98,1496.92C857.876,1494.88 856.364,1493.28 854.444,1492.1C852.524,1490.92 850.34,1490.34 847.892,1490.34C845.492,1490.34 843.32,1490.92 841.376,1492.1C839.432,1493.28 837.896,1494.88 836.768,1496.92C835.64,1498.96 835.076,1501.28 835.076,1503.87C835.076,1506.42 835.64,1508.71 836.768,1510.75C837.896,1512.79 839.432,1514.4 841.376,1515.57C843.32,1516.75 845.492,1517.34 847.892,1517.34ZM864.02,1523.6C862.964,1523.6 862.088,1523.25 861.392,1522.56C860.696,1521.86 860.348,1520.98 860.348,1519.93L860.348,1508.91L861.716,1501.28L867.692,1503.87L867.692,1519.93C867.692,1520.98 867.344,1521.86 866.648,1522.56C865.952,1523.25 865.076,1523.6 864.02,1523.6Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M892.964,1523.82C889.604,1523.82 886.496,1523.3 883.64,1522.27C880.784,1521.24 878.564,1519.93 876.98,1518.34C876.26,1517.58 875.96,1516.7 876.08,1515.72C876.2,1514.73 876.668,1513.93 877.484,1513.3C878.444,1512.54 879.392,1512.21 880.328,1512.33C881.264,1512.45 882.068,1512.87 882.74,1513.59C883.556,1514.5 884.864,1515.36 886.664,1516.15C888.464,1516.94 890.468,1517.34 892.676,1517.34C895.46,1517.34 897.584,1516.88 899.048,1515.97C900.512,1515.06 901.268,1513.88 901.316,1512.44C901.364,1511 900.68,1509.75 899.264,1508.7C897.848,1507.64 895.268,1506.78 891.524,1506.1C886.676,1505.14 883.16,1503.7 880.976,1501.78C878.792,1499.86 877.7,1497.51 877.7,1494.73C877.7,1492.28 878.42,1490.25 879.86,1488.64C881.3,1487.04 883.148,1485.84 885.404,1485.04C887.66,1484.25 890.012,1483.86 892.46,1483.86C895.628,1483.86 898.436,1484.36 900.884,1485.37C903.332,1486.38 905.276,1487.77 906.716,1489.54C907.388,1490.31 907.7,1491.12 907.652,1491.96C907.604,1492.8 907.196,1493.5 906.428,1494.08C905.66,1494.61 904.748,1494.78 903.692,1494.58C902.636,1494.39 901.748,1493.96 901.028,1493.29C899.828,1492.14 898.532,1491.34 897.14,1490.91C895.748,1490.48 894.14,1490.26 892.316,1490.26C890.204,1490.26 888.416,1490.62 886.952,1491.34C885.488,1492.06 884.756,1493.12 884.756,1494.51C884.756,1495.38 884.984,1496.16 885.44,1496.85C885.896,1497.55 886.796,1498.17 888.14,1498.72C889.484,1499.28 891.452,1499.82 894.044,1500.34C897.644,1501.06 900.488,1501.98 902.576,1503.08C904.664,1504.18 906.164,1505.47 907.076,1506.93C907.988,1508.4 908.444,1510.09 908.444,1512.01C908.444,1514.22 907.856,1516.21 906.68,1517.98C905.504,1519.76 903.776,1521.18 901.496,1522.23C899.216,1523.29 896.372,1523.82 892.964,1523.82Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M936.236,1523.82C932.252,1523.82 928.712,1522.96 925.616,1521.26C922.52,1519.56 920.096,1517.2 918.344,1514.2C916.592,1511.2 915.716,1507.76 915.716,1503.87C915.716,1499.94 916.544,1496.47 918.2,1493.47C919.856,1490.47 922.148,1488.12 925.076,1486.41C928.004,1484.71 931.364,1483.86 935.156,1483.86C938.9,1483.86 942.116,1484.68 944.804,1486.34C947.492,1488 949.544,1490.28 950.96,1493.18C952.376,1496.08 953.084,1499.43 953.084,1503.22C953.084,1504.14 952.772,1504.89 952.148,1505.49C951.524,1506.09 950.732,1506.39 949.772,1506.39L920.756,1506.39L920.756,1500.63L949.556,1500.63L946.604,1502.65C946.556,1500.25 946.076,1498.1 945.164,1496.2C944.252,1494.31 942.956,1492.81 941.276,1491.7C939.596,1490.6 937.556,1490.05 935.156,1490.05C932.42,1490.05 930.08,1490.65 928.136,1491.85C926.192,1493.05 924.716,1494.69 923.708,1496.78C922.7,1498.87 922.196,1501.23 922.196,1503.87C922.196,1506.51 922.796,1508.86 923.996,1510.93C925.196,1512.99 926.852,1514.62 928.964,1515.82C931.076,1517.02 933.5,1517.62 936.236,1517.62C937.724,1517.62 939.248,1517.35 940.808,1516.8C942.368,1516.24 943.628,1515.61 944.588,1514.89C945.308,1514.36 946.088,1514.08 946.928,1514.06C947.768,1514.04 948.5,1514.29 949.124,1514.82C949.94,1515.54 950.372,1516.33 950.42,1517.19C950.468,1518.06 950.084,1518.8 949.268,1519.42C947.636,1520.72 945.608,1521.78 943.184,1522.59C940.76,1523.41 938.444,1523.82 936.236,1523.82Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M965.972,1498.9C965.972,1496.02 966.68,1493.44 968.096,1491.16C969.512,1488.88 971.42,1487.07 973.82,1485.73C976.22,1484.38 978.86,1483.71 981.74,1483.71C984.62,1483.71 986.768,1484.18 988.184,1485.12C989.6,1486.05 990.14,1487.17 989.804,1488.46C989.66,1489.14 989.384,1489.65 988.976,1490.01C988.568,1490.37 988.1,1490.6 987.572,1490.7C987.044,1490.79 986.468,1490.77 985.844,1490.62C982.772,1490 980.012,1489.95 977.564,1490.48C975.116,1491.01 973.184,1491.99 971.768,1493.43C970.352,1494.87 969.644,1496.7 969.644,1498.9L965.972,1498.9ZM966.044,1523.53C964.892,1523.53 964.004,1523.23 963.38,1522.63C962.756,1522.03 962.444,1521.13 962.444,1519.93L962.444,1487.74C962.444,1486.59 962.756,1485.7 963.38,1485.08C964.004,1484.46 964.892,1484.14 966.044,1484.14C967.244,1484.14 968.144,1484.44 968.744,1485.04C969.344,1485.64 969.644,1486.54 969.644,1487.74L969.644,1519.93C969.644,1521.08 969.344,1521.97 968.744,1522.59C968.144,1523.22 967.244,1523.53 966.044,1523.53Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M1010.76,1523.46C1009.27,1523.46 1008.12,1522.66 1007.3,1521.08L992.036,1488.9C991.652,1488.08 991.616,1487.28 991.928,1486.48C992.24,1485.69 992.852,1485.08 993.764,1484.65C994.58,1484.22 995.408,1484.14 996.248,1484.43C997.088,1484.72 997.724,1485.27 998.156,1486.09L1012.41,1516.83L1008.96,1516.83L1023.07,1486.09C1023.5,1485.27 1024.16,1484.72 1025.05,1484.43C1025.94,1484.14 1026.84,1484.22 1027.75,1484.65C1028.61,1485.03 1029.19,1485.63 1029.48,1486.45C1029.76,1487.26 1029.72,1488.08 1029.33,1488.9L1014.07,1521.08C1013.35,1522.66 1012.24,1523.46 1010.76,1523.46Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M1053.96,1523.82C1049.97,1523.82 1046.43,1522.96 1043.34,1521.26C1040.24,1519.56 1037.82,1517.2 1036.06,1514.2C1034.31,1511.2 1033.44,1507.76 1033.44,1503.87C1033.44,1499.94 1034.26,1496.47 1035.92,1493.47C1037.58,1490.47 1039.87,1488.12 1042.8,1486.41C1045.72,1484.71 1049.08,1483.86 1052.88,1483.86C1056.62,1483.86 1059.84,1484.68 1062.52,1486.34C1065.21,1488 1067.26,1490.28 1068.68,1493.18C1070.1,1496.08 1070.8,1499.43 1070.8,1503.22C1070.8,1504.14 1070.49,1504.89 1069.87,1505.49C1069.24,1506.09 1068.45,1506.39 1067.49,1506.39L1038.48,1506.39L1038.48,1500.63L1067.28,1500.63L1064.32,1502.65C1064.28,1500.25 1063.8,1498.1 1062.88,1496.2C1061.97,1494.31 1060.68,1492.81 1059,1491.7C1057.32,1490.6 1055.28,1490.05 1052.88,1490.05C1050.14,1490.05 1047.8,1490.65 1045.86,1491.85C1043.91,1493.05 1042.44,1494.69 1041.43,1496.78C1040.42,1498.87 1039.92,1501.23 1039.92,1503.87C1039.92,1506.51 1040.52,1508.86 1041.72,1510.93C1042.92,1512.99 1044.57,1514.62 1046.68,1515.82C1048.8,1517.02 1051.22,1517.62 1053.96,1517.62C1055.44,1517.62 1056.97,1517.35 1058.53,1516.8C1060.09,1516.24 1061.35,1515.61 1062.31,1514.89C1063.03,1514.36 1063.81,1514.08 1064.65,1514.06C1065.49,1514.04 1066.22,1514.29 1066.84,1514.82C1067.66,1515.54 1068.09,1516.33 1068.14,1517.19C1068.19,1518.06 1067.8,1518.8 1066.99,1519.42C1065.36,1520.72 1063.33,1521.78 1060.9,1522.59C1058.48,1523.41 1056.16,1523.82 1053.96,1523.82Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(0.526643,0,0,0.587879,-2148.24,-513.767)">
|
||||
<g transform="matrix(1,0,0,1,735.951,511.402)">
|
||||
<path d="M0,-104.412C-0.158,-107.349 -0.612,-110.275 -1.449,-113.098C-2.362,-116.179 -3.535,-119.184 -4.931,-122.079C-4.985,-122.193 -5.041,-122.308 -5.097,-122.421C-5.228,-122.687 -5.363,-122.951 -5.498,-123.214C-7.231,-126.603 -9.219,-129.534 -12.043,-132.104C-16.201,-135.889 -21.338,-139.039 -26.458,-141.341C-34.216,-144.829 -42.774,-145.322 -51.171,-145.322C-61.669,-145.322 -155.843,-146.222 -166.559,-146.325L-166.554,-146.334C-166.554,-146.334 -120.073,-426.226 -119.996,-426.693C-118.955,-432.957 -118.006,-439.397 -117.982,-445.808C-117.972,-449.063 -118.199,-452.328 -118.796,-455.531C-119.355,-458.539 -119.806,-461.522 -121.02,-464.374C-121.165,-464.715 -121.318,-465.052 -121.481,-465.384C-123.422,-469.333 -126.557,-472.738 -130.13,-475.276C-137.152,-480.265 -148.817,-480.186 -156.233,-476.057C-162.988,-472.295 -168.569,-465.351 -172.734,-458.982C-182.516,-444.022 -192.515,-429.18 -201.853,-413.933C-205.017,-408.766 -208.051,-403.524 -211.146,-398.313C-213.341,-394.616 -215.537,-390.918 -217.732,-387.22L-225.213,-374.622C-227.974,-369.974 -230.734,-365.327 -233.494,-360.679C-236.489,-355.635 -239.484,-350.591 -242.479,-345.547C-245.679,-340.159 -248.878,-334.772 -252.077,-329.385C-255.449,-323.706 -258.821,-318.028 -262.193,-312.35C-265.706,-306.434 -269.219,-300.518 -272.732,-294.602C-276.355,-288.5 -279.979,-282.399 -283.602,-276.298C-287.304,-270.064 -291.006,-263.829 -294.708,-257.596L-305.956,-238.654C-309.721,-232.313 -313.487,-225.972 -317.252,-219.63C-321.003,-213.315 -324.753,-206.999 -328.504,-200.683C-332.208,-194.446 -335.912,-188.208 -339.616,-181.971C-343.243,-175.864 -346.869,-169.757 -350.495,-163.65C-354.013,-157.727 -357.53,-151.804 -361.047,-145.881C-364.424,-140.194 -367.801,-134.507 -371.179,-128.82C-374.384,-123.422 -377.589,-118.024 -380.795,-112.626C-383.798,-107.57 -386.8,-102.513 -389.803,-97.456C-392.572,-92.794 -395.34,-88.132 -398.108,-83.47C-400.612,-79.255 -403.115,-75.04 -405.618,-70.825C-408.959,-65.198 -412.148,-59.527 -415.561,-53.93C-418.976,-48.328 -421.629,-42.412 -424.564,-36.579C-430.102,-25.576 -433.981,-12.468 -433.308,-0C-433.149,2.937 -432.695,5.862 -431.859,8.686C-430.945,11.767 -429.772,14.772 -428.377,17.667C-428.322,17.781 -428.267,17.895 -428.211,18.009C-428.081,18.275 -427.944,18.538 -427.81,18.802C-426.076,22.191 -424.089,25.121 -421.264,27.692C-417.107,31.476 -411.969,34.627 -406.85,36.929C-399.092,40.416 -390.533,40.909 -382.137,40.909C-371.639,40.909 -277.465,41.81 -266.749,41.912L-266.754,41.922C-266.754,41.922 -313.234,321.814 -313.312,322.28C-314.352,328.545 -315.302,334.984 -315.325,341.396C-315.336,344.651 -315.108,347.915 -314.512,351.118C-313.952,354.127 -313.502,357.109 -312.288,359.962C-312.143,360.303 -311.99,360.64 -311.826,360.972C-309.886,364.921 -306.751,368.325 -303.178,370.863C-296.156,375.853 -284.491,375.774 -277.074,371.644C-270.32,367.883 -264.738,360.94 -260.574,354.569C-250.792,339.609 -240.792,324.768 -231.455,309.522C-228.291,304.355 -225.257,299.111 -222.163,293.901C-219.967,290.203 -217.771,286.506 -215.575,282.808C-213.081,278.609 -210.588,274.409 -208.095,270.21C-205.334,265.563 -202.574,260.914 -199.813,256.267C-196.818,251.223 -193.823,246.179 -190.828,241.134C-187.629,235.747 -184.43,230.359 -181.23,224.972C-177.858,219.294 -174.486,213.616 -171.115,207.938C-167.602,202.022 -164.089,196.106 -160.575,190.19C-156.952,184.088 -153.329,177.986 -149.706,171.886C-146.004,165.651 -142.302,159.417 -138.6,153.184C-134.851,146.869 -131.102,140.556 -127.352,134.241C-123.586,127.9 -119.82,121.559 -116.055,115.218C-112.305,108.902 -108.555,102.587 -104.804,96.271C-101.1,90.033 -97.396,83.796 -93.691,77.558C-90.065,71.451 -86.438,65.345 -82.813,59.238C-79.295,53.315 -75.777,47.392 -72.261,41.469C-68.884,35.781 -65.506,30.095 -62.129,24.407C-58.924,19.01 -55.718,13.611 -52.513,8.214C-49.51,3.157 -46.507,-1.899 -43.505,-6.956L-35.199,-20.942C-32.696,-25.157 -30.193,-29.372 -27.689,-33.588C-24.349,-39.215 -21.159,-44.886 -17.747,-50.482C-14.332,-56.084 -11.679,-62.001 -8.743,-67.833C-3.206,-78.837 0.674,-91.944 0,-104.412" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,550.012,53.3397)">
|
||||
<path d="M0,328.108C0.024,328.061 0.046,328.012 0.071,327.965L3.88,307.015L9.88,272.012L16.88,228.009L28.88,157.004L40.88,84.999L49.88,29.995L50.88,22.994C50.88,22.994 51.88,15.994 51.88,14.994C51.88,11.104 52.017,6.773 49.376,3.619C47.641,1.546 45.363,0 42.572,0.016C41.149,0.025 39.753,0.325 38.45,0.854L35.367,19.693C35.339,19.902 35.313,20.112 35.284,20.32C34.938,22.83 34.558,25.334 34.171,27.837C33.804,30.215 33.642,32.663 33.1,35.009C32.722,36.962 32.416,38.942 32.094,40.912C31.265,45.98 30.435,51.048 29.605,56.117L27.012,71.968C26.411,75.64 25.811,79.312 25.209,82.984C25.1,83.656 24.989,84.327 24.88,84.999L12.88,157.004L0.88,228.009L-6.12,272.012L-12.12,307.015L-15.938,328.016L-11.98,328.016C-11.98,328.016 -7.43,328.051 0,328.108" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,719.179,839.001)">
|
||||
<path d="M0,-434.473C-0.77,-438.758 -2.547,-442.987 -5.215,-446.5C-10.678,-453.693 -20.633,-457.43 -29.488,-457.43C-30.481,-457.43 -36.426,-457.47 -45.295,-457.533C-45.281,-457.497 -45.273,-457.46 -45.261,-457.424C-36.462,-457.361 -26.631,-453.631 -21.215,-446.5C-18.547,-442.987 -16.77,-438.758 -16,-434.473C-14.889,-428.287 -16.424,-422.049 -18.429,-416.214C-20.447,-410.339 -22.939,-404.635 -25.739,-399.093C-29.21,-392.222 -32.907,-385.459 -36.988,-378.93L-45.988,-363.93L-230.988,-51.93L-247.988,-23.93L-252.118,-17.613L-265.713,4.778L-275.799,20.204C-274.164,21.882 -272.118,23.06 -269.681,23.047C-266.961,23.031 -264.334,22.055 -262.322,20.222C-261.304,19.296 -260.43,18.226 -259.559,17.16C-258.371,15.706 -257.132,14.287 -256.095,12.731C-255.462,11.782 -254.83,10.833 -254.197,9.884C-252.797,7.785 -251.398,5.685 -249.998,3.586L-248.988,2.07L-231.988,-23.93L-214.988,-51.93L-29.988,-363.93L-20.988,-378.93C-16.907,-385.459 -13.21,-392.222 -9.739,-399.093C-6.939,-404.635 -4.447,-410.339 -2.429,-416.214C-0.424,-422.049 1.111,-428.287 0,-434.473" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 18 KiB |
24
Voltaserve/ui/src/components/common/brand/brand-dark.svg
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 1287 403" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,-1635.01,-566.852)">
|
||||
<g id="Brand---Dark" serif:id="Brand - Dark" transform="matrix(0.810414,0,0,0.725996,3308.37,945.36)">
|
||||
<rect x="-2064.82" y="-521.364" width="1588.08" height="555.099" style="fill:none;"/>
|
||||
<g transform="matrix(2.97062,0,0,3.31604,-3727.67,-5203.26)">
|
||||
<path d="M706.268,1523.46C704.78,1523.46 703.628,1522.66 702.812,1521.08L687.548,1488.9C687.164,1488.08 687.128,1487.28 687.44,1486.48C687.752,1485.69 688.364,1485.08 689.276,1484.65C690.092,1484.22 690.92,1484.14 691.76,1484.43C692.6,1484.72 693.236,1485.27 693.668,1486.09L707.924,1516.83L704.468,1516.83L718.58,1486.09C719.012,1485.27 719.672,1484.72 720.56,1484.43C721.448,1484.14 722.348,1484.22 723.26,1484.65C724.124,1485.03 724.7,1485.63 724.988,1486.45C725.276,1487.26 725.228,1488.08 724.844,1488.9L709.58,1521.08C708.86,1522.66 707.756,1523.46 706.268,1523.46Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M748.82,1523.82C744.932,1523.82 741.5,1522.96 738.524,1521.26C735.548,1519.56 733.208,1517.2 731.504,1514.2C729.8,1511.2 728.948,1507.76 728.948,1503.87C728.948,1499.94 729.8,1496.47 731.504,1493.47C733.208,1490.47 735.548,1488.12 738.524,1486.41C741.5,1484.71 744.932,1483.86 748.82,1483.86C752.66,1483.86 756.068,1484.71 759.044,1486.41C762.02,1488.12 764.36,1490.47 766.064,1493.47C767.768,1496.47 768.62,1499.94 768.62,1503.87C768.62,1507.76 767.78,1511.2 766.1,1514.2C764.42,1517.2 762.092,1519.56 759.116,1521.26C756.14,1522.96 752.708,1523.82 748.82,1523.82ZM748.82,1517.34C751.316,1517.34 753.524,1516.76 755.444,1515.61C757.364,1514.46 758.864,1512.87 759.944,1510.86C761.024,1508.84 761.564,1506.51 761.564,1503.87C761.564,1501.23 761.024,1498.89 759.944,1496.85C758.864,1494.81 757.364,1493.22 755.444,1492.06C753.524,1490.91 751.316,1490.34 748.82,1490.34C746.324,1490.34 744.116,1490.91 742.196,1492.06C740.276,1493.22 738.764,1494.81 737.66,1496.85C736.556,1498.89 736.004,1501.23 736.004,1503.87C736.004,1506.51 736.556,1508.84 737.66,1510.86C738.764,1512.87 740.276,1514.46 742.196,1515.61C744.116,1516.76 746.324,1517.34 748.82,1517.34Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M788.924,1523.53C786.812,1523.53 784.94,1522.95 783.308,1521.8C781.676,1520.65 780.404,1519.08 779.492,1517.08C778.58,1515.09 778.124,1512.8 778.124,1510.21L778.124,1470.9C778.124,1469.84 778.46,1468.98 779.132,1468.3C779.804,1467.63 780.668,1467.3 781.724,1467.3C782.78,1467.3 783.644,1467.63 784.316,1468.3C784.988,1468.98 785.324,1469.84 785.324,1470.9L785.324,1510.21C785.324,1511.98 785.66,1513.45 786.332,1514.6C787.004,1515.75 787.868,1516.33 788.924,1516.33L790.724,1516.33C791.684,1516.33 792.464,1516.66 793.064,1517.34C793.664,1518.01 793.964,1518.87 793.964,1519.93C793.964,1520.98 793.508,1521.85 792.596,1522.52C791.684,1523.19 790.508,1523.53 789.068,1523.53L788.924,1523.53Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M815.492,1523.53C813.092,1523.53 810.932,1522.92 809.012,1521.69C807.092,1520.47 805.58,1518.8 804.476,1516.69C803.372,1514.58 802.82,1512.2 802.82,1509.56L802.82,1475.22C802.82,1474.16 803.156,1473.3 803.828,1472.62C804.5,1471.95 805.364,1471.62 806.42,1471.62C807.476,1471.62 808.34,1471.95 809.012,1472.62C809.684,1473.3 810.02,1474.16 810.02,1475.22L810.02,1509.56C810.02,1511.48 810.548,1513.09 811.604,1514.38C812.66,1515.68 813.956,1516.33 815.492,1516.33L817.94,1516.33C818.804,1516.33 819.536,1516.66 820.136,1517.34C820.736,1518.01 821.036,1518.87 821.036,1519.93C821.036,1520.98 820.64,1521.85 819.848,1522.52C819.056,1523.19 818.036,1523.53 816.788,1523.53L815.492,1523.53ZM799.508,1491.78C798.5,1491.78 797.684,1491.49 797.06,1490.91C796.436,1490.34 796.124,1489.62 796.124,1488.75C796.124,1487.79 796.436,1487.02 797.06,1486.45C797.684,1485.87 798.5,1485.58 799.508,1485.58L816.14,1485.58C817.148,1485.58 817.964,1485.87 818.588,1486.45C819.212,1487.02 819.524,1487.79 819.524,1488.75C819.524,1489.62 819.212,1490.34 818.588,1490.91C817.964,1491.49 817.148,1491.78 816.14,1491.78L799.508,1491.78Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M846.452,1523.82C842.948,1523.82 839.804,1522.94 837.02,1521.19C834.236,1519.44 832.04,1517.06 830.432,1514.06C828.824,1511.06 828.02,1507.66 828.02,1503.87C828.02,1500.08 828.896,1496.67 830.648,1493.65C832.4,1490.62 834.776,1488.24 837.776,1486.48C840.776,1484.73 844.148,1483.86 847.892,1483.86C851.636,1483.86 854.996,1484.73 857.972,1486.48C860.948,1488.24 863.312,1490.62 865.064,1493.65C866.816,1496.67 867.692,1500.08 867.692,1503.87L864.884,1503.87C864.884,1507.66 864.08,1511.06 862.472,1514.06C860.864,1517.06 858.668,1519.44 855.884,1521.19C853.1,1522.94 849.956,1523.82 846.452,1523.82ZM847.892,1517.34C850.34,1517.34 852.524,1516.75 854.444,1515.57C856.364,1514.4 857.876,1512.79 858.98,1510.75C860.084,1508.71 860.636,1506.42 860.636,1503.87C860.636,1501.28 860.084,1498.96 858.98,1496.92C857.876,1494.88 856.364,1493.28 854.444,1492.1C852.524,1490.92 850.34,1490.34 847.892,1490.34C845.492,1490.34 843.32,1490.92 841.376,1492.1C839.432,1493.28 837.896,1494.88 836.768,1496.92C835.64,1498.96 835.076,1501.28 835.076,1503.87C835.076,1506.42 835.64,1508.71 836.768,1510.75C837.896,1512.79 839.432,1514.4 841.376,1515.57C843.32,1516.75 845.492,1517.34 847.892,1517.34ZM864.02,1523.6C862.964,1523.6 862.088,1523.25 861.392,1522.56C860.696,1521.86 860.348,1520.98 860.348,1519.93L860.348,1508.91L861.716,1501.28L867.692,1503.87L867.692,1519.93C867.692,1520.98 867.344,1521.86 866.648,1522.56C865.952,1523.25 865.076,1523.6 864.02,1523.6Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M892.964,1523.82C889.604,1523.82 886.496,1523.3 883.64,1522.27C880.784,1521.24 878.564,1519.93 876.98,1518.34C876.26,1517.58 875.96,1516.7 876.08,1515.72C876.2,1514.73 876.668,1513.93 877.484,1513.3C878.444,1512.54 879.392,1512.21 880.328,1512.33C881.264,1512.45 882.068,1512.87 882.74,1513.59C883.556,1514.5 884.864,1515.36 886.664,1516.15C888.464,1516.94 890.468,1517.34 892.676,1517.34C895.46,1517.34 897.584,1516.88 899.048,1515.97C900.512,1515.06 901.268,1513.88 901.316,1512.44C901.364,1511 900.68,1509.75 899.264,1508.7C897.848,1507.64 895.268,1506.78 891.524,1506.1C886.676,1505.14 883.16,1503.7 880.976,1501.78C878.792,1499.86 877.7,1497.51 877.7,1494.73C877.7,1492.28 878.42,1490.25 879.86,1488.64C881.3,1487.04 883.148,1485.84 885.404,1485.04C887.66,1484.25 890.012,1483.86 892.46,1483.86C895.628,1483.86 898.436,1484.36 900.884,1485.37C903.332,1486.38 905.276,1487.77 906.716,1489.54C907.388,1490.31 907.7,1491.12 907.652,1491.96C907.604,1492.8 907.196,1493.5 906.428,1494.08C905.66,1494.61 904.748,1494.78 903.692,1494.58C902.636,1494.39 901.748,1493.96 901.028,1493.29C899.828,1492.14 898.532,1491.34 897.14,1490.91C895.748,1490.48 894.14,1490.26 892.316,1490.26C890.204,1490.26 888.416,1490.62 886.952,1491.34C885.488,1492.06 884.756,1493.12 884.756,1494.51C884.756,1495.38 884.984,1496.16 885.44,1496.85C885.896,1497.55 886.796,1498.17 888.14,1498.72C889.484,1499.28 891.452,1499.82 894.044,1500.34C897.644,1501.06 900.488,1501.98 902.576,1503.08C904.664,1504.18 906.164,1505.47 907.076,1506.93C907.988,1508.4 908.444,1510.09 908.444,1512.01C908.444,1514.22 907.856,1516.21 906.68,1517.98C905.504,1519.76 903.776,1521.18 901.496,1522.23C899.216,1523.29 896.372,1523.82 892.964,1523.82Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M936.236,1523.82C932.252,1523.82 928.712,1522.96 925.616,1521.26C922.52,1519.56 920.096,1517.2 918.344,1514.2C916.592,1511.2 915.716,1507.76 915.716,1503.87C915.716,1499.94 916.544,1496.47 918.2,1493.47C919.856,1490.47 922.148,1488.12 925.076,1486.41C928.004,1484.71 931.364,1483.86 935.156,1483.86C938.9,1483.86 942.116,1484.68 944.804,1486.34C947.492,1488 949.544,1490.28 950.96,1493.18C952.376,1496.08 953.084,1499.43 953.084,1503.22C953.084,1504.14 952.772,1504.89 952.148,1505.49C951.524,1506.09 950.732,1506.39 949.772,1506.39L920.756,1506.39L920.756,1500.63L949.556,1500.63L946.604,1502.65C946.556,1500.25 946.076,1498.1 945.164,1496.2C944.252,1494.31 942.956,1492.81 941.276,1491.7C939.596,1490.6 937.556,1490.05 935.156,1490.05C932.42,1490.05 930.08,1490.65 928.136,1491.85C926.192,1493.05 924.716,1494.69 923.708,1496.78C922.7,1498.87 922.196,1501.23 922.196,1503.87C922.196,1506.51 922.796,1508.86 923.996,1510.93C925.196,1512.99 926.852,1514.62 928.964,1515.82C931.076,1517.02 933.5,1517.62 936.236,1517.62C937.724,1517.62 939.248,1517.35 940.808,1516.8C942.368,1516.24 943.628,1515.61 944.588,1514.89C945.308,1514.36 946.088,1514.08 946.928,1514.06C947.768,1514.04 948.5,1514.29 949.124,1514.82C949.94,1515.54 950.372,1516.33 950.42,1517.19C950.468,1518.06 950.084,1518.8 949.268,1519.42C947.636,1520.72 945.608,1521.78 943.184,1522.59C940.76,1523.41 938.444,1523.82 936.236,1523.82Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M965.972,1498.9C965.972,1496.02 966.68,1493.44 968.096,1491.16C969.512,1488.88 971.42,1487.07 973.82,1485.73C976.22,1484.38 978.86,1483.71 981.74,1483.71C984.62,1483.71 986.768,1484.18 988.184,1485.12C989.6,1486.05 990.14,1487.17 989.804,1488.46C989.66,1489.14 989.384,1489.65 988.976,1490.01C988.568,1490.37 988.1,1490.6 987.572,1490.7C987.044,1490.79 986.468,1490.77 985.844,1490.62C982.772,1490 980.012,1489.95 977.564,1490.48C975.116,1491.01 973.184,1491.99 971.768,1493.43C970.352,1494.87 969.644,1496.7 969.644,1498.9L965.972,1498.9ZM966.044,1523.53C964.892,1523.53 964.004,1523.23 963.38,1522.63C962.756,1522.03 962.444,1521.13 962.444,1519.93L962.444,1487.74C962.444,1486.59 962.756,1485.7 963.38,1485.08C964.004,1484.46 964.892,1484.14 966.044,1484.14C967.244,1484.14 968.144,1484.44 968.744,1485.04C969.344,1485.64 969.644,1486.54 969.644,1487.74L969.644,1519.93C969.644,1521.08 969.344,1521.97 968.744,1522.59C968.144,1523.22 967.244,1523.53 966.044,1523.53Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M1010.76,1523.46C1009.27,1523.46 1008.12,1522.66 1007.3,1521.08L992.036,1488.9C991.652,1488.08 991.616,1487.28 991.928,1486.48C992.24,1485.69 992.852,1485.08 993.764,1484.65C994.58,1484.22 995.408,1484.14 996.248,1484.43C997.088,1484.72 997.724,1485.27 998.156,1486.09L1012.41,1516.83L1008.96,1516.83L1023.07,1486.09C1023.5,1485.27 1024.16,1484.72 1025.05,1484.43C1025.94,1484.14 1026.84,1484.22 1027.75,1484.65C1028.61,1485.03 1029.19,1485.63 1029.48,1486.45C1029.76,1487.26 1029.72,1488.08 1029.33,1488.9L1014.07,1521.08C1013.35,1522.66 1012.24,1523.46 1010.76,1523.46Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M1053.96,1523.82C1049.97,1523.82 1046.43,1522.96 1043.34,1521.26C1040.24,1519.56 1037.82,1517.2 1036.06,1514.2C1034.31,1511.2 1033.44,1507.76 1033.44,1503.87C1033.44,1499.94 1034.26,1496.47 1035.92,1493.47C1037.58,1490.47 1039.87,1488.12 1042.8,1486.41C1045.72,1484.71 1049.08,1483.86 1052.88,1483.86C1056.62,1483.86 1059.84,1484.68 1062.52,1486.34C1065.21,1488 1067.26,1490.28 1068.68,1493.18C1070.1,1496.08 1070.8,1499.43 1070.8,1503.22C1070.8,1504.14 1070.49,1504.89 1069.87,1505.49C1069.24,1506.09 1068.45,1506.39 1067.49,1506.39L1038.48,1506.39L1038.48,1500.63L1067.28,1500.63L1064.32,1502.65C1064.28,1500.25 1063.8,1498.1 1062.88,1496.2C1061.97,1494.31 1060.68,1492.81 1059,1491.7C1057.32,1490.6 1055.28,1490.05 1052.88,1490.05C1050.14,1490.05 1047.8,1490.65 1045.86,1491.85C1043.91,1493.05 1042.44,1494.69 1041.43,1496.78C1040.42,1498.87 1039.92,1501.23 1039.92,1503.87C1039.92,1506.51 1040.52,1508.86 1041.72,1510.93C1042.92,1512.99 1044.57,1514.62 1046.68,1515.82C1048.8,1517.02 1051.22,1517.62 1053.96,1517.62C1055.44,1517.62 1056.97,1517.35 1058.53,1516.8C1060.09,1516.24 1061.35,1515.61 1062.31,1514.89C1063.03,1514.36 1063.81,1514.08 1064.65,1514.06C1065.49,1514.04 1066.22,1514.29 1066.84,1514.82C1067.66,1515.54 1068.09,1516.33 1068.14,1517.19C1068.19,1518.06 1067.8,1518.8 1066.99,1519.42C1065.36,1520.72 1063.33,1521.78 1060.9,1522.59C1058.48,1523.41 1056.16,1523.82 1053.96,1523.82Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(0.526643,0,0,0.587879,-1760.91,-213.124)">
|
||||
<path d="M0,-104.412C-0.158,-107.349 -0.612,-110.275 -1.449,-113.098C-2.362,-116.179 -3.535,-119.184 -4.931,-122.079C-4.985,-122.193 -5.041,-122.308 -5.097,-122.421C-5.228,-122.687 -5.363,-122.951 -5.498,-123.214C-7.231,-126.603 -9.219,-129.534 -12.043,-132.104C-16.201,-135.889 -21.338,-139.039 -26.458,-141.341C-34.216,-144.829 -42.774,-145.322 -51.171,-145.322C-61.669,-145.322 -155.843,-146.222 -166.559,-146.325L-166.554,-146.334C-166.554,-146.334 -120.073,-426.226 -119.996,-426.693C-118.955,-432.957 -118.006,-439.397 -117.982,-445.808C-117.972,-449.063 -118.199,-452.328 -118.796,-455.531C-119.355,-458.539 -119.806,-461.522 -121.02,-464.374C-121.165,-464.715 -121.318,-465.052 -121.481,-465.384C-123.422,-469.333 -126.557,-472.738 -130.13,-475.276C-137.152,-480.265 -148.817,-480.186 -156.233,-476.057C-162.988,-472.295 -168.569,-465.351 -172.734,-458.982C-182.516,-444.022 -192.515,-429.18 -201.853,-413.933C-205.017,-408.766 -208.051,-403.524 -211.146,-398.313C-213.341,-394.616 -215.537,-390.918 -217.732,-387.22L-225.213,-374.622C-227.974,-369.974 -230.734,-365.327 -233.494,-360.679C-236.489,-355.635 -239.484,-350.591 -242.479,-345.547C-245.679,-340.159 -248.878,-334.772 -252.077,-329.385C-255.449,-323.706 -258.821,-318.028 -262.193,-312.35C-265.706,-306.434 -269.219,-300.518 -272.732,-294.602C-276.355,-288.5 -279.979,-282.399 -283.602,-276.298C-287.304,-270.064 -291.006,-263.829 -294.708,-257.596L-305.956,-238.654C-309.721,-232.313 -313.487,-225.972 -317.252,-219.63C-321.003,-213.315 -324.753,-206.999 -328.504,-200.683C-332.208,-194.446 -335.912,-188.208 -339.616,-181.971C-343.243,-175.864 -346.869,-169.757 -350.495,-163.65C-354.013,-157.727 -357.53,-151.804 -361.047,-145.881C-364.424,-140.194 -367.801,-134.507 -371.179,-128.82C-374.384,-123.422 -377.589,-118.024 -380.795,-112.626C-383.798,-107.57 -386.8,-102.513 -389.803,-97.456C-392.572,-92.794 -395.34,-88.132 -398.108,-83.47C-400.612,-79.255 -403.115,-75.04 -405.618,-70.825C-408.959,-65.198 -412.148,-59.527 -415.561,-53.93C-418.976,-48.328 -421.629,-42.412 -424.564,-36.579C-430.102,-25.576 -433.981,-12.468 -433.308,-0C-433.149,2.937 -432.695,5.862 -431.859,8.686C-430.945,11.767 -429.772,14.772 -428.377,17.667C-428.322,17.781 -428.267,17.895 -428.211,18.009C-428.081,18.275 -427.944,18.538 -427.81,18.802C-426.076,22.191 -424.089,25.121 -421.264,27.692C-417.107,31.476 -411.969,34.627 -406.85,36.929C-399.092,40.416 -390.533,40.909 -382.137,40.909C-371.639,40.909 -277.465,41.81 -266.749,41.912L-266.754,41.922C-266.754,41.922 -313.234,321.814 -313.312,322.28C-314.352,328.545 -315.302,334.984 -315.325,341.396C-315.336,344.651 -315.108,347.915 -314.512,351.118C-313.952,354.127 -313.502,357.109 -312.288,359.962C-312.143,360.303 -311.99,360.64 -311.826,360.972C-309.886,364.921 -306.751,368.325 -303.178,370.863C-296.156,375.853 -284.491,375.774 -277.074,371.644C-270.32,367.883 -264.738,360.94 -260.574,354.569C-250.792,339.609 -240.792,324.768 -231.455,309.522C-228.291,304.355 -225.257,299.111 -222.163,293.901C-219.967,290.203 -217.771,286.506 -215.575,282.808C-213.081,278.609 -210.588,274.409 -208.095,270.21C-205.334,265.563 -202.574,260.914 -199.813,256.267C-196.818,251.223 -193.823,246.179 -190.828,241.134C-187.629,235.747 -184.43,230.359 -181.23,224.972C-177.858,219.294 -174.486,213.616 -171.115,207.938C-167.602,202.022 -164.089,196.106 -160.575,190.19C-156.952,184.088 -153.329,177.986 -149.706,171.886C-146.004,165.651 -142.302,159.417 -138.6,153.184C-134.851,146.869 -131.102,140.556 -127.352,134.241C-123.586,127.9 -119.82,121.559 -116.055,115.218C-112.305,108.902 -108.555,102.587 -104.804,96.271C-101.1,90.033 -97.396,83.796 -93.691,77.558C-90.065,71.451 -86.438,65.345 -82.813,59.238C-79.295,53.315 -75.777,47.392 -72.261,41.469C-68.884,35.781 -65.506,30.095 -62.129,24.407C-58.924,19.01 -55.718,13.611 -52.513,8.214C-49.51,3.157 -46.507,-1.899 -43.505,-6.956L-35.199,-20.942C-32.696,-25.157 -30.193,-29.372 -27.689,-33.588C-24.349,-39.215 -21.159,-44.886 -17.747,-50.482C-14.332,-56.084 -11.679,-62.001 -8.743,-67.833C-3.206,-78.837 0.674,-91.944 0,-104.412" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 16 KiB |
32
Voltaserve/ui/src/components/common/brand/brand-glossy.svg
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 1287 403" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,-217.01,-1.28317)">
|
||||
<g id="Brand---Glossy" serif:id="Brand - Glossy" transform="matrix(0.810414,0,0,0.725996,1890.37,379.792)">
|
||||
<rect x="-2064.82" y="-521.364" width="1588.08" height="555.099" style="fill:none;"/>
|
||||
<g transform="matrix(2.97062,0,0,3.31604,-3727.67,-5203.26)">
|
||||
<path d="M706.268,1523.46C704.78,1523.46 703.628,1522.66 702.812,1521.08L687.548,1488.9C687.164,1488.08 687.128,1487.28 687.44,1486.48C687.752,1485.69 688.364,1485.08 689.276,1484.65C690.092,1484.22 690.92,1484.14 691.76,1484.43C692.6,1484.72 693.236,1485.27 693.668,1486.09L707.924,1516.83L704.468,1516.83L718.58,1486.09C719.012,1485.27 719.672,1484.72 720.56,1484.43C721.448,1484.14 722.348,1484.22 723.26,1484.65C724.124,1485.03 724.7,1485.63 724.988,1486.45C725.276,1487.26 725.228,1488.08 724.844,1488.9L709.58,1521.08C708.86,1522.66 707.756,1523.46 706.268,1523.46Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M748.82,1523.82C744.932,1523.82 741.5,1522.96 738.524,1521.26C735.548,1519.56 733.208,1517.2 731.504,1514.2C729.8,1511.2 728.948,1507.76 728.948,1503.87C728.948,1499.94 729.8,1496.47 731.504,1493.47C733.208,1490.47 735.548,1488.12 738.524,1486.41C741.5,1484.71 744.932,1483.86 748.82,1483.86C752.66,1483.86 756.068,1484.71 759.044,1486.41C762.02,1488.12 764.36,1490.47 766.064,1493.47C767.768,1496.47 768.62,1499.94 768.62,1503.87C768.62,1507.76 767.78,1511.2 766.1,1514.2C764.42,1517.2 762.092,1519.56 759.116,1521.26C756.14,1522.96 752.708,1523.82 748.82,1523.82ZM748.82,1517.34C751.316,1517.34 753.524,1516.76 755.444,1515.61C757.364,1514.46 758.864,1512.87 759.944,1510.86C761.024,1508.84 761.564,1506.51 761.564,1503.87C761.564,1501.23 761.024,1498.89 759.944,1496.85C758.864,1494.81 757.364,1493.22 755.444,1492.06C753.524,1490.91 751.316,1490.34 748.82,1490.34C746.324,1490.34 744.116,1490.91 742.196,1492.06C740.276,1493.22 738.764,1494.81 737.66,1496.85C736.556,1498.89 736.004,1501.23 736.004,1503.87C736.004,1506.51 736.556,1508.84 737.66,1510.86C738.764,1512.87 740.276,1514.46 742.196,1515.61C744.116,1516.76 746.324,1517.34 748.82,1517.34Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M788.924,1523.53C786.812,1523.53 784.94,1522.95 783.308,1521.8C781.676,1520.65 780.404,1519.08 779.492,1517.08C778.58,1515.09 778.124,1512.8 778.124,1510.21L778.124,1470.9C778.124,1469.84 778.46,1468.98 779.132,1468.3C779.804,1467.63 780.668,1467.3 781.724,1467.3C782.78,1467.3 783.644,1467.63 784.316,1468.3C784.988,1468.98 785.324,1469.84 785.324,1470.9L785.324,1510.21C785.324,1511.98 785.66,1513.45 786.332,1514.6C787.004,1515.75 787.868,1516.33 788.924,1516.33L790.724,1516.33C791.684,1516.33 792.464,1516.66 793.064,1517.34C793.664,1518.01 793.964,1518.87 793.964,1519.93C793.964,1520.98 793.508,1521.85 792.596,1522.52C791.684,1523.19 790.508,1523.53 789.068,1523.53L788.924,1523.53Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M815.492,1523.53C813.092,1523.53 810.932,1522.92 809.012,1521.69C807.092,1520.47 805.58,1518.8 804.476,1516.69C803.372,1514.58 802.82,1512.2 802.82,1509.56L802.82,1475.22C802.82,1474.16 803.156,1473.3 803.828,1472.62C804.5,1471.95 805.364,1471.62 806.42,1471.62C807.476,1471.62 808.34,1471.95 809.012,1472.62C809.684,1473.3 810.02,1474.16 810.02,1475.22L810.02,1509.56C810.02,1511.48 810.548,1513.09 811.604,1514.38C812.66,1515.68 813.956,1516.33 815.492,1516.33L817.94,1516.33C818.804,1516.33 819.536,1516.66 820.136,1517.34C820.736,1518.01 821.036,1518.87 821.036,1519.93C821.036,1520.98 820.64,1521.85 819.848,1522.52C819.056,1523.19 818.036,1523.53 816.788,1523.53L815.492,1523.53ZM799.508,1491.78C798.5,1491.78 797.684,1491.49 797.06,1490.91C796.436,1490.34 796.124,1489.62 796.124,1488.75C796.124,1487.79 796.436,1487.02 797.06,1486.45C797.684,1485.87 798.5,1485.58 799.508,1485.58L816.14,1485.58C817.148,1485.58 817.964,1485.87 818.588,1486.45C819.212,1487.02 819.524,1487.79 819.524,1488.75C819.524,1489.62 819.212,1490.34 818.588,1490.91C817.964,1491.49 817.148,1491.78 816.14,1491.78L799.508,1491.78Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M846.452,1523.82C842.948,1523.82 839.804,1522.94 837.02,1521.19C834.236,1519.44 832.04,1517.06 830.432,1514.06C828.824,1511.06 828.02,1507.66 828.02,1503.87C828.02,1500.08 828.896,1496.67 830.648,1493.65C832.4,1490.62 834.776,1488.24 837.776,1486.48C840.776,1484.73 844.148,1483.86 847.892,1483.86C851.636,1483.86 854.996,1484.73 857.972,1486.48C860.948,1488.24 863.312,1490.62 865.064,1493.65C866.816,1496.67 867.692,1500.08 867.692,1503.87L864.884,1503.87C864.884,1507.66 864.08,1511.06 862.472,1514.06C860.864,1517.06 858.668,1519.44 855.884,1521.19C853.1,1522.94 849.956,1523.82 846.452,1523.82ZM847.892,1517.34C850.34,1517.34 852.524,1516.75 854.444,1515.57C856.364,1514.4 857.876,1512.79 858.98,1510.75C860.084,1508.71 860.636,1506.42 860.636,1503.87C860.636,1501.28 860.084,1498.96 858.98,1496.92C857.876,1494.88 856.364,1493.28 854.444,1492.1C852.524,1490.92 850.34,1490.34 847.892,1490.34C845.492,1490.34 843.32,1490.92 841.376,1492.1C839.432,1493.28 837.896,1494.88 836.768,1496.92C835.64,1498.96 835.076,1501.28 835.076,1503.87C835.076,1506.42 835.64,1508.71 836.768,1510.75C837.896,1512.79 839.432,1514.4 841.376,1515.57C843.32,1516.75 845.492,1517.34 847.892,1517.34ZM864.02,1523.6C862.964,1523.6 862.088,1523.25 861.392,1522.56C860.696,1521.86 860.348,1520.98 860.348,1519.93L860.348,1508.91L861.716,1501.28L867.692,1503.87L867.692,1519.93C867.692,1520.98 867.344,1521.86 866.648,1522.56C865.952,1523.25 865.076,1523.6 864.02,1523.6Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M892.964,1523.82C889.604,1523.82 886.496,1523.3 883.64,1522.27C880.784,1521.24 878.564,1519.93 876.98,1518.34C876.26,1517.58 875.96,1516.7 876.08,1515.72C876.2,1514.73 876.668,1513.93 877.484,1513.3C878.444,1512.54 879.392,1512.21 880.328,1512.33C881.264,1512.45 882.068,1512.87 882.74,1513.59C883.556,1514.5 884.864,1515.36 886.664,1516.15C888.464,1516.94 890.468,1517.34 892.676,1517.34C895.46,1517.34 897.584,1516.88 899.048,1515.97C900.512,1515.06 901.268,1513.88 901.316,1512.44C901.364,1511 900.68,1509.75 899.264,1508.7C897.848,1507.64 895.268,1506.78 891.524,1506.1C886.676,1505.14 883.16,1503.7 880.976,1501.78C878.792,1499.86 877.7,1497.51 877.7,1494.73C877.7,1492.28 878.42,1490.25 879.86,1488.64C881.3,1487.04 883.148,1485.84 885.404,1485.04C887.66,1484.25 890.012,1483.86 892.46,1483.86C895.628,1483.86 898.436,1484.36 900.884,1485.37C903.332,1486.38 905.276,1487.77 906.716,1489.54C907.388,1490.31 907.7,1491.12 907.652,1491.96C907.604,1492.8 907.196,1493.5 906.428,1494.08C905.66,1494.61 904.748,1494.78 903.692,1494.58C902.636,1494.39 901.748,1493.96 901.028,1493.29C899.828,1492.14 898.532,1491.34 897.14,1490.91C895.748,1490.48 894.14,1490.26 892.316,1490.26C890.204,1490.26 888.416,1490.62 886.952,1491.34C885.488,1492.06 884.756,1493.12 884.756,1494.51C884.756,1495.38 884.984,1496.16 885.44,1496.85C885.896,1497.55 886.796,1498.17 888.14,1498.72C889.484,1499.28 891.452,1499.82 894.044,1500.34C897.644,1501.06 900.488,1501.98 902.576,1503.08C904.664,1504.18 906.164,1505.47 907.076,1506.93C907.988,1508.4 908.444,1510.09 908.444,1512.01C908.444,1514.22 907.856,1516.21 906.68,1517.98C905.504,1519.76 903.776,1521.18 901.496,1522.23C899.216,1523.29 896.372,1523.82 892.964,1523.82Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M936.236,1523.82C932.252,1523.82 928.712,1522.96 925.616,1521.26C922.52,1519.56 920.096,1517.2 918.344,1514.2C916.592,1511.2 915.716,1507.76 915.716,1503.87C915.716,1499.94 916.544,1496.47 918.2,1493.47C919.856,1490.47 922.148,1488.12 925.076,1486.41C928.004,1484.71 931.364,1483.86 935.156,1483.86C938.9,1483.86 942.116,1484.68 944.804,1486.34C947.492,1488 949.544,1490.28 950.96,1493.18C952.376,1496.08 953.084,1499.43 953.084,1503.22C953.084,1504.14 952.772,1504.89 952.148,1505.49C951.524,1506.09 950.732,1506.39 949.772,1506.39L920.756,1506.39L920.756,1500.63L949.556,1500.63L946.604,1502.65C946.556,1500.25 946.076,1498.1 945.164,1496.2C944.252,1494.31 942.956,1492.81 941.276,1491.7C939.596,1490.6 937.556,1490.05 935.156,1490.05C932.42,1490.05 930.08,1490.65 928.136,1491.85C926.192,1493.05 924.716,1494.69 923.708,1496.78C922.7,1498.87 922.196,1501.23 922.196,1503.87C922.196,1506.51 922.796,1508.86 923.996,1510.93C925.196,1512.99 926.852,1514.62 928.964,1515.82C931.076,1517.02 933.5,1517.62 936.236,1517.62C937.724,1517.62 939.248,1517.35 940.808,1516.8C942.368,1516.24 943.628,1515.61 944.588,1514.89C945.308,1514.36 946.088,1514.08 946.928,1514.06C947.768,1514.04 948.5,1514.29 949.124,1514.82C949.94,1515.54 950.372,1516.33 950.42,1517.19C950.468,1518.06 950.084,1518.8 949.268,1519.42C947.636,1520.72 945.608,1521.78 943.184,1522.59C940.76,1523.41 938.444,1523.82 936.236,1523.82Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M965.972,1498.9C965.972,1496.02 966.68,1493.44 968.096,1491.16C969.512,1488.88 971.42,1487.07 973.82,1485.73C976.22,1484.38 978.86,1483.71 981.74,1483.71C984.62,1483.71 986.768,1484.18 988.184,1485.12C989.6,1486.05 990.14,1487.17 989.804,1488.46C989.66,1489.14 989.384,1489.65 988.976,1490.01C988.568,1490.37 988.1,1490.6 987.572,1490.7C987.044,1490.79 986.468,1490.77 985.844,1490.62C982.772,1490 980.012,1489.95 977.564,1490.48C975.116,1491.01 973.184,1491.99 971.768,1493.43C970.352,1494.87 969.644,1496.7 969.644,1498.9L965.972,1498.9ZM966.044,1523.53C964.892,1523.53 964.004,1523.23 963.38,1522.63C962.756,1522.03 962.444,1521.13 962.444,1519.93L962.444,1487.74C962.444,1486.59 962.756,1485.7 963.38,1485.08C964.004,1484.46 964.892,1484.14 966.044,1484.14C967.244,1484.14 968.144,1484.44 968.744,1485.04C969.344,1485.64 969.644,1486.54 969.644,1487.74L969.644,1519.93C969.644,1521.08 969.344,1521.97 968.744,1522.59C968.144,1523.22 967.244,1523.53 966.044,1523.53Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M1010.76,1523.46C1009.27,1523.46 1008.12,1522.66 1007.3,1521.08L992.036,1488.9C991.652,1488.08 991.616,1487.28 991.928,1486.48C992.24,1485.69 992.852,1485.08 993.764,1484.65C994.58,1484.22 995.408,1484.14 996.248,1484.43C997.088,1484.72 997.724,1485.27 998.156,1486.09L1012.41,1516.83L1008.96,1516.83L1023.07,1486.09C1023.5,1485.27 1024.16,1484.72 1025.05,1484.43C1025.94,1484.14 1026.84,1484.22 1027.75,1484.65C1028.61,1485.03 1029.19,1485.63 1029.48,1486.45C1029.76,1487.26 1029.72,1488.08 1029.33,1488.9L1014.07,1521.08C1013.35,1522.66 1012.24,1523.46 1010.76,1523.46Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M1053.96,1523.82C1049.97,1523.82 1046.43,1522.96 1043.34,1521.26C1040.24,1519.56 1037.82,1517.2 1036.06,1514.2C1034.31,1511.2 1033.44,1507.76 1033.44,1503.87C1033.44,1499.94 1034.26,1496.47 1035.92,1493.47C1037.58,1490.47 1039.87,1488.12 1042.8,1486.41C1045.72,1484.71 1049.08,1483.86 1052.88,1483.86C1056.62,1483.86 1059.84,1484.68 1062.52,1486.34C1065.21,1488 1067.26,1490.28 1068.68,1493.18C1070.1,1496.08 1070.8,1499.43 1070.8,1503.22C1070.8,1504.14 1070.49,1504.89 1069.87,1505.49C1069.24,1506.09 1068.45,1506.39 1067.49,1506.39L1038.48,1506.39L1038.48,1500.63L1067.28,1500.63L1064.32,1502.65C1064.28,1500.25 1063.8,1498.1 1062.88,1496.2C1061.97,1494.31 1060.68,1492.81 1059,1491.7C1057.32,1490.6 1055.28,1490.05 1052.88,1490.05C1050.14,1490.05 1047.8,1490.65 1045.86,1491.85C1043.91,1493.05 1042.44,1494.69 1041.43,1496.78C1040.42,1498.87 1039.92,1501.23 1039.92,1503.87C1039.92,1506.51 1040.52,1508.86 1041.72,1510.93C1042.92,1512.99 1044.57,1514.62 1046.68,1515.82C1048.8,1517.02 1051.22,1517.62 1053.96,1517.62C1055.44,1517.62 1056.97,1517.35 1058.53,1516.8C1060.09,1516.24 1061.35,1515.61 1062.31,1514.89C1063.03,1514.36 1063.81,1514.08 1064.65,1514.06C1065.49,1514.04 1066.22,1514.29 1066.84,1514.82C1067.66,1515.54 1068.09,1516.33 1068.14,1517.19C1068.19,1518.06 1067.8,1518.8 1066.99,1519.42C1065.36,1520.72 1063.33,1521.78 1060.9,1522.59C1058.48,1523.41 1056.16,1523.82 1053.96,1523.82Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(0.526643,0,0,0.587879,-2149.71,-513.767)">
|
||||
<g transform="matrix(1,0,0,1,735.951,511.402)">
|
||||
<path d="M0,-104.412C-0.158,-107.349 -0.612,-110.275 -1.449,-113.098C-2.362,-116.179 -3.535,-119.184 -4.931,-122.079C-4.985,-122.193 -5.041,-122.308 -5.097,-122.421C-5.228,-122.687 -5.363,-122.951 -5.498,-123.214C-7.231,-126.603 -9.219,-129.534 -12.043,-132.104C-16.201,-135.889 -21.338,-139.039 -26.458,-141.341C-34.216,-144.829 -42.774,-145.322 -51.171,-145.322C-61.669,-145.322 -155.843,-146.222 -166.559,-146.325L-166.554,-146.334C-166.554,-146.334 -120.073,-426.226 -119.996,-426.693C-118.955,-432.957 -118.006,-439.397 -117.982,-445.808C-117.972,-449.063 -118.199,-452.328 -118.796,-455.531C-119.355,-458.539 -119.806,-461.522 -121.02,-464.374C-121.165,-464.715 -121.318,-465.052 -121.481,-465.384C-123.422,-469.333 -126.557,-472.738 -130.13,-475.276C-137.152,-480.265 -148.817,-480.186 -156.233,-476.057C-162.988,-472.295 -168.569,-465.351 -172.734,-458.982C-182.516,-444.022 -192.515,-429.18 -201.853,-413.933C-205.017,-408.766 -208.051,-403.524 -211.146,-398.313C-213.341,-394.616 -215.537,-390.918 -217.732,-387.22L-225.213,-374.622C-227.974,-369.974 -230.734,-365.327 -233.494,-360.679C-236.489,-355.635 -239.484,-350.591 -242.479,-345.547C-245.679,-340.159 -248.878,-334.772 -252.077,-329.385C-255.449,-323.706 -258.821,-318.028 -262.193,-312.35C-265.706,-306.434 -269.219,-300.518 -272.732,-294.602C-276.355,-288.5 -279.979,-282.399 -283.602,-276.298C-287.304,-270.064 -291.006,-263.829 -294.708,-257.596L-305.956,-238.654C-309.721,-232.313 -313.487,-225.972 -317.252,-219.63C-321.003,-213.315 -324.753,-206.999 -328.504,-200.683C-332.208,-194.446 -335.912,-188.208 -339.616,-181.971C-343.243,-175.864 -346.869,-169.757 -350.495,-163.65C-354.013,-157.727 -357.53,-151.804 -361.047,-145.881C-364.424,-140.194 -367.801,-134.507 -371.179,-128.82C-374.384,-123.422 -377.589,-118.024 -380.795,-112.626C-383.798,-107.57 -386.8,-102.513 -389.803,-97.456C-392.572,-92.794 -395.34,-88.132 -398.108,-83.47C-400.612,-79.255 -403.115,-75.04 -405.618,-70.825C-408.959,-65.198 -412.148,-59.527 -415.561,-53.93C-418.976,-48.328 -421.629,-42.412 -424.564,-36.579C-430.102,-25.576 -433.981,-12.468 -433.308,-0C-433.149,2.937 -432.695,5.862 -431.859,8.686C-430.945,11.767 -429.772,14.772 -428.377,17.667C-428.322,17.781 -428.267,17.895 -428.211,18.009C-428.081,18.275 -427.944,18.538 -427.81,18.802C-426.076,22.191 -424.089,25.121 -421.264,27.692C-417.107,31.476 -411.969,34.627 -406.85,36.929C-399.092,40.416 -390.533,40.909 -382.137,40.909C-371.639,40.909 -277.465,41.81 -266.749,41.912L-266.754,41.922C-266.754,41.922 -313.234,321.814 -313.312,322.28C-314.352,328.545 -315.302,334.984 -315.325,341.396C-315.336,344.651 -315.108,347.915 -314.512,351.118C-313.952,354.127 -313.502,357.109 -312.288,359.962C-312.143,360.303 -311.99,360.64 -311.826,360.972C-309.886,364.921 -306.751,368.325 -303.178,370.863C-296.156,375.853 -284.491,375.774 -277.074,371.644C-270.32,367.883 -264.738,360.94 -260.574,354.569C-250.792,339.609 -240.792,324.768 -231.455,309.522C-228.291,304.355 -225.257,299.111 -222.163,293.901C-219.967,290.203 -217.771,286.506 -215.575,282.808C-213.081,278.609 -210.588,274.409 -208.095,270.21C-205.334,265.563 -202.574,260.914 -199.813,256.267C-196.818,251.223 -193.823,246.179 -190.828,241.134C-187.629,235.747 -184.43,230.359 -181.23,224.972C-177.858,219.294 -174.486,213.616 -171.115,207.938C-167.602,202.022 -164.089,196.106 -160.575,190.19C-156.952,184.088 -153.329,177.986 -149.706,171.886C-146.004,165.651 -142.302,159.417 -138.6,153.184C-134.851,146.869 -131.102,140.556 -127.352,134.241C-123.586,127.9 -119.82,121.559 -116.055,115.218C-112.305,108.902 -108.555,102.587 -104.804,96.271C-101.1,90.033 -97.396,83.796 -93.691,77.558C-90.065,71.451 -86.438,65.345 -82.813,59.238C-79.295,53.315 -75.777,47.392 -72.261,41.469C-68.884,35.781 -65.506,30.095 -62.129,24.407C-58.924,19.01 -55.718,13.611 -52.513,8.214C-49.51,3.157 -46.507,-1.899 -43.505,-6.956L-35.199,-20.942C-32.696,-25.157 -30.193,-29.372 -27.689,-33.588C-24.349,-39.215 -21.159,-44.886 -17.747,-50.482C-14.332,-56.084 -11.679,-62.001 -8.743,-67.833C-3.206,-78.837 0.674,-91.944 0,-104.412" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,550.012,53.3397)">
|
||||
<path d="M0,328.108C0.024,328.061 0.046,328.012 0.071,327.965L3.88,307.015L9.88,272.012L16.88,228.009L28.88,157.004L40.88,84.999L49.88,29.995L50.88,22.994C50.88,22.994 51.88,15.994 51.88,14.994C51.88,11.104 52.017,6.773 49.376,3.619C47.641,1.546 45.363,0 42.572,0.016C41.149,0.025 39.753,0.325 38.45,0.854L35.367,19.693C35.339,19.902 35.313,20.112 35.284,20.32C34.938,22.83 34.558,25.334 34.171,27.837C33.804,30.215 33.642,32.663 33.1,35.009C32.722,36.962 32.416,38.942 32.094,40.912C31.265,45.98 30.435,51.048 29.605,56.117L27.012,71.968C26.411,75.64 25.811,79.312 25.209,82.984C25.1,83.656 24.989,84.327 24.88,84.999L12.88,157.004L0.88,228.009L-6.12,272.012L-12.12,307.015L-15.938,328.016L-11.98,328.016C-11.98,328.016 -7.43,328.051 0,328.108" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,719.179,839.001)">
|
||||
<path d="M0,-434.473C-0.77,-438.758 -2.547,-442.987 -5.215,-446.5C-10.678,-453.693 -20.633,-457.43 -29.488,-457.43C-30.481,-457.43 -36.426,-457.47 -45.295,-457.533C-45.281,-457.497 -45.273,-457.46 -45.261,-457.424C-36.462,-457.361 -26.631,-453.631 -21.215,-446.5C-18.547,-442.987 -16.77,-438.758 -16,-434.473C-14.889,-428.287 -16.424,-422.049 -18.429,-416.214C-20.447,-410.339 -22.939,-404.635 -25.739,-399.093C-29.21,-392.222 -32.907,-385.459 -36.988,-378.93L-45.988,-363.93L-230.988,-51.93L-247.988,-23.93L-252.118,-17.613L-265.713,4.778L-275.799,20.204C-274.164,21.882 -272.118,23.06 -269.681,23.047C-266.961,23.031 -264.334,22.055 -262.322,20.222C-261.304,19.296 -260.43,18.226 -259.559,17.16C-258.371,15.706 -257.132,14.287 -256.095,12.731C-255.462,11.782 -254.83,10.833 -254.197,9.884C-252.797,7.785 -251.398,5.685 -249.998,3.586L-248.988,2.07L-231.988,-23.93L-214.988,-51.93L-29.988,-363.93L-20.988,-378.93C-16.907,-385.459 -13.21,-392.222 -9.739,-399.093C-6.939,-404.635 -4.447,-410.339 -2.429,-416.214C-0.424,-422.049 1.111,-428.287 0,-434.473" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 18 KiB |
24
Voltaserve/ui/src/components/common/brand/brand.svg
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 1287 403" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,-218.924,-566.942)">
|
||||
<g id="Brand" transform="matrix(0.810414,0,0,0.725996,1892.28,945.451)">
|
||||
<rect x="-2064.82" y="-521.364" width="1588.08" height="555.099" style="fill:none;"/>
|
||||
<g transform="matrix(2.97062,0,0,3.31604,-3727.67,-5203.26)">
|
||||
<path d="M706.268,1523.46C704.78,1523.46 703.628,1522.66 702.812,1521.08L687.548,1488.9C687.164,1488.08 687.128,1487.28 687.44,1486.48C687.752,1485.69 688.364,1485.08 689.276,1484.65C690.092,1484.22 690.92,1484.14 691.76,1484.43C692.6,1484.72 693.236,1485.27 693.668,1486.09L707.924,1516.83L704.468,1516.83L718.58,1486.09C719.012,1485.27 719.672,1484.72 720.56,1484.43C721.448,1484.14 722.348,1484.22 723.26,1484.65C724.124,1485.03 724.7,1485.63 724.988,1486.45C725.276,1487.26 725.228,1488.08 724.844,1488.9L709.58,1521.08C708.86,1522.66 707.756,1523.46 706.268,1523.46Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M748.82,1523.82C744.932,1523.82 741.5,1522.96 738.524,1521.26C735.548,1519.56 733.208,1517.2 731.504,1514.2C729.8,1511.2 728.948,1507.76 728.948,1503.87C728.948,1499.94 729.8,1496.47 731.504,1493.47C733.208,1490.47 735.548,1488.12 738.524,1486.41C741.5,1484.71 744.932,1483.86 748.82,1483.86C752.66,1483.86 756.068,1484.71 759.044,1486.41C762.02,1488.12 764.36,1490.47 766.064,1493.47C767.768,1496.47 768.62,1499.94 768.62,1503.87C768.62,1507.76 767.78,1511.2 766.1,1514.2C764.42,1517.2 762.092,1519.56 759.116,1521.26C756.14,1522.96 752.708,1523.82 748.82,1523.82ZM748.82,1517.34C751.316,1517.34 753.524,1516.76 755.444,1515.61C757.364,1514.46 758.864,1512.87 759.944,1510.86C761.024,1508.84 761.564,1506.51 761.564,1503.87C761.564,1501.23 761.024,1498.89 759.944,1496.85C758.864,1494.81 757.364,1493.22 755.444,1492.06C753.524,1490.91 751.316,1490.34 748.82,1490.34C746.324,1490.34 744.116,1490.91 742.196,1492.06C740.276,1493.22 738.764,1494.81 737.66,1496.85C736.556,1498.89 736.004,1501.23 736.004,1503.87C736.004,1506.51 736.556,1508.84 737.66,1510.86C738.764,1512.87 740.276,1514.46 742.196,1515.61C744.116,1516.76 746.324,1517.34 748.82,1517.34Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M788.924,1523.53C786.812,1523.53 784.94,1522.95 783.308,1521.8C781.676,1520.65 780.404,1519.08 779.492,1517.08C778.58,1515.09 778.124,1512.8 778.124,1510.21L778.124,1470.9C778.124,1469.84 778.46,1468.98 779.132,1468.3C779.804,1467.63 780.668,1467.3 781.724,1467.3C782.78,1467.3 783.644,1467.63 784.316,1468.3C784.988,1468.98 785.324,1469.84 785.324,1470.9L785.324,1510.21C785.324,1511.98 785.66,1513.45 786.332,1514.6C787.004,1515.75 787.868,1516.33 788.924,1516.33L790.724,1516.33C791.684,1516.33 792.464,1516.66 793.064,1517.34C793.664,1518.01 793.964,1518.87 793.964,1519.93C793.964,1520.98 793.508,1521.85 792.596,1522.52C791.684,1523.19 790.508,1523.53 789.068,1523.53L788.924,1523.53Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M815.492,1523.53C813.092,1523.53 810.932,1522.92 809.012,1521.69C807.092,1520.47 805.58,1518.8 804.476,1516.69C803.372,1514.58 802.82,1512.2 802.82,1509.56L802.82,1475.22C802.82,1474.16 803.156,1473.3 803.828,1472.62C804.5,1471.95 805.364,1471.62 806.42,1471.62C807.476,1471.62 808.34,1471.95 809.012,1472.62C809.684,1473.3 810.02,1474.16 810.02,1475.22L810.02,1509.56C810.02,1511.48 810.548,1513.09 811.604,1514.38C812.66,1515.68 813.956,1516.33 815.492,1516.33L817.94,1516.33C818.804,1516.33 819.536,1516.66 820.136,1517.34C820.736,1518.01 821.036,1518.87 821.036,1519.93C821.036,1520.98 820.64,1521.85 819.848,1522.52C819.056,1523.19 818.036,1523.53 816.788,1523.53L815.492,1523.53ZM799.508,1491.78C798.5,1491.78 797.684,1491.49 797.06,1490.91C796.436,1490.34 796.124,1489.62 796.124,1488.75C796.124,1487.79 796.436,1487.02 797.06,1486.45C797.684,1485.87 798.5,1485.58 799.508,1485.58L816.14,1485.58C817.148,1485.58 817.964,1485.87 818.588,1486.45C819.212,1487.02 819.524,1487.79 819.524,1488.75C819.524,1489.62 819.212,1490.34 818.588,1490.91C817.964,1491.49 817.148,1491.78 816.14,1491.78L799.508,1491.78Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M846.452,1523.82C842.948,1523.82 839.804,1522.94 837.02,1521.19C834.236,1519.44 832.04,1517.06 830.432,1514.06C828.824,1511.06 828.02,1507.66 828.02,1503.87C828.02,1500.08 828.896,1496.67 830.648,1493.65C832.4,1490.62 834.776,1488.24 837.776,1486.48C840.776,1484.73 844.148,1483.86 847.892,1483.86C851.636,1483.86 854.996,1484.73 857.972,1486.48C860.948,1488.24 863.312,1490.62 865.064,1493.65C866.816,1496.67 867.692,1500.08 867.692,1503.87L864.884,1503.87C864.884,1507.66 864.08,1511.06 862.472,1514.06C860.864,1517.06 858.668,1519.44 855.884,1521.19C853.1,1522.94 849.956,1523.82 846.452,1523.82ZM847.892,1517.34C850.34,1517.34 852.524,1516.75 854.444,1515.57C856.364,1514.4 857.876,1512.79 858.98,1510.75C860.084,1508.71 860.636,1506.42 860.636,1503.87C860.636,1501.28 860.084,1498.96 858.98,1496.92C857.876,1494.88 856.364,1493.28 854.444,1492.1C852.524,1490.92 850.34,1490.34 847.892,1490.34C845.492,1490.34 843.32,1490.92 841.376,1492.1C839.432,1493.28 837.896,1494.88 836.768,1496.92C835.64,1498.96 835.076,1501.28 835.076,1503.87C835.076,1506.42 835.64,1508.71 836.768,1510.75C837.896,1512.79 839.432,1514.4 841.376,1515.57C843.32,1516.75 845.492,1517.34 847.892,1517.34ZM864.02,1523.6C862.964,1523.6 862.088,1523.25 861.392,1522.56C860.696,1521.86 860.348,1520.98 860.348,1519.93L860.348,1508.91L861.716,1501.28L867.692,1503.87L867.692,1519.93C867.692,1520.98 867.344,1521.86 866.648,1522.56C865.952,1523.25 865.076,1523.6 864.02,1523.6Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M892.964,1523.82C889.604,1523.82 886.496,1523.3 883.64,1522.27C880.784,1521.24 878.564,1519.93 876.98,1518.34C876.26,1517.58 875.96,1516.7 876.08,1515.72C876.2,1514.73 876.668,1513.93 877.484,1513.3C878.444,1512.54 879.392,1512.21 880.328,1512.33C881.264,1512.45 882.068,1512.87 882.74,1513.59C883.556,1514.5 884.864,1515.36 886.664,1516.15C888.464,1516.94 890.468,1517.34 892.676,1517.34C895.46,1517.34 897.584,1516.88 899.048,1515.97C900.512,1515.06 901.268,1513.88 901.316,1512.44C901.364,1511 900.68,1509.75 899.264,1508.7C897.848,1507.64 895.268,1506.78 891.524,1506.1C886.676,1505.14 883.16,1503.7 880.976,1501.78C878.792,1499.86 877.7,1497.51 877.7,1494.73C877.7,1492.28 878.42,1490.25 879.86,1488.64C881.3,1487.04 883.148,1485.84 885.404,1485.04C887.66,1484.25 890.012,1483.86 892.46,1483.86C895.628,1483.86 898.436,1484.36 900.884,1485.37C903.332,1486.38 905.276,1487.77 906.716,1489.54C907.388,1490.31 907.7,1491.12 907.652,1491.96C907.604,1492.8 907.196,1493.5 906.428,1494.08C905.66,1494.61 904.748,1494.78 903.692,1494.58C902.636,1494.39 901.748,1493.96 901.028,1493.29C899.828,1492.14 898.532,1491.34 897.14,1490.91C895.748,1490.48 894.14,1490.26 892.316,1490.26C890.204,1490.26 888.416,1490.62 886.952,1491.34C885.488,1492.06 884.756,1493.12 884.756,1494.51C884.756,1495.38 884.984,1496.16 885.44,1496.85C885.896,1497.55 886.796,1498.17 888.14,1498.72C889.484,1499.28 891.452,1499.82 894.044,1500.34C897.644,1501.06 900.488,1501.98 902.576,1503.08C904.664,1504.18 906.164,1505.47 907.076,1506.93C907.988,1508.4 908.444,1510.09 908.444,1512.01C908.444,1514.22 907.856,1516.21 906.68,1517.98C905.504,1519.76 903.776,1521.18 901.496,1522.23C899.216,1523.29 896.372,1523.82 892.964,1523.82Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M936.236,1523.82C932.252,1523.82 928.712,1522.96 925.616,1521.26C922.52,1519.56 920.096,1517.2 918.344,1514.2C916.592,1511.2 915.716,1507.76 915.716,1503.87C915.716,1499.94 916.544,1496.47 918.2,1493.47C919.856,1490.47 922.148,1488.12 925.076,1486.41C928.004,1484.71 931.364,1483.86 935.156,1483.86C938.9,1483.86 942.116,1484.68 944.804,1486.34C947.492,1488 949.544,1490.28 950.96,1493.18C952.376,1496.08 953.084,1499.43 953.084,1503.22C953.084,1504.14 952.772,1504.89 952.148,1505.49C951.524,1506.09 950.732,1506.39 949.772,1506.39L920.756,1506.39L920.756,1500.63L949.556,1500.63L946.604,1502.65C946.556,1500.25 946.076,1498.1 945.164,1496.2C944.252,1494.31 942.956,1492.81 941.276,1491.7C939.596,1490.6 937.556,1490.05 935.156,1490.05C932.42,1490.05 930.08,1490.65 928.136,1491.85C926.192,1493.05 924.716,1494.69 923.708,1496.78C922.7,1498.87 922.196,1501.23 922.196,1503.87C922.196,1506.51 922.796,1508.86 923.996,1510.93C925.196,1512.99 926.852,1514.62 928.964,1515.82C931.076,1517.02 933.5,1517.62 936.236,1517.62C937.724,1517.62 939.248,1517.35 940.808,1516.8C942.368,1516.24 943.628,1515.61 944.588,1514.89C945.308,1514.36 946.088,1514.08 946.928,1514.06C947.768,1514.04 948.5,1514.29 949.124,1514.82C949.94,1515.54 950.372,1516.33 950.42,1517.19C950.468,1518.06 950.084,1518.8 949.268,1519.42C947.636,1520.72 945.608,1521.78 943.184,1522.59C940.76,1523.41 938.444,1523.82 936.236,1523.82Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M965.972,1498.9C965.972,1496.02 966.68,1493.44 968.096,1491.16C969.512,1488.88 971.42,1487.07 973.82,1485.73C976.22,1484.38 978.86,1483.71 981.74,1483.71C984.62,1483.71 986.768,1484.18 988.184,1485.12C989.6,1486.05 990.14,1487.17 989.804,1488.46C989.66,1489.14 989.384,1489.65 988.976,1490.01C988.568,1490.37 988.1,1490.6 987.572,1490.7C987.044,1490.79 986.468,1490.77 985.844,1490.62C982.772,1490 980.012,1489.95 977.564,1490.48C975.116,1491.01 973.184,1491.99 971.768,1493.43C970.352,1494.87 969.644,1496.7 969.644,1498.9L965.972,1498.9ZM966.044,1523.53C964.892,1523.53 964.004,1523.23 963.38,1522.63C962.756,1522.03 962.444,1521.13 962.444,1519.93L962.444,1487.74C962.444,1486.59 962.756,1485.7 963.38,1485.08C964.004,1484.46 964.892,1484.14 966.044,1484.14C967.244,1484.14 968.144,1484.44 968.744,1485.04C969.344,1485.64 969.644,1486.54 969.644,1487.74L969.644,1519.93C969.644,1521.08 969.344,1521.97 968.744,1522.59C968.144,1523.22 967.244,1523.53 966.044,1523.53Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M1010.76,1523.46C1009.27,1523.46 1008.12,1522.66 1007.3,1521.08L992.036,1488.9C991.652,1488.08 991.616,1487.28 991.928,1486.48C992.24,1485.69 992.852,1485.08 993.764,1484.65C994.58,1484.22 995.408,1484.14 996.248,1484.43C997.088,1484.72 997.724,1485.27 998.156,1486.09L1012.41,1516.83L1008.96,1516.83L1023.07,1486.09C1023.5,1485.27 1024.16,1484.72 1025.05,1484.43C1025.94,1484.14 1026.84,1484.22 1027.75,1484.65C1028.61,1485.03 1029.19,1485.63 1029.48,1486.45C1029.76,1487.26 1029.72,1488.08 1029.33,1488.9L1014.07,1521.08C1013.35,1522.66 1012.24,1523.46 1010.76,1523.46Z" style="fill-rule:nonzero;"/>
|
||||
<path d="M1053.96,1523.82C1049.97,1523.82 1046.43,1522.96 1043.34,1521.26C1040.24,1519.56 1037.82,1517.2 1036.06,1514.2C1034.31,1511.2 1033.44,1507.76 1033.44,1503.87C1033.44,1499.94 1034.26,1496.47 1035.92,1493.47C1037.58,1490.47 1039.87,1488.12 1042.8,1486.41C1045.72,1484.71 1049.08,1483.86 1052.88,1483.86C1056.62,1483.86 1059.84,1484.68 1062.52,1486.34C1065.21,1488 1067.26,1490.28 1068.68,1493.18C1070.1,1496.08 1070.8,1499.43 1070.8,1503.22C1070.8,1504.14 1070.49,1504.89 1069.87,1505.49C1069.24,1506.09 1068.45,1506.39 1067.49,1506.39L1038.48,1506.39L1038.48,1500.63L1067.28,1500.63L1064.32,1502.65C1064.28,1500.25 1063.8,1498.1 1062.88,1496.2C1061.97,1494.31 1060.68,1492.81 1059,1491.7C1057.32,1490.6 1055.28,1490.05 1052.88,1490.05C1050.14,1490.05 1047.8,1490.65 1045.86,1491.85C1043.91,1493.05 1042.44,1494.69 1041.43,1496.78C1040.42,1498.87 1039.92,1501.23 1039.92,1503.87C1039.92,1506.51 1040.52,1508.86 1041.72,1510.93C1042.92,1512.99 1044.57,1514.62 1046.68,1515.82C1048.8,1517.02 1051.22,1517.62 1053.96,1517.62C1055.44,1517.62 1056.97,1517.35 1058.53,1516.8C1060.09,1516.24 1061.35,1515.61 1062.31,1514.89C1063.03,1514.36 1063.81,1514.08 1064.65,1514.06C1065.49,1514.04 1066.22,1514.29 1066.84,1514.82C1067.66,1515.54 1068.09,1516.33 1068.14,1517.19C1068.19,1518.06 1067.8,1518.8 1066.99,1519.42C1065.36,1520.72 1063.33,1521.78 1060.9,1522.59C1058.48,1523.41 1056.16,1523.82 1053.96,1523.82Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(0.526643,0,0,0.587879,-1760.91,-213.124)">
|
||||
<path d="M0,-104.412C-0.158,-107.349 -0.612,-110.275 -1.449,-113.098C-2.362,-116.179 -3.535,-119.184 -4.931,-122.079C-4.985,-122.193 -5.041,-122.308 -5.097,-122.421C-5.228,-122.687 -5.363,-122.951 -5.498,-123.214C-7.231,-126.603 -9.219,-129.534 -12.043,-132.104C-16.201,-135.889 -21.338,-139.039 -26.458,-141.341C-34.216,-144.829 -42.774,-145.322 -51.171,-145.322C-61.669,-145.322 -155.843,-146.222 -166.559,-146.325L-166.554,-146.334C-166.554,-146.334 -120.073,-426.226 -119.996,-426.693C-118.955,-432.957 -118.006,-439.397 -117.982,-445.808C-117.972,-449.063 -118.199,-452.328 -118.796,-455.531C-119.355,-458.539 -119.806,-461.522 -121.02,-464.374C-121.165,-464.715 -121.318,-465.052 -121.481,-465.384C-123.422,-469.333 -126.557,-472.738 -130.13,-475.276C-137.152,-480.265 -148.817,-480.186 -156.233,-476.057C-162.988,-472.295 -168.569,-465.351 -172.734,-458.982C-182.516,-444.022 -192.515,-429.18 -201.853,-413.933C-205.017,-408.766 -208.051,-403.524 -211.146,-398.313C-213.341,-394.616 -215.537,-390.918 -217.732,-387.22L-225.213,-374.622C-227.974,-369.974 -230.734,-365.327 -233.494,-360.679C-236.489,-355.635 -239.484,-350.591 -242.479,-345.547C-245.679,-340.159 -248.878,-334.772 -252.077,-329.385C-255.449,-323.706 -258.821,-318.028 -262.193,-312.35C-265.706,-306.434 -269.219,-300.518 -272.732,-294.602C-276.355,-288.5 -279.979,-282.399 -283.602,-276.298C-287.304,-270.064 -291.006,-263.829 -294.708,-257.596L-305.956,-238.654C-309.721,-232.313 -313.487,-225.972 -317.252,-219.63C-321.003,-213.315 -324.753,-206.999 -328.504,-200.683C-332.208,-194.446 -335.912,-188.208 -339.616,-181.971C-343.243,-175.864 -346.869,-169.757 -350.495,-163.65C-354.013,-157.727 -357.53,-151.804 -361.047,-145.881C-364.424,-140.194 -367.801,-134.507 -371.179,-128.82C-374.384,-123.422 -377.589,-118.024 -380.795,-112.626C-383.798,-107.57 -386.8,-102.513 -389.803,-97.456C-392.572,-92.794 -395.34,-88.132 -398.108,-83.47C-400.612,-79.255 -403.115,-75.04 -405.618,-70.825C-408.959,-65.198 -412.148,-59.527 -415.561,-53.93C-418.976,-48.328 -421.629,-42.412 -424.564,-36.579C-430.102,-25.576 -433.981,-12.468 -433.308,-0C-433.149,2.937 -432.695,5.862 -431.859,8.686C-430.945,11.767 -429.772,14.772 -428.377,17.667C-428.322,17.781 -428.267,17.895 -428.211,18.009C-428.081,18.275 -427.944,18.538 -427.81,18.802C-426.076,22.191 -424.089,25.121 -421.264,27.692C-417.107,31.476 -411.969,34.627 -406.85,36.929C-399.092,40.416 -390.533,40.909 -382.137,40.909C-371.639,40.909 -277.465,41.81 -266.749,41.912L-266.754,41.922C-266.754,41.922 -313.234,321.814 -313.312,322.28C-314.352,328.545 -315.302,334.984 -315.325,341.396C-315.336,344.651 -315.108,347.915 -314.512,351.118C-313.952,354.127 -313.502,357.109 -312.288,359.962C-312.143,360.303 -311.99,360.64 -311.826,360.972C-309.886,364.921 -306.751,368.325 -303.178,370.863C-296.156,375.853 -284.491,375.774 -277.074,371.644C-270.32,367.883 -264.738,360.94 -260.574,354.569C-250.792,339.609 -240.792,324.768 -231.455,309.522C-228.291,304.355 -225.257,299.111 -222.163,293.901C-219.967,290.203 -217.771,286.506 -215.575,282.808C-213.081,278.609 -210.588,274.409 -208.095,270.21C-205.334,265.563 -202.574,260.914 -199.813,256.267C-196.818,251.223 -193.823,246.179 -190.828,241.134C-187.629,235.747 -184.43,230.359 -181.23,224.972C-177.858,219.294 -174.486,213.616 -171.115,207.938C-167.602,202.022 -164.089,196.106 -160.575,190.19C-156.952,184.088 -153.329,177.986 -149.706,171.886C-146.004,165.651 -142.302,159.417 -138.6,153.184C-134.851,146.869 -131.102,140.556 -127.352,134.241C-123.586,127.9 -119.82,121.559 -116.055,115.218C-112.305,108.902 -108.555,102.587 -104.804,96.271C-101.1,90.033 -97.396,83.796 -93.691,77.558C-90.065,71.451 -86.438,65.345 -82.813,59.238C-79.295,53.315 -75.777,47.392 -72.261,41.469C-68.884,35.781 -65.506,30.095 -62.129,24.407C-58.924,19.01 -55.718,13.611 -52.513,8.214C-49.51,3.157 -46.507,-1.899 -43.505,-6.956L-35.199,-20.942C-32.696,-25.157 -30.193,-29.372 -27.689,-33.588C-24.349,-39.215 -21.159,-44.886 -17.747,-50.482C-14.332,-56.084 -11.679,-62.001 -8.743,-67.833C-3.206,-78.837 0.674,-91.944 0,-104.412" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 16 KiB |
29
Voltaserve/ui/src/components/common/brand/index.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { useCallback } from 'react'
|
||||
import { CenterProps, useColorMode } from '@chakra-ui/react'
|
||||
import cx from 'classnames'
|
||||
import BrandDarkGlossySvg from './brand-dark-glossy.svg?react'
|
||||
import BrandDarkSvg from './brand-dark.svg?react'
|
||||
import BrandGlossySvg from './brand-glossy.svg?react'
|
||||
import BrandSvg from './brand.svg?react'
|
||||
|
||||
type LogoProps = CenterProps & {
|
||||
isGlossy?: boolean
|
||||
}
|
||||
|
||||
const Brand = ({ isGlossy = false }: LogoProps) => {
|
||||
const { colorMode } = useColorMode()
|
||||
const renderSvg = useCallback(() => {
|
||||
if (isGlossy) {
|
||||
return colorMode === 'dark' ? <BrandDarkGlossySvg /> : <BrandGlossySvg />
|
||||
} else {
|
||||
return colorMode === 'dark' ? <BrandDarkSvg /> : <BrandSvg />
|
||||
}
|
||||
}, [colorMode, isGlossy])
|
||||
return (
|
||||
<div className={cx('flex', 'items-center', 'justify-center')}>
|
||||
{renderSvg()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Brand
|
32
Voltaserve/ui/src/components/common/email-tokenizer.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { useMemo } from 'react'
|
||||
import { Tag } from '@chakra-ui/react'
|
||||
import cx from 'classnames'
|
||||
import parseEmailList from '@/helpers/parse-email-list'
|
||||
|
||||
export type EmailTokenizerProps = {
|
||||
value: string
|
||||
}
|
||||
|
||||
const EmailTokenizer = ({ value }: EmailTokenizerProps) => {
|
||||
const emails = useMemo(() => parseEmailList(value), [value])
|
||||
return (
|
||||
<>
|
||||
{emails.length > 0 ? (
|
||||
<div className={cx('flex', 'flex-wrap', 'gap-0.5')}>
|
||||
{emails.map((email, index) => (
|
||||
<Tag
|
||||
key={index}
|
||||
size="md"
|
||||
variant="solid"
|
||||
className={cx('rounded-full')}
|
||||
>
|
||||
{email}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default EmailTokenizer
|
216
Voltaserve/ui/src/components/common/group-selector.tsx
Normal file
@ -0,0 +1,216 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
useDisclosure,
|
||||
Table,
|
||||
Tr,
|
||||
Tbody,
|
||||
Td,
|
||||
Avatar,
|
||||
Radio,
|
||||
} from '@chakra-ui/react'
|
||||
import cx from 'classnames'
|
||||
import GroupAPI, { Group, SortOrder } from '@/client/api/group'
|
||||
import { swrConfig } from '@/client/options'
|
||||
import { SectionSpinner, Pagination, SearchInput } from '@/lib'
|
||||
|
||||
export type GroupSelectorProps = {
|
||||
value?: Group
|
||||
organizationId?: string
|
||||
onConfirm?: (group: Group) => void
|
||||
}
|
||||
|
||||
const GroupSelector = ({
|
||||
value,
|
||||
organizationId,
|
||||
onConfirm,
|
||||
}: GroupSelectorProps) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const [page, setPage] = useState(1)
|
||||
const [query, setQuery] = useState('')
|
||||
const [selected, setSelected] = useState<Group>()
|
||||
const {
|
||||
data: list,
|
||||
error,
|
||||
mutate,
|
||||
} = GroupAPI.useList(
|
||||
{ query, organizationId, page, size: 5, sortOrder: SortOrder.Desc },
|
||||
swrConfig(),
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
mutate()
|
||||
}, [page, query, mutate])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setPage(1)
|
||||
setSelected(undefined)
|
||||
setQuery('')
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
if (selected) {
|
||||
onConfirm?.(selected)
|
||||
onClose()
|
||||
}
|
||||
}, [selected, onConfirm, onClose])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cx(
|
||||
'w-full',
|
||||
{ 'text-black': value },
|
||||
{ 'dark:text-white': value },
|
||||
{ 'text-gray-500': !value },
|
||||
{ 'dark:text-gray-500': !value },
|
||||
)}
|
||||
onClick={onOpen}
|
||||
>
|
||||
{value ? value.name : 'Select Group'}
|
||||
</Button>
|
||||
<Modal
|
||||
size="xl"
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
closeOnOverlayClick={false}
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Select Group</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<div className={cx('flex', 'flex-col', 'gap-1.5')}>
|
||||
<SearchInput
|
||||
query={query}
|
||||
onChange={(value) => setQuery(value)}
|
||||
/>
|
||||
{!list && error && (
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'h-[300px]',
|
||||
)}
|
||||
>
|
||||
<span>Failed to load groups.</span>
|
||||
</div>
|
||||
)}
|
||||
{!list && !error && <SectionSpinner />}
|
||||
{list && list.data.length === 0 && (
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'h-[300px]',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'flex-col',
|
||||
'items-center',
|
||||
'gap-1.5',
|
||||
)}
|
||||
>
|
||||
<span>There are no organizations.</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{list && list.data.length > 0 && (
|
||||
<Table variant="simple" size="sm">
|
||||
<colgroup>
|
||||
<col className={cx('w-[40px]')} />
|
||||
<col className={cx('w-[auto]')} />
|
||||
</colgroup>
|
||||
<Tbody>
|
||||
{list.data.map((g) => (
|
||||
<Tr
|
||||
key={g.id}
|
||||
className={cx(
|
||||
'cursor-pointer',
|
||||
{ 'bg-gray-100': selected?.id === g.id },
|
||||
{ 'dark:bg-gray-600': selected?.id === g.id },
|
||||
{ 'bg-transparent': selected?.id !== g.id },
|
||||
)}
|
||||
onClick={() => setSelected(g)}
|
||||
>
|
||||
<Td className={cx('px-0.5', 'text-center')}>
|
||||
<Radio size="md" isChecked={selected?.id === g.id} />
|
||||
</Td>
|
||||
<Td className={cx('px-0.5')}>
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'flex-row',
|
||||
'items-center',
|
||||
'gap-1.5',
|
||||
)}
|
||||
>
|
||||
<Avatar
|
||||
name={g.name}
|
||||
size="sm"
|
||||
className={cx('w-[40px]', 'h-[40px]')}
|
||||
/>
|
||||
<span className={cx('text-base')}>{g.name}</span>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
)}
|
||||
{list && (
|
||||
<div className={cx('self-end')}>
|
||||
{list.totalPages > 1 ? (
|
||||
<Pagination
|
||||
uiSize="md"
|
||||
maxButtons={3}
|
||||
page={page}
|
||||
totalPages={list.totalPages}
|
||||
onPageChange={(value) => setPage(value)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div className={cx('flex', 'flex-row', 'items-center', 'gap-1')}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
colorScheme="blue"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
colorScheme="blue"
|
||||
isDisabled={!selected}
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default GroupSelector
|
29
Voltaserve/ui/src/components/common/logo/index.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { useCallback } from 'react'
|
||||
import { CenterProps, useColorMode } from '@chakra-ui/react'
|
||||
import cx from 'classnames'
|
||||
import LogoDarkGlossySvg from './logo-dark-glossy.svg?react'
|
||||
import LogoDarkSvg from './logo-dark.svg?react'
|
||||
import LogoGlossySvg from './logo-glossy.svg?react'
|
||||
import LogoSvg from './logo.svg?react'
|
||||
|
||||
type LogoProps = CenterProps & {
|
||||
isGlossy?: boolean
|
||||
}
|
||||
|
||||
const Logo = ({ isGlossy = false }: LogoProps) => {
|
||||
const { colorMode } = useColorMode()
|
||||
const renderSvg = useCallback(() => {
|
||||
if (isGlossy) {
|
||||
return colorMode === 'dark' ? <LogoDarkGlossySvg /> : <LogoGlossySvg />
|
||||
} else {
|
||||
return colorMode === 'dark' ? <LogoDarkSvg /> : <LogoSvg />
|
||||
}
|
||||
}, [colorMode, isGlossy])
|
||||
return (
|
||||
<div className={cx('flex', 'items-center', 'justify-center')}>
|
||||
{renderSvg()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Logo
|
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 358 705" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,-1577.43,-2903.1)">
|
||||
<g transform="matrix(1,0,0,1,1070.18,2027.17)">
|
||||
<g transform="matrix(0.824738,0,0,0.824738,257.716,849.283)">
|
||||
<g>
|
||||
<g transform="matrix(1,0,0,1,735.951,511.402)">
|
||||
<path d="M0,-104.412C-0.158,-107.349 -0.612,-110.275 -1.449,-113.098C-2.362,-116.179 -3.535,-119.184 -4.931,-122.079C-4.985,-122.193 -5.041,-122.308 -5.097,-122.421C-5.228,-122.687 -5.363,-122.951 -5.498,-123.214C-7.231,-126.603 -9.219,-129.534 -12.043,-132.104C-16.201,-135.889 -21.338,-139.039 -26.458,-141.341C-34.216,-144.829 -42.774,-145.322 -51.171,-145.322C-61.669,-145.322 -155.843,-146.222 -166.559,-146.325L-166.554,-146.334C-166.554,-146.334 -120.073,-426.226 -119.996,-426.693C-118.955,-432.957 -118.006,-439.397 -117.982,-445.808C-117.972,-449.063 -118.199,-452.328 -118.796,-455.531C-119.355,-458.539 -119.806,-461.522 -121.02,-464.374C-121.165,-464.715 -121.318,-465.052 -121.481,-465.384C-123.422,-469.333 -126.557,-472.738 -130.13,-475.276C-137.152,-480.265 -148.817,-480.186 -156.233,-476.057C-162.988,-472.295 -168.569,-465.351 -172.734,-458.982C-182.516,-444.022 -192.515,-429.18 -201.853,-413.933C-205.017,-408.766 -208.051,-403.524 -211.146,-398.313C-213.341,-394.616 -215.537,-390.918 -217.732,-387.22L-225.213,-374.622C-227.974,-369.974 -230.734,-365.327 -233.494,-360.679C-236.489,-355.635 -239.484,-350.591 -242.479,-345.547C-245.679,-340.159 -248.878,-334.772 -252.077,-329.385C-255.449,-323.706 -258.821,-318.028 -262.193,-312.35C-265.706,-306.434 -269.219,-300.518 -272.732,-294.602C-276.355,-288.5 -279.979,-282.399 -283.602,-276.298C-287.304,-270.064 -291.006,-263.829 -294.708,-257.596L-305.956,-238.654C-309.721,-232.313 -313.487,-225.972 -317.252,-219.63C-321.003,-213.315 -324.753,-206.999 -328.504,-200.683C-332.208,-194.446 -335.912,-188.208 -339.616,-181.971C-343.243,-175.864 -346.869,-169.757 -350.495,-163.65C-354.013,-157.727 -357.53,-151.804 -361.047,-145.881C-364.424,-140.194 -367.801,-134.507 -371.179,-128.82C-374.384,-123.422 -377.589,-118.024 -380.795,-112.626C-383.798,-107.57 -386.8,-102.513 -389.803,-97.456C-392.572,-92.794 -395.34,-88.132 -398.108,-83.47C-400.612,-79.255 -403.115,-75.04 -405.618,-70.825C-408.959,-65.198 -412.148,-59.527 -415.561,-53.93C-418.976,-48.328 -421.629,-42.412 -424.564,-36.579C-430.102,-25.576 -433.981,-12.468 -433.308,-0C-433.149,2.937 -432.695,5.862 -431.859,8.686C-430.945,11.767 -429.772,14.772 -428.377,17.667C-428.322,17.781 -428.267,17.895 -428.211,18.009C-428.081,18.275 -427.944,18.538 -427.81,18.802C-426.076,22.191 -424.089,25.121 -421.264,27.692C-417.107,31.476 -411.969,34.627 -406.85,36.929C-399.092,40.416 -390.533,40.909 -382.137,40.909C-371.639,40.909 -277.465,41.81 -266.749,41.912L-266.754,41.922C-266.754,41.922 -313.234,321.814 -313.312,322.28C-314.352,328.545 -315.302,334.984 -315.325,341.396C-315.336,344.651 -315.108,347.915 -314.512,351.118C-313.952,354.127 -313.502,357.109 -312.288,359.962C-312.143,360.303 -311.99,360.64 -311.826,360.972C-309.886,364.921 -306.751,368.325 -303.178,370.863C-296.156,375.853 -284.491,375.774 -277.074,371.644C-270.32,367.883 -264.738,360.94 -260.574,354.569C-250.792,339.609 -240.792,324.768 -231.455,309.522C-228.291,304.355 -225.257,299.111 -222.163,293.901C-219.967,290.203 -217.771,286.506 -215.575,282.808C-213.081,278.609 -210.588,274.409 -208.095,270.21C-205.334,265.563 -202.574,260.914 -199.813,256.267C-196.818,251.223 -193.823,246.179 -190.828,241.134C-187.629,235.747 -184.43,230.359 -181.23,224.972C-177.858,219.294 -174.486,213.616 -171.115,207.938C-167.602,202.022 -164.089,196.106 -160.575,190.19C-156.952,184.088 -153.329,177.986 -149.706,171.886C-146.004,165.651 -142.302,159.417 -138.6,153.184C-134.851,146.869 -131.102,140.556 -127.352,134.241C-123.586,127.9 -119.82,121.559 -116.055,115.218C-112.305,108.902 -108.555,102.587 -104.804,96.271C-101.1,90.033 -97.396,83.796 -93.691,77.558C-90.065,71.451 -86.438,65.345 -82.813,59.238C-79.295,53.315 -75.777,47.392 -72.261,41.469C-68.884,35.781 -65.506,30.095 -62.129,24.407C-58.924,19.01 -55.718,13.611 -52.513,8.214C-49.51,3.157 -46.507,-1.899 -43.505,-6.956L-35.199,-20.942C-32.696,-25.157 -30.193,-29.372 -27.689,-33.588C-24.349,-39.215 -21.159,-44.886 -17.747,-50.482C-14.332,-56.084 -11.679,-62.001 -8.743,-67.833C-3.206,-78.837 0.674,-91.944 0,-104.412" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,550.012,53.3397)">
|
||||
<path d="M0,328.108C0.024,328.061 0.046,328.012 0.071,327.965L3.88,307.015L9.88,272.012L16.88,228.009L28.88,157.004L40.88,84.999L49.88,29.995L50.88,22.994C50.88,22.994 51.88,15.994 51.88,14.994C51.88,11.104 52.017,6.773 49.376,3.619C47.641,1.546 45.363,0 42.572,0.016C41.149,0.025 39.753,0.325 38.45,0.854L35.367,19.693C35.339,19.902 35.313,20.112 35.284,20.32C34.938,22.83 34.558,25.334 34.171,27.837C33.804,30.215 33.642,32.663 33.1,35.009C32.722,36.962 32.416,38.942 32.094,40.912C31.265,45.98 30.435,51.048 29.605,56.117L27.012,71.968C26.411,75.64 25.811,79.312 25.209,82.984C25.1,83.656 24.989,84.327 24.88,84.999L12.88,157.004L0.88,228.009L-6.12,272.012L-12.12,307.015L-15.938,328.016L-11.98,328.016C-11.98,328.016 -7.43,328.051 0,328.108" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,719.179,839.001)">
|
||||
<path d="M0,-434.473C-0.77,-438.758 -2.547,-442.987 -5.215,-446.5C-10.678,-453.693 -20.633,-457.43 -29.488,-457.43C-30.481,-457.43 -36.426,-457.47 -45.295,-457.533C-45.281,-457.497 -45.273,-457.46 -45.261,-457.424C-36.462,-457.361 -26.631,-453.631 -21.215,-446.5C-18.547,-442.987 -16.77,-438.758 -16,-434.473C-14.889,-428.287 -16.424,-422.049 -18.429,-416.214C-20.447,-410.339 -22.939,-404.635 -25.739,-399.093C-29.21,-392.222 -32.907,-385.459 -36.988,-378.93L-45.988,-363.93L-230.988,-51.93L-247.988,-23.93L-252.118,-17.613L-265.713,4.778L-275.799,20.204C-274.164,21.882 -272.118,23.06 -269.681,23.047C-266.961,23.031 -264.334,22.055 -262.322,20.222C-261.304,19.296 -260.43,18.226 -259.559,17.16C-258.371,15.706 -257.132,14.287 -256.095,12.731C-255.462,11.782 -254.83,10.833 -254.197,9.884C-252.797,7.785 -251.398,5.685 -249.998,3.586L-248.988,2.07L-231.988,-23.93L-214.988,-51.93L-29.988,-363.93L-20.988,-378.93C-16.907,-385.459 -13.21,-392.222 -9.739,-399.093C-6.939,-404.635 -4.447,-410.339 -2.429,-416.214C-0.424,-422.049 1.111,-428.287 0,-434.473" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.9 KiB |
13
Voltaserve/ui/src/components/common/logo/logo-dark.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 358 705" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,-344.03,-2904.05)">
|
||||
<g transform="matrix(1,0,0,1,-163.224,2028.12)">
|
||||
<g transform="matrix(0.824738,0,0,0.824738,864.683,1271.06)">
|
||||
<g>
|
||||
<path d="M0,-104.412C-0.158,-107.349 -0.612,-110.275 -1.449,-113.098C-2.362,-116.179 -3.535,-119.184 -4.931,-122.079C-4.985,-122.193 -5.041,-122.308 -5.097,-122.421C-5.228,-122.687 -5.363,-122.951 -5.498,-123.214C-7.231,-126.603 -9.219,-129.534 -12.043,-132.104C-16.201,-135.889 -21.338,-139.039 -26.458,-141.341C-34.216,-144.829 -42.774,-145.322 -51.171,-145.322C-61.669,-145.322 -155.843,-146.222 -166.559,-146.325L-166.554,-146.334C-166.554,-146.334 -120.073,-426.226 -119.996,-426.693C-118.955,-432.957 -118.006,-439.397 -117.982,-445.808C-117.972,-449.063 -118.199,-452.328 -118.796,-455.531C-119.355,-458.539 -119.806,-461.522 -121.02,-464.374C-121.165,-464.715 -121.318,-465.052 -121.481,-465.384C-123.422,-469.333 -126.557,-472.738 -130.13,-475.276C-137.152,-480.265 -148.817,-480.186 -156.233,-476.057C-162.988,-472.295 -168.569,-465.351 -172.734,-458.982C-182.516,-444.022 -192.515,-429.18 -201.853,-413.933C-205.017,-408.766 -208.051,-403.524 -211.146,-398.313C-213.341,-394.616 -215.537,-390.918 -217.732,-387.22L-225.213,-374.622C-227.974,-369.974 -230.734,-365.327 -233.494,-360.679C-236.489,-355.635 -239.484,-350.591 -242.479,-345.547C-245.679,-340.159 -248.878,-334.772 -252.077,-329.385C-255.449,-323.706 -258.821,-318.028 -262.193,-312.35C-265.706,-306.434 -269.219,-300.518 -272.732,-294.602C-276.355,-288.5 -279.979,-282.399 -283.602,-276.298C-287.304,-270.064 -291.006,-263.829 -294.708,-257.596L-305.956,-238.654C-309.721,-232.313 -313.487,-225.972 -317.252,-219.63C-321.003,-213.315 -324.753,-206.999 -328.504,-200.683C-332.208,-194.446 -335.912,-188.208 -339.616,-181.971C-343.243,-175.864 -346.869,-169.757 -350.495,-163.65C-354.013,-157.727 -357.53,-151.804 -361.047,-145.881C-364.424,-140.194 -367.801,-134.507 -371.179,-128.82C-374.384,-123.422 -377.589,-118.024 -380.795,-112.626C-383.798,-107.57 -386.8,-102.513 -389.803,-97.456C-392.572,-92.794 -395.34,-88.132 -398.108,-83.47C-400.612,-79.255 -403.115,-75.04 -405.618,-70.825C-408.959,-65.198 -412.148,-59.527 -415.561,-53.93C-418.976,-48.328 -421.629,-42.412 -424.564,-36.579C-430.102,-25.576 -433.981,-12.468 -433.308,-0C-433.149,2.937 -432.695,5.862 -431.859,8.686C-430.945,11.767 -429.772,14.772 -428.377,17.667C-428.322,17.781 -428.267,17.895 -428.211,18.009C-428.081,18.275 -427.944,18.538 -427.81,18.802C-426.076,22.191 -424.089,25.121 -421.264,27.692C-417.107,31.476 -411.969,34.627 -406.85,36.929C-399.092,40.416 -390.533,40.909 -382.137,40.909C-371.639,40.909 -277.465,41.81 -266.749,41.912L-266.754,41.922C-266.754,41.922 -313.234,321.814 -313.312,322.28C-314.352,328.545 -315.302,334.984 -315.325,341.396C-315.336,344.651 -315.108,347.915 -314.512,351.118C-313.952,354.127 -313.502,357.109 -312.288,359.962C-312.143,360.303 -311.99,360.64 -311.826,360.972C-309.886,364.921 -306.751,368.325 -303.178,370.863C-296.156,375.853 -284.491,375.774 -277.074,371.644C-270.32,367.883 -264.738,360.94 -260.574,354.569C-250.792,339.609 -240.792,324.768 -231.455,309.522C-228.291,304.355 -225.257,299.111 -222.163,293.901C-219.967,290.203 -217.771,286.506 -215.575,282.808C-213.081,278.609 -210.588,274.409 -208.095,270.21C-205.334,265.563 -202.574,260.914 -199.813,256.267C-196.818,251.223 -193.823,246.179 -190.828,241.134C-187.629,235.747 -184.43,230.359 -181.23,224.972C-177.858,219.294 -174.486,213.616 -171.115,207.938C-167.602,202.022 -164.089,196.106 -160.575,190.19C-156.952,184.088 -153.329,177.986 -149.706,171.886C-146.004,165.651 -142.302,159.417 -138.6,153.184C-134.851,146.869 -131.102,140.556 -127.352,134.241C-123.586,127.9 -119.82,121.559 -116.055,115.218C-112.305,108.902 -108.555,102.587 -104.804,96.271C-101.1,90.033 -97.396,83.796 -93.691,77.558C-90.065,71.451 -86.438,65.345 -82.813,59.238C-79.295,53.315 -75.777,47.392 -72.261,41.469C-68.884,35.781 -65.506,30.095 -62.129,24.407C-58.924,19.01 -55.718,13.611 -52.513,8.214C-49.51,3.157 -46.507,-1.899 -43.505,-6.956L-35.199,-20.942C-32.696,-25.157 -30.193,-29.372 -27.689,-33.588C-24.349,-39.215 -21.159,-44.886 -17.747,-50.482C-14.332,-56.084 -11.679,-62.001 -8.743,-67.833C-3.206,-78.837 0.674,-91.944 0,-104.412" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.7 KiB |
21
Voltaserve/ui/src/components/common/logo/logo-glossy.svg
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 358 705" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,-1578.79,-1684.71)">
|
||||
<g transform="matrix(1,0,0,1,1071.54,808.774)">
|
||||
<g transform="matrix(0.824738,0,0,0.824738,257.716,849.283)">
|
||||
<g>
|
||||
<g transform="matrix(1,0,0,1,735.951,511.402)">
|
||||
<path d="M0,-104.412C-0.158,-107.349 -0.612,-110.275 -1.449,-113.098C-2.362,-116.179 -3.535,-119.184 -4.931,-122.079C-4.985,-122.193 -5.041,-122.308 -5.097,-122.421C-5.228,-122.687 -5.363,-122.951 -5.498,-123.214C-7.231,-126.603 -9.219,-129.534 -12.043,-132.104C-16.201,-135.889 -21.338,-139.039 -26.458,-141.341C-34.216,-144.829 -42.774,-145.322 -51.171,-145.322C-61.669,-145.322 -155.843,-146.222 -166.559,-146.325L-166.554,-146.334C-166.554,-146.334 -120.073,-426.226 -119.996,-426.693C-118.955,-432.957 -118.006,-439.397 -117.982,-445.808C-117.972,-449.063 -118.199,-452.328 -118.796,-455.531C-119.355,-458.539 -119.806,-461.522 -121.02,-464.374C-121.165,-464.715 -121.318,-465.052 -121.481,-465.384C-123.422,-469.333 -126.557,-472.738 -130.13,-475.276C-137.152,-480.265 -148.817,-480.186 -156.233,-476.057C-162.988,-472.295 -168.569,-465.351 -172.734,-458.982C-182.516,-444.022 -192.515,-429.18 -201.853,-413.933C-205.017,-408.766 -208.051,-403.524 -211.146,-398.313C-213.341,-394.616 -215.537,-390.918 -217.732,-387.22L-225.213,-374.622C-227.974,-369.974 -230.734,-365.327 -233.494,-360.679C-236.489,-355.635 -239.484,-350.591 -242.479,-345.547C-245.679,-340.159 -248.878,-334.772 -252.077,-329.385C-255.449,-323.706 -258.821,-318.028 -262.193,-312.35C-265.706,-306.434 -269.219,-300.518 -272.732,-294.602C-276.355,-288.5 -279.979,-282.399 -283.602,-276.298C-287.304,-270.064 -291.006,-263.829 -294.708,-257.596L-305.956,-238.654C-309.721,-232.313 -313.487,-225.972 -317.252,-219.63C-321.003,-213.315 -324.753,-206.999 -328.504,-200.683C-332.208,-194.446 -335.912,-188.208 -339.616,-181.971C-343.243,-175.864 -346.869,-169.757 -350.495,-163.65C-354.013,-157.727 -357.53,-151.804 -361.047,-145.881C-364.424,-140.194 -367.801,-134.507 -371.179,-128.82C-374.384,-123.422 -377.589,-118.024 -380.795,-112.626C-383.798,-107.57 -386.8,-102.513 -389.803,-97.456C-392.572,-92.794 -395.34,-88.132 -398.108,-83.47C-400.612,-79.255 -403.115,-75.04 -405.618,-70.825C-408.959,-65.198 -412.148,-59.527 -415.561,-53.93C-418.976,-48.328 -421.629,-42.412 -424.564,-36.579C-430.102,-25.576 -433.981,-12.468 -433.308,-0C-433.149,2.937 -432.695,5.862 -431.859,8.686C-430.945,11.767 -429.772,14.772 -428.377,17.667C-428.322,17.781 -428.267,17.895 -428.211,18.009C-428.081,18.275 -427.944,18.538 -427.81,18.802C-426.076,22.191 -424.089,25.121 -421.264,27.692C-417.107,31.476 -411.969,34.627 -406.85,36.929C-399.092,40.416 -390.533,40.909 -382.137,40.909C-371.639,40.909 -277.465,41.81 -266.749,41.912L-266.754,41.922C-266.754,41.922 -313.234,321.814 -313.312,322.28C-314.352,328.545 -315.302,334.984 -315.325,341.396C-315.336,344.651 -315.108,347.915 -314.512,351.118C-313.952,354.127 -313.502,357.109 -312.288,359.962C-312.143,360.303 -311.99,360.64 -311.826,360.972C-309.886,364.921 -306.751,368.325 -303.178,370.863C-296.156,375.853 -284.491,375.774 -277.074,371.644C-270.32,367.883 -264.738,360.94 -260.574,354.569C-250.792,339.609 -240.792,324.768 -231.455,309.522C-228.291,304.355 -225.257,299.111 -222.163,293.901C-219.967,290.203 -217.771,286.506 -215.575,282.808C-213.081,278.609 -210.588,274.409 -208.095,270.21C-205.334,265.563 -202.574,260.914 -199.813,256.267C-196.818,251.223 -193.823,246.179 -190.828,241.134C-187.629,235.747 -184.43,230.359 -181.23,224.972C-177.858,219.294 -174.486,213.616 -171.115,207.938C-167.602,202.022 -164.089,196.106 -160.575,190.19C-156.952,184.088 -153.329,177.986 -149.706,171.886C-146.004,165.651 -142.302,159.417 -138.6,153.184C-134.851,146.869 -131.102,140.556 -127.352,134.241C-123.586,127.9 -119.82,121.559 -116.055,115.218C-112.305,108.902 -108.555,102.587 -104.804,96.271C-101.1,90.033 -97.396,83.796 -93.691,77.558C-90.065,71.451 -86.438,65.345 -82.813,59.238C-79.295,53.315 -75.777,47.392 -72.261,41.469C-68.884,35.781 -65.506,30.095 -62.129,24.407C-58.924,19.01 -55.718,13.611 -52.513,8.214C-49.51,3.157 -46.507,-1.899 -43.505,-6.956L-35.199,-20.942C-32.696,-25.157 -30.193,-29.372 -27.689,-33.588C-24.349,-39.215 -21.159,-44.886 -17.747,-50.482C-14.332,-56.084 -11.679,-62.001 -8.743,-67.833C-3.206,-78.837 0.674,-91.944 0,-104.412" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,550.012,53.3397)">
|
||||
<path d="M0,328.108C0.024,328.061 0.046,328.012 0.071,327.965L3.88,307.015L9.88,272.012L16.88,228.009L28.88,157.004L40.88,84.999L49.88,29.995L50.88,22.994C50.88,22.994 51.88,15.994 51.88,14.994C51.88,11.104 52.017,6.773 49.376,3.619C47.641,1.546 45.363,0 42.572,0.016C41.149,0.025 39.753,0.325 38.45,0.854L35.367,19.693C35.339,19.902 35.313,20.112 35.284,20.32C34.938,22.83 34.558,25.334 34.171,27.837C33.804,30.215 33.642,32.663 33.1,35.009C32.722,36.962 32.416,38.942 32.094,40.912C31.265,45.98 30.435,51.048 29.605,56.117L27.012,71.968C26.411,75.64 25.811,79.312 25.209,82.984C25.1,83.656 24.989,84.327 24.88,84.999L12.88,157.004L0.88,228.009L-6.12,272.012L-12.12,307.015L-15.938,328.016L-11.98,328.016C-11.98,328.016 -7.43,328.051 0,328.108" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,719.179,839.001)">
|
||||
<path d="M0,-434.473C-0.77,-438.758 -2.547,-442.987 -5.215,-446.5C-10.678,-453.693 -20.633,-457.43 -29.488,-457.43C-30.481,-457.43 -36.426,-457.47 -45.295,-457.533C-45.281,-457.497 -45.273,-457.46 -45.261,-457.424C-36.462,-457.361 -26.631,-453.631 -21.215,-446.5C-18.547,-442.987 -16.77,-438.758 -16,-434.473C-14.889,-428.287 -16.424,-422.049 -18.429,-416.214C-20.447,-410.339 -22.939,-404.635 -25.739,-399.093C-29.21,-392.222 -32.907,-385.459 -36.988,-378.93L-45.988,-363.93L-230.988,-51.93L-247.988,-23.93L-252.118,-17.613L-265.713,4.778L-275.799,20.204C-274.164,21.882 -272.118,23.06 -269.681,23.047C-266.961,23.031 -264.334,22.055 -262.322,20.222C-261.304,19.296 -260.43,18.226 -259.559,17.16C-258.371,15.706 -257.132,14.287 -256.095,12.731C-255.462,11.782 -254.83,10.833 -254.197,9.884C-252.797,7.785 -251.398,5.685 -249.998,3.586L-248.988,2.07L-231.988,-23.93L-214.988,-51.93L-29.988,-363.93L-20.988,-378.93C-16.907,-385.459 -13.21,-392.222 -9.739,-399.093C-6.939,-404.635 -4.447,-410.339 -2.429,-416.214C-0.424,-422.049 1.111,-428.287 0,-434.473" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.9 KiB |
13
Voltaserve/ui/src/components/common/logo/logo.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 358 705" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,-333.255,-1676.47)">
|
||||
<g transform="matrix(1,0,0,1,-174,800.543)">
|
||||
<g transform="matrix(0.824738,0,0,0.824738,864.683,1271.06)">
|
||||
<g>
|
||||
<path d="M0,-104.412C-0.158,-107.349 -0.612,-110.275 -1.449,-113.098C-2.362,-116.179 -3.535,-119.184 -4.931,-122.079C-4.985,-122.193 -5.041,-122.308 -5.097,-122.421C-5.228,-122.687 -5.363,-122.951 -5.498,-123.214C-7.231,-126.603 -9.219,-129.534 -12.043,-132.104C-16.201,-135.889 -21.338,-139.039 -26.458,-141.341C-34.216,-144.829 -42.774,-145.322 -51.171,-145.322C-61.669,-145.322 -155.843,-146.222 -166.559,-146.325L-166.554,-146.334C-166.554,-146.334 -120.073,-426.226 -119.996,-426.693C-118.955,-432.957 -118.006,-439.397 -117.982,-445.808C-117.972,-449.063 -118.199,-452.328 -118.796,-455.531C-119.355,-458.539 -119.806,-461.522 -121.02,-464.374C-121.165,-464.715 -121.318,-465.052 -121.481,-465.384C-123.422,-469.333 -126.557,-472.738 -130.13,-475.276C-137.152,-480.265 -148.817,-480.186 -156.233,-476.057C-162.988,-472.295 -168.569,-465.351 -172.734,-458.982C-182.516,-444.022 -192.515,-429.18 -201.853,-413.933C-205.017,-408.766 -208.051,-403.524 -211.146,-398.313C-213.341,-394.616 -215.537,-390.918 -217.732,-387.22L-225.213,-374.622C-227.974,-369.974 -230.734,-365.327 -233.494,-360.679C-236.489,-355.635 -239.484,-350.591 -242.479,-345.547C-245.679,-340.159 -248.878,-334.772 -252.077,-329.385C-255.449,-323.706 -258.821,-318.028 -262.193,-312.35C-265.706,-306.434 -269.219,-300.518 -272.732,-294.602C-276.355,-288.5 -279.979,-282.399 -283.602,-276.298C-287.304,-270.064 -291.006,-263.829 -294.708,-257.596L-305.956,-238.654C-309.721,-232.313 -313.487,-225.972 -317.252,-219.63C-321.003,-213.315 -324.753,-206.999 -328.504,-200.683C-332.208,-194.446 -335.912,-188.208 -339.616,-181.971C-343.243,-175.864 -346.869,-169.757 -350.495,-163.65C-354.013,-157.727 -357.53,-151.804 -361.047,-145.881C-364.424,-140.194 -367.801,-134.507 -371.179,-128.82C-374.384,-123.422 -377.589,-118.024 -380.795,-112.626C-383.798,-107.57 -386.8,-102.513 -389.803,-97.456C-392.572,-92.794 -395.34,-88.132 -398.108,-83.47C-400.612,-79.255 -403.115,-75.04 -405.618,-70.825C-408.959,-65.198 -412.148,-59.527 -415.561,-53.93C-418.976,-48.328 -421.629,-42.412 -424.564,-36.579C-430.102,-25.576 -433.981,-12.468 -433.308,-0C-433.149,2.937 -432.695,5.862 -431.859,8.686C-430.945,11.767 -429.772,14.772 -428.377,17.667C-428.322,17.781 -428.267,17.895 -428.211,18.009C-428.081,18.275 -427.944,18.538 -427.81,18.802C-426.076,22.191 -424.089,25.121 -421.264,27.692C-417.107,31.476 -411.969,34.627 -406.85,36.929C-399.092,40.416 -390.533,40.909 -382.137,40.909C-371.639,40.909 -277.465,41.81 -266.749,41.912L-266.754,41.922C-266.754,41.922 -313.234,321.814 -313.312,322.28C-314.352,328.545 -315.302,334.984 -315.325,341.396C-315.336,344.651 -315.108,347.915 -314.512,351.118C-313.952,354.127 -313.502,357.109 -312.288,359.962C-312.143,360.303 -311.99,360.64 -311.826,360.972C-309.886,364.921 -306.751,368.325 -303.178,370.863C-296.156,375.853 -284.491,375.774 -277.074,371.644C-270.32,367.883 -264.738,360.94 -260.574,354.569C-250.792,339.609 -240.792,324.768 -231.455,309.522C-228.291,304.355 -225.257,299.111 -222.163,293.901C-219.967,290.203 -217.771,286.506 -215.575,282.808C-213.081,278.609 -210.588,274.409 -208.095,270.21C-205.334,265.563 -202.574,260.914 -199.813,256.267C-196.818,251.223 -193.823,246.179 -190.828,241.134C-187.629,235.747 -184.43,230.359 -181.23,224.972C-177.858,219.294 -174.486,213.616 -171.115,207.938C-167.602,202.022 -164.089,196.106 -160.575,190.19C-156.952,184.088 -153.329,177.986 -149.706,171.886C-146.004,165.651 -142.302,159.417 -138.6,153.184C-134.851,146.869 -131.102,140.556 -127.352,134.241C-123.586,127.9 -119.82,121.559 -116.055,115.218C-112.305,108.902 -108.555,102.587 -104.804,96.271C-101.1,90.033 -97.396,83.796 -93.691,77.558C-90.065,71.451 -86.438,65.345 -82.813,59.238C-79.295,53.315 -75.777,47.392 -72.261,41.469C-68.884,35.781 -65.506,30.095 -62.129,24.407C-58.924,19.01 -55.718,13.611 -52.513,8.214C-49.51,3.157 -46.507,-1.899 -43.505,-6.956L-35.199,-20.942C-32.696,-25.157 -30.193,-29.372 -27.689,-33.588C-24.349,-39.215 -21.159,-44.886 -17.747,-50.482C-14.332,-56.084 -11.679,-62.001 -8.743,-67.833C-3.206,-78.837 0.674,-91.944 0,-104.412" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.7 KiB |
215
Voltaserve/ui/src/components/common/organization-selector.tsx
Normal file
@ -0,0 +1,215 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
useDisclosure,
|
||||
Table,
|
||||
Tr,
|
||||
Tbody,
|
||||
Td,
|
||||
Avatar,
|
||||
Radio,
|
||||
} from '@chakra-ui/react'
|
||||
import cx from 'classnames'
|
||||
import OrganizationAPI, {
|
||||
Organization,
|
||||
SortOrder,
|
||||
} from '@/client/api/organization'
|
||||
import { swrConfig } from '@/client/options'
|
||||
import { SectionSpinner, Pagination, SearchInput } from '@/lib'
|
||||
|
||||
export type OrganizationSelectorProps = {
|
||||
onConfirm?: (organization: Organization) => void
|
||||
}
|
||||
|
||||
const OrganizationSelector = ({ onConfirm }: OrganizationSelectorProps) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const [page, setPage] = useState(1)
|
||||
const [query, setQuery] = useState('')
|
||||
const [selected, setSelected] = useState<Organization>()
|
||||
const [confirmed, setConfirmed] = useState<Organization>()
|
||||
const {
|
||||
data: list,
|
||||
error,
|
||||
mutate,
|
||||
} = OrganizationAPI.useList(
|
||||
{ query, page, size: 5, sortOrder: SortOrder.Desc },
|
||||
swrConfig(),
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
mutate()
|
||||
}, [page, query, mutate])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setPage(1)
|
||||
setSelected(undefined)
|
||||
setQuery('')
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
if (selected) {
|
||||
setConfirmed(selected)
|
||||
onConfirm?.(selected)
|
||||
onClose()
|
||||
}
|
||||
}, [selected, onConfirm, onClose])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cx(
|
||||
'w-full',
|
||||
{ 'text-black': confirmed },
|
||||
{ 'dark:text-white': confirmed },
|
||||
{ 'text-gray-500': !confirmed },
|
||||
{ 'dark:text-gray-500': !confirmed },
|
||||
)}
|
||||
onClick={onOpen}
|
||||
>
|
||||
{confirmed ? confirmed.name : 'Select Organization'}
|
||||
</Button>
|
||||
<Modal
|
||||
size="xl"
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
closeOnOverlayClick={false}
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Select Organization</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<div className={cx('flex', 'flex-col', 'gap-1.5')}>
|
||||
<SearchInput
|
||||
query={query}
|
||||
onChange={(value) => setQuery(value)}
|
||||
/>
|
||||
{!list && error && (
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'h-[300px]',
|
||||
)}
|
||||
>
|
||||
<span>Failed to load organizations.</span>
|
||||
</div>
|
||||
)}
|
||||
{!list && !error && <SectionSpinner />}
|
||||
{list && list.data.length === 0 && (
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'h-[300px]',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'flex-col',
|
||||
'items-center',
|
||||
'gap-1.5',
|
||||
)}
|
||||
>
|
||||
<span>There are no organizations.</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{list && list.data.length > 0 && (
|
||||
<Table variant="simple" size="sm">
|
||||
<colgroup>
|
||||
<col className={cx('w-[40px]')} />
|
||||
<col className={cx('w-auto')} />
|
||||
</colgroup>
|
||||
<Tbody>
|
||||
{list.data.map((o) => (
|
||||
<Tr
|
||||
key={o.id}
|
||||
className={cx(
|
||||
'cursor-pointer',
|
||||
{ 'bg-gray-100': selected?.id === o.id },
|
||||
{ 'dark:bg-gray-600': selected?.id === o.id },
|
||||
{ 'bg-transparent': selected?.id !== o.id },
|
||||
)}
|
||||
onClick={() => setSelected(o)}
|
||||
>
|
||||
<Td className={cx('px-0.5', 'text-center')}>
|
||||
<Radio size="md" isChecked={selected?.id === o.id} />
|
||||
</Td>
|
||||
<Td className={cx('px-0.5')}>
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'flex-row',
|
||||
'items-center',
|
||||
'gap-1.5',
|
||||
)}
|
||||
>
|
||||
<Avatar
|
||||
name={o.name}
|
||||
size="sm"
|
||||
className={cx('w-[40px]', 'h-[40px]')}
|
||||
/>
|
||||
<span className={cx('text-base')}>{o.name}</span>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
)}
|
||||
{list && (
|
||||
<div className={cx('self-end')}>
|
||||
{list.totalPages > 1 ? (
|
||||
<Pagination
|
||||
uiSize="md"
|
||||
maxButtons={3}
|
||||
page={page}
|
||||
totalPages={list.totalPages}
|
||||
onPageChange={(value) => setPage(value)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div className={cx('flex', 'flex-row', 'items-center', 'gap-1')}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
colorScheme="blue"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
colorScheme="blue"
|
||||
isDisabled={!selected}
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrganizationSelector
|
83
Voltaserve/ui/src/components/common/path.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
import { useMemo } from 'react'
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
Skeleton,
|
||||
} from '@chakra-ui/react'
|
||||
import cx from 'classnames'
|
||||
import FileAPI from '@/client/api/file'
|
||||
import { Text } from '@/lib'
|
||||
|
||||
export type PathProps = {
|
||||
rootId: string
|
||||
fileId: string
|
||||
maxCharacters?: number
|
||||
onClick?: (fileId: string) => void
|
||||
}
|
||||
|
||||
const Path = ({ rootId, fileId, maxCharacters, onClick }: PathProps) => {
|
||||
const { data: path, error, isLoading } = FileAPI.useGetPath(fileId)
|
||||
const hasMore = path && path.length > 3
|
||||
const shortPath = useMemo(() => {
|
||||
if (!path) {
|
||||
return []
|
||||
}
|
||||
return hasMore ? path.slice(1).slice(-3) : path.slice(1)
|
||||
}, [hasMore, path])
|
||||
|
||||
return (
|
||||
<>
|
||||
{path && !error ? (
|
||||
<Breadcrumb className={cx('overflow-hidden', 'shrink-0')}>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink
|
||||
className={cx('nowrap')}
|
||||
onClick={() => onClick?.(rootId)}
|
||||
>
|
||||
Home
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
{hasMore ? (
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink>…</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
) : null}
|
||||
{shortPath.map((file) => (
|
||||
<BreadcrumbItem key={file.id}>
|
||||
<BreadcrumbLink
|
||||
isCurrentPage={fileId === file.id}
|
||||
onClick={() => onClick?.(file.id)}
|
||||
>
|
||||
{maxCharacters ? (
|
||||
<Text maxCharacters={maxCharacters}>{file.name}</Text>
|
||||
) : (
|
||||
file.name
|
||||
)}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
))}
|
||||
</Breadcrumb>
|
||||
) : null}
|
||||
{isLoading ? (
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'flex-row',
|
||||
'items-center',
|
||||
'gap-0.5',
|
||||
'flex-shrink-0',
|
||||
)}
|
||||
>
|
||||
<Skeleton className={cx('w-[100px]', 'h-[20px]', 'rounded-[20px]')} />
|
||||
<span>/</span>
|
||||
<Skeleton className={cx('w-[100px]', 'h-[20px]', 'rounded-[20px]')} />
|
||||
<span>/</span>
|
||||
<Skeleton className={cx('w-[100px]', 'h-[20px]', 'rounded-[20px]')} />
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Path
|
55
Voltaserve/ui/src/components/common/storage-input/convert.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import {
|
||||
byteToGigabyte,
|
||||
byteToMegabyte,
|
||||
byteToTerabyte,
|
||||
gigabyteToByte,
|
||||
megabyteToByte,
|
||||
terabyteToByte,
|
||||
} from '@/helpers/convert-storage'
|
||||
|
||||
export type Unit = 'b' | 'mb' | 'gb' | 'tb'
|
||||
|
||||
export function getUnit(value: number): Unit {
|
||||
if (value >= 1e12) {
|
||||
return 'tb'
|
||||
}
|
||||
if (value >= 1e9) {
|
||||
return 'gb'
|
||||
}
|
||||
if (value >= 1e6) {
|
||||
return 'mb'
|
||||
}
|
||||
return 'b'
|
||||
}
|
||||
|
||||
export function convertFromByte(value: number, unit: Unit): number {
|
||||
if (unit === 'b') {
|
||||
return value
|
||||
}
|
||||
if (unit === 'mb') {
|
||||
return byteToMegabyte(value)
|
||||
}
|
||||
if (unit === 'gb') {
|
||||
return byteToGigabyte(value)
|
||||
}
|
||||
if (unit === 'tb') {
|
||||
return byteToTerabyte(value)
|
||||
}
|
||||
throw new Error(`Invalid unit: ${unit}`)
|
||||
}
|
||||
|
||||
export function normalizeToByte(value: number, unit: Unit) {
|
||||
if (unit === 'b') {
|
||||
return value
|
||||
}
|
||||
if (unit === 'mb') {
|
||||
return megabyteToByte(value)
|
||||
}
|
||||
if (unit === 'gb') {
|
||||
return gigabyteToByte(value)
|
||||
}
|
||||
if (unit === 'tb') {
|
||||
return terabyteToByte(value)
|
||||
}
|
||||
throw new Error(`Invalid unit: ${unit}`)
|
||||
}
|
65
Voltaserve/ui/src/components/common/storage-input/index.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import { useState } from 'react'
|
||||
import { Input, Select } from '@chakra-ui/react'
|
||||
import { FieldAttributes, FieldProps } from 'formik'
|
||||
import cx from 'classnames'
|
||||
import { Unit, convertFromByte, getUnit, normalizeToByte } from './convert'
|
||||
|
||||
const StorageInput = ({ id, field, form }: FieldAttributes<FieldProps>) => {
|
||||
const [value, setValue] = useState<number | null>(
|
||||
field.value ? convertFromByte(field.value, getUnit(field.value)) : null,
|
||||
)
|
||||
const [unit, setUnit] = useState<Unit>(
|
||||
field.value ? getUnit(field.value) : 'b',
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<input id={id} type="hidden" {...field} />
|
||||
<div className={cx('flex', 'flex-row', 'gap-0.5')}>
|
||||
<Input
|
||||
type="number"
|
||||
disabled={form.isSubmitting}
|
||||
value={value || ''}
|
||||
onChange={(event) => {
|
||||
if (event.target.value) {
|
||||
const newValue = parseInt(event.target.value)
|
||||
setValue(newValue)
|
||||
form.setFieldValue(field.name, normalizeToByte(newValue, unit))
|
||||
} else {
|
||||
setValue(null)
|
||||
form.setFieldValue(field.name, '')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className={cx('min-w-[80px]')}>
|
||||
<Select
|
||||
defaultValue={unit}
|
||||
disabled={form.isSubmitting}
|
||||
onChange={(event) => {
|
||||
const newUnit = event.target.value as Unit
|
||||
setUnit(newUnit)
|
||||
if (value) {
|
||||
const newValue = convertFromByte(
|
||||
normalizeToByte(value, unit),
|
||||
newUnit,
|
||||
)
|
||||
setValue(newValue)
|
||||
form.setFieldValue(
|
||||
field.name,
|
||||
normalizeToByte(newValue, newUnit),
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="b">B</option>
|
||||
<option value="mb">MB</option>
|
||||
<option value="gb">GB</option>
|
||||
<option value="tb">TB</option>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default StorageInput
|
231
Voltaserve/ui/src/components/common/user-selector.tsx
Normal file
@ -0,0 +1,231 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
useDisclosure,
|
||||
Table,
|
||||
Tr,
|
||||
Tbody,
|
||||
Td,
|
||||
Avatar,
|
||||
Radio,
|
||||
} from '@chakra-ui/react'
|
||||
import cx from 'classnames'
|
||||
import UserAPI, { SortOrder, User } from '@/client/api/user'
|
||||
import { swrConfig } from '@/client/options'
|
||||
import userToString from '@/helpers/user-to-string'
|
||||
import { SectionSpinner, Pagination, SearchInput } from '@/lib'
|
||||
|
||||
export type UserSelectorProps = {
|
||||
value?: User
|
||||
organizationId?: string
|
||||
groupId?: string
|
||||
nonGroupMembersOnly?: boolean
|
||||
onConfirm?: (user: User) => void
|
||||
}
|
||||
|
||||
const UserSelector = ({
|
||||
value,
|
||||
organizationId,
|
||||
groupId,
|
||||
nonGroupMembersOnly,
|
||||
onConfirm,
|
||||
}: UserSelectorProps) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const [page, setPage] = useState(1)
|
||||
const [query, setQuery] = useState('')
|
||||
const [selected, setSelected] = useState<User>()
|
||||
const {
|
||||
data: list,
|
||||
error,
|
||||
mutate,
|
||||
} = UserAPI.useList(
|
||||
{
|
||||
query,
|
||||
organizationId,
|
||||
groupId,
|
||||
nonGroupMembersOnly,
|
||||
page,
|
||||
size: 5,
|
||||
sortOrder: SortOrder.Desc,
|
||||
},
|
||||
swrConfig(),
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
mutate()
|
||||
}, [page, query, mutate])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setPage(1)
|
||||
setSelected(undefined)
|
||||
setQuery('')
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
if (selected) {
|
||||
onConfirm?.(selected)
|
||||
onClose()
|
||||
}
|
||||
}, [selected, onConfirm, onClose])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cx(
|
||||
'w-full',
|
||||
{ 'text-black': value },
|
||||
{ 'dark:text-white': value },
|
||||
{ 'text-gray-500': !value },
|
||||
{ 'dark:text-gray-500': !value },
|
||||
)}
|
||||
onClick={onOpen}
|
||||
>
|
||||
{value ? userToString(value) : 'Select User'}
|
||||
</Button>
|
||||
<Modal
|
||||
size="xl"
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
closeOnOverlayClick={false}
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Select User</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<div className={cx('flex', 'flex-col', 'gap-1.5')}>
|
||||
<SearchInput
|
||||
query={query}
|
||||
onChange={(value) => setQuery(value)}
|
||||
/>
|
||||
{!list && error && (
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'h-[300px]',
|
||||
)}
|
||||
>
|
||||
<span>Failed to load users.</span>
|
||||
</div>
|
||||
)}
|
||||
{!list && !error && <SectionSpinner />}
|
||||
{list && list.data.length === 0 && (
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'h-[300px]',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'flex-col',
|
||||
'items-center',
|
||||
'gap-1.5',
|
||||
)}
|
||||
>
|
||||
<span>There are no users.</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{list && list.data.length > 0 && (
|
||||
<Table variant="simple" size="sm">
|
||||
<colgroup>
|
||||
<col className={cx('w-[40px]')} />
|
||||
<col className={cx('w-[auto]')} />
|
||||
</colgroup>
|
||||
<Tbody>
|
||||
{list.data.map((u) => (
|
||||
<Tr
|
||||
key={u.id}
|
||||
className={cx(
|
||||
'cursor-pointer',
|
||||
{ 'bg-gray-100': selected?.id === u.id },
|
||||
{ 'dark:bg-gray-600': selected?.id === u.id },
|
||||
{ 'bg-transparent': selected?.id !== u.id },
|
||||
)}
|
||||
onClick={() => setSelected(u)}
|
||||
>
|
||||
<Td className={cx('px-0.5', 'text-center')}>
|
||||
<Radio size="md" isChecked={selected?.id === u.id} />
|
||||
</Td>
|
||||
<Td className={cx('p-0.5')}>
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'flex-row',
|
||||
'items-center',
|
||||
'gap-1.5',
|
||||
)}
|
||||
>
|
||||
<Avatar
|
||||
name={u.fullName}
|
||||
size="sm"
|
||||
className={cx('w-[40px]', 'h-[40px]')}
|
||||
/>
|
||||
<span className={cx('text-base')}>
|
||||
{userToString(u)}
|
||||
</span>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
)}
|
||||
{list && (
|
||||
<div className={cx('self-end')}>
|
||||
{list.totalPages > 1 ? (
|
||||
<Pagination
|
||||
uiSize="md"
|
||||
maxButtons={3}
|
||||
page={page}
|
||||
totalPages={list.totalPages}
|
||||
onPageChange={(value) => setPage(value)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div className={cx('flex', 'flex-row', 'items-center', 'gap-1')}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
colorScheme="blue"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
colorScheme="blue"
|
||||
isDisabled={!selected}
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserSelector
|
157
Voltaserve/ui/src/components/file/file-browse.tsx
Normal file
@ -0,0 +1,157 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { Button } from '@chakra-ui/react'
|
||||
import cx from 'classnames'
|
||||
import FileAPI, { File, FileType } from '@/client/api/file'
|
||||
import WorkspaceAPI from '@/client/api/workspace'
|
||||
import Path from '@/components/common/path'
|
||||
import FolderSvg from '@/components/file/list/item/icon/icon-folder/assets/icon-folder.svg'
|
||||
import { IconChevronRight, SectionSpinner, Text } from '@/lib'
|
||||
|
||||
export type FileBrowseProps = {
|
||||
onChange?: (id: string) => void
|
||||
}
|
||||
|
||||
const FileBrowse = ({ onChange }: FileBrowseProps) => {
|
||||
const { id } = useParams()
|
||||
const { data: workspace } = WorkspaceAPI.useGetById(id)
|
||||
const [folders, setFolders] = useState<File[]>([])
|
||||
const [page, setPage] = useState(1)
|
||||
const [totalPages, setTotalPages] = useState(1)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [isSpinnerVisible, setIsSpinnerVisible] = useState(false)
|
||||
const [fileId, setFileId] = useState<string>()
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
setFileId(workspace.rootId)
|
||||
}
|
||||
}, [workspace])
|
||||
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
if (fileId) {
|
||||
try {
|
||||
const timeoutId = setTimeout(() => setIsSpinnerVisible(true), 250)
|
||||
const result = await FileAPI.list(fileId, {
|
||||
page: 1,
|
||||
type: FileType.Folder,
|
||||
})
|
||||
clearTimeout(timeoutId)
|
||||
setTotalPages(result.totalPages)
|
||||
setFolders(result.data)
|
||||
} finally {
|
||||
setIsSpinnerVisible(false)
|
||||
}
|
||||
}
|
||||
})()
|
||||
}, [fileId])
|
||||
|
||||
useEffect(() => {
|
||||
if (fileId) {
|
||||
onChange?.(fileId)
|
||||
}
|
||||
}, [fileId, onChange])
|
||||
|
||||
const handleLoadMore = useCallback(async (fileId: string, page: number) => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const result = await FileAPI.list(fileId, {
|
||||
page,
|
||||
type: FileType.Folder,
|
||||
})
|
||||
setTotalPages(result.totalPages)
|
||||
setFolders(result.data)
|
||||
setPage(page + 1)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (isSpinnerVisible) {
|
||||
return <SectionSpinner />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx('flex', 'flex-col', 'gap-1')}>
|
||||
{workspace && fileId ? (
|
||||
<Path
|
||||
rootId={workspace.rootId}
|
||||
fileId={fileId}
|
||||
maxCharacters={10}
|
||||
onClick={(fileId) => setFileId(fileId)}
|
||||
/>
|
||||
) : null}
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'flex-col',
|
||||
'gap-0',
|
||||
'border-t',
|
||||
'pt-1.5',
|
||||
'h-[250px]',
|
||||
'xl:h-[400px]',
|
||||
'overflow-y-scroll',
|
||||
'border-t-gray-300',
|
||||
'dark:border-t-gray-600',
|
||||
)}
|
||||
>
|
||||
{folders.length > 0 ? (
|
||||
folders.map((f) => (
|
||||
<div
|
||||
key={f.id}
|
||||
className={cx(
|
||||
'flex',
|
||||
'flex-row',
|
||||
'gap-1.5',
|
||||
'items-center',
|
||||
'cursor-pointer',
|
||||
'p-1',
|
||||
'rounded-md',
|
||||
'hover:bg-gray-100',
|
||||
'hover:dark:bg-gray-700',
|
||||
'active:bg-gray-100',
|
||||
'active:dark:bg-gray-700',
|
||||
)}
|
||||
onClick={() => setFileId(f.id)}
|
||||
>
|
||||
<img
|
||||
src={FolderSvg}
|
||||
className={cx('shrink-0', 'w-[36px]', 'h-[28.84px]')}
|
||||
/>
|
||||
<Text noOfLines={1}>{f.name}</Text>
|
||||
<div className={cx('grow')} />
|
||||
<IconChevronRight />
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div
|
||||
className={cx('flex', 'items-center', 'justify-center', 'h-full')}
|
||||
>
|
||||
<span>There are no folders.</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{totalPages > page && fileId ? (
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'w-full',
|
||||
'p-1.5',
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
onClick={() => handleLoadMore(fileId, page)}
|
||||
isLoading={loading}
|
||||
>
|
||||
Load More
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FileBrowse
|
89
Voltaserve/ui/src/components/file/file-copy.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import {
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalCloseButton,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Button,
|
||||
} from '@chakra-ui/react'
|
||||
import { useSWRConfig } from 'swr'
|
||||
import cx from 'classnames'
|
||||
import FileAPI, { List } from '@/client/api/file'
|
||||
import useFileListSearchParams from '@/hooks/use-file-list-params'
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hook'
|
||||
import { copyModalDidClose, selectionUpdated } from '@/store/ui/files'
|
||||
import FileBrowse from './file-browse'
|
||||
|
||||
const FileCopy = () => {
|
||||
const { mutate } = useSWRConfig()
|
||||
const dispatch = useAppDispatch()
|
||||
const { fileId } = useParams()
|
||||
const isModalOpen = useAppSelector((state) => state.ui.files.isCopyModalOpen)
|
||||
const selection = useAppSelector((state) => state.ui.files.selection)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [targetId, setTargetId] = useState<string>()
|
||||
const fileListSearchParams = useFileListSearchParams()
|
||||
|
||||
const handleMove = useCallback(async () => {
|
||||
if (!targetId) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
setLoading(true)
|
||||
await FileAPI.copy(targetId, {
|
||||
ids: selection,
|
||||
})
|
||||
if (fileId === targetId) {
|
||||
await mutate<List>(`/files/${targetId}/list?${fileListSearchParams}`)
|
||||
}
|
||||
dispatch(selectionUpdated([]))
|
||||
dispatch(copyModalDidClose())
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [targetId, fileId, selection, fileListSearchParams, mutate, dispatch])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => dispatch(copyModalDidClose())}
|
||||
closeOnOverlayClick={false}
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Copy {selection.length} Item(s) to…</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<FileBrowse onChange={(id) => setTargetId(id)} />
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div className={cx('flex', 'flex-row', 'items-center', 'gap-1')}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
colorScheme="blue"
|
||||
disabled={loading}
|
||||
onClick={() => dispatch(copyModalDidClose())}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
colorScheme="blue"
|
||||
isLoading={loading}
|
||||
onClick={handleMove}
|
||||
>
|
||||
Copy Here
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default FileCopy
|
147
Voltaserve/ui/src/components/file/file-create.tsx
Normal file
@ -0,0 +1,147 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
Input,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
} from '@chakra-ui/react'
|
||||
import { useSWRConfig } from 'swr'
|
||||
import {
|
||||
Field,
|
||||
FieldAttributes,
|
||||
FieldProps,
|
||||
Form,
|
||||
Formik,
|
||||
FormikHelpers,
|
||||
} from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import cx from 'classnames'
|
||||
import FileAPI, { List } from '@/client/api/file'
|
||||
import useFileListSearchParams from '@/hooks/use-file-list-params'
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hook'
|
||||
import { createModalDidClose } from '@/store/ui/files'
|
||||
|
||||
type FormValues = {
|
||||
name: string
|
||||
}
|
||||
|
||||
const FileCreate = () => {
|
||||
const { mutate } = useSWRConfig()
|
||||
const { id, fileId } = useParams()
|
||||
const dispatch = useAppDispatch()
|
||||
const isModalOpen = useAppSelector(
|
||||
(state) => state.ui.files.isCreateModalOpen,
|
||||
)
|
||||
const [inputRef, setInputRef] = useState<HTMLInputElement | null>()
|
||||
const formSchema = Yup.object().shape({
|
||||
name: Yup.string().required('Name is required').max(255),
|
||||
})
|
||||
const fileListSearchParams = useFileListSearchParams()
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef) {
|
||||
inputRef.select()
|
||||
}
|
||||
}, [inputRef])
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (
|
||||
{ name }: FormValues,
|
||||
{ setSubmitting }: FormikHelpers<FormValues>,
|
||||
) => {
|
||||
setSubmitting(true)
|
||||
try {
|
||||
await FileAPI.createFolder({
|
||||
name,
|
||||
workspaceId: id!,
|
||||
parentId: fileId!,
|
||||
})
|
||||
await mutate<List>(`/files/${fileId}/list?${fileListSearchParams}`)
|
||||
setSubmitting(false)
|
||||
dispatch(createModalDidClose())
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
},
|
||||
[id, fileId, fileListSearchParams, mutate, dispatch],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => dispatch(createModalDidClose())}
|
||||
closeOnOverlayClick={false}
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>New Folder</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<Formik
|
||||
enableReinitialize={true}
|
||||
initialValues={{ name: '' }}
|
||||
validationSchema={formSchema}
|
||||
validateOnBlur={false}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({ errors, touched, isSubmitting }) => (
|
||||
<Form>
|
||||
<ModalBody>
|
||||
<Field name="name">
|
||||
{({ field }: FieldAttributes<FieldProps>) => (
|
||||
<FormControl
|
||||
isInvalid={errors.name && touched.name ? true : false}
|
||||
>
|
||||
<Input
|
||||
ref={(r) => setInputRef(r)}
|
||||
{...field}
|
||||
placeholder="Name"
|
||||
disabled={isSubmitting}
|
||||
autoFocus
|
||||
/>
|
||||
<FormErrorMessage>{errors.name}</FormErrorMessage>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div
|
||||
className={cx('flex', 'flex-row', 'items-center', 'gap-1')}
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
colorScheme="blue"
|
||||
disabled={isSubmitting}
|
||||
onClick={() => dispatch(createModalDidClose())}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="solid"
|
||||
colorScheme="blue"
|
||||
isLoading={isSubmitting}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default FileCreate
|
86
Voltaserve/ui/src/components/file/file-move.tsx
Normal file
@ -0,0 +1,86 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import {
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalCloseButton,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Button,
|
||||
} from '@chakra-ui/react'
|
||||
import { useSWRConfig } from 'swr'
|
||||
import cx from 'classnames'
|
||||
import FileAPI, { List } from '@/client/api/file'
|
||||
import useFileListSearchParams from '@/hooks/use-file-list-params'
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hook'
|
||||
import { moveModalDidClose, selectionUpdated } from '@/store/ui/files'
|
||||
import FileBrowse from './file-browse'
|
||||
|
||||
const FileMove = () => {
|
||||
const { mutate } = useSWRConfig()
|
||||
const { fileId } = useParams()
|
||||
const dispatch = useAppDispatch()
|
||||
const selection = useAppSelector((state) => state.ui.files.selection)
|
||||
const isModalOpen = useAppSelector((state) => state.ui.files.isMoveModalOpen)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [targetId, setTargetId] = useState<string>()
|
||||
const fileListSearchParams = useFileListSearchParams()
|
||||
|
||||
const handleMove = useCallback(async () => {
|
||||
if (!targetId) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
setLoading(true)
|
||||
await FileAPI.move(targetId, { ids: selection })
|
||||
await mutate<List>(`/files/${fileId}/list?${fileListSearchParams}`)
|
||||
dispatch(selectionUpdated([]))
|
||||
dispatch(moveModalDidClose())
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [targetId, fileId, selection, fileListSearchParams, mutate, dispatch])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => dispatch(moveModalDidClose())}
|
||||
closeOnOverlayClick={false}
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Move {selection.length} Item(s) to…</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<FileBrowse onChange={(id) => setTargetId(id)} />
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div className={cx('flex', 'flex-row', 'items-center', 'gap-1')}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
colorScheme="blue"
|
||||
disabled={loading}
|
||||
onClick={() => dispatch(moveModalDidClose())}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
colorScheme="blue"
|
||||
isDisabled={targetId === fileId}
|
||||
isLoading={loading}
|
||||
onClick={handleMove}
|
||||
>
|
||||
Move Here
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default FileMove
|
138
Voltaserve/ui/src/components/file/file-rename.tsx
Normal file
@ -0,0 +1,138 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
Input,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
} from '@chakra-ui/react'
|
||||
import { useSWRConfig } from 'swr'
|
||||
import {
|
||||
Field,
|
||||
FieldAttributes,
|
||||
FieldProps,
|
||||
Form,
|
||||
Formik,
|
||||
FormikHelpers,
|
||||
} from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import cx from 'classnames'
|
||||
import FileAPI, { List } from '@/client/api/file'
|
||||
import useFileListSearchParams from '@/hooks/use-file-list-params'
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hook'
|
||||
import { renameModalDidClose } from '@/store/ui/files'
|
||||
|
||||
type FormValues = {
|
||||
name: string
|
||||
}
|
||||
|
||||
const FileRename = () => {
|
||||
const { mutate } = useSWRConfig()
|
||||
const dispatch = useAppDispatch()
|
||||
const { fileId } = useParams()
|
||||
const isModalOpen = useAppSelector(
|
||||
(state) => state.ui.files.isRenameModalOpen,
|
||||
)
|
||||
const id = useAppSelector((state) => state.ui.files.selection[0])
|
||||
const { data: file, mutate: mutateFile } = FileAPI.useGetById(id)
|
||||
const formSchema = Yup.object().shape({
|
||||
name: Yup.string().required('Name is required').max(255),
|
||||
})
|
||||
const fileListSearchParams = useFileListSearchParams()
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (
|
||||
{ name }: FormValues,
|
||||
{ setSubmitting }: FormikHelpers<FormValues>,
|
||||
) => {
|
||||
if (!file) {
|
||||
return
|
||||
}
|
||||
setSubmitting(true)
|
||||
try {
|
||||
await mutateFile(await FileAPI.rename(file.id, { name }))
|
||||
await mutate<List>(`/files/${fileId}/list?${fileListSearchParams}`)
|
||||
setSubmitting(false)
|
||||
dispatch(renameModalDidClose())
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
},
|
||||
[file, fileId, fileListSearchParams, dispatch, mutate, mutateFile],
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => dispatch(renameModalDidClose())}
|
||||
closeOnOverlayClick={false}
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Rename File</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<Formik
|
||||
enableReinitialize={true}
|
||||
initialValues={{ name: file?.name || '' }}
|
||||
validationSchema={formSchema}
|
||||
validateOnBlur={false}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({ errors, touched, isSubmitting }) => (
|
||||
<Form>
|
||||
<ModalBody>
|
||||
<Field name="name">
|
||||
{({ field }: FieldAttributes<FieldProps>) => (
|
||||
<FormControl
|
||||
isInvalid={errors.name && touched.name ? true : false}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="Name"
|
||||
disabled={isSubmitting}
|
||||
autoFocus
|
||||
/>
|
||||
<FormErrorMessage>{errors.name}</FormErrorMessage>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div
|
||||
className={cx('flex', 'flex-row', 'items-center', 'gap-1')}
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
colorScheme="blue"
|
||||
disabled={isSubmitting}
|
||||
onClick={() => dispatch(renameModalDidClose())}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="solid"
|
||||
colorScheme="blue"
|
||||
isLoading={isSubmitting}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default FileRename
|
489
Voltaserve/ui/src/components/file/file-toolbar.tsx
Normal file
@ -0,0 +1,489 @@
|
||||
import { ChangeEvent, ReactElement, useCallback, useRef, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuDivider,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
Portal,
|
||||
Spacer,
|
||||
Slider,
|
||||
SliderTrack,
|
||||
SliderFilledTrack,
|
||||
SliderThumb,
|
||||
} from '@chakra-ui/react'
|
||||
import { useSWRConfig } from 'swr'
|
||||
import cx from 'classnames'
|
||||
import FileAPI, { List, SortBy, SortOrder } from '@/client/api/file'
|
||||
import { ltEditorPermission, ltOwnerPermission } from '@/client/api/permission'
|
||||
import downloadFile from '@/helpers/download-file'
|
||||
import mapFileList from '@/helpers/map-file-list'
|
||||
import useFileListSearchParams from '@/hooks/use-file-list-params'
|
||||
import {
|
||||
IconAdd,
|
||||
IconFileCopy,
|
||||
IconMoreVert,
|
||||
IconDownload,
|
||||
IconEdit,
|
||||
IconArrowTopRight,
|
||||
IconGroup,
|
||||
IconDelete,
|
||||
IconUpload,
|
||||
IconRefresh,
|
||||
IconGridView,
|
||||
IconArrowDownward,
|
||||
IconArrowUpward,
|
||||
IconCheck,
|
||||
IconSelectCheckBox,
|
||||
IconCheckBoxOutlineBlank,
|
||||
IconLibraryAddCheck,
|
||||
IconExpandMore,
|
||||
IconClose,
|
||||
IconList,
|
||||
} from '@/lib'
|
||||
import { uploadAdded, UploadDecorator } from '@/store/entities/uploads'
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hook'
|
||||
import {
|
||||
sortByUpdated,
|
||||
viewTypeToggled,
|
||||
selectionModeToggled,
|
||||
sortOrderToggled,
|
||||
} from '@/store/ui/files'
|
||||
import {
|
||||
copyModalDidOpen,
|
||||
createModalDidOpen,
|
||||
deleteModalDidOpen,
|
||||
iconScaleUpdated,
|
||||
moveModalDidOpen,
|
||||
renameModalDidOpen,
|
||||
selectionUpdated,
|
||||
sharingModalDidOpen,
|
||||
} from '@/store/ui/files'
|
||||
import { uploadsDrawerOpened } from '@/store/ui/uploads-drawer'
|
||||
import { FileViewType } from '@/types/file'
|
||||
|
||||
const ICON_SCALE_SLIDER_STEP = 0.25
|
||||
const ICON_SCALE_SLIDER_MIN = 1
|
||||
const ICON_SCALE_SLIDER_MAX = ICON_SCALE_SLIDER_STEP * 9
|
||||
|
||||
export type FileToolbarProps = {
|
||||
list?: List
|
||||
}
|
||||
|
||||
const FileToolbar = ({ list }: FileToolbarProps) => {
|
||||
const dispatch = useAppDispatch()
|
||||
const { mutate } = useSWRConfig()
|
||||
const { id, fileId } = useParams()
|
||||
const [isRefreshing, setIsRefreshing] = useState(false)
|
||||
const fileCount = useAppSelector(
|
||||
(state) => state.entities.files.list?.data.length,
|
||||
)
|
||||
const selectionCount = useAppSelector(
|
||||
(state) => state.ui.files.selection.length,
|
||||
)
|
||||
const singleFile = useAppSelector((state) =>
|
||||
state.ui.files.selection.length === 1
|
||||
? list?.data.find((e) => e.id === state.ui.files.selection[0])
|
||||
: null,
|
||||
)
|
||||
const iconScale = useAppSelector((state) => state.ui.files.iconScale)
|
||||
const viewType = useAppSelector((state) => state.ui.files.viewType)
|
||||
const sortBy = useAppSelector((state) => state.ui.files.sortBy)
|
||||
const sortOrder = useAppSelector((state) => state.ui.files.sortOrder)
|
||||
const hasOwnerPermission = useAppSelector(
|
||||
(state) =>
|
||||
list?.data.findIndex(
|
||||
(f) =>
|
||||
state.ui.files.selection.findIndex(
|
||||
(s) => f.id === s && ltOwnerPermission(f.permission),
|
||||
) !== -1,
|
||||
) === -1,
|
||||
)
|
||||
const hasEditorPermission = useAppSelector(
|
||||
(state) =>
|
||||
list?.data.findIndex(
|
||||
(f) =>
|
||||
state.ui.files.selection.findIndex(
|
||||
(s) => f.id === s && ltEditorPermission(f.permission),
|
||||
) !== -1,
|
||||
) === -1,
|
||||
)
|
||||
const isSelectionMode = useAppSelector(
|
||||
(state) => state.ui.files.isSelectionMode,
|
||||
)
|
||||
const fileUploadInput = useRef<HTMLInputElement>(null)
|
||||
const folderUploadInput = useRef<HTMLInputElement>(null)
|
||||
const fileListSearchParams = useFileListSearchParams()
|
||||
const { data: folder } = FileAPI.useGetById(fileId)
|
||||
const stackClassName = cx('flex', 'flex-row', 'gap-0.5')
|
||||
|
||||
const handleFileChange = useCallback(
|
||||
async (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const files = mapFileList(event.target.files)
|
||||
if (files.length === 0) {
|
||||
return
|
||||
}
|
||||
for (const file of files) {
|
||||
dispatch(
|
||||
uploadAdded(
|
||||
new UploadDecorator({
|
||||
workspaceId: id!,
|
||||
parentId: fileId!,
|
||||
file,
|
||||
}).value,
|
||||
),
|
||||
)
|
||||
}
|
||||
dispatch(uploadsDrawerOpened())
|
||||
if (fileUploadInput && fileUploadInput.current) {
|
||||
fileUploadInput.current.value = ''
|
||||
}
|
||||
if (folderUploadInput && folderUploadInput.current) {
|
||||
folderUploadInput.current.value = ''
|
||||
}
|
||||
},
|
||||
[id, fileId, dispatch],
|
||||
)
|
||||
|
||||
const handleIconScaleChange = useCallback(
|
||||
(value: number) => {
|
||||
dispatch(iconScaleUpdated(value))
|
||||
},
|
||||
[dispatch],
|
||||
)
|
||||
|
||||
const handleRefresh = useCallback(async () => {
|
||||
setIsRefreshing(true)
|
||||
dispatch(selectionUpdated([]))
|
||||
await mutate<List>(`/files/${fileId}/list?${fileListSearchParams}`)
|
||||
setIsRefreshing(false)
|
||||
}, [fileId, fileListSearchParams, mutate, dispatch])
|
||||
|
||||
const handleSortByChange = useCallback(
|
||||
(value: SortBy) => {
|
||||
dispatch(sortByUpdated(value))
|
||||
},
|
||||
[dispatch],
|
||||
)
|
||||
|
||||
const handleSortOrderToggle = useCallback(() => {
|
||||
dispatch(sortOrderToggled())
|
||||
}, [dispatch])
|
||||
|
||||
const handleViewTypeToggle = useCallback(() => {
|
||||
dispatch(viewTypeToggled())
|
||||
}, [dispatch])
|
||||
|
||||
const handleSelectAllClick = useCallback(() => {
|
||||
if (list?.data) {
|
||||
dispatch(selectionUpdated(list?.data.map((f) => f.id)))
|
||||
}
|
||||
}, [list?.data, dispatch])
|
||||
|
||||
const handleToggleSelection = useCallback(() => {
|
||||
dispatch(selectionUpdated([]))
|
||||
dispatch(selectionModeToggled())
|
||||
}, [dispatch])
|
||||
|
||||
const getSortByIcon = useCallback(
|
||||
(value: SortBy): ReactElement => {
|
||||
if (value === sortBy) {
|
||||
return <IconCheck />
|
||||
} else {
|
||||
return <IconCheck className={cx('text-transparent')} />
|
||||
}
|
||||
},
|
||||
[sortBy],
|
||||
)
|
||||
|
||||
const getSortOrderIcon = useCallback(() => {
|
||||
if (sortOrder === SortOrder.Asc) {
|
||||
return <IconArrowDownward />
|
||||
} else if (sortOrder === SortOrder.Desc) {
|
||||
return <IconArrowUpward />
|
||||
}
|
||||
}, [sortOrder])
|
||||
|
||||
const getViewTypeIcon = useCallback(() => {
|
||||
if (viewType === FileViewType.Grid) {
|
||||
return <IconList />
|
||||
} else if (viewType === FileViewType.List) {
|
||||
return <IconGridView />
|
||||
}
|
||||
}, [viewType])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={stackClassName}>
|
||||
<ButtonGroup isAttached>
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
variant="solid"
|
||||
colorScheme="blue"
|
||||
leftIcon={<IconExpandMore />}
|
||||
isDisabled={
|
||||
!folder || ltEditorPermission(folder.permission) || !list
|
||||
}
|
||||
>
|
||||
Upload
|
||||
</MenuButton>
|
||||
<Portal>
|
||||
<MenuList>
|
||||
<MenuItem
|
||||
icon={<IconAdd />}
|
||||
onClick={() => fileUploadInput?.current?.click()}
|
||||
>
|
||||
Upload Files
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<IconUpload />}
|
||||
onClick={() => folderUploadInput?.current?.click()}
|
||||
>
|
||||
Upload Folder
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Portal>
|
||||
</Menu>
|
||||
<Button
|
||||
variant="outline"
|
||||
colorScheme="blue"
|
||||
leftIcon={<IconAdd />}
|
||||
isDisabled={
|
||||
!folder || ltEditorPermission(folder.permission) || !list
|
||||
}
|
||||
onClick={() => dispatch(createModalDidOpen())}
|
||||
>
|
||||
New Folder
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<div className={stackClassName}>
|
||||
{selectionCount > 0 && hasOwnerPermission && (
|
||||
<Button
|
||||
leftIcon={<IconGroup />}
|
||||
onClick={() => dispatch(sharingModalDidOpen())}
|
||||
>
|
||||
Sharing
|
||||
</Button>
|
||||
)}
|
||||
{singleFile?.type === 'file' && (
|
||||
<Button
|
||||
leftIcon={<IconDownload />}
|
||||
onClick={() => downloadFile(singleFile)}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
)}
|
||||
{selectionCount > 0 && hasOwnerPermission && (
|
||||
<Button
|
||||
leftIcon={<IconDelete />}
|
||||
className={cx('text-red-500')}
|
||||
onClick={() => dispatch(deleteModalDidOpen())}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
{selectionCount > 0 ? (
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
icon={<IconMoreVert />}
|
||||
variant="solid"
|
||||
aria-label=""
|
||||
isDisabled={!list}
|
||||
/>
|
||||
<Portal>
|
||||
<MenuList zIndex="dropdown">
|
||||
<MenuItem
|
||||
icon={<IconGroup />}
|
||||
isDisabled={selectionCount === 0 || !hasOwnerPermission}
|
||||
onClick={() => dispatch(sharingModalDidOpen())}
|
||||
>
|
||||
Sharing
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<IconDownload />}
|
||||
isDisabled={singleFile?.type !== 'file'}
|
||||
onClick={() => {
|
||||
if (singleFile) {
|
||||
downloadFile(singleFile)
|
||||
}
|
||||
}}
|
||||
>
|
||||
Download
|
||||
</MenuItem>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
icon={<IconDelete />}
|
||||
className={cx('text-red-500')}
|
||||
isDisabled={selectionCount === 0 || !hasOwnerPermission}
|
||||
onClick={() => dispatch(deleteModalDidOpen())}
|
||||
>
|
||||
Delete
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<IconEdit />}
|
||||
isDisabled={selectionCount !== 1 || !hasEditorPermission}
|
||||
onClick={() => dispatch(renameModalDidOpen())}
|
||||
>
|
||||
Rename
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<IconArrowTopRight />}
|
||||
isDisabled={selectionCount === 0 || !hasEditorPermission}
|
||||
onClick={() => dispatch(moveModalDidOpen())}
|
||||
>
|
||||
Move
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<IconFileCopy />}
|
||||
isDisabled={selectionCount === 0 || !hasEditorPermission}
|
||||
onClick={() => dispatch(copyModalDidOpen())}
|
||||
>
|
||||
Copy
|
||||
</MenuItem>
|
||||
{isSelectionMode ? (
|
||||
<>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
icon={<IconSelectCheckBox />}
|
||||
onClick={handleSelectAllClick}
|
||||
>
|
||||
Select All
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<IconCheckBoxOutlineBlank />}
|
||||
onClick={() => dispatch(selectionUpdated([]))}
|
||||
>
|
||||
Unselect All
|
||||
</MenuItem>
|
||||
</>
|
||||
) : null}
|
||||
</MenuList>
|
||||
</Portal>
|
||||
</Menu>
|
||||
) : null}
|
||||
</div>
|
||||
{fileCount ? (
|
||||
<IconButton
|
||||
icon={isSelectionMode ? <IconClose /> : <IconLibraryAddCheck />}
|
||||
isDisabled={!list}
|
||||
variant="solid"
|
||||
aria-label=""
|
||||
onClick={handleToggleSelection}
|
||||
/>
|
||||
) : null}
|
||||
<IconButton
|
||||
icon={<IconRefresh />}
|
||||
isLoading={isRefreshing}
|
||||
isDisabled={!list}
|
||||
variant="solid"
|
||||
aria-label=""
|
||||
onClick={handleRefresh}
|
||||
/>
|
||||
<Spacer />
|
||||
<div className={cx('flex', 'flex-row', 'gap-2.5')}>
|
||||
<Slider
|
||||
className={cx('w-[120px]')}
|
||||
value={iconScale}
|
||||
min={ICON_SCALE_SLIDER_MIN}
|
||||
max={ICON_SCALE_SLIDER_MAX}
|
||||
step={ICON_SCALE_SLIDER_STEP}
|
||||
isDisabled={!list}
|
||||
onChange={handleIconScaleChange}
|
||||
>
|
||||
<SliderTrack>
|
||||
<div className={cx('relative')} />
|
||||
<SliderFilledTrack />
|
||||
</SliderTrack>
|
||||
<SliderThumb boxSize={8}>
|
||||
<IconGridView className={cx('text-gray-500')} />
|
||||
</SliderThumb>
|
||||
</Slider>
|
||||
<div className={stackClassName}>
|
||||
<IconButton
|
||||
icon={getSortOrderIcon()}
|
||||
variant="solid"
|
||||
aria-label=""
|
||||
isDisabled={!list}
|
||||
onClick={handleSortOrderToggle}
|
||||
/>
|
||||
<IconButton
|
||||
icon={getViewTypeIcon()}
|
||||
variant="solid"
|
||||
aria-label=""
|
||||
isDisabled={!list}
|
||||
onClick={handleViewTypeToggle}
|
||||
/>
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
icon={<IconMoreVert />}
|
||||
variant="solid"
|
||||
aria-label=""
|
||||
isDisabled={!list}
|
||||
/>
|
||||
<Portal>
|
||||
<MenuList zIndex="dropdown">
|
||||
<MenuItem
|
||||
icon={getSortByIcon(SortBy.Name)}
|
||||
onClick={() => handleSortByChange(SortBy.Name)}
|
||||
>
|
||||
Sort By Name
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={getSortByIcon(SortBy.Kind)}
|
||||
onClick={() => handleSortByChange(SortBy.Kind)}
|
||||
>
|
||||
Sort By Kind
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={getSortByIcon(SortBy.Size)}
|
||||
onClick={() => handleSortByChange(SortBy.Size)}
|
||||
>
|
||||
Sort By Size
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={getSortByIcon(SortBy.DateCreated)}
|
||||
onClick={() => handleSortByChange(SortBy.DateCreated)}
|
||||
>
|
||||
Sort By Date Created
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={getSortByIcon(SortBy.DateModified)}
|
||||
onClick={() => handleSortByChange(SortBy.DateModified)}
|
||||
>
|
||||
Sort By Date Modified
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Portal>
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
ref={fileUploadInput}
|
||||
className={cx('hidden')}
|
||||
type="file"
|
||||
multiple
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
<input
|
||||
ref={folderUploadInput}
|
||||
className={cx('hidden')}
|
||||
type="file"
|
||||
/* @ts-expect-error intentionaly ignored */
|
||||
directory=""
|
||||
webkitdirectory=""
|
||||
mozdirectory=""
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default FileToolbar
|
95
Voltaserve/ui/src/components/file/fle-idelete.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
} from '@chakra-ui/react'
|
||||
import { useSWRConfig } from 'swr'
|
||||
import cx from 'classnames'
|
||||
import FileAPI, { List } from '@/client/api/file'
|
||||
import useFileListSearchParams from '@/hooks/use-file-list-params'
|
||||
import { useAppSelector } from '@/store/hook'
|
||||
import { deleteModalDidClose, selectionUpdated } from '@/store/ui/files'
|
||||
|
||||
const FileDelete = () => {
|
||||
const { mutate } = useSWRConfig()
|
||||
const { fileId } = useParams()
|
||||
const dispatch = useDispatch()
|
||||
const selection = useAppSelector((state) => state.ui.files.selection)
|
||||
const isModalOpen = useAppSelector(
|
||||
(state) => state.ui.files.isDeleteModalOpen,
|
||||
)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const fileListSearchParams = useFileListSearchParams()
|
||||
|
||||
const handleDelete = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
await FileAPI.batchDelete({ ids: selection })
|
||||
await mutate<List>(`/files/${fileId}/list?${fileListSearchParams}`)
|
||||
dispatch(selectionUpdated([]))
|
||||
dispatch(deleteModalDidClose())
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [selection, fileId, fileListSearchParams, mutate, dispatch])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => dispatch(deleteModalDidClose())}
|
||||
closeOnOverlayClick={false}
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
{selection.length > 1 ? (
|
||||
<ModalHeader>Delete {selection.length} Item(s)</ModalHeader>
|
||||
) : (
|
||||
<ModalHeader>Delete Item</ModalHeader>
|
||||
)}
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
{selection.length > 1 ? (
|
||||
<span>
|
||||
Are you sure you would like to delete ({selection.length})
|
||||
item(s)?
|
||||
</span>
|
||||
) : (
|
||||
<span>Are you sure you would like to delete this item?</span>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div className={cx('flex', 'flex-row', 'items-center', 'gap-1')}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
colorScheme="blue"
|
||||
disabled={loading}
|
||||
onClick={() => dispatch(deleteModalDidClose())}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="solid"
|
||||
colorScheme="red"
|
||||
isLoading={loading}
|
||||
onClick={handleDelete}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default FileDelete
|
322
Voltaserve/ui/src/components/file/list/index.tsx
Normal file
@ -0,0 +1,322 @@
|
||||
import { MouseEvent, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { Portal, Menu, MenuList, MenuItem, MenuDivider } from '@chakra-ui/react'
|
||||
import {
|
||||
DndContext,
|
||||
useSensors,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
DragStartEvent,
|
||||
} from '@dnd-kit/core'
|
||||
import cx from 'classnames'
|
||||
import { FileWithPath, useDropzone } from 'react-dropzone'
|
||||
import { List as ApiFileList } from '@/client/api/file'
|
||||
import {
|
||||
geEditorPermission,
|
||||
ltEditorPermission,
|
||||
ltOwnerPermission,
|
||||
ltViewerPermission,
|
||||
} from '@/client/api/permission'
|
||||
import downloadFile from '@/helpers/download-file'
|
||||
import {
|
||||
IconFileCopy,
|
||||
IconDownload,
|
||||
IconEdit,
|
||||
IconArrowTopRight,
|
||||
IconGroup,
|
||||
IconDelete,
|
||||
} from '@/lib'
|
||||
import { UploadDecorator, uploadAdded } from '@/store/entities/uploads'
|
||||
import { useAppDispatch, useAppSelector } from '@/store/hook'
|
||||
import {
|
||||
copyModalDidOpen,
|
||||
deleteModalDidOpen,
|
||||
moveModalDidOpen,
|
||||
multiSelectKeyUpdated,
|
||||
rangeSelectKeyUpdated,
|
||||
renameModalDidOpen,
|
||||
selectionUpdated,
|
||||
sharingModalDidOpen,
|
||||
} from '@/store/ui/files'
|
||||
import { uploadsDrawerOpened } from '@/store/ui/uploads-drawer'
|
||||
import { FileViewType } from '@/types/file'
|
||||
import ListDragOverlay from './list-drag-overlay'
|
||||
import ListDraggableDroppable from './list-draggable-droppable'
|
||||
|
||||
type FileListProps = {
|
||||
list: ApiFileList
|
||||
scale: number
|
||||
}
|
||||
|
||||
const FileList = ({ list, scale }: FileListProps) => {
|
||||
const dispatch = useAppDispatch()
|
||||
const { id, fileId } = useParams()
|
||||
const singleFile = useAppSelector((state) =>
|
||||
state.ui.files.selection.length === 1
|
||||
? list.data.find((e) => e.id === state.ui.files.selection[0])
|
||||
: null,
|
||||
)
|
||||
const hidden = useAppSelector((state) => state.ui.files.hidden)
|
||||
const viewType = useAppSelector((state) => state.ui.files.viewType)
|
||||
const isSelectionMode = useAppSelector(
|
||||
(state) => state.ui.files.isSelectionMode,
|
||||
)
|
||||
const [activeId, setActiveId] = useState<string | null>(null)
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false)
|
||||
const [menuPosition, setMenuPosition] = useState<{ x: number; y: number }>()
|
||||
const activeFile = useMemo(
|
||||
() => list.data.find((e) => e.id === activeId),
|
||||
[list, activeId],
|
||||
)
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, {
|
||||
activationConstraint: {
|
||||
delay: 200,
|
||||
tolerance: 5,
|
||||
},
|
||||
}),
|
||||
)
|
||||
const onDrop = useCallback(
|
||||
(files: FileWithPath[]) => {
|
||||
if (files.length === 0) {
|
||||
return
|
||||
}
|
||||
for (const file of files) {
|
||||
dispatch(
|
||||
uploadAdded(
|
||||
new UploadDecorator({
|
||||
workspaceId: id!,
|
||||
parentId: fileId!,
|
||||
file,
|
||||
}).value,
|
||||
),
|
||||
)
|
||||
}
|
||||
dispatch(uploadsDrawerOpened())
|
||||
},
|
||||
[id, fileId, dispatch],
|
||||
)
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
onDrop,
|
||||
noClick: true,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
dispatch(multiSelectKeyUpdated(true))
|
||||
}
|
||||
if (event.shiftKey) {
|
||||
dispatch(rangeSelectKeyUpdated(true))
|
||||
}
|
||||
}
|
||||
const handleKeyup = () => {
|
||||
dispatch(multiSelectKeyUpdated(false))
|
||||
dispatch(rangeSelectKeyUpdated(false))
|
||||
}
|
||||
window.addEventListener('keydown', handleKeydown)
|
||||
window.addEventListener('keyup', handleKeyup)
|
||||
return () => {
|
||||
dispatch(selectionUpdated([]))
|
||||
window.removeEventListener('keydown', handleKeydown)
|
||||
window.removeEventListener('keyup', handleKeyup)
|
||||
}
|
||||
}, [dispatch])
|
||||
|
||||
const handleDragStart = useCallback((event: DragStartEvent) => {
|
||||
setActiveId(event.active.id as string)
|
||||
}, [])
|
||||
|
||||
const handleDragEnd = useCallback(() => {
|
||||
setActiveId(null)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cx(
|
||||
'border-2',
|
||||
{ 'border-green-300': isDragActive },
|
||||
{ 'border-transparent': !isDragActive },
|
||||
'rounded-md',
|
||||
)}
|
||||
{...getRootProps()}
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
{list.totalElements === 0 && (
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'w-full',
|
||||
'h-[300px]',
|
||||
)}
|
||||
>
|
||||
<span>There are no items.</span>
|
||||
</div>
|
||||
)}
|
||||
{viewType === FileViewType.Grid && list.totalElements > 0 ? (
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'flex-wrap',
|
||||
'gap-1.5',
|
||||
'overflow-hidden',
|
||||
'pb-2.5',
|
||||
)}
|
||||
>
|
||||
{list.data
|
||||
.filter((e) => !hidden.includes(e.id))
|
||||
.map((f) => (
|
||||
<ListDraggableDroppable
|
||||
key={f.id}
|
||||
file={f}
|
||||
scale={scale}
|
||||
viewType={viewType}
|
||||
isSelectionMode={isSelectionMode}
|
||||
onContextMenu={(event: MouseEvent) => {
|
||||
setMenuPosition({ x: event.pageX, y: event.pageY })
|
||||
setIsMenuOpen(true)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{viewType === FileViewType.List && list.totalElements > 0 ? (
|
||||
<div
|
||||
className={cx(
|
||||
'flex',
|
||||
'flex-col',
|
||||
'gap-0.5',
|
||||
'overflow-hidden',
|
||||
'pb-2.5',
|
||||
)}
|
||||
>
|
||||
{list.data
|
||||
.filter((e) => !hidden.includes(e.id))
|
||||
.map((f) => (
|
||||
<ListDraggableDroppable
|
||||
key={f.id}
|
||||
file={f}
|
||||
scale={scale}
|
||||
viewType={viewType}
|
||||
isSelectionMode={isSelectionMode}
|
||||
onContextMenu={(event: MouseEvent) => {
|
||||
setMenuPosition({ x: event.pageX, y: event.pageY })
|
||||
setIsMenuOpen(true)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
<ListDragOverlay
|
||||
file={activeFile!}
|
||||
scale={scale}
|
||||
viewType={viewType}
|
||||
/>
|
||||
</DndContext>
|
||||
</div>
|
||||
<Portal>
|
||||
<Menu isOpen={isMenuOpen} onClose={() => setIsMenuOpen(false)}>
|
||||
<MenuList
|
||||
zIndex="dropdown"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: menuPosition?.x,
|
||||
top: menuPosition?.y,
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
icon={<IconGroup />}
|
||||
isDisabled={
|
||||
singleFile ? ltOwnerPermission(singleFile.permission) : false
|
||||
}
|
||||
onClick={(event: MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
dispatch(sharingModalDidOpen())
|
||||
}}
|
||||
>
|
||||
Sharing
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<IconDownload />}
|
||||
isDisabled={
|
||||
!singleFile ||
|
||||
singleFile.type !== 'file' ||
|
||||
ltViewerPermission(singleFile.permission)
|
||||
}
|
||||
onClick={(event: MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
if (singleFile) {
|
||||
downloadFile(singleFile)
|
||||
}
|
||||
}}
|
||||
>
|
||||
Download
|
||||
</MenuItem>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
icon={<IconDelete />}
|
||||
className={cx('text-red-500')}
|
||||
isDisabled={
|
||||
singleFile ? ltOwnerPermission(singleFile.permission) : false
|
||||
}
|
||||
onClick={(event: MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
dispatch(deleteModalDidOpen())
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<IconEdit />}
|
||||
isDisabled={
|
||||
singleFile && geEditorPermission(singleFile.permission)
|
||||
? false
|
||||
: true
|
||||
}
|
||||
onClick={(event: MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
dispatch(renameModalDidOpen())
|
||||
}}
|
||||
>
|
||||
Rename
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<IconArrowTopRight />}
|
||||
isDisabled={
|
||||
singleFile ? ltEditorPermission(singleFile.permission) : false
|
||||
}
|
||||
onClick={(event: MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
dispatch(moveModalDidOpen())
|
||||
}}
|
||||
>
|
||||
Move
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon={<IconFileCopy />}
|
||||
isDisabled={
|
||||
singleFile ? ltEditorPermission(singleFile.permission) : false
|
||||
}
|
||||
onClick={(event: MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
dispatch(copyModalDidOpen())
|
||||
}}
|
||||
>
|
||||
Copy
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Portal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default FileList
|
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="90" viewBox="0 0 67 90" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M65.7786 18.457L48.6971 1.23047C47.912 0.439453 46.8477 0 45.731 0H44.6667V22.5H67V21.4277C67 20.3203 66.5638 19.248 65.7786 18.457ZM22.4031 59.0625C19.2799 59.0625 16.75 61.1895 16.75 63.8086C16.75 66.4453 19.2974 68.5547 22.4206 68.5547C25.5438 68.5547 28.0737 66.4277 28.0737 63.8086C28.0737 61.1895 25.5263 59.0625 22.4031 59.0625ZM39.0833 23.9062V0H27.9865V5.625H22.4031V0H4.1875C1.86693 0 0 1.88086 0 4.21875V85.7812C0 88.1191 1.86693 90 4.1875 90H62.8125C65.1331 90 67 88.1191 67 85.7812V28.125H43.2708C40.9677 28.125 39.0833 26.2266 39.0833 23.9062ZM16.7326 5.625H22.3159V11.25H16.7326V5.625ZM22.3682 73.125C16.5755 73.125 12.2484 67.7813 13.4 62.0684L16.8198 45V39.375H22.4031V33.75H16.8198V28.125H22.4031V22.5H16.8198V16.875H22.4031V11.25H27.9865V16.875H22.4031V22.5H27.9865V28.125H22.4031V33.75H27.9865V39.375H22.4031V45H26.2591C27.2536 45 28.126 45.7207 28.318 46.7051L31.3365 62.1211C32.4531 67.8164 28.126 73.125 22.3682 73.125Z" fill="#D1D5DB"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M39.0833 23.6406V0H4.1875C1.86693 0 0 1.85996 0 4.17188V84.8281C0 87.14 1.86693 89 4.1875 89H62.8125C65.1331 89 67 87.14 67 84.8281V27.8125H43.2708C40.9677 27.8125 39.0833 25.9352 39.0833 23.6406ZM27.9167 70.2266C27.9167 72.0865 25.6659 73.0078 24.3398 71.7041L18.1458 65.3594H13.2604C12.1089 65.3594 11.1667 64.4207 11.1667 63.2734V53.5391C11.1667 52.3918 12.1089 51.4531 13.2604 51.4531H18.1458L24.3398 45.1084C25.6659 43.7873 27.9167 44.726 27.9167 46.5859V70.2266ZM33.7094 61.9523C35.2971 60.3357 35.2971 57.7631 33.7094 56.1465C29.8534 52.1832 35.838 46.3773 39.7115 50.3232C44.4573 55.173 44.4573 62.9084 39.7115 67.7756C35.9078 71.652 29.7836 65.9678 33.7094 61.9523ZM48.7146 41.5971C58.2063 51.3141 58.2063 66.7674 48.7146 76.5018C44.9109 80.3955 38.7693 74.7113 42.7125 70.6785C49.0286 64.2121 49.0461 53.9041 42.7125 47.4203C38.8565 43.457 44.8586 37.6338 48.7146 41.5971ZM67 21.1896V22.25H44.6667V0H45.731C46.8477 0 47.912 0.43457 48.6971 1.2168L65.7786 18.252C66.5638 19.0342 67 20.0945 67 21.1896Z" fill="#D1D5DB"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M66.75 21.1968V22.25H44.5V0H45.5532C46.6596 0 47.7209 0.439611 48.5033 1.22184L65.5282 18.2467C66.3105 19.0292 66.7499 20.0903 66.75 21.1968ZM43.1094 27.8125C40.8148 27.8125 38.9375 25.9352 38.9375 23.6406V0H4.17188C1.86778 0 0 1.86778 0 4.17188V84.8281C0 87.1322 1.86778 89 4.17188 89H62.5781C64.8822 89 66.75 87.1322 66.75 84.8281V27.8125H43.1094ZM21.4167 69.619C21.3324 69.709 21.2312 69.7814 21.1188 69.8322C21.0065 69.883 20.8853 69.9112 20.7621 69.9152C20.6389 69.9192 20.5161 69.8989 20.4008 69.8554C20.2854 69.8119 20.1798 69.7461 20.0898 69.6618L8.8143 59.091C8.72066 59.0032 8.64601 58.8971 8.59498 58.7793C8.54396 58.6615 8.51763 58.5345 8.51763 58.4062C8.51763 58.2778 8.54396 58.1508 8.59498 58.033C8.64601 57.9152 8.72066 57.8092 8.8143 57.7214L20.0898 47.1505C20.1798 47.0662 20.2854 47.0004 20.4008 46.957C20.5161 46.9135 20.6389 46.8931 20.7621 46.8971C20.8853 46.9011 21.0065 46.9293 21.1188 46.9801C21.2312 47.0309 21.3324 47.1034 21.4167 47.1933L24.82 50.8237C24.9061 50.9154 24.9728 51.0235 25.0162 51.1415C25.0596 51.2596 25.0789 51.3851 25.0728 51.5107C25.0668 51.6363 25.0356 51.7594 24.9811 51.8727C24.9265 51.9861 24.8498 52.0873 24.7554 52.1703L17.6696 58.4062L24.7554 64.6422C24.8498 64.7252 24.9265 64.8264 24.9811 64.9398C25.0356 65.0531 25.0668 65.1762 25.0728 65.3018C25.0789 65.4274 25.0596 65.5529 25.0162 65.671C24.9728 65.789 24.9061 65.8971 24.82 65.9888L21.4167 69.619ZM30.3332 78.3937L25.5611 77.0083C25.322 76.9388 25.1203 76.7773 25.0003 76.5591C24.8803 76.341 24.8519 76.0841 24.9212 75.845L35.6012 39.0585C35.6707 38.8194 35.8322 38.6177 36.0504 38.4977C36.2685 38.3777 36.5254 38.3493 36.7645 38.4186L41.5364 39.804C41.6548 39.8384 41.7652 39.8958 41.8615 39.9728C41.9577 40.0499 42.0378 40.1452 42.0972 40.2532C42.1566 40.3612 42.1941 40.4799 42.2077 40.6024C42.2212 40.7249 42.2105 40.8489 42.1761 40.9673L31.4961 77.7538C31.4618 77.8722 31.4045 77.9827 31.3275 78.079C31.2505 78.1752 31.1552 78.2553 31.0472 78.3148C30.9392 78.3742 30.8206 78.4118 30.6981 78.4253C30.5755 78.4388 30.4516 78.4281 30.3332 78.3937ZM58.2834 59.091L47.0078 69.6618C46.9179 69.7461 46.8122 69.8119 46.6969 69.8554C46.5815 69.8989 46.4588 69.9192 46.3355 69.9152C46.2123 69.9112 46.0911 69.883 45.9788 69.8322C45.8665 69.7814 45.7653 69.709 45.681 69.619L42.2774 65.9886C42.1914 65.8969 42.1247 65.7888 42.0813 65.6708C42.0379 65.5528 42.0186 65.4272 42.0246 65.3016C42.0307 65.176 42.0619 65.0529 42.1164 64.9396C42.1709 64.8263 42.2477 64.7251 42.3421 64.642L49.428 58.4062L42.3423 52.1703C42.2479 52.0873 42.1711 51.9861 42.1166 51.8727C42.0621 51.7594 42.0308 51.6363 42.0248 51.5107C42.0188 51.3851 42.0381 51.2596 42.0815 51.1415C42.1249 51.0235 42.1916 50.9154 42.2776 50.8237L45.6812 47.1933C45.7655 47.1034 45.8667 47.0309 45.979 46.9801C46.0913 46.9293 46.2125 46.9011 46.3357 46.8971C46.4589 46.8931 46.5817 46.9135 46.6971 46.957C46.8124 47.0004 46.9181 47.0662 47.008 47.1505L58.2835 57.7214C58.3772 57.8092 58.4518 57.9152 58.5028 58.033C58.5538 58.1508 58.5801 58.2778 58.5801 58.4062C58.5801 58.5346 58.5538 58.6616 58.5027 58.7793C58.4517 58.8971 58.377 59.0032 58.2834 59.091Z" fill="#D1D5DB"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M39.0833 23.6406V0H4.1875C1.86693 0 0 1.85996 0 4.17188V84.8281C0 87.14 1.86693 89 4.1875 89H62.8125C65.1331 89 67 87.14 67 84.8281V27.8125H43.2708C40.9677 27.8125 39.0833 25.9352 39.0833 23.6406ZM22.3333 48.6719C22.3333 49.4402 21.7087 50.0625 20.9375 50.0625H19.5417C17.9993 50.0625 16.75 51.3071 16.75 52.8438V58.4062C16.75 59.9429 17.9993 61.1875 19.5417 61.1875H20.9375C21.7087 61.1875 22.3333 61.8098 22.3333 62.5781V65.3594C22.3333 66.1277 21.7087 66.75 20.9375 66.75H19.5417C14.9162 66.75 11.1667 63.0144 11.1667 58.4062V52.8438C11.1667 48.2356 14.9162 44.5 19.5417 44.5H20.9375C21.7087 44.5 22.3333 45.1223 22.3333 45.8906V48.6719ZM30.0575 66.75H27.9167C27.1455 66.75 26.5208 66.1277 26.5208 65.3594V62.5781C26.5208 61.8098 27.1455 61.1875 27.9167 61.1875H30.0575C31.0957 61.1875 31.8739 60.5791 31.8739 60.0368C31.8739 59.8108 31.743 59.5744 31.504 59.3693L27.6846 56.1065C26.2068 54.8515 25.3588 53.068 25.3588 51.215C25.3588 47.5124 28.6774 44.5017 32.7585 44.5017H34.8958C35.667 44.5017 36.2917 45.124 36.2917 45.8924V48.6736C36.2917 49.4419 35.667 50.0642 34.8958 50.0642H32.755C31.7168 50.0642 30.9386 50.6726 30.9386 51.215C30.9386 51.441 31.0695 51.6774 31.3085 51.8825L35.1279 55.1452C36.6057 56.4003 37.4537 58.1838 37.4537 60.0368C37.4554 63.7376 34.1386 66.75 30.0575 66.75ZM44.6667 45.8906V49.5062C44.6667 53.0297 45.6612 56.4889 47.4583 59.3936C49.2555 56.4907 50.25 53.0297 50.25 49.5062V45.8906C50.25 45.1223 50.8746 44.5 51.6458 44.5H54.4375C55.2087 44.5 55.8333 45.1223 55.8333 45.8906V49.5062C55.8333 55.6737 53.586 61.4813 49.5032 65.8617C48.9763 66.4267 48.2348 66.75 47.4583 66.75C46.6819 66.75 45.9404 66.4267 45.4134 65.8617C41.3306 61.4813 39.0833 55.6737 39.0833 49.5062V45.8906C39.0833 45.1223 39.708 44.5 40.4792 44.5H43.2708C44.042 44.5 44.6667 45.1223 44.6667 45.8906ZM65.7786 18.252L48.6971 1.2168C47.912 0.43457 46.8477 0 45.731 0H44.6667V22.25H67V21.1896C67 20.0945 66.5638 19.0342 65.7786 18.252Z" fill="#D1D5DB"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M39.0833 23.6406V0H4.1875C1.86693 0 0 1.85996 0 4.17188V84.8281C0 87.14 1.86693 89 4.1875 89H62.8125C65.1331 89 67 87.14 67 84.8281V27.8125H43.2708C40.9677 27.8125 39.0833 25.9352 39.0833 23.6406ZM67 21.1896V22.25H44.6667V0H45.731C46.8477 0 47.912 0.43457 48.6971 1.2168L65.7786 18.252C66.5638 19.0342 67 20.0945 67 21.1896Z" fill="#D1D5DB"/>
|
||||
</svg>
|
After Width: | Height: | Size: 455 B |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M31.7378 44.5174C30.8654 41.7361 30.8828 36.3648 31.3888 36.3648C32.8544 36.3648 32.7148 42.7791 31.7378 44.5174ZM31.4411 52.7221C30.0977 56.2334 28.4227 60.2488 26.4859 63.6211C29.6789 62.4043 33.2906 60.6313 37.4607 59.8143C35.2448 58.1455 33.1161 55.7467 31.4411 52.7221ZM15.0227 74.4158C15.0227 74.5549 17.3258 73.4771 21.112 67.4279C19.943 68.523 16.0346 71.6867 15.0227 74.4158ZM43.2708 27.8125H67V84.8281C67 87.14 65.1331 89 62.8125 89H4.1875C1.86693 89 0 87.14 0 84.8281V4.17188C0 1.85996 1.86693 0 4.1875 0H39.0833V23.6406C39.0833 25.9352 40.9677 27.8125 43.2708 27.8125ZM41.875 57.6762C38.3854 55.5555 36.0648 52.6352 34.4247 48.3242C35.2099 45.1084 36.4487 40.2238 35.5065 37.1645C34.6865 32.0539 28.1086 32.558 27.1664 35.9824C26.294 39.1635 27.0966 43.6482 28.5797 49.3672C26.5557 54.1648 23.5721 60.5965 21.4609 64.2816C21.4435 64.2816 21.4435 64.299 21.426 64.299C16.6977 66.7152 8.58438 72.0344 11.9169 76.1193C12.894 77.3187 14.7086 77.8576 15.6682 77.8576C18.7914 77.8576 21.8971 74.7287 26.3289 67.115C30.8305 65.6375 35.7682 63.7949 40.1128 63.0822C43.899 65.1334 48.3307 66.4719 51.2794 66.4719C56.3742 66.4719 56.7232 60.9094 54.7167 58.9277C52.2914 56.5637 45.2424 57.2416 41.875 57.6762ZM65.7786 18.252L48.6797 1.2168C47.8945 0.43457 46.8302 0 45.7135 0H44.6667V22.25H67V21.1896C67 20.0945 66.5638 19.0342 65.7786 18.252ZM52.8497 62.6303C53.5651 62.1609 52.4135 60.5617 45.382 61.0658C51.8552 63.8123 52.8497 62.6303 52.8497 62.6303Z" fill="#D1D5DB"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="90" viewBox="0 0 67 90" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M33.7966 47.6719C35.332 47.6719 36.501 48.1465 37.3385 49.0957C39.0135 51.0117 39.0484 54.8437 37.3036 56.8477C36.4487 57.832 35.2273 58.3418 33.6221 58.3418H28.9286V47.6719H33.7966ZM65.7786 18.457L48.6797 1.23047C47.8945 0.439453 46.8302 0 45.7135 0H44.6667V22.5H67V21.4277C67 20.3203 66.5638 19.248 65.7786 18.457ZM39.0833 23.9062V0H4.1875C1.86693 0 0 1.88086 0 4.21875V85.7812C0 88.1191 1.86693 90 4.1875 90H62.8125C65.1331 90 67 88.1191 67 85.7812V28.125H43.2708C40.9677 28.125 39.0833 26.2266 39.0833 23.9062ZM48.3307 52.9453C48.3307 68.8184 32.837 66.5859 28.9461 66.5859V76.6406C28.9461 77.8008 28.0039 78.75 26.8523 78.75H21.4784C20.3268 78.75 19.3846 77.8008 19.3846 76.6406V41.5195C19.3846 40.3594 20.3268 39.4102 21.4784 39.4102H35.6112C43.3755 39.4102 48.3307 45.1758 48.3307 52.9453Z" fill="#D1D5DB"/>
|
||||
</svg>
|
After Width: | Height: | Size: 927 B |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M39.0833 23.6406V0H4.1875C1.86693 0 0 1.85996 0 4.17188V84.8281C0 87.14 1.86693 89 4.1875 89H62.8125C65.1331 89 67 87.14 67 84.8281V27.8125H43.2708C40.9677 27.8125 39.0833 25.9352 39.0833 23.6406ZM11.1667 12.5156C11.1667 11.7473 11.7913 11.125 12.5625 11.125H26.5208C27.292 11.125 27.9167 11.7473 27.9167 12.5156V15.2969C27.9167 16.0652 27.292 16.6875 26.5208 16.6875H12.5625C11.7913 16.6875 11.1667 16.0652 11.1667 15.2969V12.5156ZM11.1667 23.6406C11.1667 22.8723 11.7913 22.25 12.5625 22.25H26.5208C27.292 22.25 27.9167 22.8723 27.9167 23.6406V26.4219C27.9167 27.1902 27.292 27.8125 26.5208 27.8125H12.5625C11.7913 27.8125 11.1667 27.1902 11.1667 26.4219V23.6406ZM44.808 66.75H53.0417C54.5841 66.75 55.8333 67.9946 55.8333 69.5312C55.8333 71.0679 54.5841 72.3125 53.0417 72.3125H44.808C41.9378 72.3125 39.352 70.7237 38.0661 68.165C37.5514 67.1359 36.6546 67.0316 36.2917 67.0316C35.9288 67.0316 35.0319 67.1342 34.5434 68.1076L33.2051 70.7741C32.9734 71.2361 32.617 71.6246 32.176 71.8961C31.735 72.1675 31.2267 72.3111 30.7083 72.3108C30.642 72.3108 30.5775 72.3073 30.5094 72.3021C29.384 72.2238 28.4157 71.4764 28.0615 70.4091L25.125 61.6377L23.2738 67.1794C22.2461 70.2492 19.3689 72.3125 16.1201 72.3125H13.9583C12.4159 72.3125 11.1667 71.0679 11.1667 69.5312C11.1667 67.9946 12.4159 66.75 13.9583 66.75H16.1201C16.9629 66.75 17.7096 66.2146 17.9766 65.4185L21.1504 55.9205C21.7261 54.2153 23.3209 53.068 25.125 53.068C26.9291 53.068 28.5239 54.2135 29.0979 55.9205L31.5197 63.1587C34.9691 60.3444 40.9503 61.4726 43.0353 65.6201C43.3877 66.3259 44.0752 66.75 44.808 66.75ZM65.7786 18.252L48.6971 1.2168C47.912 0.43457 46.8477 0 45.731 0H44.6667V22.25H67V21.1896C67 20.0945 66.5638 19.0342 65.7786 18.252Z" fill="#D1D5DB"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M39.0833 23.6406V0H4.1875C1.86693 0 0 1.85996 0 4.17188V84.8281C0 87.14 1.86693 89 4.1875 89H62.8125C65.1331 89 67 87.14 67 84.8281V27.8125H43.2708C40.9677 27.8125 39.0833 25.9352 39.0833 23.6406ZM49.5695 42.1533L39.0833 58.4062L49.5695 74.6592C50.4594 76.0498 49.4648 77.875 47.8073 77.875H41.718C40.9503 77.875 40.2349 77.4578 39.8685 76.7799C36.4487 70.4873 33.5 64.8379 33.5 64.8379C32.3833 67.4105 31.7552 68.3145 27.1141 76.7973C26.7477 77.4752 26.0497 77.8924 25.282 77.8924H19.1927C17.5352 77.8924 16.5406 76.0672 17.4305 74.6766L27.9516 58.4236L17.4305 42.1707C16.5232 40.7801 17.5352 38.9549 19.1927 38.9549H25.2646C26.0323 38.9549 26.7477 39.3721 27.1141 40.05C31.668 48.5328 30.6036 45.8906 33.5 51.9572C33.5 51.9572 34.5643 49.9234 39.8859 40.05C40.2523 39.3721 40.9677 38.9549 41.7354 38.9549H47.8073C49.4648 38.9375 50.4594 40.7627 49.5695 42.1533ZM67 21.1896V22.25H44.6667V0H45.731C46.8477 0 47.912 0.43457 48.6971 1.2168L65.7786 18.252C66.5638 19.0342 67 20.0945 67 21.1896Z" fill="#D1D5DB"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M39.0833 23.6406V0H4.1875C1.86693 0 0 1.85996 0 4.17188V84.8281C0 87.14 1.86693 89 4.1875 89H62.8125C65.1331 89 67 87.14 67 84.8281V27.8125H43.2708C40.9677 27.8125 39.0833 25.9352 39.0833 23.6406ZM50.25 64.6641C50.25 65.8113 49.3078 66.75 48.1563 66.75H18.8438C17.6922 66.75 16.75 65.8113 16.75 64.6641V63.2734C16.75 62.1262 17.6922 61.1875 18.8438 61.1875H48.1563C49.3078 61.1875 50.25 62.1262 50.25 63.2734V64.6641ZM50.25 53.5391C50.25 54.6863 49.3078 55.625 48.1563 55.625H18.8438C17.6922 55.625 16.75 54.6863 16.75 53.5391V52.1484C16.75 51.0012 17.6922 50.0625 18.8438 50.0625H48.1563C49.3078 50.0625 50.25 51.0012 50.25 52.1484V53.5391ZM50.25 41.0234V42.4141C50.25 43.5613 49.3078 44.5 48.1563 44.5H18.8438C17.6922 44.5 16.75 43.5613 16.75 42.4141V41.0234C16.75 39.8762 17.6922 38.9375 18.8438 38.9375H48.1563C49.3078 38.9375 50.25 39.8762 50.25 41.0234ZM67 21.1896V22.25H44.6667V0H45.731C46.8477 0 47.912 0.43457 48.6971 1.2168L65.7786 18.252C66.5638 19.0342 67 20.0945 67 21.1896Z" fill="#D1D5DB"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M67 21.1968V22.25H44.6667V0H45.7238C46.8344 0 47.8996 0.439611 48.6849 1.22184L65.7736 18.2467C66.5588 19.0292 66.9999 20.0903 67 21.1968ZM39.0833 23.6406V0H4.1875C1.87478 0 0 1.86778 0 4.17188V84.8281C0 87.1322 1.87478 89 4.1875 89H62.8125C65.1252 89 67 87.1322 67 84.8281V27.8125H43.2708C40.9677 27.8125 39.0833 25.9352 39.0833 23.6406ZM55.8333 48.6747V68.137C55.8333 71.8647 51.3068 73.6991 48.6847 71.087L39.0833 61.5249V68.1406C39.0833 70.4447 37.2086 72.3125 34.8958 72.3125H15.3542C13.0414 72.3125 11.1667 70.4447 11.1667 68.1406V48.6719C11.1667 46.3678 13.0414 44.5 15.3542 44.5H34.8958C37.2086 44.5 39.0833 46.3678 39.0833 48.6719V55.2876L48.6847 45.7248C51.3039 43.1156 55.8333 44.9441 55.8333 48.6747Z" fill="#D1D5DB"/>
|
||||
</svg>
|
After Width: | Height: | Size: 843 B |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M39.0833 23.6406V0H4.1875C1.86693 0 0 1.85996 0 4.17188V84.8281C0 87.14 1.86693 89 4.1875 89H62.8125C65.1331 89 67 87.14 67 84.8281V27.8125H43.2708C40.9677 27.8125 39.0833 25.9352 39.0833 23.6406ZM49.0461 44.5H53.2161C54.5596 44.5 55.5542 45.7342 55.2576 47.0553L48.6273 76.2584C48.418 77.2145 47.563 77.875 46.5859 77.875H39.9557C38.9961 77.875 38.1586 77.2145 37.9318 76.2932C33.4302 58.302 34.3026 62.1783 33.4651 57.0852H33.3779C33.1859 59.5709 32.9591 60.1098 28.9112 76.2932C28.6844 77.2145 27.8469 77.875 26.8872 77.875H20.4141C19.437 77.875 18.582 77.1971 18.3727 76.241L11.7773 47.0379C11.4807 45.7342 12.4753 44.5 13.8187 44.5H18.0935C19.088 44.5 19.9604 45.1953 20.1523 46.1861C22.8742 59.7447 23.6594 65.2203 23.8164 67.4279C24.0956 65.6549 25.0901 61.7438 28.9461 46.0992C29.1729 45.1605 30.0104 44.5174 30.9875 44.5174H36.0648C37.0419 44.5174 37.8794 45.1779 38.1063 46.1166C42.2938 63.5689 43.1313 67.6713 43.2708 68.61C43.2359 66.6631 42.8172 65.5158 47.0396 46.1514C47.2141 45.1779 48.069 44.5 49.0461 44.5ZM67 21.1896V22.25H44.6667V0H45.731C46.8477 0 47.912 0.43457 48.6971 1.2168L65.7786 18.252C66.5638 19.0342 67 20.0945 67 21.1896Z" fill="#D1D5DB"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="90" viewBox="0 0 67 90" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M65.7786 18.457L48.6971 1.23047C47.912 0.439453 46.8477 0 45.731 0H44.6667V22.5H67V21.4277C67 20.3203 66.5638 19.248 65.7786 18.457ZM22.4031 59.0625C19.2799 59.0625 16.75 61.1895 16.75 63.8086C16.75 66.4453 19.2974 68.5547 22.4206 68.5547C25.5438 68.5547 28.0737 66.4277 28.0737 63.8086C28.0737 61.1895 25.5263 59.0625 22.4031 59.0625ZM39.0833 23.9062V0H27.9865V5.625H22.4031V0H4.1875C1.86693 0 0 1.88086 0 4.21875V85.7812C0 88.1191 1.86693 90 4.1875 90H62.8125C65.1331 90 67 88.1191 67 85.7812V28.125H43.2708C40.9677 28.125 39.0833 26.2266 39.0833 23.9062ZM16.7326 5.625H22.3159V11.25H16.7326V5.625ZM22.3682 73.125C16.5755 73.125 12.2484 67.7813 13.4 62.0684L16.8198 45V39.375H22.4031V33.75H16.8198V28.125H22.4031V22.5H16.8198V16.875H22.4031V11.25H27.9865V16.875H22.4031V22.5H27.9865V28.125H22.4031V33.75H27.9865V39.375H22.4031V45H26.2591C27.2536 45 28.126 45.7207 28.318 46.7051L31.3365 62.1211C32.4531 67.8164 28.126 73.125 22.3682 73.125Z" fill="#6B7280"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M39.0833 23.6406V0H4.1875C1.86693 0 0 1.85996 0 4.17188V84.8281C0 87.14 1.86693 89 4.1875 89H62.8125C65.1331 89 67 87.14 67 84.8281V27.8125H43.2708C40.9677 27.8125 39.0833 25.9352 39.0833 23.6406ZM27.9167 70.2266C27.9167 72.0865 25.6659 73.0078 24.3398 71.7041L18.1458 65.3594H13.2604C12.1089 65.3594 11.1667 64.4207 11.1667 63.2734V53.5391C11.1667 52.3918 12.1089 51.4531 13.2604 51.4531H18.1458L24.3398 45.1084C25.6659 43.7873 27.9167 44.726 27.9167 46.5859V70.2266ZM33.7094 61.9523C35.2971 60.3357 35.2971 57.7631 33.7094 56.1465C29.8534 52.1832 35.838 46.3773 39.7115 50.3232C44.4573 55.173 44.4573 62.9084 39.7115 67.7756C35.9078 71.652 29.7836 65.9678 33.7094 61.9523ZM48.7146 41.5971C58.2063 51.3141 58.2063 66.7674 48.7146 76.5018C44.9109 80.3955 38.7693 74.7113 42.7125 70.6785C49.0286 64.2121 49.0461 53.9041 42.7125 47.4203C38.8565 43.457 44.8586 37.6338 48.7146 41.5971ZM67 21.1896V22.25H44.6667V0H45.731C46.8477 0 47.912 0.43457 48.6971 1.2168L65.7786 18.252C66.5638 19.0342 67 20.0945 67 21.1896Z" fill="#6B7280"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M66.75 21.1968V22.25H44.5V0H45.5532C46.6596 0 47.7209 0.439611 48.5033 1.22184L65.5282 18.2467C66.3105 19.0292 66.7499 20.0903 66.75 21.1968ZM43.1094 27.8125C40.8148 27.8125 38.9375 25.9352 38.9375 23.6406V0H4.17188C1.86778 0 0 1.86778 0 4.17188V84.8281C0 87.1322 1.86778 89 4.17188 89H62.5781C64.8822 89 66.75 87.1322 66.75 84.8281V27.8125H43.1094ZM21.4167 69.619C21.3324 69.709 21.2312 69.7814 21.1188 69.8322C21.0065 69.883 20.8853 69.9112 20.7621 69.9152C20.6389 69.9192 20.5161 69.8989 20.4008 69.8554C20.2854 69.8119 20.1798 69.7461 20.0898 69.6618L8.8143 59.091C8.72066 59.0032 8.64601 58.8971 8.59498 58.7793C8.54396 58.6615 8.51763 58.5345 8.51763 58.4062C8.51763 58.2778 8.54396 58.1508 8.59498 58.033C8.64601 57.9152 8.72066 57.8092 8.8143 57.7214L20.0898 47.1505C20.1798 47.0662 20.2854 47.0004 20.4008 46.957C20.5161 46.9135 20.6389 46.8931 20.7621 46.8971C20.8853 46.9011 21.0065 46.9293 21.1188 46.9801C21.2312 47.0309 21.3324 47.1034 21.4167 47.1933L24.82 50.8237C24.9061 50.9154 24.9728 51.0235 25.0162 51.1415C25.0596 51.2596 25.0789 51.3851 25.0728 51.5107C25.0668 51.6363 25.0356 51.7594 24.9811 51.8727C24.9265 51.9861 24.8498 52.0873 24.7554 52.1703L17.6696 58.4062L24.7554 64.6422C24.8498 64.7252 24.9265 64.8264 24.9811 64.9398C25.0356 65.0531 25.0668 65.1762 25.0728 65.3018C25.0789 65.4274 25.0596 65.5529 25.0162 65.671C24.9728 65.789 24.9061 65.8971 24.82 65.9888L21.4167 69.619ZM30.3332 78.3937L25.5611 77.0083C25.322 76.9388 25.1203 76.7773 25.0003 76.5591C24.8803 76.341 24.8519 76.0841 24.9212 75.845L35.6012 39.0585C35.6707 38.8194 35.8322 38.6177 36.0504 38.4977C36.2685 38.3777 36.5254 38.3493 36.7645 38.4186L41.5364 39.804C41.6548 39.8384 41.7652 39.8958 41.8615 39.9728C41.9577 40.0499 42.0378 40.1452 42.0972 40.2532C42.1566 40.3612 42.1941 40.4799 42.2077 40.6024C42.2212 40.7249 42.2105 40.8489 42.1761 40.9673L31.4961 77.7538C31.4618 77.8722 31.4045 77.9827 31.3275 78.079C31.2505 78.1752 31.1552 78.2553 31.0472 78.3148C30.9392 78.3742 30.8206 78.4118 30.6981 78.4253C30.5755 78.4388 30.4516 78.4281 30.3332 78.3937ZM58.2834 59.091L47.0078 69.6618C46.9179 69.7461 46.8122 69.8119 46.6969 69.8554C46.5815 69.8989 46.4588 69.9192 46.3355 69.9152C46.2123 69.9112 46.0911 69.883 45.9788 69.8322C45.8665 69.7814 45.7653 69.709 45.681 69.619L42.2774 65.9886C42.1914 65.8969 42.1247 65.7888 42.0813 65.6708C42.0379 65.5528 42.0186 65.4272 42.0246 65.3016C42.0307 65.176 42.0619 65.0529 42.1164 64.9396C42.1709 64.8263 42.2477 64.7251 42.3421 64.642L49.428 58.4062L42.3423 52.1703C42.2479 52.0873 42.1711 51.9861 42.1166 51.8727C42.0621 51.7594 42.0308 51.6363 42.0248 51.5107C42.0188 51.3851 42.0381 51.2596 42.0815 51.1415C42.1249 51.0235 42.1916 50.9154 42.2776 50.8237L45.6812 47.1933C45.7655 47.1034 45.8667 47.0309 45.979 46.9801C46.0913 46.9293 46.2125 46.9011 46.3357 46.8971C46.4589 46.8931 46.5817 46.9135 46.6971 46.957C46.8124 47.0004 46.9181 47.0662 47.008 47.1505L58.2835 57.7214C58.3772 57.8092 58.4518 57.9152 58.5028 58.033C58.5538 58.1508 58.5801 58.2778 58.5801 58.4062C58.5801 58.5346 58.5538 58.6616 58.5027 58.7793C58.4517 58.8971 58.377 59.0032 58.2834 59.091Z" fill="#6B7280"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M39.0833 23.6406V0H4.1875C1.86693 0 0 1.85996 0 4.17188V84.8281C0 87.14 1.86693 89 4.1875 89H62.8125C65.1331 89 67 87.14 67 84.8281V27.8125H43.2708C40.9677 27.8125 39.0833 25.9352 39.0833 23.6406ZM22.3333 48.6719C22.3333 49.4402 21.7087 50.0625 20.9375 50.0625H19.5417C17.9993 50.0625 16.75 51.3071 16.75 52.8438V58.4062C16.75 59.9429 17.9993 61.1875 19.5417 61.1875H20.9375C21.7087 61.1875 22.3333 61.8098 22.3333 62.5781V65.3594C22.3333 66.1277 21.7087 66.75 20.9375 66.75H19.5417C14.9162 66.75 11.1667 63.0144 11.1667 58.4062V52.8438C11.1667 48.2356 14.9162 44.5 19.5417 44.5H20.9375C21.7087 44.5 22.3333 45.1223 22.3333 45.8906V48.6719ZM30.0575 66.75H27.9167C27.1455 66.75 26.5208 66.1277 26.5208 65.3594V62.5781C26.5208 61.8098 27.1455 61.1875 27.9167 61.1875H30.0575C31.0957 61.1875 31.8739 60.5791 31.8739 60.0368C31.8739 59.8108 31.743 59.5744 31.504 59.3693L27.6846 56.1065C26.2068 54.8515 25.3588 53.068 25.3588 51.215C25.3588 47.5124 28.6774 44.5017 32.7585 44.5017H34.8958C35.667 44.5017 36.2917 45.124 36.2917 45.8924V48.6736C36.2917 49.4419 35.667 50.0642 34.8958 50.0642H32.755C31.7168 50.0642 30.9386 50.6726 30.9386 51.215C30.9386 51.441 31.0695 51.6774 31.3085 51.8825L35.1279 55.1452C36.6057 56.4003 37.4537 58.1838 37.4537 60.0368C37.4554 63.7376 34.1386 66.75 30.0575 66.75ZM44.6667 45.8906V49.5062C44.6667 53.0297 45.6612 56.4889 47.4583 59.3936C49.2555 56.4907 50.25 53.0297 50.25 49.5062V45.8906C50.25 45.1223 50.8746 44.5 51.6458 44.5H54.4375C55.2087 44.5 55.8333 45.1223 55.8333 45.8906V49.5062C55.8333 55.6737 53.586 61.4813 49.5032 65.8617C48.9763 66.4267 48.2348 66.75 47.4583 66.75C46.6819 66.75 45.9404 66.4267 45.4134 65.8617C41.3306 61.4813 39.0833 55.6737 39.0833 49.5062V45.8906C39.0833 45.1223 39.708 44.5 40.4792 44.5H43.2708C44.042 44.5 44.6667 45.1223 44.6667 45.8906ZM65.7786 18.252L48.6971 1.2168C47.912 0.43457 46.8477 0 45.731 0H44.6667V22.25H67V21.1896C67 20.0945 66.5638 19.0342 65.7786 18.252Z" fill="#6B7280"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M39.0833 23.6406V0H4.1875C1.86693 0 0 1.85996 0 4.17188V84.8281C0 87.14 1.86693 89 4.1875 89H62.8125C65.1331 89 67 87.14 67 84.8281V27.8125H43.2708C40.9677 27.8125 39.0833 25.9352 39.0833 23.6406ZM67 21.1896V22.25H44.6667V0H45.731C46.8477 0 47.912 0.43457 48.6971 1.2168L65.7786 18.252C66.5638 19.0342 67 20.0945 67 21.1896Z" fill="#6B7280"/>
|
||||
</svg>
|
After Width: | Height: | Size: 455 B |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M31.7378 44.5174C30.8654 41.7361 30.8828 36.3648 31.3888 36.3648C32.8544 36.3648 32.7148 42.7791 31.7378 44.5174ZM31.4411 52.7221C30.0977 56.2334 28.4227 60.2488 26.4859 63.6211C29.6789 62.4043 33.2906 60.6313 37.4607 59.8143C35.2448 58.1455 33.1161 55.7467 31.4411 52.7221ZM15.0227 74.4158C15.0227 74.5549 17.3258 73.4771 21.112 67.4279C19.943 68.523 16.0346 71.6867 15.0227 74.4158ZM43.2708 27.8125H67V84.8281C67 87.14 65.1331 89 62.8125 89H4.1875C1.86693 89 0 87.14 0 84.8281V4.17188C0 1.85996 1.86693 0 4.1875 0H39.0833V23.6406C39.0833 25.9352 40.9677 27.8125 43.2708 27.8125ZM41.875 57.6762C38.3854 55.5555 36.0648 52.6352 34.4247 48.3242C35.2099 45.1084 36.4487 40.2238 35.5065 37.1645C34.6865 32.0539 28.1086 32.558 27.1664 35.9824C26.294 39.1635 27.0966 43.6482 28.5797 49.3672C26.5557 54.1648 23.5721 60.5965 21.4609 64.2816C21.4435 64.2816 21.4435 64.299 21.426 64.299C16.6977 66.7152 8.58438 72.0344 11.9169 76.1193C12.894 77.3187 14.7086 77.8576 15.6682 77.8576C18.7914 77.8576 21.8971 74.7287 26.3289 67.115C30.8305 65.6375 35.7682 63.7949 40.1128 63.0822C43.899 65.1334 48.3307 66.4719 51.2794 66.4719C56.3742 66.4719 56.7232 60.9094 54.7167 58.9277C52.2914 56.5637 45.2424 57.2416 41.875 57.6762ZM65.7786 18.252L48.6797 1.2168C47.8945 0.43457 46.8302 0 45.7135 0H44.6667V22.25H67V21.1896C67 20.0945 66.5638 19.0342 65.7786 18.252ZM52.8497 62.6303C53.5651 62.1609 52.4135 60.5617 45.382 61.0658C51.8552 63.8123 52.8497 62.6303 52.8497 62.6303Z" fill="#6B7280"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="90" viewBox="0 0 67 90" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M33.7966 47.6719C35.332 47.6719 36.501 48.1465 37.3385 49.0957C39.0135 51.0117 39.0484 54.8437 37.3036 56.8477C36.4487 57.832 35.2273 58.3418 33.6221 58.3418H28.9286V47.6719H33.7966ZM65.7786 18.457L48.6797 1.23047C47.8945 0.439453 46.8302 0 45.7135 0H44.6667V22.5H67V21.4277C67 20.3203 66.5638 19.248 65.7786 18.457ZM39.0833 23.9062V0H4.1875C1.86693 0 0 1.88086 0 4.21875V85.7812C0 88.1191 1.86693 90 4.1875 90H62.8125C65.1331 90 67 88.1191 67 85.7812V28.125H43.2708C40.9677 28.125 39.0833 26.2266 39.0833 23.9062ZM48.3307 52.9453C48.3307 68.8184 32.837 66.5859 28.9461 66.5859V76.6406C28.9461 77.8008 28.0039 78.75 26.8523 78.75H21.4784C20.3268 78.75 19.3846 77.8008 19.3846 76.6406V41.5195C19.3846 40.3594 20.3268 39.4102 21.4784 39.4102H35.6112C43.3755 39.4102 48.3307 45.1758 48.3307 52.9453Z" fill="#6B7280"/>
|
||||
</svg>
|
After Width: | Height: | Size: 927 B |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M39.0833 23.6406V0H4.1875C1.86693 0 0 1.85996 0 4.17188V84.8281C0 87.14 1.86693 89 4.1875 89H62.8125C65.1331 89 67 87.14 67 84.8281V27.8125H43.2708C40.9677 27.8125 39.0833 25.9352 39.0833 23.6406ZM11.1667 12.5156C11.1667 11.7473 11.7913 11.125 12.5625 11.125H26.5208C27.292 11.125 27.9167 11.7473 27.9167 12.5156V15.2969C27.9167 16.0652 27.292 16.6875 26.5208 16.6875H12.5625C11.7913 16.6875 11.1667 16.0652 11.1667 15.2969V12.5156ZM11.1667 23.6406C11.1667 22.8723 11.7913 22.25 12.5625 22.25H26.5208C27.292 22.25 27.9167 22.8723 27.9167 23.6406V26.4219C27.9167 27.1902 27.292 27.8125 26.5208 27.8125H12.5625C11.7913 27.8125 11.1667 27.1902 11.1667 26.4219V23.6406ZM44.808 66.75H53.0417C54.5841 66.75 55.8333 67.9946 55.8333 69.5312C55.8333 71.0679 54.5841 72.3125 53.0417 72.3125H44.808C41.9378 72.3125 39.352 70.7237 38.0661 68.165C37.5514 67.1359 36.6546 67.0316 36.2917 67.0316C35.9288 67.0316 35.0319 67.1342 34.5434 68.1076L33.2051 70.7741C32.9734 71.2361 32.617 71.6246 32.176 71.8961C31.735 72.1675 31.2267 72.3111 30.7083 72.3108C30.642 72.3108 30.5775 72.3073 30.5094 72.3021C29.384 72.2238 28.4157 71.4764 28.0615 70.4091L25.125 61.6377L23.2738 67.1794C22.2461 70.2492 19.3689 72.3125 16.1201 72.3125H13.9583C12.4159 72.3125 11.1667 71.0679 11.1667 69.5312C11.1667 67.9946 12.4159 66.75 13.9583 66.75H16.1201C16.9629 66.75 17.7096 66.2146 17.9766 65.4185L21.1504 55.9205C21.7261 54.2153 23.3209 53.068 25.125 53.068C26.9291 53.068 28.5239 54.2135 29.0979 55.9205L31.5197 63.1587C34.9691 60.3444 40.9503 61.4726 43.0353 65.6201C43.3877 66.3259 44.0752 66.75 44.808 66.75ZM65.7786 18.252L48.6971 1.2168C47.912 0.43457 46.8477 0 45.731 0H44.6667V22.25H67V21.1896C67 20.0945 66.5638 19.0342 65.7786 18.252Z" fill="#6B7280"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M39.0833 23.6406V0H4.1875C1.86693 0 0 1.85996 0 4.17188V84.8281C0 87.14 1.86693 89 4.1875 89H62.8125C65.1331 89 67 87.14 67 84.8281V27.8125H43.2708C40.9677 27.8125 39.0833 25.9352 39.0833 23.6406ZM49.5695 42.1533L39.0833 58.4062L49.5695 74.6592C50.4594 76.0498 49.4648 77.875 47.8073 77.875H41.718C40.9503 77.875 40.2349 77.4578 39.8685 76.7799C36.4487 70.4873 33.5 64.8379 33.5 64.8379C32.3833 67.4105 31.7552 68.3145 27.1141 76.7973C26.7477 77.4752 26.0497 77.8924 25.282 77.8924H19.1927C17.5352 77.8924 16.5406 76.0672 17.4305 74.6766L27.9516 58.4236L17.4305 42.1707C16.5232 40.7801 17.5352 38.9549 19.1927 38.9549H25.2646C26.0323 38.9549 26.7477 39.3721 27.1141 40.05C31.668 48.5328 30.6036 45.8906 33.5 51.9572C33.5 51.9572 34.5643 49.9234 39.8859 40.05C40.2523 39.3721 40.9677 38.9549 41.7354 38.9549H47.8073C49.4648 38.9375 50.4594 40.7627 49.5695 42.1533ZM67 21.1896V22.25H44.6667V0H45.731C46.8477 0 47.912 0.43457 48.6971 1.2168L65.7786 18.252C66.5638 19.0342 67 20.0945 67 21.1896Z" fill="#6B7280"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M39.0833 23.6406V0H4.1875C1.86693 0 0 1.85996 0 4.17188V84.8281C0 87.14 1.86693 89 4.1875 89H62.8125C65.1331 89 67 87.14 67 84.8281V27.8125H43.2708C40.9677 27.8125 39.0833 25.9352 39.0833 23.6406ZM50.25 64.6641C50.25 65.8113 49.3078 66.75 48.1563 66.75H18.8438C17.6922 66.75 16.75 65.8113 16.75 64.6641V63.2734C16.75 62.1262 17.6922 61.1875 18.8438 61.1875H48.1563C49.3078 61.1875 50.25 62.1262 50.25 63.2734V64.6641ZM50.25 53.5391C50.25 54.6863 49.3078 55.625 48.1563 55.625H18.8438C17.6922 55.625 16.75 54.6863 16.75 53.5391V52.1484C16.75 51.0012 17.6922 50.0625 18.8438 50.0625H48.1563C49.3078 50.0625 50.25 51.0012 50.25 52.1484V53.5391ZM50.25 41.0234V42.4141C50.25 43.5613 49.3078 44.5 48.1563 44.5H18.8438C17.6922 44.5 16.75 43.5613 16.75 42.4141V41.0234C16.75 39.8762 17.6922 38.9375 18.8438 38.9375H48.1563C49.3078 38.9375 50.25 39.8762 50.25 41.0234ZM67 21.1896V22.25H44.6667V0H45.731C46.8477 0 47.912 0.43457 48.6971 1.2168L65.7786 18.252C66.5638 19.0342 67 20.0945 67 21.1896Z" fill="#6B7280"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M67 21.1968V22.25H44.6667V0H45.7238C46.8344 0 47.8996 0.439611 48.6849 1.22184L65.7736 18.2467C66.5588 19.0292 66.9999 20.0903 67 21.1968ZM39.0833 23.6406V0H4.1875C1.87478 0 0 1.86778 0 4.17188V84.8281C0 87.1322 1.87478 89 4.1875 89H62.8125C65.1252 89 67 87.1322 67 84.8281V27.8125H43.2708C40.9677 27.8125 39.0833 25.9352 39.0833 23.6406ZM55.8333 48.6747V68.137C55.8333 71.8647 51.3068 73.6991 48.6847 71.087L39.0833 61.5249V68.1406C39.0833 70.4447 37.2086 72.3125 34.8958 72.3125H15.3542C13.0414 72.3125 11.1667 70.4447 11.1667 68.1406V48.6719C11.1667 46.3678 13.0414 44.5 15.3542 44.5H34.8958C37.2086 44.5 39.0833 46.3678 39.0833 48.6719V55.2876L48.6847 45.7248C51.3039 43.1156 55.8333 44.9441 55.8333 48.6747Z" fill="#6B7280"/>
|
||||
</svg>
|
After Width: | Height: | Size: 843 B |
@ -0,0 +1,3 @@
|
||||
<svg width="67" height="89" viewBox="0 0 67 89" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M39.0833 23.6406V0H4.1875C1.86693 0 0 1.85996 0 4.17188V84.8281C0 87.14 1.86693 89 4.1875 89H62.8125C65.1331 89 67 87.14 67 84.8281V27.8125H43.2708C40.9677 27.8125 39.0833 25.9352 39.0833 23.6406ZM49.0461 44.5H53.2161C54.5596 44.5 55.5542 45.7342 55.2576 47.0553L48.6273 76.2584C48.418 77.2145 47.563 77.875 46.5859 77.875H39.9557C38.9961 77.875 38.1586 77.2145 37.9318 76.2932C33.4302 58.302 34.3026 62.1783 33.4651 57.0852H33.3779C33.1859 59.5709 32.9591 60.1098 28.9112 76.2932C28.6844 77.2145 27.8469 77.875 26.8872 77.875H20.4141C19.437 77.875 18.582 77.1971 18.3727 76.241L11.7773 47.0379C11.4807 45.7342 12.4753 44.5 13.8187 44.5H18.0935C19.088 44.5 19.9604 45.1953 20.1523 46.1861C22.8742 59.7447 23.6594 65.2203 23.8164 67.4279C24.0956 65.6549 25.0901 61.7438 28.9461 46.0992C29.1729 45.1605 30.0104 44.5174 30.9875 44.5174H36.0648C37.0419 44.5174 37.8794 45.1779 38.1063 46.1166C42.2938 63.5689 43.1313 67.6713 43.2708 68.61C43.2359 66.6631 42.8172 65.5158 47.0396 46.1514C47.2141 45.1779 48.069 44.5 49.0461 44.5ZM67 21.1896V22.25H44.6667V0H45.731C46.8477 0 47.912 0.43457 48.6971 1.2168L65.7786 18.252C66.5638 19.0342 67 20.0945 67 21.1896Z" fill="#6B7280"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
@ -0,0 +1,88 @@
|
||||
import { useColorMode } from '@chakra-ui/react'
|
||||
import { File } from '@/client/api/file'
|
||||
import * as fe from '@/helpers/file-extension'
|
||||
import DarkArchiveSvg from './assets/dark-icon-archive.svg'
|
||||
import DarkAudioSvg from './assets/dark-icon-audio.svg'
|
||||
import DarkCodeSvg from './assets/dark-icon-code.svg'
|
||||
import DarkCsvSvg from './assets/dark-icon-csv.svg'
|
||||
import DarkFileSvg from './assets/dark-icon-file.svg'
|
||||
import DarkPdfSvg from './assets/dark-icon-pdf.svg'
|
||||
import DarkPowerPointSvg from './assets/dark-icon-power-point.svg'
|
||||
import DarkRichTextSvg from './assets/dark-icon-rich-text.svg'
|
||||
import DarkSpreadsheetSvg from './assets/dark-icon-spreadsheet.svg'
|
||||
import DarkTextSvg from './assets/dark-icon-text.svg'
|
||||
import DarkVideoSvg from './assets/dark-icon-video.svg'
|
||||
import DarkWordSvg from './assets/dark-icon-word.svg'
|
||||
import ArchiveSvg from './assets/icon-archive.svg'
|
||||
import AudioSvg from './assets/icon-audio.svg'
|
||||
import CodeSvg from './assets/icon-code.svg'
|
||||
import CsvSvg from './assets/icon-csv.svg'
|
||||
import FileSvg from './assets/icon-file.svg'
|
||||
import PdfSvg from './assets/icon-pdf.svg'
|
||||
import PowerPointSvg from './assets/icon-power-point.svg'
|
||||
import RichTextSvg from './assets/icon-rich-text.svg'
|
||||
import SpreadsheetSvg from './assets/icon-spreadsheet.svg'
|
||||
import TextSvg from './assets/icon-text.svg'
|
||||
import VideoSvg from './assets/icon-video.svg'
|
||||
import WordSvg from './assets/icon-word.svg'
|
||||
|
||||
export type IconFontProps = {
|
||||
file: File
|
||||
scale: number
|
||||
}
|
||||
|
||||
const MIN_WIDTH = 45
|
||||
const MIN_HEIGHT = 59.78
|
||||
const BASE_WIDTH = 67
|
||||
const BASE_HEIGHT = 89
|
||||
|
||||
const IconDiverse = ({ file, scale }: IconFontProps) => {
|
||||
const { colorMode } = useColorMode()
|
||||
const isDark = colorMode === 'dark'
|
||||
const width = Math.max(MIN_WIDTH, BASE_WIDTH * scale)
|
||||
const height = Math.max(MIN_HEIGHT, BASE_HEIGHT * scale)
|
||||
|
||||
const { original } = file
|
||||
let image
|
||||
if (fe.isImage(original?.extension)) {
|
||||
image = isDark ? DarkFileSvg : FileSvg
|
||||
} else if (fe.isPDF(original?.extension)) {
|
||||
image = isDark ? DarkPdfSvg : PdfSvg
|
||||
} else if (fe.isText(original?.extension)) {
|
||||
image = isDark ? DarkTextSvg : TextSvg
|
||||
} else if (fe.isRichText(original?.extension)) {
|
||||
image = isDark ? DarkRichTextSvg : RichTextSvg
|
||||
} else if (fe.isWord(original?.extension)) {
|
||||
image = isDark ? DarkWordSvg : WordSvg
|
||||
} else if (fe.isPowerPoint(original?.extension)) {
|
||||
image = isDark ? DarkPowerPointSvg : PowerPointSvg
|
||||
} else if (fe.isExcel(original?.extension)) {
|
||||
image = isDark ? DarkSpreadsheetSvg : SpreadsheetSvg
|
||||
} else if (fe.isDocument(original?.extension)) {
|
||||
image = isDark ? DarkWordSvg : WordSvg
|
||||
} else if (fe.isSpreadsheet(original?.extension)) {
|
||||
image = isDark ? DarkSpreadsheetSvg : SpreadsheetSvg
|
||||
} else if (fe.isSlides(original?.extension)) {
|
||||
image = isDark ? DarkPowerPointSvg : PowerPointSvg
|
||||
} else if (fe.isVideo(original?.extension)) {
|
||||
image = isDark ? DarkVideoSvg : VideoSvg
|
||||
} else if (fe.isAudio(original?.extension)) {
|
||||
image = isDark ? DarkAudioSvg : AudioSvg
|
||||
} else if (fe.isArchive(original?.extension)) {
|
||||
image = isDark ? DarkArchiveSvg : ArchiveSvg
|
||||
} else if (fe.isFont(original?.extension)) {
|
||||
image = isDark ? DarkFileSvg : FileSvg
|
||||
} else if (fe.isCode(original?.extension)) {
|
||||
image = isDark ? DarkCodeSvg : CodeSvg
|
||||
} else if (fe.isCSV(original?.extension)) {
|
||||
image = isDark ? DarkCsvSvg : CsvSvg
|
||||
} else {
|
||||
image = isDark ? DarkFileSvg : FileSvg
|
||||
}
|
||||
|
||||
return (
|
||||
<img src={image} style={{ width: `${width}px`, height: `${height}px` }} />
|
||||
)
|
||||
}
|
||||
|
||||
export default IconDiverse
|
@ -0,0 +1,22 @@
|
||||
import { Circle, Tooltip } from '@chakra-ui/react'
|
||||
import cx from 'classnames'
|
||||
import { IconClose } from '@/lib'
|
||||
|
||||
const IconErrorBadge = () => (
|
||||
<Tooltip label="An error occured while processing this item">
|
||||
<Circle
|
||||
className={cx(
|
||||
'text-orange-600',
|
||||
'bg-white',
|
||||
'w-[23px]',
|
||||
'h-[23px]',
|
||||
'border',
|
||||
'border-gray-200',
|
||||
)}
|
||||
>
|
||||
<IconClose className={cx('text-red-600')} />
|
||||
</Circle>
|
||||
</Tooltip>
|
||||
)
|
||||
|
||||
export default IconErrorBadge
|
@ -0,0 +1,43 @@
|
||||
import cx from 'classnames'
|
||||
import { SnapshotStatus } from '@/client/api/file'
|
||||
import { FileCommonProps } from '@/types/file'
|
||||
import IconDiverse from './icon-diverse'
|
||||
import IconErrorBadge from './icon-error-badge'
|
||||
import IconNewBadge from './icon-new-badge'
|
||||
import IconProcessingBadge from './icon-processing-badge'
|
||||
import IconSharedBadge from './icon-shared-badge'
|
||||
import IconThumbnail from './icon-thumbnail'
|
||||
|
||||
type IconFileProps = FileCommonProps
|
||||
|
||||
const IconFile = ({ file, scale }: IconFileProps) => (
|
||||
<>
|
||||
{file.thumbnail ? (
|
||||
<IconThumbnail file={file} scale={scale} />
|
||||
) : (
|
||||
<>
|
||||
<IconDiverse file={file} scale={scale} />
|
||||
<div
|
||||
className={cx(
|
||||
'absolute',
|
||||
'flex',
|
||||
'flex-row',
|
||||
'items-center',
|
||||
'gap-[2px]',
|
||||
'bottom-[-5px]',
|
||||
'right-[-5px]',
|
||||
)}
|
||||
>
|
||||
{file.status === SnapshotStatus.New ? <IconNewBadge /> : null}
|
||||
{file.status === SnapshotStatus.Processing ? (
|
||||
<IconProcessingBadge />
|
||||
) : null}
|
||||
{file.status === SnapshotStatus.Error ? <IconErrorBadge /> : null}
|
||||
{file.isShared ? <IconSharedBadge /> : null}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
export default IconFile
|
@ -0,0 +1,4 @@
|
||||
<svg width="139" height="111" viewBox="0 0 139 111" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M124.5 13.8333H62.25L48.4167 0H13.8333C6.225 0 0 6.225 0 13.8333V41.5H138.333V27.6667C138.333 20.0583 132.108 13.8333 124.5 13.8333Z" fill="#FFA000"/>
|
||||
<path d="M124.5 14H13.8333C6.225 14 0 20.225 0 27.8333V97C0 104.608 6.225 110.833 13.8333 110.833H124.5C132.108 110.833 138.333 104.608 138.333 97V27.8333C138.333 20.225 132.108 14 124.5 14Z" fill="#FFCA28"/>
|
||||
</svg>
|
After Width: | Height: | Size: 476 B |
@ -0,0 +1,44 @@
|
||||
import cx from 'classnames'
|
||||
import { FileCommonProps } from '@/types/file'
|
||||
import IconProcessingBadge from '../icon-processing-badge'
|
||||
import IconSharedBadge from '../icon-shared-badge'
|
||||
import FolderSvg from './assets/icon-folder.svg'
|
||||
|
||||
type IconFolderProps = {
|
||||
isLoading?: boolean
|
||||
} & FileCommonProps
|
||||
|
||||
const MIN_WIDTH = 45
|
||||
const MIN_HEIGHT = 36.05
|
||||
const BASE_WIDTH = 67
|
||||
const BASE_HEIGHT = 53.68
|
||||
|
||||
const IconFolder = ({ file, scale, isLoading }: IconFolderProps) => {
|
||||
const width = Math.max(MIN_WIDTH, BASE_WIDTH * scale)
|
||||
const height = Math.max(MIN_HEIGHT, BASE_HEIGHT * scale)
|
||||
|
||||
return (
|
||||
<>
|
||||
<img
|
||||
src={FolderSvg}
|
||||
style={{ width: `${width}px`, height: `${height}px` }}
|
||||
/>
|
||||
<div
|
||||
className={cx(
|
||||
'absolute',
|
||||
'flex',
|
||||
'flex-row',
|
||||
'items-center',
|
||||
'gap-[2px]',
|
||||
'bottom-[-5px]',
|
||||
'right-[-5px]',
|
||||
)}
|
||||
>
|
||||
{file.isShared ? <IconSharedBadge /> : null}
|
||||
{isLoading ? <IconProcessingBadge /> : null}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconFolder
|
@ -0,0 +1,22 @@
|
||||
import { Circle, Tooltip } from '@chakra-ui/react'
|
||||
import cx from 'classnames'
|
||||
import { IconHourglass } from '@/lib'
|
||||
|
||||
const IconNewBadge = () => (
|
||||
<Tooltip label="Waiting for processing">
|
||||
<Circle
|
||||
className={cx(
|
||||
'text-purple-600',
|
||||
'bg-white',
|
||||
'w-[23px]',
|
||||
'h-[23px]',
|
||||
'border',
|
||||
'border-gray-200',
|
||||
)}
|
||||
>
|
||||
<IconHourglass />
|
||||
</Circle>
|
||||
</Tooltip>
|
||||
)
|
||||
|
||||
export default IconNewBadge
|
@ -0,0 +1,25 @@
|
||||
import { Circle, Spinner, Tooltip } from '@chakra-ui/react'
|
||||
import cx from 'classnames'
|
||||
|
||||
const IconProcessingBadge = () => (
|
||||
<Tooltip label="Processing in progress">
|
||||
<Circle
|
||||
className={cx(
|
||||
'text-purple-600',
|
||||
'bg-white',
|
||||
'w-[23px]',
|
||||
'h-[23px]',
|
||||
'border',
|
||||
'border-gray-200',
|
||||
)}
|
||||
>
|
||||
<Spinner
|
||||
size="sm"
|
||||
thickness="4px"
|
||||
className={cx('text-gray-400', 'dark:text-gray-500')}
|
||||
/>
|
||||
</Circle>
|
||||
</Tooltip>
|
||||
)
|
||||
|
||||
export default IconProcessingBadge
|
@ -0,0 +1,22 @@
|
||||
import { Circle, Tooltip } from '@chakra-ui/react'
|
||||
import cx from 'classnames'
|
||||
import { IconGroup } from '@/lib'
|
||||
|
||||
const IconSharedBadge = () => (
|
||||
<Tooltip label="This item is shared">
|
||||
<Circle
|
||||
className={cx(
|
||||
'text-orange-600',
|
||||
'bg-white',
|
||||
'w-[23px]',
|
||||
'h-[23px]',
|
||||
'border',
|
||||
'border-gray-200',
|
||||
)}
|
||||
>
|
||||
<IconGroup className={cx('text-[12px]')} />
|
||||
</Circle>
|
||||
</Tooltip>
|
||||
)
|
||||
|
||||
export default IconSharedBadge
|
@ -0,0 +1,87 @@
|
||||
import { useState } from 'react'
|
||||
import { Image, Skeleton } from '@chakra-ui/react'
|
||||
import cx from 'classnames'
|
||||
import { File, SnapshotStatus } from '@/client/api/file'
|
||||
import * as fe from '@/helpers/file-extension'
|
||||
import { IconPlayArrow } from '@/lib'
|
||||
import IconErrorBadge from '../icon-error-badge'
|
||||
import IconNewBadge from '../icon-new-badge'
|
||||
import IconProcessingBadge from '../icon-processing-badge'
|
||||
import IconSharedBadge from '../icon-shared-badge'
|
||||
import { getThumbnailHeight, getThumbnailWidth } from './size'
|
||||
|
||||
export type IconThumbnailProps = {
|
||||
file: File
|
||||
scale: number
|
||||
}
|
||||
|
||||
const IconThumbnail = ({ file, scale }: IconThumbnailProps) => {
|
||||
const { original, isShared, status } = file
|
||||
const width = getThumbnailWidth(file, scale)
|
||||
const height = getThumbnailHeight(file, scale)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
return (
|
||||
<>
|
||||
<Image
|
||||
src={file.thumbnail?.base64}
|
||||
style={{
|
||||
width: isLoading ? 0 : width,
|
||||
height: isLoading ? 0 : height,
|
||||
}}
|
||||
className={cx(
|
||||
'pointer-events-none',
|
||||
'object-cover',
|
||||
'border',
|
||||
'border-solid',
|
||||
{ 'invisible': isLoading },
|
||||
'border',
|
||||
'border-gray-300',
|
||||
'dark:border-gray-700',
|
||||
'rounded-md',
|
||||
)}
|
||||
alt={file.name}
|
||||
onLoad={() => setIsLoading(false)}
|
||||
/>
|
||||
{isLoading && (
|
||||
<Skeleton className={cx('rounded-md')} style={{ width, height }} />
|
||||
)}
|
||||
{fe.isVideo(original?.extension) && (
|
||||
<div
|
||||
className={cx(
|
||||
'absolute',
|
||||
'top-0',
|
||||
'left-0',
|
||||
'opacity-50',
|
||||
'flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
)}
|
||||
style={{ width, height }}
|
||||
>
|
||||
<IconPlayArrow
|
||||
className={cx('text-white', 'text-[40px]')}
|
||||
filled={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={cx(
|
||||
'absolute',
|
||||
'flex',
|
||||
'flex-row',
|
||||
'items-center',
|
||||
'gap-[2px]',
|
||||
'bottom-[-5px]',
|
||||
'right-[-5px]',
|
||||
)}
|
||||
>
|
||||
{isShared ? <IconSharedBadge /> : null}
|
||||
{status === SnapshotStatus.New ? <IconNewBadge /> : null}
|
||||
{status === SnapshotStatus.Processing ? <IconProcessingBadge /> : null}
|
||||
{status === SnapshotStatus.Error ? <IconErrorBadge /> : null}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconThumbnail
|
@ -0,0 +1,33 @@
|
||||
import { File } from '@/client/api/file'
|
||||
import { getSizeWithAspectRatio } from '@/helpers/aspect-ratio'
|
||||
|
||||
const MAX_WIDTH = 130
|
||||
const MAX_HEIGHT = 130
|
||||
|
||||
export function getThumbnailWidth(file: File, scale: number): string {
|
||||
if (file.thumbnail) {
|
||||
const { width } = getSizeWithAspectRatio(
|
||||
file.thumbnail.width,
|
||||
file.thumbnail.height,
|
||||
MAX_WIDTH,
|
||||
MAX_HEIGHT,
|
||||
)
|
||||
return `${width * scale}px`
|
||||
} else {
|
||||
return `${MAX_WIDTH * scale}px`
|
||||
}
|
||||
}
|
||||
|
||||
export function getThumbnailHeight(file: File, scale: number): string {
|
||||
if (file.thumbnail) {
|
||||
const { height } = getSizeWithAspectRatio(
|
||||
file.thumbnail.width,
|
||||
file.thumbnail.height,
|
||||
MAX_WIDTH,
|
||||
MAX_HEIGHT,
|
||||
)
|
||||
return `${height * scale}px`
|
||||
} else {
|
||||
return `${MAX_HEIGHT * scale}px`
|
||||
}
|
||||
}
|
34
Voltaserve/ui/src/components/file/list/item/icon/index.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import cx from 'classnames'
|
||||
import { FileCommonProps } from '@/types/file'
|
||||
import { computeScale } from '../scale'
|
||||
import IconFile from './icon-file'
|
||||
import IconFolder from './icon-folder'
|
||||
|
||||
export type ItemIconProps = {
|
||||
isLoading?: boolean
|
||||
} & FileCommonProps
|
||||
|
||||
const ItemIcon = ({ file, scale, viewType, isLoading }: ItemIconProps) => (
|
||||
<>
|
||||
<div
|
||||
className={cx('z-0', 'text-gray-500', 'dark:text-gray-300', 'relative')}
|
||||
>
|
||||
{file.type === 'file' ? (
|
||||
<IconFile
|
||||
file={file}
|
||||
scale={computeScale(scale, viewType)}
|
||||
viewType={viewType}
|
||||
/>
|
||||
) : file.type === 'folder' ? (
|
||||
<IconFolder
|
||||
file={file}
|
||||
scale={computeScale(scale, viewType)}
|
||||
viewType={viewType}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
export default ItemIcon
|
245
Voltaserve/ui/src/components/file/list/item/index.tsx
Normal file
@ -0,0 +1,245 @@
|
||||
import { ChangeEvent, MouseEvent, useEffect } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Link as ChakraLink, Checkbox } from '@chakra-ui/react'
|
||||
import cx from 'classnames'
|
||||
import { SnapshotStatus } from '@/client/api/file'
|
||||
import relativeDate from '@/helpers/relative-date'
|
||||
import { variables, Text } from '@/lib'
|
||||
import store from '@/store/configure-store'
|
||||
import { useAppDispatch } from '@/store/hook'
|
||||
import {
|
||||
selectionAdded,
|
||||
selectionRemoved,
|
||||
selectionUpdated,
|
||||
} from '@/store/ui/files'
|
||||
import { FileCommonProps, FileViewType } from '@/types/file'
|
||||
import ItemIcon from './icon'
|
||||
import { performMultiSelect, performRangeSelect } from './item-perform-select'
|
||||
import { computeScale } from './scale'
|
||||
|
||||
export type ListItemProps = {
|
||||
onContextMenu?: (event: MouseEvent) => void
|
||||
} & FileCommonProps
|
||||
|
||||
const WIDTH = 147
|
||||
const MIN_HEIGHT = 110
|
||||
|
||||
const ListItem = ({
|
||||
file,
|
||||
scale,
|
||||
viewType,
|
||||
isPresentational,
|
||||
isLoading,
|
||||
isSelectionMode,
|
||||
onContextMenu,
|
||||
}: ListItemProps) => {
|
||||
const dispatch = useAppDispatch()
|
||||
const navigate = useNavigate()
|
||||
const [isChecked, setIsChecked] = useState(false)
|
||||
const [isSelected, setIsSelected] = useState(false)
|
||||
const date = relativeDate(new Date(file.createTime))
|
||||
const computedScale = computeScale(scale, viewType)
|
||||
const width = `${WIDTH * computedScale}px`
|
||||
const minHeight = `${MIN_HEIGHT * computedScale}px`
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = store.subscribe(() => {
|
||||
if (store.getState().ui.files.selection.includes(file.id)) {
|
||||
setIsSelected(true)
|
||||
setIsChecked(true)
|
||||
} else {
|
||||
if (isSelected) {
|
||||
setIsSelected(false)
|
||||
}
|
||||
if (isChecked) {
|
||||
setIsChecked(false)
|
||||
}
|
||||
}
|
||||
})
|
||||
return () => unsubscribe()
|
||||
}, [file, isSelected, isChecked])
|
||||
|
||||
const handleIconClick = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
if (isSelectionMode) {
|
||||
setIsChecked(!isChecked)
|
||||
if (isChecked) {
|
||||
dispatch(selectionRemoved(file.id))
|
||||
} else {
|
||||
dispatch(selectionAdded(file.id))
|
||||
}
|
||||
} else {
|
||||
if (store.getState().ui.files.isMultiSelectActive) {
|
||||
performMultiSelect(file, isSelected)
|
||||
} else if (store.getState().ui.files.isRangeSelectActive) {
|
||||
performRangeSelect(file)
|
||||
} else {
|
||||
dispatch(selectionUpdated([file.id]))
|
||||
}
|
||||
}
|
||||
},
|
||||
[file, isSelected, isChecked, isSelectionMode, dispatch],
|
||||
)
|
||||
|
||||
const handleFolderLinkClick = useCallback(() => {
|
||||
navigate(`/workspace/${file.workspaceId}/file/${file.id}`)
|
||||
}, [file.id, file.workspaceId, navigate])
|
||||
|
||||
const handleFileLinkClick = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
handleIconClick(event)
|
||||
window.open(`/file/${file.id}`, '_blank')?.focus()
|
||||
},
|
||||
[file.id, handleIconClick],
|
||||
)
|
||||
|
||||
const handleIconDoubleClick = useCallback(() => {
|
||||
dispatch(selectionUpdated([]))
|
||||
if (file.type === 'folder') {
|
||||
navigate(`/workspace/${file.workspaceId}/file/${file.id}`)
|
||||
} else if (file.type === 'file' && file.status === SnapshotStatus.Ready) {
|
||||
window.open(`/file/${file.id}`, '_blank')?.focus()
|
||||
}
|
||||
}, [file, navigate, dispatch])
|
||||
|
||||
const handleCheckboxChange = useCallback(
|
||||
(event: ChangeEvent<HTMLInputElement>) => {
|
||||
event.stopPropagation()
|
||||
if (!event.target.checked) {
|
||||
setIsChecked(true)
|
||||
dispatch(selectionAdded(file.id))
|
||||
} else {
|
||||
setIsChecked(false)
|
||||
dispatch(selectionRemoved(file.id))
|
||||
}
|
||||
},
|
||||
[file.id, dispatch],
|
||||
)
|
||||
|
||||
const handleContextMenu = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
if (event) {
|
||||
event.preventDefault()
|
||||
onContextMenu?.(event)
|
||||
if (!isSelected) {
|
||||
handleIconClick(event)
|
||||
}
|
||||
}
|
||||
},
|
||||
[isSelected, handleIconClick, onContextMenu],
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
'relative',
|
||||
'flex',
|
||||
{ 'flex-col': viewType === FileViewType.Grid },
|
||||
{ 'flex-row': viewType === FileViewType.List },
|
||||
'items-center',
|
||||
'gap-0.5',
|
||||
{ 'px-1.5': viewType === FileViewType.List },
|
||||
{ 'px-0': viewType === FileViewType.Grid },
|
||||
'py-1',
|
||||
'transition',
|
||||
'duration-400',
|
||||
'ease-in-out',
|
||||
{ 'bg-transparent': !isChecked },
|
||||
'rounded-md',
|
||||
'select-none',
|
||||
'cursor-default',
|
||||
'hover:bg-gray-100',
|
||||
'hover:dark:bg-gray-700',
|
||||
'active:gray-200',
|
||||
'active:dark:gray-600',
|
||||
{
|
||||
'bg-gray-100': isChecked,
|
||||
'dark:bg-gray-700': isChecked,
|
||||
},
|
||||
)}
|
||||
style={{
|
||||
width: viewType === FileViewType.List ? '100%' : width,
|
||||
}}
|
||||
onClick={handleIconClick}
|
||||
onDoubleClick={isSelectionMode ? undefined : handleIconDoubleClick}
|
||||
onContextMenu={isSelectionMode ? undefined : handleContextMenu}
|
||||
>
|
||||
{isSelectionMode && !isPresentational ? (
|
||||
<Checkbox
|
||||
position={viewType === FileViewType.List ? 'relative' : 'absolute'}
|
||||
top={viewType === FileViewType.List ? 'auto' : variables.spacingSm}
|
||||
left={viewType === FileViewType.List ? 'auto' : variables.spacingSm}
|
||||
isChecked={isChecked}
|
||||
zIndex={1}
|
||||
size="lg"
|
||||
onChange={handleCheckboxChange}
|
||||
/>
|
||||
) : null}
|
||||
<div
|
||||
className={cx('flex', 'items-center', 'justify-center')}
|
||||
style={{ width, minHeight }}
|
||||
>
|
||||
<ItemIcon
|
||||
file={file}
|
||||
scale={scale}
|
||||
viewType={viewType}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={cx(
|
||||
'px-0.5',
|
||||
{ 'flex': viewType === FileViewType.List },
|
||||
{ 'block': viewType === FileViewType.Grid },
|
||||
{ 'grow': viewType === FileViewType.List },
|
||||
{ 'grow-0': viewType === FileViewType.Grid },
|
||||
)}
|
||||
style={{ width }}
|
||||
title={file.name}
|
||||
>
|
||||
{file.type === 'folder' && (
|
||||
<ChakraLink
|
||||
className={cx('text-center', 'no-underline', {
|
||||
'hover:no-underline': isSelectionMode,
|
||||
'hover:underline': !isSelectionMode,
|
||||
})}
|
||||
noOfLines={3}
|
||||
cursor={isSelectionMode ? 'default' : 'pointer'}
|
||||
onClick={isSelectionMode ? undefined : handleFolderLinkClick}
|
||||
>
|
||||
{file.name}
|
||||
</ChakraLink>
|
||||
)}
|
||||
{file.type === 'file' && file.status === SnapshotStatus.Ready ? (
|
||||
<ChakraLink
|
||||
className={cx('text-center', 'no-underline', {
|
||||
'hover:no-underline': isSelectionMode,
|
||||
'hover:underline': !isSelectionMode,
|
||||
})}
|
||||
noOfLines={3}
|
||||
cursor={isSelectionMode ? 'default' : 'pointer'}
|
||||
onClick={isSelectionMode ? undefined : handleFileLinkClick}
|
||||
>
|
||||
{file.name}
|
||||
</ChakraLink>
|
||||
) : null}
|
||||
{file.type === 'file' && file.status !== SnapshotStatus.Ready ? (
|
||||
<Text
|
||||
className={cx('text-center')}
|
||||
noOfLines={3}
|
||||
onClick={handleIconClick}
|
||||
>
|
||||
{file.name}
|
||||
</Text>
|
||||
) : null}
|
||||
</div>
|
||||
<Text noOfLines={3} className={cx('text-gray-500', 'text-center')}>
|
||||
{date}
|
||||
</Text>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ListItem
|