mirror of
https://github.com/filebrowser/filebrowser.git
synced 2025-05-11 20:53:00 +00:00
feat: migrate to vue 3 (#2689)
--------- Co-authored-by: Joep <jcbuhre@gmail.com> Co-authored-by: Omar Hussein <omarmohammad1951@gmail.com> Co-authored-by: Oleg Lobanov <oleg@lobanov.me>
This commit is contained in:
parent
0e3b35b30e
commit
5100e587d7
9
.gitignore
vendored
9
.gitignore
vendored
@ -30,5 +30,14 @@ yarn-error.log*
|
|||||||
bin/
|
bin/
|
||||||
build/
|
build/
|
||||||
|
|
||||||
|
# Vue distributable files
|
||||||
/frontend/dist/*
|
/frontend/dist/*
|
||||||
!/frontend/dist/.gitkeep
|
!/frontend/dist/.gitkeep
|
||||||
|
|
||||||
|
# Playwright files
|
||||||
|
/frontend/test-results/
|
||||||
|
/frontend/playwright-report/
|
||||||
|
/frontend/playwright/.cache/
|
||||||
|
|
||||||
|
default.nix
|
||||||
|
Dockerfile.dev
|
||||||
|
@ -9,12 +9,14 @@ import (
|
|||||||
"hash"
|
"hash"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -27,6 +29,11 @@ import (
|
|||||||
const PermFile = 0644
|
const PermFile = 0644
|
||||||
const PermDir = 0755
|
const PermDir = 0755
|
||||||
|
|
||||||
|
var (
|
||||||
|
reSubDirs = regexp.MustCompile("(?i)^sub(s|titles)$")
|
||||||
|
reSubExts = regexp.MustCompile("(?i)(.vtt|.srt|.ass|.ssa)$")
|
||||||
|
)
|
||||||
|
|
||||||
// FileInfo describes a file.
|
// FileInfo describes a file.
|
||||||
type FileInfo struct {
|
type FileInfo struct {
|
||||||
*Listing
|
*Listing
|
||||||
@ -277,8 +284,8 @@ func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateImageResolution(fs afero.Fs, filePath string) (*ImageResolution, error) {
|
func calculateImageResolution(fs_ afero.Fs, filePath string) (*ImageResolution, error) {
|
||||||
file, err := fs.Open(filePath)
|
file, err := fs_.Open(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -343,12 +350,45 @@ func (i *FileInfo) detectSubtitles() {
|
|||||||
|
|
||||||
base := strings.TrimSuffix(i.Name, ext)
|
base := strings.TrimSuffix(i.Name, ext)
|
||||||
for _, f := range dir {
|
for _, f := range dir {
|
||||||
if !f.IsDir() && strings.HasPrefix(f.Name(), base) && strings.HasSuffix(f.Name(), ".vtt") {
|
// load all supported subtitles from subs directories
|
||||||
i.Subtitles = append(i.Subtitles, path.Join(parentDir, f.Name()))
|
// should cover all instances of subtitle distributions
|
||||||
|
// like tv-shows with multiple episodes in single dir
|
||||||
|
if f.IsDir() && reSubDirs.MatchString(f.Name()) {
|
||||||
|
subsDir := path.Join(parentDir, f.Name())
|
||||||
|
i.loadSubtitles(subsDir, base, true)
|
||||||
|
} else if isSubtitleMatch(f, base) {
|
||||||
|
i.addSubtitle(path.Join(parentDir, f.Name()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *FileInfo) loadSubtitles(subsPath, baseName string, recursive bool) {
|
||||||
|
dir, err := afero.ReadDir(i.Fs, subsPath)
|
||||||
|
if err == nil {
|
||||||
|
for _, f := range dir {
|
||||||
|
if isSubtitleMatch(f, "") {
|
||||||
|
i.addSubtitle(path.Join(subsPath, f.Name()))
|
||||||
|
} else if f.IsDir() && recursive && strings.HasPrefix(f.Name(), baseName) {
|
||||||
|
subsDir := path.Join(subsPath, f.Name())
|
||||||
|
i.loadSubtitles(subsDir, baseName, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsSupportedSubtitle(fileName string) bool {
|
||||||
|
return reSubExts.MatchString(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSubtitleMatch(f fs.FileInfo, baseName string) bool {
|
||||||
|
return !f.IsDir() && strings.HasPrefix(f.Name(), baseName) &&
|
||||||
|
IsSupportedSubtitle(f.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *FileInfo) addSubtitle(path_ string) {
|
||||||
|
i.Subtitles = append(i.Subtitles, path_)
|
||||||
|
}
|
||||||
|
|
||||||
func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
|
func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error {
|
||||||
afs := &afero.Afero{Fs: i.Fs}
|
afs := &afero.Afero{Fs: i.Fs}
|
||||||
dir, err := afs.ReadDir(i.Path)
|
dir, err := afs.ReadDir(i.Path)
|
||||||
|
@ -4,14 +4,21 @@
|
|||||||
"node": true
|
"node": true
|
||||||
},
|
},
|
||||||
"extends": [
|
"extends": [
|
||||||
"plugin:vue/essential",
|
"plugin:vue/vue3-essential",
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
|
"@vue/eslint-config-typescript",
|
||||||
"@vue/eslint-config-prettier"
|
"@vue/eslint-config-prettier"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"vue/multi-word-component-names": "off",
|
"vue/multi-word-component-names": "off",
|
||||||
"vue/no-reserved-component-names": "warn",
|
"vue/no-mutating-props": [
|
||||||
"vue/no-mutating-props": "warn"
|
"error",
|
||||||
|
{
|
||||||
|
"shallowOnly": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
// no-undef is already included in
|
||||||
|
// @vue/eslint-config-typescript
|
||||||
},
|
},
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": "latest",
|
"ecmaVersion": "latest",
|
||||||
|
@ -187,6 +187,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
5612
frontend/package-lock.json
generated
5612
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,64 +1,71 @@
|
|||||||
{
|
{
|
||||||
"name": "filebrowser-frontend",
|
"name": "filebrowser-frontend",
|
||||||
"version": "2.0.0",
|
"version": "3.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"npm": ">=7.0.0",
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
"serve": "vite serve",
|
"build": "npm run typecheck && vite build",
|
||||||
"build": "vite build",
|
|
||||||
"watch": "vite build --watch",
|
|
||||||
"clean": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitkeep' -exec rm -r {} +",
|
"clean": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitkeep' -exec rm -r {} +",
|
||||||
"lint": "eslint --ext .vue,.js src/",
|
"typecheck": "vue-tsc -p ./tsconfig.json --noEmit",
|
||||||
"lint:fix": "eslint --ext .vue,.js --fix src/",
|
"lint": "npm run typecheck && eslint --ext .vue,.ts src/",
|
||||||
"format": "prettier --write ."
|
"lint:fix": "eslint --ext .vue,.ts --fix src/",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"test": "playwright test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ace-builds": "^1.23.4",
|
"@chenfengyuan/vue-number-input": "^2.0.1",
|
||||||
"clipboard": "^2.0.11",
|
"@vueuse/core": "^10.9.0",
|
||||||
"core-js": "^3.32.0",
|
"@vueuse/integrations": "^10.9.0",
|
||||||
"css-vars-ponyfill": "^2.4.8",
|
"ace-builds": "^1.32.9",
|
||||||
"filesize": "^10.0.8",
|
"core-js": "^3.36.1",
|
||||||
"js-base64": "^3.7.5",
|
"dayjs": "^1.11.10",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"filesize": "^10.1.1",
|
||||||
"lodash.throttle": "^4.1.1",
|
"js-base64": "^3.7.7",
|
||||||
"material-icons": "^1.13.9",
|
"jwt-decode": "^4.0.0",
|
||||||
"moment": "^2.29.4",
|
"lodash-es": "^4.17.21",
|
||||||
|
"material-icons": "^1.13.12",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"noty": "^3.2.0-beta",
|
"pinia": "^2.1.7",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"qrcode.vue": "^1.7.0",
|
"qrcode.vue": "^3.4.1",
|
||||||
"tus-js-client": "^3.1.1",
|
"tus-js-client": "^4.1.0",
|
||||||
"utif": "^3.1.0",
|
"utif": "^3.1.0",
|
||||||
"vue": "^2.7.14",
|
"video.js": "^8.10.0",
|
||||||
"vue-async-computed": "^3.9.0",
|
"videojs-hotkeys": "^0.2.28",
|
||||||
"vue-i18n": "^8.28.2",
|
"videojs-mobile-ui": "^1.1.1",
|
||||||
"vue-lazyload": "^1.3.5",
|
"vue": "^3.4.21",
|
||||||
"vue-router": "^3.6.5",
|
"vue-final-modal": "^4.5.4",
|
||||||
"vue-simple-progress": "^1.1.1",
|
"vue-i18n": "^9.10.2",
|
||||||
"vuex": "^3.6.2",
|
"vue-lazyload": "^3.0.0",
|
||||||
"vuex-router-sync": "^5.0.0",
|
"vue-router": "^4.3.0",
|
||||||
"whatwg-fetch": "^3.6.17"
|
"vue-toastification": "^2.0.0-rc.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-legacy": "^4.1.1",
|
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||||
"@vitejs/plugin-vue2": "^2.2.0",
|
"@playwright/test": "^1.42.1",
|
||||||
"@vue/eslint-config-prettier": "^8.0.0",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"autoprefixer": "^10.4.14",
|
"@types/node": "^20.12.2",
|
||||||
"eslint": "^8.46.0",
|
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"@vitejs/plugin-legacy": "^5.3.2",
|
||||||
"eslint-plugin-vue": "^9.16.1",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"jsdom": "^22.1.0",
|
"@vue/eslint-config-prettier": "^9.0.0",
|
||||||
"postcss": "^8.4.31",
|
"@vue/eslint-config-typescript": "^13.0.0",
|
||||||
"prettier": "^3.0.1",
|
"autoprefixer": "^10.4.19",
|
||||||
"terser": "^5.19.2",
|
"concurrently": "^8.2.2",
|
||||||
"vite": "^4.5.2",
|
"eslint": "^8.57.0",
|
||||||
"vite-plugin-compression2": "^0.10.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"vite-plugin-rewrite-all": "^1.0.1"
|
"eslint-plugin-vue": "^9.24.0",
|
||||||
},
|
"jsdom": "^24.0.0",
|
||||||
"browserslist": [
|
"postcss": "^8.4.38",
|
||||||
"> 1%",
|
"prettier": "^3.2.5",
|
||||||
"last 2 versions",
|
"terser": "^5.30.0",
|
||||||
"not ie < 11"
|
"vite": "^5.2.7",
|
||||||
]
|
"vite-plugin-compression2": "^1.0.0",
|
||||||
|
"vue-tsc": "^2.0.7"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
80
frontend/playwright.config.ts
Normal file
80
frontend/playwright.config.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read environment variables from file.
|
||||||
|
* https://github.com/motdotla/dotenv
|
||||||
|
*/
|
||||||
|
// require('dotenv').config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
|
*/
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: "./tests",
|
||||||
|
/* Run tests in files in parallel */
|
||||||
|
fullyParallel: true,
|
||||||
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
/* Retry on CI only */
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
/* Opt out of parallel tests on CI. */
|
||||||
|
workers: process.env.CI ? 1 : undefined,
|
||||||
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
|
reporter: "html",
|
||||||
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
use: {
|
||||||
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
|
baseURL: "http://127.0.0.1:5173",
|
||||||
|
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
|
trace: "on-first-retry",
|
||||||
|
|
||||||
|
/* Set default locale to English (US) */
|
||||||
|
locale: "en-US",
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Configure projects for major browsers */
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: "chromium",
|
||||||
|
use: { ...devices["Desktop Chrome"] },
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "firefox",
|
||||||
|
use: { ...devices["Desktop Firefox"] },
|
||||||
|
},
|
||||||
|
|
||||||
|
// {
|
||||||
|
// name: "webkit",
|
||||||
|
// use: { ...devices["Desktop Safari"] },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against mobile viewports. */
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Chrome',
|
||||||
|
// use: { ...devices['Pixel 5'] },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Safari',
|
||||||
|
// use: { ...devices['iPhone 12'] },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against branded browsers. */
|
||||||
|
// {
|
||||||
|
// name: 'Microsoft Edge',
|
||||||
|
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Google Chrome',
|
||||||
|
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
|
||||||
|
/* Run your local dev server before starting the tests */
|
||||||
|
webServer: {
|
||||||
|
command: "npm run dev",
|
||||||
|
url: "http://127.0.0.1:5173",
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
},
|
||||||
|
});
|
@ -16,7 +16,7 @@
|
|||||||
[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]
|
[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]
|
||||||
</title>
|
</title>
|
||||||
|
|
||||||
<meta name="robots" content="noindex,nofollow">
|
<meta name="robots" content="noindex,nofollow" />
|
||||||
|
|
||||||
<link
|
<link
|
||||||
rel="icon"
|
rel="icon"
|
||||||
@ -181,14 +181,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
|
||||||
[{[ if .Theme -]}]
|
[{[ if .CSS -]}]
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="[{[ .StaticURL ]}]/themes/[{[ .Theme ]}].css"
|
|
||||||
/>
|
|
||||||
[{[ end ]}] [{[ if .CSS -]}]
|
|
||||||
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
|
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
|
||||||
[{[ end ]}]
|
[{[ end ]}]
|
||||||
</body>
|
</body>
|
||||||
|
@ -1,217 +0,0 @@
|
|||||||
:root {
|
|
||||||
--background: #141D24;
|
|
||||||
--surfacePrimary: #20292F;
|
|
||||||
--surfaceSecondary: #3A4147;
|
|
||||||
--divider: rgba(255, 255, 255, 0.12);
|
|
||||||
--icon: #ffffff;
|
|
||||||
--textPrimary: rgba(255, 255, 255, 0.87);
|
|
||||||
--textSecondary: rgba(255, 255, 255, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background: var(--background);
|
|
||||||
color: var(--textPrimary);
|
|
||||||
}
|
|
||||||
|
|
||||||
#loading {
|
|
||||||
background: var(--background);
|
|
||||||
}
|
|
||||||
#loading .spinner div, main .spinner div {
|
|
||||||
background: var(--icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
#login {
|
|
||||||
background: var(--background);
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
background: var(--surfacePrimary);
|
|
||||||
}
|
|
||||||
|
|
||||||
#search #input {
|
|
||||||
background: var(--surfaceSecondary);
|
|
||||||
border-color: var(--surfacePrimary);
|
|
||||||
}
|
|
||||||
#search #input input::placeholder {
|
|
||||||
color: var(--textSecondary);
|
|
||||||
}
|
|
||||||
#search.active #input {
|
|
||||||
background: var(--surfacePrimary);
|
|
||||||
}
|
|
||||||
#search.active input {
|
|
||||||
color: var(--textPrimary);
|
|
||||||
}
|
|
||||||
#search #result {
|
|
||||||
background: var(--background);
|
|
||||||
color: var(--textPrimary);
|
|
||||||
}
|
|
||||||
#search .boxes {
|
|
||||||
background: var(--surfaceSecondary);
|
|
||||||
}
|
|
||||||
#search .boxes h3 {
|
|
||||||
color: var(--textPrimary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.action {
|
|
||||||
color: var(--textPrimary) !important;
|
|
||||||
}
|
|
||||||
.action:hover {
|
|
||||||
background-color: rgba(255, 255, 255, .1);
|
|
||||||
}
|
|
||||||
.action i {
|
|
||||||
color: var(--icon) !important;
|
|
||||||
}
|
|
||||||
.action .counter {
|
|
||||||
border-color: var(--surfacePrimary);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav > div {
|
|
||||||
border-color: var(--divider);
|
|
||||||
}
|
|
||||||
|
|
||||||
.breadcrumbs {
|
|
||||||
border-color: var(--divider);
|
|
||||||
color: var(--textPrimary) !important;
|
|
||||||
}
|
|
||||||
.breadcrumbs span {
|
|
||||||
color: var(--textPrimary) !important;
|
|
||||||
}
|
|
||||||
.breadcrumbs a:hover {
|
|
||||||
background-color: rgba(255, 255, 255, .1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#listing .item {
|
|
||||||
background: var(--surfacePrimary);
|
|
||||||
color: var(--textPrimary);
|
|
||||||
border-color: var(--divider) !important;
|
|
||||||
}
|
|
||||||
#listing .item i {
|
|
||||||
color: var(--icon);
|
|
||||||
}
|
|
||||||
#listing .item .modified {
|
|
||||||
color: var(--textSecondary);
|
|
||||||
}
|
|
||||||
#listing h2,
|
|
||||||
#listing.list .header span {
|
|
||||||
color: var(--textPrimary) !important;
|
|
||||||
}
|
|
||||||
#listing.list .header span {
|
|
||||||
color: var(--textPrimary);
|
|
||||||
}
|
|
||||||
#listing.list .header i {
|
|
||||||
color: var(--icon);
|
|
||||||
}
|
|
||||||
#listing.list .item.header {
|
|
||||||
background: var(--background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
color: var(--textPrimary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
background: var(--surfacePrimary);
|
|
||||||
color: var(--textPrimary);
|
|
||||||
}
|
|
||||||
.button--flat:hover {
|
|
||||||
background: var(--surfaceSecondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard #nav ul li {
|
|
||||||
color: var(--textSecondary);
|
|
||||||
}
|
|
||||||
.dashboard #nav ul li:hover {
|
|
||||||
background: var(--surfaceSecondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card h3,
|
|
||||||
.dashboard #nav,
|
|
||||||
.dashboard p label {
|
|
||||||
color: var(--textPrimary);
|
|
||||||
}
|
|
||||||
.card#share input,
|
|
||||||
.card#share select,
|
|
||||||
.input {
|
|
||||||
background: var(--surfaceSecondary);
|
|
||||||
color: var(--textPrimary);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
||||||
}
|
|
||||||
.input:hover,
|
|
||||||
.input:focus {
|
|
||||||
border-color: rgba(255, 255, 255, 0.15);
|
|
||||||
}
|
|
||||||
.input--red {
|
|
||||||
background: #73302D;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input--green {
|
|
||||||
background: #147A41;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard #nav .wrapper,
|
|
||||||
.collapsible {
|
|
||||||
border-color: var(--divider);
|
|
||||||
}
|
|
||||||
.collapsible > label * {
|
|
||||||
color: var(--textPrimary);
|
|
||||||
}
|
|
||||||
|
|
||||||
table th {
|
|
||||||
color: var(--textSecondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-list li:hover {
|
|
||||||
background: var(--surfaceSecondary);
|
|
||||||
}
|
|
||||||
.file-list li:before {
|
|
||||||
color: var(--textSecondary);
|
|
||||||
}
|
|
||||||
.file-list li[aria-selected=true]:before {
|
|
||||||
color: var(--icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
.shell {
|
|
||||||
background: var(--surfacePrimary);
|
|
||||||
color: var(--textPrimary);
|
|
||||||
}
|
|
||||||
.shell__divider {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
.shell__divider:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.4);
|
|
||||||
}
|
|
||||||
.shell__result {
|
|
||||||
border-top: 1px solid var(--divider);
|
|
||||||
}
|
|
||||||
|
|
||||||
#editor-container {
|
|
||||||
background: var(--background);
|
|
||||||
}
|
|
||||||
|
|
||||||
#editor-container .bar {
|
|
||||||
background: var(--surfacePrimary);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 736px) {
|
|
||||||
#file-selection {
|
|
||||||
background: var(--surfaceSecondary) !important;
|
|
||||||
}
|
|
||||||
#file-selection span {
|
|
||||||
color: var(--textPrimary) !important;
|
|
||||||
}
|
|
||||||
nav {
|
|
||||||
background: var(--surfaceSecondary) !important;
|
|
||||||
}
|
|
||||||
#dropdown {
|
|
||||||
background: var(--surfaceSecondary) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.share__box {
|
|
||||||
background: var(--surfacePrimary) !important;
|
|
||||||
color: var(--textPrimary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.share__box__element {
|
|
||||||
border-top-color: var(--divider);
|
|
||||||
}
|
|
@ -4,23 +4,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
// eslint-disable-next-line no-undef
|
import { ref, onMounted, watch } from "vue";
|
||||||
// __webpack_public_path__ = window.FileBrowser.StaticURL + "/";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { setHtmlLocale } from "./i18n";
|
||||||
|
import { getMediaPreference, getTheme, setTheme } from "./utils/theme";
|
||||||
|
|
||||||
export default {
|
const { locale } = useI18n();
|
||||||
name: "app",
|
|
||||||
mounted() {
|
const userTheme = ref<UserTheme>(getTheme() || getMediaPreference());
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setTheme(userTheme.value);
|
||||||
|
setHtmlLocale(locale.value);
|
||||||
|
// this might be null during HMR
|
||||||
const loading = document.getElementById("loading");
|
const loading = document.getElementById("loading");
|
||||||
loading.classList.add("done");
|
loading?.classList.add("done");
|
||||||
|
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
loading.parentNode.removeChild(loading);
|
loading?.parentNode?.removeChild(loading);
|
||||||
}, 200);
|
}, 200);
|
||||||
},
|
});
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
// handles ltr/rtl changes
|
||||||
@import "./css/styles.css";
|
watch(locale, (newValue) => {
|
||||||
</style>
|
newValue && setHtmlLocale(newValue);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@ -1,15 +1,22 @@
|
|||||||
import { removePrefix } from "./utils";
|
import { removePrefix } from "./utils";
|
||||||
import { baseURL } from "@/utils/constants";
|
import { baseURL } from "@/utils/constants";
|
||||||
import store from "@/store";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
|
||||||
const ssl = window.location.protocol === "https:";
|
const ssl = window.location.protocol === "https:";
|
||||||
const protocol = ssl ? "wss:" : "ws:";
|
const protocol = ssl ? "wss:" : "ws:";
|
||||||
|
|
||||||
export default function command(url, command, onmessage, onclose) {
|
export default function command(
|
||||||
url = removePrefix(url);
|
url: string,
|
||||||
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${store.state.jwt}`;
|
command: string,
|
||||||
|
onmessage: WebSocket["onmessage"],
|
||||||
|
onclose: WebSocket["onclose"]
|
||||||
|
) {
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
let conn = new window.WebSocket(url);
|
url = removePrefix(url);
|
||||||
|
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${authStore.jwt}`;
|
||||||
|
|
||||||
|
const conn = new window.WebSocket(url);
|
||||||
conn.onopen = () => conn.send(command);
|
conn.onopen = () => conn.send(command);
|
||||||
conn.onmessage = onmessage;
|
conn.onmessage = onmessage;
|
||||||
conn.onclose = onclose;
|
conn.onclose = onclose;
|
@ -1,19 +1,20 @@
|
|||||||
import { createURL, fetchURL, removePrefix } from "./utils";
|
import { createURL, fetchURL, removePrefix } from "./utils";
|
||||||
import { baseURL } from "@/utils/constants";
|
import { baseURL } from "@/utils/constants";
|
||||||
import store from "@/store";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
import { upload as postTus, useTus } from "./tus";
|
import { upload as postTus, useTus } from "./tus";
|
||||||
|
|
||||||
export async function fetch(url) {
|
export async function fetch(url: string) {
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
|
|
||||||
const res = await fetchURL(`/api/resources${url}`, {});
|
const res = await fetchURL(`/api/resources${url}`, {});
|
||||||
|
|
||||||
let data = await res.json();
|
const data = (await res.json()) as Resource;
|
||||||
data.url = `/files${url}`;
|
data.url = `/files${url}`;
|
||||||
|
|
||||||
if (data.isDir) {
|
if (data.isDir) {
|
||||||
if (!data.url.endsWith("/")) data.url += "/";
|
if (!data.url.endsWith("/")) data.url += "/";
|
||||||
data.items = data.items.map((item, index) => {
|
// Perhaps change the any
|
||||||
|
data.items = data.items.map((item: any, index: any) => {
|
||||||
item.index = index;
|
item.index = index;
|
||||||
item.url = `${data.url}${encodeURIComponent(item.name)}`;
|
item.url = `${data.url}${encodeURIComponent(item.name)}`;
|
||||||
|
|
||||||
@ -28,10 +29,12 @@ export async function fetch(url) {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resourceAction(url, method, content) {
|
async function resourceAction(url: string, method: ApiMethod, content?: any) {
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
|
|
||||||
let opts = { method };
|
const opts: ApiOpts = {
|
||||||
|
method,
|
||||||
|
};
|
||||||
|
|
||||||
if (content) {
|
if (content) {
|
||||||
opts.body = content;
|
opts.body = content;
|
||||||
@ -42,15 +45,15 @@ async function resourceAction(url, method, content) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(url) {
|
export async function remove(url: string) {
|
||||||
return resourceAction(url, "DELETE");
|
return resourceAction(url, "DELETE");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function put(url, content = "") {
|
export async function put(url: string, content = "") {
|
||||||
return resourceAction(url, "PUT", content);
|
return resourceAction(url, "PUT", content);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function download(format, ...files) {
|
export function download(format: any, ...files: string[]) {
|
||||||
let url = `${baseURL}/api/raw`;
|
let url = `${baseURL}/api/raw`;
|
||||||
|
|
||||||
if (files.length === 1) {
|
if (files.length === 1) {
|
||||||
@ -58,7 +61,7 @@ export function download(format, ...files) {
|
|||||||
} else {
|
} else {
|
||||||
let arg = "";
|
let arg = "";
|
||||||
|
|
||||||
for (let file of files) {
|
for (const file of files) {
|
||||||
arg += removePrefix(file) + ",";
|
arg += removePrefix(file) + ",";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,14 +74,20 @@ export function download(format, ...files) {
|
|||||||
url += `algo=${format}&`;
|
url += `algo=${format}&`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (store.state.jwt) {
|
const authStore = useAuthStore();
|
||||||
url += `auth=${store.state.jwt}&`;
|
if (authStore.jwt) {
|
||||||
|
url += `auth=${authStore.jwt}&`;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.open(url);
|
window.open(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function post(url, content = "", overwrite = false, onupload) {
|
export async function post(
|
||||||
|
url: string,
|
||||||
|
content: ApiContent = "",
|
||||||
|
overwrite = false,
|
||||||
|
onupload: any = () => {}
|
||||||
|
) {
|
||||||
// Use the pre-existing API if:
|
// Use the pre-existing API if:
|
||||||
const useResourcesApi =
|
const useResourcesApi =
|
||||||
// a folder is being created
|
// a folder is being created
|
||||||
@ -93,10 +102,15 @@ export async function post(url, content = "", overwrite = false, onupload) {
|
|||||||
: postTus(url, content, overwrite, onupload);
|
: postTus(url, content, overwrite, onupload);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function postResources(url, content = "", overwrite = false, onupload) {
|
async function postResources(
|
||||||
|
url: string,
|
||||||
|
content: ApiContent = "",
|
||||||
|
overwrite = false,
|
||||||
|
onupload: any
|
||||||
|
) {
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
|
|
||||||
let bufferContent;
|
let bufferContent: ArrayBuffer;
|
||||||
if (
|
if (
|
||||||
content instanceof Blob &&
|
content instanceof Blob &&
|
||||||
!["http:", "https:"].includes(window.location.protocol)
|
!["http:", "https:"].includes(window.location.protocol)
|
||||||
@ -104,14 +118,15 @@ async function postResources(url, content = "", overwrite = false, onupload) {
|
|||||||
bufferContent = await new Response(content).arrayBuffer();
|
bufferContent = await new Response(content).arrayBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let request = new XMLHttpRequest();
|
const request = new XMLHttpRequest();
|
||||||
request.open(
|
request.open(
|
||||||
"POST",
|
"POST",
|
||||||
`${baseURL}/api/resources${url}?override=${overwrite}`,
|
`${baseURL}/api/resources${url}?override=${overwrite}`,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
request.setRequestHeader("X-Auth", store.state.jwt);
|
request.setRequestHeader("X-Auth", authStore.jwt);
|
||||||
|
|
||||||
if (typeof onupload === "function") {
|
if (typeof onupload === "function") {
|
||||||
request.upload.onprogress = onupload;
|
request.upload.onprogress = onupload;
|
||||||
@ -135,12 +150,17 @@ async function postResources(url, content = "", overwrite = false, onupload) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveCopy(items, copy = false, overwrite = false, rename = false) {
|
function moveCopy(
|
||||||
let promises = [];
|
items: any[],
|
||||||
|
copy = false,
|
||||||
|
overwrite = false,
|
||||||
|
rename = false
|
||||||
|
) {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
for (let item of items) {
|
for (const item of items) {
|
||||||
const from = item.from;
|
const from = item.from;
|
||||||
const to = encodeURIComponent(removePrefix(item.to));
|
const to = encodeURIComponent(removePrefix(item.to ?? ""));
|
||||||
const url = `${from}?action=${
|
const url = `${from}?action=${
|
||||||
copy ? "copy" : "rename"
|
copy ? "copy" : "rename"
|
||||||
}&destination=${to}&override=${overwrite}&rename=${rename}`;
|
}&destination=${to}&override=${overwrite}&rename=${rename}`;
|
||||||
@ -150,20 +170,20 @@ function moveCopy(items, copy = false, overwrite = false, rename = false) {
|
|||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function move(items, overwrite = false, rename = false) {
|
export function move(items: any[], overwrite = false, rename = false) {
|
||||||
return moveCopy(items, false, overwrite, rename);
|
return moveCopy(items, false, overwrite, rename);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function copy(items, overwrite = false, rename = false) {
|
export function copy(items: any[], overwrite = false, rename = false) {
|
||||||
return moveCopy(items, true, overwrite, rename);
|
return moveCopy(items, true, overwrite, rename);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checksum(url, algo) {
|
export async function checksum(url: string, algo: ChecksumAlg) {
|
||||||
const data = await resourceAction(`${url}?checksum=${algo}`, "GET");
|
const data = await resourceAction(`${url}?checksum=${algo}`, "GET");
|
||||||
return (await data.json()).checksums[algo];
|
return (await data.json()).checksums[algo];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDownloadURL(file, inline) {
|
export function getDownloadURL(file: ResourceItem, inline: any) {
|
||||||
const params = {
|
const params = {
|
||||||
...(inline && { inline: "true" }),
|
...(inline && { inline: "true" }),
|
||||||
};
|
};
|
||||||
@ -171,7 +191,7 @@ export function getDownloadURL(file, inline) {
|
|||||||
return createURL("api/raw" + file.path, params);
|
return createURL("api/raw" + file.path, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPreviewURL(file, size) {
|
export function getPreviewURL(file: ResourceItem, size: string) {
|
||||||
const params = {
|
const params = {
|
||||||
inline: "true",
|
inline: "true",
|
||||||
key: Date.parse(file.modified),
|
key: Date.parse(file.modified),
|
||||||
@ -180,20 +200,15 @@ export function getPreviewURL(file, size) {
|
|||||||
return createURL("api/preview/" + size + file.path, params);
|
return createURL("api/preview/" + size + file.path, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSubtitlesURL(file) {
|
export function getSubtitlesURL(file: ResourceItem) {
|
||||||
const params = {
|
const params = {
|
||||||
inline: "true",
|
inline: "true",
|
||||||
};
|
};
|
||||||
|
|
||||||
const subtitles = [];
|
return file.subtitles?.map((d) => createURL("api/subtitle" + d, params));
|
||||||
for (const sub of file.subtitles) {
|
|
||||||
subtitles.push(createURL("api/raw" + sub, params));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return subtitles;
|
export async function usage(url: string) {
|
||||||
}
|
|
||||||
|
|
||||||
export async function usage(url) {
|
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
|
|
||||||
const res = await fetchURL(`/api/usage${url}`, {});
|
const res = await fetchURL(`/api/usage${url}`, {});
|
@ -1,7 +1,7 @@
|
|||||||
import { fetchURL, removePrefix, createURL } from "./utils";
|
import { fetchURL, removePrefix, createURL } from "./utils";
|
||||||
import { baseURL } from "@/utils/constants";
|
import { baseURL } from "@/utils/constants";
|
||||||
|
|
||||||
export async function fetch(url, password = "") {
|
export async function fetch(url: string, password: string = "") {
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
|
|
||||||
const res = await fetchURL(
|
const res = await fetchURL(
|
||||||
@ -12,12 +12,12 @@ export async function fetch(url, password = "") {
|
|||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
let data = await res.json();
|
const data = (await res.json()) as Resource;
|
||||||
data.url = `/share${url}`;
|
data.url = `/share${url}`;
|
||||||
|
|
||||||
if (data.isDir) {
|
if (data.isDir) {
|
||||||
if (!data.url.endsWith("/")) data.url += "/";
|
if (!data.url.endsWith("/")) data.url += "/";
|
||||||
data.items = data.items.map((item, index) => {
|
data.items = data.items.map((item: any, index: any) => {
|
||||||
item.index = index;
|
item.index = index;
|
||||||
item.url = `${data.url}${encodeURIComponent(item.name)}`;
|
item.url = `${data.url}${encodeURIComponent(item.name)}`;
|
||||||
|
|
||||||
@ -32,7 +32,12 @@ export async function fetch(url, password = "") {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function download(format, hash, token, ...files) {
|
export function download(
|
||||||
|
format: DownloadFormat,
|
||||||
|
hash: string,
|
||||||
|
token: string,
|
||||||
|
...files: string[]
|
||||||
|
) {
|
||||||
let url = `${baseURL}/api/public/dl/${hash}`;
|
let url = `${baseURL}/api/public/dl/${hash}`;
|
||||||
|
|
||||||
if (files.length === 1) {
|
if (files.length === 1) {
|
||||||
@ -40,7 +45,7 @@ export function download(format, hash, token, ...files) {
|
|||||||
} else {
|
} else {
|
||||||
let arg = "";
|
let arg = "";
|
||||||
|
|
||||||
for (let file of files) {
|
for (const file of files) {
|
||||||
arg += encodeURIComponent(file) + ",";
|
arg += encodeURIComponent(file) + ",";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,11 +65,11 @@ export function download(format, hash, token, ...files) {
|
|||||||
window.open(url);
|
window.open(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDownloadURL(share, inline = false) {
|
export function getDownloadURL(res: Resource, inline = false) {
|
||||||
const params = {
|
const params = {
|
||||||
...(inline && { inline: "true" }),
|
...(inline && { inline: "true" }),
|
||||||
...(share.token && { token: share.token }),
|
...(res.token && { token: res.token }),
|
||||||
};
|
};
|
||||||
|
|
||||||
return createURL("api/public/dl/" + share.hash + share.path, params, false);
|
return createURL("api/public/dl/" + res.hash + res.path, params, false);
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { fetchURL, removePrefix } from "./utils";
|
import { fetchURL, removePrefix } from "./utils";
|
||||||
import url from "../utils/url";
|
import url from "../utils/url";
|
||||||
|
|
||||||
export default async function search(base, query) {
|
export default async function search(base: string, query: string) {
|
||||||
base = removePrefix(base);
|
base = removePrefix(base);
|
||||||
query = encodeURIComponent(query);
|
query = encodeURIComponent(query);
|
||||||
|
|
||||||
@ -9,11 +9,11 @@ export default async function search(base, query) {
|
|||||||
base += "/";
|
base += "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = await fetchURL(`/api/search${base}?query=${query}`, {});
|
const res = await fetchURL(`/api/search${base}?query=${query}`, {});
|
||||||
|
|
||||||
let data = await res.json();
|
let data = await res.json();
|
||||||
|
|
||||||
data = data.map((item) => {
|
data = data.map((item: UploadItem) => {
|
||||||
item.url = `/files${base}` + url.encodePath(item.path);
|
item.url = `/files${base}` + url.encodePath(item.path);
|
||||||
|
|
||||||
if (item.dir) {
|
if (item.dir) {
|
@ -1,10 +1,10 @@
|
|||||||
import { fetchURL, fetchJSON } from "./utils";
|
import { fetchURL, fetchJSON } from "./utils";
|
||||||
|
|
||||||
export function get() {
|
export function get() {
|
||||||
return fetchJSON(`/api/settings`, {});
|
return fetchJSON<ISettings>(`/api/settings`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function update(settings) {
|
export async function update(settings: ISettings) {
|
||||||
await fetchURL(`/api/settings`, {
|
await fetchURL(`/api/settings`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify(settings),
|
body: JSON.stringify(settings),
|
@ -1,21 +1,26 @@
|
|||||||
import { fetchURL, fetchJSON, removePrefix, createURL } from "./utils";
|
import { fetchURL, fetchJSON, removePrefix, createURL } from "./utils";
|
||||||
|
|
||||||
export async function list() {
|
export async function list() {
|
||||||
return fetchJSON("/api/shares");
|
return fetchJSON<Share[]>("/api/shares");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function get(url) {
|
export async function get(url: string) {
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
return fetchJSON(`/api/share${url}`);
|
return fetchJSON<Share>(`/api/share${url}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(hash) {
|
export async function remove(hash: string) {
|
||||||
await fetchURL(`/api/share/${hash}`, {
|
await fetchURL(`/api/share/${hash}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function create(url, password = "", expires = "", unit = "hours") {
|
export async function create(
|
||||||
|
url: string,
|
||||||
|
password = "",
|
||||||
|
expires = "",
|
||||||
|
unit = "hours"
|
||||||
|
) {
|
||||||
url = removePrefix(url);
|
url = removePrefix(url);
|
||||||
url = `/api/share${url}`;
|
url = `/api/share${url}`;
|
||||||
if (expires !== "") {
|
if (expires !== "") {
|
||||||
@ -23,7 +28,11 @@ export async function create(url, password = "", expires = "", unit = "hours") {
|
|||||||
}
|
}
|
||||||
let body = "{}";
|
let body = "{}";
|
||||||
if (password != "" || expires !== "" || unit !== "hours") {
|
if (password != "" || expires !== "" || unit !== "hours") {
|
||||||
body = JSON.stringify({ password: password, expires: expires, unit: unit });
|
body = JSON.stringify({
|
||||||
|
password: password,
|
||||||
|
expires: expires.toString(), // backend expects string not number
|
||||||
|
unit: unit,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return fetchJSON(url, {
|
return fetchJSON(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -31,6 +40,6 @@ export async function create(url, password = "", expires = "", unit = "hours") {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getShareURL(share) {
|
export function getShareURL(share: Share) {
|
||||||
return createURL("share/" + share.hash, {}, false);
|
return createURL("share/" + share.hash, {}, false);
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import * as tus from "tus-js-client";
|
import * as tus from "tus-js-client";
|
||||||
import { baseURL, tusEndpoint, tusSettings } from "@/utils/constants";
|
import { baseURL, tusEndpoint, tusSettings } from "@/utils/constants";
|
||||||
import store from "@/store";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
import { useUploadStore } from "@/stores/upload";
|
||||||
import { removePrefix } from "@/api/utils";
|
import { removePrefix } from "@/api/utils";
|
||||||
import { fetchURL } from "./utils";
|
import { fetchURL } from "./utils";
|
||||||
|
|
||||||
@ -11,13 +12,13 @@ const ALPHA = 0.2;
|
|||||||
const ONE_MINUS_ALPHA = 1 - ALPHA;
|
const ONE_MINUS_ALPHA = 1 - ALPHA;
|
||||||
const RECENT_SPEEDS_LIMIT = 5;
|
const RECENT_SPEEDS_LIMIT = 5;
|
||||||
const MB_DIVISOR = 1024 * 1024;
|
const MB_DIVISOR = 1024 * 1024;
|
||||||
const CURRENT_UPLOAD_LIST = {};
|
const CURRENT_UPLOAD_LIST: CurrentUploadList = {};
|
||||||
|
|
||||||
export async function upload(
|
export async function upload(
|
||||||
filePath,
|
filePath: string,
|
||||||
content = "",
|
content: ApiContent = "",
|
||||||
overwrite = false,
|
overwrite = false,
|
||||||
onupload
|
onupload: any
|
||||||
) {
|
) {
|
||||||
if (!tusSettings) {
|
if (!tusSettings) {
|
||||||
// Shouldn't happen as we check for tus support before calling this function
|
// Shouldn't happen as we check for tus support before calling this function
|
||||||
@ -25,29 +26,35 @@ export async function upload(
|
|||||||
}
|
}
|
||||||
|
|
||||||
filePath = removePrefix(filePath);
|
filePath = removePrefix(filePath);
|
||||||
let resourcePath = `${tusEndpoint}${filePath}?override=${overwrite}`;
|
const resourcePath = `${tusEndpoint}${filePath}?override=${overwrite}`;
|
||||||
|
|
||||||
await createUpload(resourcePath);
|
await createUpload(resourcePath);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
const authStore = useAuthStore();
|
||||||
let upload = new tus.Upload(content, {
|
|
||||||
|
// Exit early because of typescript, tus content can't be a string
|
||||||
|
if (content === "") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return new Promise<void | string>((resolve, reject) => {
|
||||||
|
const upload = new tus.Upload(content, {
|
||||||
uploadUrl: `${baseURL}${resourcePath}`,
|
uploadUrl: `${baseURL}${resourcePath}`,
|
||||||
chunkSize: tusSettings.chunkSize,
|
chunkSize: tusSettings.chunkSize,
|
||||||
retryDelays: computeRetryDelays(tusSettings),
|
retryDelays: computeRetryDelays(tusSettings),
|
||||||
parallelUploads: 1,
|
parallelUploads: 1,
|
||||||
storeFingerprintForResuming: false,
|
storeFingerprintForResuming: false,
|
||||||
headers: {
|
headers: {
|
||||||
"X-Auth": store.state.jwt,
|
"X-Auth": authStore.jwt,
|
||||||
},
|
},
|
||||||
onError: function (error) {
|
onError: function (error) {
|
||||||
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
||||||
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
||||||
}
|
}
|
||||||
delete CURRENT_UPLOAD_LIST[filePath];
|
delete CURRENT_UPLOAD_LIST[filePath];
|
||||||
reject("Upload failed: " + error);
|
reject(new Error(`Upload failed: ${error.message}`));
|
||||||
},
|
},
|
||||||
onProgress: function (bytesUploaded) {
|
onProgress: function (bytesUploaded) {
|
||||||
let fileData = CURRENT_UPLOAD_LIST[filePath];
|
const fileData = CURRENT_UPLOAD_LIST[filePath];
|
||||||
fileData.currentBytesUploaded = bytesUploaded;
|
fileData.currentBytesUploaded = bytesUploaded;
|
||||||
|
|
||||||
if (!fileData.hasStarted) {
|
if (!fileData.hasStarted) {
|
||||||
@ -79,14 +86,14 @@ export async function upload(
|
|||||||
lastProgressTimestamp: null,
|
lastProgressTimestamp: null,
|
||||||
sumOfRecentSpeeds: 0,
|
sumOfRecentSpeeds: 0,
|
||||||
hasStarted: false,
|
hasStarted: false,
|
||||||
interval: null,
|
interval: undefined,
|
||||||
};
|
};
|
||||||
upload.start();
|
upload.start();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createUpload(resourcePath) {
|
async function createUpload(resourcePath: string) {
|
||||||
let headResp = await fetchURL(resourcePath, {
|
const headResp = await fetchURL(resourcePath, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
});
|
||||||
if (headResp.status !== 201) {
|
if (headResp.status !== 201) {
|
||||||
@ -96,10 +103,10 @@ async function createUpload(resourcePath) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeRetryDelays(tusSettings) {
|
function computeRetryDelays(tusSettings: TusSettings): number[] | undefined {
|
||||||
if (!tusSettings.retryCount || tusSettings.retryCount < 1) {
|
if (!tusSettings.retryCount || tusSettings.retryCount < 1) {
|
||||||
// Disable retries altogether
|
// Disable retries altogether
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
// The tus client expects our retries as an array with computed backoffs
|
// The tus client expects our retries as an array with computed backoffs
|
||||||
// E.g.: [0, 3000, 5000, 10000, 20000]
|
// E.g.: [0, 3000, 5000, 10000, 20000]
|
||||||
@ -115,7 +122,7 @@ function computeRetryDelays(tusSettings) {
|
|||||||
return retryDelays;
|
return retryDelays;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function useTus(content) {
|
export async function useTus(content: ApiContent) {
|
||||||
return isTusSupported() && content instanceof Blob;
|
return isTusSupported() && content instanceof Blob;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,25 +130,34 @@ function isTusSupported() {
|
|||||||
return tus.isSupported === true;
|
return tus.isSupported === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeETA(state) {
|
function computeETA(state: ETAState, speed?: number) {
|
||||||
if (state.speedMbyte === 0) {
|
if (state.speedMbyte === 0) {
|
||||||
return Infinity;
|
return Infinity;
|
||||||
}
|
}
|
||||||
const totalSize = state.sizes.reduce((acc, size) => acc + size, 0);
|
const totalSize = state.sizes.reduce(
|
||||||
|
(acc: number, size: number) => acc + size,
|
||||||
|
0
|
||||||
|
);
|
||||||
const uploadedSize = state.progress.reduce(
|
const uploadedSize = state.progress.reduce(
|
||||||
(acc, progress) => acc + progress,
|
(acc: number, progress: Progress) => {
|
||||||
|
if (typeof progress === "number") {
|
||||||
|
return acc + progress;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
const remainingSize = totalSize - uploadedSize;
|
const remainingSize = totalSize - uploadedSize;
|
||||||
const speedBytesPerSecond = state.speedMbyte * 1024 * 1024;
|
const speedBytesPerSecond = (speed ?? state.speedMbyte) * 1024 * 1024;
|
||||||
return remainingSize / speedBytesPerSecond;
|
return remainingSize / speedBytesPerSecond;
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeGlobalSpeedAndETA() {
|
function computeGlobalSpeedAndETA() {
|
||||||
|
const uploadStore = useUploadStore();
|
||||||
let totalSpeed = 0;
|
let totalSpeed = 0;
|
||||||
let totalCount = 0;
|
let totalCount = 0;
|
||||||
|
|
||||||
for (let filePath in CURRENT_UPLOAD_LIST) {
|
for (const filePath in CURRENT_UPLOAD_LIST) {
|
||||||
totalSpeed += CURRENT_UPLOAD_LIST[filePath].currentAverageSpeed;
|
totalSpeed += CURRENT_UPLOAD_LIST[filePath].currentAverageSpeed;
|
||||||
totalCount++;
|
totalCount++;
|
||||||
}
|
}
|
||||||
@ -149,41 +165,43 @@ function computeGlobalSpeedAndETA() {
|
|||||||
if (totalCount === 0) return { speed: 0, eta: Infinity };
|
if (totalCount === 0) return { speed: 0, eta: Infinity };
|
||||||
|
|
||||||
const averageSpeed = totalSpeed / totalCount;
|
const averageSpeed = totalSpeed / totalCount;
|
||||||
const averageETA = computeETA(store.state.upload, averageSpeed);
|
const averageETA = computeETA(uploadStore, averageSpeed);
|
||||||
|
|
||||||
return { speed: averageSpeed, eta: averageETA };
|
return { speed: averageSpeed, eta: averageETA };
|
||||||
}
|
}
|
||||||
|
|
||||||
function calcProgress(filePath) {
|
function calcProgress(filePath: string) {
|
||||||
let fileData = CURRENT_UPLOAD_LIST[filePath];
|
const uploadStore = useUploadStore();
|
||||||
|
const fileData = CURRENT_UPLOAD_LIST[filePath];
|
||||||
|
|
||||||
let elapsedTime = (Date.now() - fileData.lastProgressTimestamp) / 1000;
|
const elapsedTime =
|
||||||
let bytesSinceLastUpdate =
|
(Date.now() - (fileData.lastProgressTimestamp ?? 0)) / 1000;
|
||||||
|
const bytesSinceLastUpdate =
|
||||||
fileData.currentBytesUploaded - fileData.initialBytesUploaded;
|
fileData.currentBytesUploaded - fileData.initialBytesUploaded;
|
||||||
let currentSpeed = bytesSinceLastUpdate / MB_DIVISOR / elapsedTime;
|
const currentSpeed = bytesSinceLastUpdate / MB_DIVISOR / elapsedTime;
|
||||||
|
|
||||||
if (fileData.recentSpeeds.length >= RECENT_SPEEDS_LIMIT) {
|
if (fileData.recentSpeeds.length >= RECENT_SPEEDS_LIMIT) {
|
||||||
fileData.sumOfRecentSpeeds -= fileData.recentSpeeds.shift();
|
fileData.sumOfRecentSpeeds -= fileData.recentSpeeds.shift() ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
fileData.recentSpeeds.push(currentSpeed);
|
fileData.recentSpeeds.push(currentSpeed);
|
||||||
fileData.sumOfRecentSpeeds += currentSpeed;
|
fileData.sumOfRecentSpeeds += currentSpeed;
|
||||||
|
|
||||||
let avgRecentSpeed =
|
const avgRecentSpeed =
|
||||||
fileData.sumOfRecentSpeeds / fileData.recentSpeeds.length;
|
fileData.sumOfRecentSpeeds / fileData.recentSpeeds.length;
|
||||||
fileData.currentAverageSpeed =
|
fileData.currentAverageSpeed =
|
||||||
ALPHA * avgRecentSpeed + ONE_MINUS_ALPHA * fileData.currentAverageSpeed;
|
ALPHA * avgRecentSpeed + ONE_MINUS_ALPHA * fileData.currentAverageSpeed;
|
||||||
|
|
||||||
const { speed, eta } = computeGlobalSpeedAndETA();
|
const { speed, eta } = computeGlobalSpeedAndETA();
|
||||||
store.commit("setUploadSpeed", speed);
|
uploadStore.setUploadSpeed(speed);
|
||||||
store.commit("setETA", eta);
|
uploadStore.setETA(eta);
|
||||||
|
|
||||||
fileData.initialBytesUploaded = fileData.currentBytesUploaded;
|
fileData.initialBytesUploaded = fileData.currentBytesUploaded;
|
||||||
fileData.lastProgressTimestamp = Date.now();
|
fileData.lastProgressTimestamp = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function abortAllUploads() {
|
export function abortAllUploads() {
|
||||||
for (let filePath in CURRENT_UPLOAD_LIST) {
|
for (const filePath in CURRENT_UPLOAD_LIST) {
|
||||||
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
if (CURRENT_UPLOAD_LIST[filePath].interval) {
|
||||||
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
clearInterval(CURRENT_UPLOAD_LIST[filePath].interval);
|
||||||
}
|
}
|
@ -1,14 +1,14 @@
|
|||||||
import { fetchURL, fetchJSON } from "./utils";
|
import { fetchURL, fetchJSON, StatusError } from "./utils";
|
||||||
|
|
||||||
export async function getAll() {
|
export async function getAll() {
|
||||||
return fetchJSON(`/api/users`, {});
|
return fetchJSON<IUser[]>(`/api/users`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function get(id) {
|
export async function get(id: number) {
|
||||||
return fetchJSON(`/api/users/${id}`, {});
|
return fetchJSON<IUser>(`/api/users/${id}`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function create(user) {
|
export async function create(user: IUser) {
|
||||||
const res = await fetchURL(`/api/users`, {
|
const res = await fetchURL(`/api/users`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@ -21,9 +21,11 @@ export async function create(user) {
|
|||||||
if (res.status === 201) {
|
if (res.status === 201) {
|
||||||
return res.headers.get("Location");
|
return res.headers.get("Location");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new StatusError(await res.text(), res.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function update(user, which = ["all"]) {
|
export async function update(user: IUser, which = ["all"]) {
|
||||||
await fetchURL(`/api/users/${user.id}`, {
|
await fetchURL(`/api/users/${user.id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@ -34,7 +36,7 @@ export async function update(user, which = ["all"]) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(id) {
|
export async function remove(id: number) {
|
||||||
await fetchURL(`/api/users/${id}`, {
|
await fetchURL(`/api/users/${id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
});
|
});
|
@ -1,80 +0,0 @@
|
|||||||
import store from "@/store";
|
|
||||||
import { renew, logout } from "@/utils/auth";
|
|
||||||
import { baseURL } from "@/utils/constants";
|
|
||||||
import { encodePath } from "@/utils/url";
|
|
||||||
|
|
||||||
export async function fetchURL(url, opts, auth = true) {
|
|
||||||
opts = opts || {};
|
|
||||||
opts.headers = opts.headers || {};
|
|
||||||
|
|
||||||
let { headers, ...rest } = opts;
|
|
||||||
let res;
|
|
||||||
try {
|
|
||||||
res = await fetch(`${baseURL}${url}`, {
|
|
||||||
headers: {
|
|
||||||
"X-Auth": store.state.jwt,
|
|
||||||
...headers,
|
|
||||||
},
|
|
||||||
...rest,
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
const error = new Error("000 No connection");
|
|
||||||
error.status = 0;
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auth && res.headers.get("X-Renew-Token") === "true") {
|
|
||||||
await renew(store.state.jwt);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.status < 200 || res.status > 299) {
|
|
||||||
const error = new Error(await res.text());
|
|
||||||
error.status = res.status;
|
|
||||||
|
|
||||||
if (auth && res.status == 401) {
|
|
||||||
logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchJSON(url, opts) {
|
|
||||||
const res = await fetchURL(url, opts);
|
|
||||||
|
|
||||||
if (res.status === 200) {
|
|
||||||
return res.json();
|
|
||||||
} else {
|
|
||||||
throw new Error(res.status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removePrefix(url) {
|
|
||||||
url = url.split("/").splice(2).join("/");
|
|
||||||
|
|
||||||
if (url === "") url = "/";
|
|
||||||
if (url[0] !== "/") url = "/" + url;
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createURL(endpoint, params = {}, auth = true) {
|
|
||||||
let prefix = baseURL;
|
|
||||||
if (!prefix.endsWith("/")) {
|
|
||||||
prefix = prefix + "/";
|
|
||||||
}
|
|
||||||
const url = new URL(prefix + encodePath(endpoint), origin);
|
|
||||||
|
|
||||||
const searchParams = {
|
|
||||||
...(auth && { auth: store.state.jwt }),
|
|
||||||
...params,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const key in searchParams) {
|
|
||||||
url.searchParams.set(key, searchParams[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return url.toString();
|
|
||||||
}
|
|
98
frontend/src/api/utils.ts
Normal file
98
frontend/src/api/utils.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
import { renew, logout } from "@/utils/auth";
|
||||||
|
import { baseURL } from "@/utils/constants";
|
||||||
|
import { encodePath } from "@/utils/url";
|
||||||
|
|
||||||
|
export class StatusError extends Error {
|
||||||
|
constructor(
|
||||||
|
message: any,
|
||||||
|
public status?: number
|
||||||
|
) {
|
||||||
|
super(message);
|
||||||
|
this.name = "StatusError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchURL(
|
||||||
|
url: string,
|
||||||
|
opts: ApiOpts,
|
||||||
|
auth = true
|
||||||
|
): Promise<Response> {
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
opts = opts || {};
|
||||||
|
opts.headers = opts.headers || {};
|
||||||
|
|
||||||
|
const { headers, ...rest } = opts;
|
||||||
|
let res;
|
||||||
|
try {
|
||||||
|
res = await fetch(`${baseURL}${url}`, {
|
||||||
|
headers: {
|
||||||
|
"X-Auth": authStore.jwt,
|
||||||
|
...headers,
|
||||||
|
},
|
||||||
|
...rest,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
throw new StatusError("000 No connection", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auth && res.headers.get("X-Renew-Token") === "true") {
|
||||||
|
await renew(authStore.jwt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.status < 200 || res.status > 299) {
|
||||||
|
const body = await res.text();
|
||||||
|
const error = new StatusError(
|
||||||
|
body || `${res.status} ${res.statusText}`,
|
||||||
|
res.status
|
||||||
|
);
|
||||||
|
|
||||||
|
if (auth && res.status == 401) {
|
||||||
|
logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchJSON<T>(url: string, opts?: any): Promise<T> {
|
||||||
|
const res = await fetchURL(url, opts);
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
return res.json() as Promise<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new StatusError(`${res.status} ${res.statusText}`, res.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removePrefix(url: string): string {
|
||||||
|
url = url.split("/").splice(2).join("/");
|
||||||
|
|
||||||
|
if (url === "") url = "/";
|
||||||
|
if (url[0] !== "/") url = "/" + url;
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createURL(endpoint: string, params = {}, auth = true): string {
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
let prefix = baseURL;
|
||||||
|
if (!prefix.endsWith("/")) {
|
||||||
|
prefix = prefix + "/";
|
||||||
|
}
|
||||||
|
const url = new URL(prefix + encodePath(endpoint), origin);
|
||||||
|
|
||||||
|
const searchParams: SearchParams = {
|
||||||
|
...(auth && { auth: authStore.jwt }),
|
||||||
|
...params,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const key in searchParams) {
|
||||||
|
url.searchParams.set(key, searchParams[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.toString();
|
||||||
|
}
|
@ -3,8 +3,8 @@
|
|||||||
<component
|
<component
|
||||||
:is="element"
|
:is="element"
|
||||||
:to="base || ''"
|
:to="base || ''"
|
||||||
:aria-label="$t('files.home')"
|
:aria-label="t('files.home')"
|
||||||
:title="$t('files.home')"
|
:title="t('files.home')"
|
||||||
>
|
>
|
||||||
<i class="material-icons">home</i>
|
<i class="material-icons">home</i>
|
||||||
</component>
|
</component>
|
||||||
@ -18,13 +18,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
export default {
|
import { computed } from "vue";
|
||||||
name: "breadcrumbs",
|
import { useI18n } from "vue-i18n";
|
||||||
props: ["base", "noLink"],
|
import { useRoute } from "vue-router";
|
||||||
computed: {
|
|
||||||
items() {
|
const { t } = useI18n();
|
||||||
const relativePath = this.$route.path.replace(this.base, "");
|
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
base: string;
|
||||||
|
noLink?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const items = computed(() => {
|
||||||
|
const relativePath = route.path.replace(props.base, "");
|
||||||
let parts = relativePath.split("/");
|
let parts = relativePath.split("/");
|
||||||
|
|
||||||
if (parts[0] === "") {
|
if (parts[0] === "") {
|
||||||
@ -35,13 +44,13 @@ export default {
|
|||||||
parts.pop();
|
parts.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
let breadcrumbs = [];
|
let breadcrumbs: BreadCrumb[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < parts.length; i++) {
|
for (let i = 0; i < parts.length; i++) {
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
breadcrumbs.push({
|
breadcrumbs.push({
|
||||||
name: decodeURIComponent(parts[i]),
|
name: decodeURIComponent(parts[i]),
|
||||||
url: this.base + "/" + parts[i] + "/",
|
url: props.base + "/" + parts[i] + "/",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
breadcrumbs.push({
|
breadcrumbs.push({
|
||||||
@ -60,16 +69,15 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return breadcrumbs;
|
return breadcrumbs;
|
||||||
},
|
});
|
||||||
element() {
|
|
||||||
if (this.noLink !== undefined) {
|
const element = computed(() => {
|
||||||
|
if (props.noLink) {
|
||||||
return "span";
|
return "span";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "router-link";
|
return "router-link";
|
||||||
},
|
});
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style></style>
|
<style></style>
|
||||||
|
45
frontend/src/components/CustomToast.vue
Normal file
45
frontend/src/components/CustomToast.vue
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<div class="t-container">
|
||||||
|
<span>{{ message }}</span>
|
||||||
|
<button v-if="isReport" class="action" @click.stop="clicked">
|
||||||
|
{{ reportText }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
message: string;
|
||||||
|
reportText?: string;
|
||||||
|
isReport?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const clicked = () => {
|
||||||
|
window.open("https://github.com/filebrowser/filebrowser/issues/new/choose");
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.t-container {
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.action {
|
||||||
|
text-align: center;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 10px;
|
||||||
|
margin-left: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
border: thin solid currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[dir="rtl"] .action {
|
||||||
|
margin-left: initial;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
224
frontend/src/components/ProgressBar.vue
Normal file
224
frontend/src/components/ProgressBar.vue
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
<!-- This component taken directly from vue-simple-progress
|
||||||
|
since it didnt support Vue 3 but the component itself does
|
||||||
|
https://raw.githubusercontent.com/dzwillia/vue-simple-progress/master/src/components/Progress.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="vue-simple-progress-text"
|
||||||
|
:style="text_style"
|
||||||
|
v-if="text.length > 0 && textPosition == 'top'"
|
||||||
|
>
|
||||||
|
{{ text }}
|
||||||
|
</div>
|
||||||
|
<div class="vue-simple-progress" :style="progress_style">
|
||||||
|
<div
|
||||||
|
class="vue-simple-progress-text"
|
||||||
|
:style="text_style"
|
||||||
|
v-if="text.length > 0 && textPosition == 'middle'"
|
||||||
|
>
|
||||||
|
{{ text }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="position: relative; left: -9999px"
|
||||||
|
:style="text_style"
|
||||||
|
v-if="text.length > 0 && textPosition == 'inside'"
|
||||||
|
>
|
||||||
|
{{ text }}
|
||||||
|
</div>
|
||||||
|
<div class="vue-simple-progress-bar" :style="bar_style">
|
||||||
|
<div
|
||||||
|
:style="text_style"
|
||||||
|
v-if="text.length > 0 && textPosition == 'inside'"
|
||||||
|
>
|
||||||
|
{{ text }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="vue-simple-progress-text"
|
||||||
|
:style="text_style"
|
||||||
|
v-if="text.length > 0 && textPosition == 'bottom'"
|
||||||
|
>
|
||||||
|
{{ text }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// We're leaving this untouched as you can read in the beginning
|
||||||
|
var isNumber = function (n) {
|
||||||
|
return !isNaN(parseFloat(n)) && isFinite(n);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "progress-bar",
|
||||||
|
props: {
|
||||||
|
val: {
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
default: 100,
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
// either a number (pixel width/height) or 'tiny', 'small',
|
||||||
|
// 'medium', 'large', 'huge', 'massive' for common sizes
|
||||||
|
default: 3,
|
||||||
|
},
|
||||||
|
"bg-color": {
|
||||||
|
type: String,
|
||||||
|
default: "#eee",
|
||||||
|
},
|
||||||
|
"bar-color": {
|
||||||
|
type: String,
|
||||||
|
default: "#2196f3", // match .blue color to Material Design's 'Blue 500' color
|
||||||
|
},
|
||||||
|
"bar-transition": {
|
||||||
|
type: String,
|
||||||
|
default: "all 0.5s ease",
|
||||||
|
},
|
||||||
|
"bar-border-radius": {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
spacing: {
|
||||||
|
type: Number,
|
||||||
|
default: 4,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
"text-align": {
|
||||||
|
type: String,
|
||||||
|
default: "center", // 'left', 'right'
|
||||||
|
},
|
||||||
|
"text-position": {
|
||||||
|
type: String,
|
||||||
|
default: "bottom", // 'bottom', 'top', 'middle', 'inside'
|
||||||
|
},
|
||||||
|
"font-size": {
|
||||||
|
type: Number,
|
||||||
|
default: 13,
|
||||||
|
},
|
||||||
|
"text-fg-color": {
|
||||||
|
type: String,
|
||||||
|
default: "#222",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
pct() {
|
||||||
|
var pct = (this.val / this.max) * 100;
|
||||||
|
pct = pct.toFixed(2);
|
||||||
|
return Math.min(pct, this.max);
|
||||||
|
},
|
||||||
|
size_px() {
|
||||||
|
switch (this.size) {
|
||||||
|
case "tiny":
|
||||||
|
return 2;
|
||||||
|
case "small":
|
||||||
|
return 4;
|
||||||
|
case "medium":
|
||||||
|
return 8;
|
||||||
|
case "large":
|
||||||
|
return 12;
|
||||||
|
case "big":
|
||||||
|
return 16;
|
||||||
|
case "huge":
|
||||||
|
return 32;
|
||||||
|
case "massive":
|
||||||
|
return 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isNumber(this.size) ? this.size : 32;
|
||||||
|
},
|
||||||
|
text_padding() {
|
||||||
|
switch (this.size) {
|
||||||
|
case "tiny":
|
||||||
|
case "small":
|
||||||
|
case "medium":
|
||||||
|
case "large":
|
||||||
|
case "big":
|
||||||
|
case "huge":
|
||||||
|
case "massive":
|
||||||
|
return Math.min(Math.max(Math.ceil(this.size_px / 8), 3), 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isNumber(this.spacing) ? this.spacing : 4;
|
||||||
|
},
|
||||||
|
text_font_size() {
|
||||||
|
switch (this.size) {
|
||||||
|
case "tiny":
|
||||||
|
case "small":
|
||||||
|
case "medium":
|
||||||
|
case "large":
|
||||||
|
case "big":
|
||||||
|
case "huge":
|
||||||
|
case "massive":
|
||||||
|
return Math.min(Math.max(Math.ceil(this.size_px * 1.4), 11), 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isNumber(this.fontSize) ? this.fontSize : 13;
|
||||||
|
},
|
||||||
|
progress_style() {
|
||||||
|
var style = {
|
||||||
|
background: this.bgColor,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.textPosition == "middle" || this.textPosition == "inside") {
|
||||||
|
style["position"] = "relative";
|
||||||
|
style["min-height"] = this.size_px + "px";
|
||||||
|
style["z-index"] = "-2";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.barBorderRadius > 0) {
|
||||||
|
style["border-radius"] = this.barBorderRadius + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
return style;
|
||||||
|
},
|
||||||
|
bar_style() {
|
||||||
|
var style = {
|
||||||
|
background: this.barColor,
|
||||||
|
width: this.pct + "%",
|
||||||
|
height: this.size_px + "px",
|
||||||
|
transition: this.barTransition,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.barBorderRadius > 0) {
|
||||||
|
style["border-radius"] = this.barBorderRadius + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.textPosition == "middle" || this.textPosition == "inside") {
|
||||||
|
style["position"] = "absolute";
|
||||||
|
style["top"] = "0";
|
||||||
|
style["height"] = "100%";
|
||||||
|
(style["min-height"] = this.size_px + "px"), (style["z-index"] = "-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
return style;
|
||||||
|
},
|
||||||
|
text_style() {
|
||||||
|
var style = {
|
||||||
|
color: this.textFgColor,
|
||||||
|
"font-size": this.text_font_size + "px",
|
||||||
|
"text-align": this.textAlign,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.textPosition == "top" ||
|
||||||
|
this.textPosition == "middle" ||
|
||||||
|
this.textPosition == "inside"
|
||||||
|
)
|
||||||
|
style["padding-bottom"] = this.text_padding + "px";
|
||||||
|
if (
|
||||||
|
this.textPosition == "bottom" ||
|
||||||
|
this.textPosition == "middle" ||
|
||||||
|
this.textPosition == "inside"
|
||||||
|
)
|
||||||
|
style["padding-top"] = this.text_padding + "px";
|
||||||
|
|
||||||
|
return style;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
@ -17,7 +17,7 @@
|
|||||||
@keyup.enter="submit"
|
@keyup.enter="submit"
|
||||||
ref="input"
|
ref="input"
|
||||||
:autofocus="active"
|
:autofocus="active"
|
||||||
v-model.trim="value"
|
v-model.trim="prompt"
|
||||||
:aria-label="$t('search.search')"
|
:aria-label="$t('search.search')"
|
||||||
:placeholder="$t('search.search')"
|
:placeholder="$t('search.search')"
|
||||||
/>
|
/>
|
||||||
@ -28,7 +28,7 @@
|
|||||||
<template v-if="isEmpty">
|
<template v-if="isEmpty">
|
||||||
<p>{{ text }}</p>
|
<p>{{ text }}</p>
|
||||||
|
|
||||||
<template v-if="value.length === 0">
|
<template v-if="prompt.length === 0">
|
||||||
<div class="boxes">
|
<div class="boxes">
|
||||||
<h3>{{ $t("search.types") }}</h3>
|
<h3>{{ $t("search.types") }}</h3>
|
||||||
<div>
|
<div>
|
||||||
@ -49,7 +49,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<ul v-show="results.length > 0">
|
<ul v-show="results.length > 0">
|
||||||
<li v-for="(s, k) in filteredResults" :key="k">
|
<li v-for="(s, k) in filteredResults" :key="k">
|
||||||
<router-link @click.native="close" :to="s.url">
|
<router-link v-on:click="close" :to="s.url">
|
||||||
<i v-if="s.dir" class="material-icons">folder</i>
|
<i v-if="s.dir" class="material-icons">folder</i>
|
||||||
<i v-else class="material-icons">insert_drive_file</i>
|
<i v-else class="material-icons">insert_drive_file</i>
|
||||||
<span>./{{ s.path }}</span>
|
<span>./{{ s.path }}</span>
|
||||||
@ -64,138 +64,155 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { mapState, mapGetters, mapMutations } from "vuex";
|
import { useFileStore } from "@/stores/file";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
import { search } from "@/api";
|
import { search } from "@/api";
|
||||||
|
import { computed, inject, onMounted, ref, watch } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
|
||||||
var boxes = {
|
const boxes = {
|
||||||
image: { label: "images", icon: "insert_photo" },
|
image: { label: "images", icon: "insert_photo" },
|
||||||
audio: { label: "music", icon: "volume_up" },
|
audio: { label: "music", icon: "volume_up" },
|
||||||
video: { label: "video", icon: "movie" },
|
video: { label: "video", icon: "movie" },
|
||||||
pdf: { label: "pdf", icon: "picture_as_pdf" },
|
pdf: { label: "pdf", icon: "picture_as_pdf" },
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
const layoutStore = useLayoutStore();
|
||||||
name: "search",
|
const fileStore = useFileStore();
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
value: "",
|
|
||||||
active: false,
|
|
||||||
ongoing: false,
|
|
||||||
results: [],
|
|
||||||
reload: false,
|
|
||||||
resultsCount: 50,
|
|
||||||
scrollable: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
currentPrompt(val, old) {
|
|
||||||
this.active = val?.prompt === "search";
|
|
||||||
|
|
||||||
if (old?.prompt === "search" && !this.active) {
|
const { currentPromptName } = storeToRefs(layoutStore);
|
||||||
if (this.reload) {
|
|
||||||
this.setReload(true);
|
const prompt = ref<string>("");
|
||||||
|
const active = ref<boolean>(false);
|
||||||
|
const ongoing = ref<boolean>(false);
|
||||||
|
const results = ref<any[]>([]);
|
||||||
|
const reload = ref<boolean>(false);
|
||||||
|
const resultsCount = ref<number>(50);
|
||||||
|
|
||||||
|
const $showError = inject<IToastError>("$showError")!;
|
||||||
|
|
||||||
|
const input = ref<HTMLInputElement | null>(null);
|
||||||
|
const result = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
watch(currentPromptName, (newVal, oldVal) => {
|
||||||
|
active.value = newVal === "search";
|
||||||
|
|
||||||
|
if (oldVal === "search" && !active.value) {
|
||||||
|
if (reload.value) {
|
||||||
|
fileStore.reload = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.body.style.overflow = "auto";
|
document.body.style.overflow = "auto";
|
||||||
this.reset();
|
reset();
|
||||||
this.value = "";
|
prompt.value = "";
|
||||||
this.active = false;
|
active.value = false;
|
||||||
this.$refs.input.blur();
|
input.value?.blur();
|
||||||
} else if (this.active) {
|
} else if (active.value) {
|
||||||
this.reload = false;
|
reload.value = false;
|
||||||
this.$refs.input.focus();
|
input.value?.focus();
|
||||||
document.body.style.overflow = "hidden";
|
document.body.style.overflow = "hidden";
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
value() {
|
|
||||||
if (this.results.length) {
|
watch(prompt, () => {
|
||||||
this.reset();
|
if (results.value.length) {
|
||||||
|
reset();
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
},
|
|
||||||
computed: {
|
// ...mapState(useFileStore, ["isListing"]),
|
||||||
...mapState(["user"]),
|
// ...mapState(useLayoutStore, ["show"]),
|
||||||
...mapGetters(["isListing", "currentPrompt"]),
|
// ...mapWritableState(useFileStore, { sReload: "reload" }),
|
||||||
boxes() {
|
|
||||||
return boxes;
|
const isEmpty = computed(() => {
|
||||||
},
|
return results.value.length === 0;
|
||||||
isEmpty() {
|
});
|
||||||
return this.results.length === 0;
|
const text = computed(() => {
|
||||||
},
|
if (ongoing.value) {
|
||||||
text() {
|
|
||||||
if (this.ongoing) {
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.value === ""
|
return prompt.value === ""
|
||||||
? this.$t("search.typeToSearch")
|
? t("search.typeToSearch")
|
||||||
: this.$t("search.pressToSearch");
|
: t("search.pressToSearch");
|
||||||
},
|
});
|
||||||
filteredResults() {
|
const filteredResults = computed(() => {
|
||||||
return this.results.slice(0, this.resultsCount);
|
return results.value.slice(0, resultsCount.value);
|
||||||
},
|
});
|
||||||
},
|
|
||||||
mounted() {
|
onMounted(() => {
|
||||||
this.$refs.result.addEventListener("scroll", (event) => {
|
if (result.value === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result.value.addEventListener("scroll", (event: Event) => {
|
||||||
if (
|
if (
|
||||||
event.target.offsetHeight + event.target.scrollTop >=
|
(event.target as HTMLElement).offsetHeight +
|
||||||
event.target.scrollHeight - 100
|
(event.target as HTMLElement).scrollTop >=
|
||||||
|
(event.target as HTMLElement).scrollHeight - 100
|
||||||
) {
|
) {
|
||||||
this.resultsCount += 50;
|
resultsCount.value += 50;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
methods: {
|
|
||||||
...mapMutations(["showHover", "closeHovers", "setReload"]),
|
const open = () => {
|
||||||
open() {
|
!active.value && layoutStore.showHover("search");
|
||||||
this.showHover("search");
|
};
|
||||||
},
|
|
||||||
close(event) {
|
const close = (event: Event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.closeHovers();
|
layoutStore.closeHovers();
|
||||||
},
|
};
|
||||||
keyup(event) {
|
|
||||||
if (event.keyCode === 27) {
|
const keyup = (event: KeyboardEvent) => {
|
||||||
this.close(event);
|
if (event.key === "Escape") {
|
||||||
|
close(event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
results.value.length = 0;
|
||||||
|
};
|
||||||
|
|
||||||
this.results.length = 0;
|
const init = (string: string) => {
|
||||||
},
|
prompt.value = `${string} `;
|
||||||
init(string) {
|
input.value !== null ? input.value.focus() : "";
|
||||||
this.value = `${string} `;
|
};
|
||||||
this.$refs.input.focus();
|
|
||||||
},
|
const reset = () => {
|
||||||
reset() {
|
ongoing.value = false;
|
||||||
this.ongoing = false;
|
resultsCount.value = 50;
|
||||||
this.resultsCount = 50;
|
results.value = [];
|
||||||
this.results = [];
|
};
|
||||||
},
|
|
||||||
async submit(event) {
|
const submit = async (event: Event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (this.value === "") {
|
if (prompt.value === "") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = this.$route.path;
|
let path = route.path;
|
||||||
if (!this.isListing) {
|
if (!fileStore.isListing) {
|
||||||
path = url.removeLastDir(path) + "/";
|
path = url.removeLastDir(path) + "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ongoing = true;
|
ongoing.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.results = await search(path, this.value);
|
results.value = await search(path, prompt.value);
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
this.$showError(error);
|
$showError(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ongoing = false;
|
ongoing.value = false;
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -29,9 +29,9 @@
|
|||||||
tabindex="0"
|
tabindex="0"
|
||||||
ref="input"
|
ref="input"
|
||||||
class="shell__text"
|
class="shell__text"
|
||||||
contenteditable="true"
|
:contenteditable="true"
|
||||||
@keydown.prevent.38="historyUp"
|
@keydown.prevent.arrow-up="historyUp"
|
||||||
@keydown.prevent.40="historyDown"
|
@keydown.prevent.arrow-down="historyDown"
|
||||||
@keypress.prevent.enter="submit"
|
@keypress.prevent.enter="submit"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -45,7 +45,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapMutations, mapState, mapGetters } from "vuex";
|
import { mapState, mapActions } from "pinia";
|
||||||
|
import { useFileStore } from "@/stores/file";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
|
||||||
import { commands } from "@/api";
|
import { commands } from "@/api";
|
||||||
import { throttle } from "lodash";
|
import { throttle } from "lodash";
|
||||||
import { theme } from "@/utils/constants";
|
import { theme } from "@/utils/constants";
|
||||||
@ -53,8 +56,8 @@ import { theme } from "@/utils/constants";
|
|||||||
export default {
|
export default {
|
||||||
name: "shell",
|
name: "shell",
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["user", "showShell"]),
|
...mapState(useLayoutStore, ["showShell"]),
|
||||||
...mapGetters(["isFiles", "isLogged"]),
|
...mapState(useFileStore, ["isFiles"]),
|
||||||
path: function () {
|
path: function () {
|
||||||
if (this.isFiles) {
|
if (this.isFiles) {
|
||||||
return this.$route.path;
|
return this.$route.path;
|
||||||
@ -75,11 +78,11 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
window.addEventListener("resize", this.resize);
|
window.addEventListener("resize", this.resize);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeUnmount() {
|
||||||
window.removeEventListener("resize", this.resize);
|
window.removeEventListener("resize", this.resize);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations(["toggleShell"]),
|
...mapActions(useLayoutStore, ["toggleShell"]),
|
||||||
checkTheme() {
|
checkTheme() {
|
||||||
if (theme == "dark") {
|
if (theme == "dark") {
|
||||||
return "rgba(255, 255, 255, 0.4)";
|
return "rgba(255, 255, 255, 0.4)";
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<nav :class="{ active }">
|
<nav :class="{ active }">
|
||||||
<template v-if="isLogged">
|
<template v-if="isLoggedIn">
|
||||||
<button
|
<button
|
||||||
class="action"
|
class="action"
|
||||||
@click="toRoot"
|
@click="toRoot"
|
||||||
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
<div v-if="user.perm.create">
|
<div v-if="user.perm.create">
|
||||||
<button
|
<button
|
||||||
@click="$store.commit('showHover', 'newDir')"
|
@click="showHover('newDir')"
|
||||||
class="action"
|
class="action"
|
||||||
:aria-label="$t('sidebar.newFolder')"
|
:aria-label="$t('sidebar.newFolder')"
|
||||||
:title="$t('sidebar.newFolder')"
|
:title="$t('sidebar.newFolder')"
|
||||||
@ -23,7 +23,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="$store.commit('showHover', 'newFile')"
|
@click="showHover('newFile')"
|
||||||
class="action"
|
class="action"
|
||||||
:aria-label="$t('sidebar.newFile')"
|
:aria-label="$t('sidebar.newFile')"
|
||||||
:title="$t('sidebar.newFile')"
|
:title="$t('sidebar.newFile')"
|
||||||
@ -82,9 +82,7 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="credits"
|
class="credits"
|
||||||
v-if="
|
v-if="isFiles && !disableUsedPercentage"
|
||||||
$router.currentRoute.path.includes('/files/') && !disableUsedPercentage
|
|
||||||
"
|
|
||||||
style="width: 90%; margin: 2em 2.5em 3em 2.5em"
|
style="width: 90%; margin: 2em 2.5em 3em 2.5em"
|
||||||
>
|
>
|
||||||
<progress-bar :val="usage.usedPercentage" size="small"></progress-bar>
|
<progress-bar :val="usage.usedPercentage" size="small"></progress-bar>
|
||||||
@ -112,7 +110,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapGetters } from "vuex";
|
import { reactive } from "vue";
|
||||||
|
import { mapActions, mapState } from "pinia";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
import { useFileStore } from "@/stores/file";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
|
||||||
import * as auth from "@/utils/auth";
|
import * as auth from "@/utils/auth";
|
||||||
import {
|
import {
|
||||||
version,
|
version,
|
||||||
@ -123,19 +126,27 @@ import {
|
|||||||
loginPage,
|
loginPage,
|
||||||
} from "@/utils/constants";
|
} from "@/utils/constants";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import ProgressBar from "vue-simple-progress";
|
import ProgressBar from "@/components/ProgressBar.vue";
|
||||||
import prettyBytes from "pretty-bytes";
|
import prettyBytes from "pretty-bytes";
|
||||||
|
|
||||||
|
const USAGE_DEFAULT = { used: "0 B", total: "0 B", usedPercentage: 0 };
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "sidebar",
|
name: "sidebar",
|
||||||
|
setup() {
|
||||||
|
const usage = reactive(USAGE_DEFAULT);
|
||||||
|
return { usage };
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
ProgressBar,
|
ProgressBar,
|
||||||
},
|
},
|
||||||
|
inject: ["$showError"],
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["user"]),
|
...mapState(useAuthStore, ["user", "isLoggedIn"]),
|
||||||
...mapGetters(["isLogged", "currentPrompt"]),
|
...mapState(useFileStore, ["isFiles", "reload"]),
|
||||||
|
...mapState(useLayoutStore, ["currentPromptName"]),
|
||||||
active() {
|
active() {
|
||||||
return this.currentPrompt?.prompt === "sidebar";
|
return this.currentPromptName === "sidebar";
|
||||||
},
|
},
|
||||||
signup: () => signup,
|
signup: () => signup,
|
||||||
version: () => version,
|
version: () => version,
|
||||||
@ -143,15 +154,15 @@ export default {
|
|||||||
disableUsedPercentage: () => disableUsedPercentage,
|
disableUsedPercentage: () => disableUsedPercentage,
|
||||||
canLogout: () => !noAuth && loginPage,
|
canLogout: () => !noAuth && loginPage,
|
||||||
},
|
},
|
||||||
asyncComputed: {
|
methods: {
|
||||||
usage: {
|
...mapActions(useLayoutStore, ["closeHovers", "showHover"]),
|
||||||
async get() {
|
async fetchUsage() {
|
||||||
let path = this.$route.path.endsWith("/")
|
let path = this.$route.path.endsWith("/")
|
||||||
? this.$route.path
|
? this.$route.path
|
||||||
: this.$route.path + "/";
|
: this.$route.path + "/";
|
||||||
let usageStats = { used: 0, total: 0, usedPercentage: 0 };
|
let usageStats = USAGE_DEFAULT;
|
||||||
if (this.disableUsedPercentage) {
|
if (this.disableUsedPercentage) {
|
||||||
return usageStats;
|
return Object.assign(this.usage, usageStats);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
let usage = await api.usage(path);
|
let usage = await api.usage(path);
|
||||||
@ -163,27 +174,25 @@ export default {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$showError(error);
|
this.$showError(error);
|
||||||
}
|
}
|
||||||
return usageStats;
|
return Object.assign(this.usage, usageStats);
|
||||||
},
|
},
|
||||||
default: { used: "0 B", total: "0 B", usedPercentage: 0 },
|
|
||||||
shouldUpdate() {
|
|
||||||
return this.$router.currentRoute.path.includes("/files/");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toRoot() {
|
toRoot() {
|
||||||
this.$router.push({ path: "/files/" }, () => {});
|
this.$router.push({ path: "/files" });
|
||||||
this.$store.commit("closeHovers");
|
this.closeHovers();
|
||||||
},
|
},
|
||||||
toSettings() {
|
toSettings() {
|
||||||
this.$router.push({ path: "/settings" }, () => {});
|
this.$router.push({ path: "/settings" });
|
||||||
this.$store.commit("closeHovers");
|
this.closeHovers();
|
||||||
},
|
},
|
||||||
help() {
|
help() {
|
||||||
this.$store.commit("showHover", "help");
|
this.showHover("help");
|
||||||
},
|
},
|
||||||
logout: auth.logout,
|
logout: auth.logout,
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
isFiles(newValue) {
|
||||||
|
newValue && this.fetchUsage();
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -13,205 +13,230 @@
|
|||||||
<img class="image-ex-img image-ex-img-center" ref="imgex" @load="onLoad" />
|
<img class="image-ex-img image-ex-img-center" ref="imgex" @load="onLoad" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import throttle from "lodash.throttle";
|
import throttle from "lodash/throttle";
|
||||||
import UTIF from "utif";
|
import UTIF from "utif";
|
||||||
|
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||||
|
|
||||||
export default {
|
interface IProps {
|
||||||
props: {
|
src: string;
|
||||||
src: String,
|
moveDisabledTime: number;
|
||||||
moveDisabledTime: {
|
classList: any[];
|
||||||
type: Number,
|
zoomStep: number;
|
||||||
default: () => 200,
|
}
|
||||||
},
|
|
||||||
classList: {
|
const props = withDefaults(defineProps<IProps>(), {
|
||||||
type: Array,
|
moveDisabledTime: () => 200,
|
||||||
default: () => [],
|
classList: () => [],
|
||||||
},
|
zoomStep: () => 0.25,
|
||||||
zoomStep: {
|
});
|
||||||
type: Number,
|
|
||||||
default: () => 0.25,
|
const scale = ref<number>(1);
|
||||||
},
|
const lastX = ref<number | null>(null);
|
||||||
},
|
const lastY = ref<number | null>(null);
|
||||||
data() {
|
const inDrag = ref<boolean>(false);
|
||||||
return {
|
const touches = ref<number>(0);
|
||||||
scale: 1,
|
const lastTouchDistance = ref<number | null>(0);
|
||||||
lastX: null,
|
const moveDisabled = ref<boolean>(false);
|
||||||
lastY: null,
|
const disabledTimer = ref<number | null>(null);
|
||||||
inDrag: false,
|
const imageLoaded = ref<boolean>(false);
|
||||||
touches: 0,
|
const position = ref<{
|
||||||
lastTouchDistance: 0,
|
center: { x: number; y: number };
|
||||||
moveDisabled: false,
|
relative: { x: number; y: number };
|
||||||
disabledTimer: null,
|
}>({
|
||||||
imageLoaded: false,
|
|
||||||
position: {
|
|
||||||
center: { x: 0, y: 0 },
|
center: { x: 0, y: 0 },
|
||||||
relative: { x: 0, y: 0 },
|
relative: { x: 0, y: 0 },
|
||||||
},
|
});
|
||||||
maxScale: 4,
|
const maxScale = ref<number>(4);
|
||||||
minScale: 0.25,
|
const minScale = ref<number>(0.25);
|
||||||
};
|
|
||||||
},
|
// Refs
|
||||||
mounted() {
|
const imgex = ref<HTMLImageElement | null>(null);
|
||||||
if (!this.decodeUTIF()) {
|
const container = ref<HTMLDivElement | null>(null);
|
||||||
this.$refs.imgex.src = this.src;
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (!decodeUTIF() && imgex.value !== null) {
|
||||||
|
imgex.value.src = props.src;
|
||||||
}
|
}
|
||||||
let container = this.$refs.container;
|
|
||||||
this.classList.forEach((className) => container.classList.add(className));
|
props.classList.forEach((className) =>
|
||||||
|
container.value !== null ? container.value.classList.add(className) : ""
|
||||||
|
);
|
||||||
|
|
||||||
|
if (container.value === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// set width and height if they are zero
|
// set width and height if they are zero
|
||||||
if (getComputedStyle(container).width === "0px") {
|
if (getComputedStyle(container.value).width === "0px") {
|
||||||
container.style.width = "100%";
|
container.value.style.width = "100%";
|
||||||
}
|
}
|
||||||
if (getComputedStyle(container).height === "0px") {
|
if (getComputedStyle(container.value).height === "0px") {
|
||||||
container.style.height = "100%";
|
container.value.style.height = "100%";
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("resize", this.onResize);
|
window.addEventListener("resize", onResize);
|
||||||
},
|
});
|
||||||
beforeDestroy() {
|
|
||||||
window.removeEventListener("resize", this.onResize);
|
onBeforeUnmount(() => {
|
||||||
document.removeEventListener("mouseup", this.onMouseUp);
|
window.removeEventListener("resize", onResize);
|
||||||
},
|
document.removeEventListener("mouseup", onMouseUp);
|
||||||
watch: {
|
});
|
||||||
src: function () {
|
|
||||||
if (!this.decodeUTIF()) {
|
watch(
|
||||||
this.$refs.imgex.src = this.src;
|
() => props.src,
|
||||||
|
() => {
|
||||||
|
if (!decodeUTIF() && imgex.value !== null) {
|
||||||
|
imgex.value.src = props.src;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scale = 1;
|
scale.value = 1;
|
||||||
this.setZoom();
|
setZoom();
|
||||||
this.setCenter();
|
setCenter();
|
||||||
},
|
}
|
||||||
},
|
);
|
||||||
methods: {
|
|
||||||
// Modified from UTIF.replaceIMG
|
// Modified from UTIF.replaceIMG
|
||||||
decodeUTIF() {
|
const decodeUTIF = () => {
|
||||||
const sufs = ["tif", "tiff", "dng", "cr2", "nef"];
|
const sufs = ["tif", "tiff", "dng", "cr2", "nef"];
|
||||||
let suff = document.location.pathname.split(".").pop().toLowerCase();
|
if (document?.location?.pathname === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let suff = document.location.pathname.split(".")?.pop()?.toLowerCase() ?? "";
|
||||||
|
|
||||||
if (sufs.indexOf(suff) == -1) return false;
|
if (sufs.indexOf(suff) == -1) return false;
|
||||||
let xhr = new XMLHttpRequest();
|
let xhr = new XMLHttpRequest();
|
||||||
UTIF._xhrs.push(xhr);
|
UTIF._xhrs.push(xhr);
|
||||||
UTIF._imgs.push(this.$refs.imgex);
|
UTIF._imgs.push(imgex.value);
|
||||||
xhr.open("GET", this.src);
|
xhr.open("GET", props.src);
|
||||||
xhr.responseType = "arraybuffer";
|
xhr.responseType = "arraybuffer";
|
||||||
xhr.onload = UTIF._imgLoaded;
|
xhr.onload = UTIF._imgLoaded;
|
||||||
xhr.send();
|
xhr.send();
|
||||||
return true;
|
return true;
|
||||||
},
|
};
|
||||||
onLoad() {
|
|
||||||
let img = this.$refs.imgex;
|
|
||||||
|
|
||||||
this.imageLoaded = true;
|
const onLoad = () => {
|
||||||
|
imageLoaded.value = true;
|
||||||
|
|
||||||
if (img === undefined) {
|
if (imgex.value === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
img.classList.remove("image-ex-img-center");
|
imgex.value.classList.remove("image-ex-img-center");
|
||||||
this.setCenter();
|
setCenter();
|
||||||
img.classList.add("image-ex-img-ready");
|
imgex.value.classList.add("image-ex-img-ready");
|
||||||
|
|
||||||
document.addEventListener("mouseup", this.onMouseUp);
|
document.addEventListener("mouseup", onMouseUp);
|
||||||
|
|
||||||
let realSize = img.naturalWidth;
|
let realSize = imgex.value.naturalWidth;
|
||||||
let displaySize = img.offsetWidth;
|
let displaySize = imgex.value.offsetWidth;
|
||||||
|
|
||||||
// Image is in portrait orientation
|
// Image is in portrait orientation
|
||||||
if (img.naturalHeight > img.naturalWidth) {
|
if (imgex.value.naturalHeight > imgex.value.naturalWidth) {
|
||||||
realSize = img.naturalHeight;
|
realSize = imgex.value.naturalHeight;
|
||||||
displaySize = img.offsetHeight;
|
displaySize = imgex.value.offsetHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale needed to display the image on full size
|
// Scale needed to display the image on full size
|
||||||
const fullScale = realSize / displaySize;
|
const fullScale = realSize / displaySize;
|
||||||
|
|
||||||
// Full size plus additional zoom
|
// Full size plus additional zoom
|
||||||
this.maxScale = fullScale + 4;
|
maxScale.value = fullScale + 4;
|
||||||
},
|
};
|
||||||
onMouseUp() {
|
|
||||||
this.inDrag = false;
|
const onMouseUp = () => {
|
||||||
},
|
inDrag.value = false;
|
||||||
onResize: throttle(function () {
|
};
|
||||||
if (this.imageLoaded) {
|
|
||||||
this.setCenter();
|
const onResize = throttle(function () {
|
||||||
this.doMove(this.position.relative.x, this.position.relative.y);
|
if (imageLoaded.value) {
|
||||||
|
setCenter();
|
||||||
|
doMove(position.value.relative.x, position.value.relative.y);
|
||||||
}
|
}
|
||||||
}, 100),
|
}, 100);
|
||||||
setCenter() {
|
|
||||||
let container = this.$refs.container;
|
|
||||||
let img = this.$refs.imgex;
|
|
||||||
|
|
||||||
this.position.center.x = Math.floor(
|
const setCenter = () => {
|
||||||
(container.clientWidth - img.clientWidth) / 2
|
if (container.value === null || imgex.value === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
position.value.center.x = Math.floor(
|
||||||
|
(container.value.clientWidth - imgex.value.clientWidth) / 2
|
||||||
);
|
);
|
||||||
this.position.center.y = Math.floor(
|
position.value.center.y = Math.floor(
|
||||||
(container.clientHeight - img.clientHeight) / 2
|
(container.value.clientHeight - imgex.value.clientHeight) / 2
|
||||||
);
|
);
|
||||||
|
|
||||||
img.style.left = this.position.center.x + "px";
|
imgex.value.style.left = position.value.center.x + "px";
|
||||||
img.style.top = this.position.center.y + "px";
|
imgex.value.style.top = position.value.center.y + "px";
|
||||||
},
|
};
|
||||||
mousedownStart(event) {
|
|
||||||
this.lastX = null;
|
const mousedownStart = (event: Event) => {
|
||||||
this.lastY = null;
|
lastX.value = null;
|
||||||
this.inDrag = true;
|
lastY.value = null;
|
||||||
|
inDrag.value = true;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
};
|
||||||
mouseMove(event) {
|
const mouseMove = (event: MouseEvent) => {
|
||||||
if (!this.inDrag) return;
|
if (!inDrag.value) return;
|
||||||
this.doMove(event.movementX, event.movementY);
|
doMove(event.movementX, event.movementY);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
};
|
||||||
mouseUp(event) {
|
const mouseUp = (event: Event) => {
|
||||||
this.inDrag = false;
|
inDrag.value = false;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
};
|
||||||
touchStart(event) {
|
const touchStart = (event: TouchEvent) => {
|
||||||
this.lastX = null;
|
lastX.value = null;
|
||||||
this.lastY = null;
|
lastY.value = null;
|
||||||
this.lastTouchDistance = null;
|
lastTouchDistance.value = null;
|
||||||
if (event.targetTouches.length < 2) {
|
if (event.targetTouches.length < 2) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.touches = 0;
|
touches.value = 0;
|
||||||
}, 300);
|
}, 300);
|
||||||
this.touches++;
|
touches.value++;
|
||||||
if (this.touches > 1) {
|
if (touches.value > 1) {
|
||||||
this.zoomAuto(event);
|
zoomAuto(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
};
|
||||||
zoomAuto(event) {
|
|
||||||
switch (this.scale) {
|
const zoomAuto = (event: Event) => {
|
||||||
|
switch (scale.value) {
|
||||||
case 1:
|
case 1:
|
||||||
this.scale = 2;
|
scale.value = 2;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
this.scale = 4;
|
scale.value = 4;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
case 4:
|
case 4:
|
||||||
this.scale = 1;
|
scale.value = 1;
|
||||||
this.setCenter();
|
setCenter();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.setZoom();
|
setZoom();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
};
|
||||||
touchMove(event) {
|
|
||||||
|
const touchMove = (event: TouchEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (this.lastX === null) {
|
if (lastX.value === null) {
|
||||||
this.lastX = event.targetTouches[0].pageX;
|
lastX.value = event.targetTouches[0].pageX;
|
||||||
this.lastY = event.targetTouches[0].pageY;
|
lastY.value = event.targetTouches[0].pageY;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let step = this.$refs.imgex.width / 5;
|
if (imgex.value === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let step = imgex.value.width / 5;
|
||||||
if (event.targetTouches.length === 2) {
|
if (event.targetTouches.length === 2) {
|
||||||
this.moveDisabled = true;
|
moveDisabled.value = true;
|
||||||
clearTimeout(this.disabledTimer);
|
if (disabledTimer.value) clearTimeout(disabledTimer.value);
|
||||||
this.disabledTimer = setTimeout(
|
disabledTimer.value = window.setTimeout(
|
||||||
() => (this.moveDisabled = false),
|
() => (moveDisabled.value = false),
|
||||||
this.moveDisabledTime
|
props.moveDisabledTime
|
||||||
);
|
);
|
||||||
|
|
||||||
let p1 = event.targetTouches[0];
|
let p1 = event.targetTouches[0];
|
||||||
@ -219,55 +244,59 @@ export default {
|
|||||||
let touchDistance = Math.sqrt(
|
let touchDistance = Math.sqrt(
|
||||||
Math.pow(p2.pageX - p1.pageX, 2) + Math.pow(p2.pageY - p1.pageY, 2)
|
Math.pow(p2.pageX - p1.pageX, 2) + Math.pow(p2.pageY - p1.pageY, 2)
|
||||||
);
|
);
|
||||||
if (!this.lastTouchDistance) {
|
if (!lastTouchDistance.value) {
|
||||||
this.lastTouchDistance = touchDistance;
|
lastTouchDistance.value = touchDistance;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.scale += (touchDistance - this.lastTouchDistance) / step;
|
scale.value += (touchDistance - lastTouchDistance.value) / step;
|
||||||
this.lastTouchDistance = touchDistance;
|
lastTouchDistance.value = touchDistance;
|
||||||
this.setZoom();
|
setZoom();
|
||||||
} else if (event.targetTouches.length === 1) {
|
} else if (event.targetTouches.length === 1) {
|
||||||
if (this.moveDisabled) return;
|
if (moveDisabled.value) return;
|
||||||
let x = event.targetTouches[0].pageX - this.lastX;
|
let x = event.targetTouches[0].pageX - (lastX.value ?? 0);
|
||||||
let y = event.targetTouches[0].pageY - this.lastY;
|
let y = event.targetTouches[0].pageY - (lastY.value ?? 0);
|
||||||
if (Math.abs(x) >= step && Math.abs(y) >= step) return;
|
if (Math.abs(x) >= step && Math.abs(y) >= step) return;
|
||||||
this.lastX = event.targetTouches[0].pageX;
|
lastX.value = event.targetTouches[0].pageX;
|
||||||
this.lastY = event.targetTouches[0].pageY;
|
lastY.value = event.targetTouches[0].pageY;
|
||||||
this.doMove(x, y);
|
doMove(x, y);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
doMove(x, y) {
|
|
||||||
let style = this.$refs.imgex.style;
|
const doMove = (x: number, y: number) => {
|
||||||
let posX = this.pxStringToNumber(style.left) + x;
|
if (imgex.value === null) {
|
||||||
let posY = this.pxStringToNumber(style.top) + y;
|
return;
|
||||||
|
}
|
||||||
|
const style = imgex.value.style;
|
||||||
|
|
||||||
|
let posX = pxStringToNumber(style.left) + x;
|
||||||
|
let posY = pxStringToNumber(style.top) + y;
|
||||||
|
|
||||||
style.left = posX + "px";
|
style.left = posX + "px";
|
||||||
style.top = posY + "px";
|
style.top = posY + "px";
|
||||||
|
|
||||||
this.position.relative.x = Math.abs(this.position.center.x - posX);
|
position.value.relative.x = Math.abs(position.value.center.x - posX);
|
||||||
this.position.relative.y = Math.abs(this.position.center.y - posY);
|
position.value.relative.y = Math.abs(position.value.center.y - posY);
|
||||||
|
|
||||||
if (posX < this.position.center.x) {
|
if (posX < position.value.center.x) {
|
||||||
this.position.relative.x = this.position.relative.x * -1;
|
position.value.relative.x = position.value.relative.x * -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (posY < this.position.center.y) {
|
if (posY < position.value.center.y) {
|
||||||
this.position.relative.y = this.position.relative.y * -1;
|
position.value.relative.y = position.value.relative.y * -1;
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
wheelMove(event) {
|
const wheelMove = (event: WheelEvent) => {
|
||||||
this.scale += -Math.sign(event.deltaY) * this.zoomStep;
|
scale.value += -Math.sign(event.deltaY) * props.zoomStep;
|
||||||
this.setZoom();
|
setZoom();
|
||||||
},
|
};
|
||||||
setZoom() {
|
const setZoom = () => {
|
||||||
this.scale = this.scale < this.minScale ? this.minScale : this.scale;
|
scale.value = scale.value < minScale.value ? minScale.value : scale.value;
|
||||||
this.scale = this.scale > this.maxScale ? this.maxScale : this.scale;
|
scale.value = scale.value > maxScale.value ? maxScale.value : scale.value;
|
||||||
this.$refs.imgex.style.transform = `scale(${this.scale})`;
|
if (imgex.value !== null)
|
||||||
},
|
imgex.value.style.transform = `scale(${scale.value})`;
|
||||||
pxStringToNumber(style) {
|
};
|
||||||
|
const pxStringToNumber = (style: string) => {
|
||||||
return +style.replace("px", "");
|
return +style.replace("px", "");
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<img
|
<img
|
||||||
v-if="readOnly == undefined && type === 'image' && isThumbsEnabled"
|
v-if="!readOnly && type === 'image' && isThumbsEnabled"
|
||||||
v-lazy="thumbnailUrl"
|
v-lazy="thumbnailUrl"
|
||||||
/>
|
/>
|
||||||
<i v-else class="material-icons"></i>
|
<i v-else class="material-icons"></i>
|
||||||
@ -34,137 +34,153 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
import { useFileStore } from "@/stores/file";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
|
||||||
import { enableThumbs } from "@/utils/constants";
|
import { enableThumbs } from "@/utils/constants";
|
||||||
import { mapMutations, mapGetters, mapState } from "vuex";
|
|
||||||
import { filesize } from "@/utils";
|
import { filesize } from "@/utils";
|
||||||
import moment from "moment/min/moment-with-locales";
|
import dayjs from "dayjs";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import * as upload from "@/utils/upload";
|
import * as upload from "@/utils/upload";
|
||||||
|
import { computed, inject, ref } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
export default {
|
const touches = ref<number>(0);
|
||||||
name: "item",
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
touches: 0,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
props: [
|
|
||||||
"name",
|
|
||||||
"isDir",
|
|
||||||
"url",
|
|
||||||
"type",
|
|
||||||
"size",
|
|
||||||
"modified",
|
|
||||||
"index",
|
|
||||||
"readOnly",
|
|
||||||
"path",
|
|
||||||
],
|
|
||||||
computed: {
|
|
||||||
...mapState(["user", "selected", "req", "jwt"]),
|
|
||||||
...mapGetters(["selectedCount"]),
|
|
||||||
singleClick() {
|
|
||||||
return this.readOnly == undefined && this.user.singleClick;
|
|
||||||
},
|
|
||||||
isSelected() {
|
|
||||||
return this.selected.indexOf(this.index) !== -1;
|
|
||||||
},
|
|
||||||
isDraggable() {
|
|
||||||
return this.readOnly == undefined && this.user.perm.rename;
|
|
||||||
},
|
|
||||||
canDrop() {
|
|
||||||
if (!this.isDir || this.readOnly !== undefined) return false;
|
|
||||||
|
|
||||||
for (let i of this.selected) {
|
const $showError = inject<IToastError>("$showError")!;
|
||||||
if (this.req.items[i].url === this.url) {
|
const router = useRouter();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
name: string;
|
||||||
|
isDir: boolean;
|
||||||
|
url: string;
|
||||||
|
type: string;
|
||||||
|
size: number;
|
||||||
|
modified: string;
|
||||||
|
index: number;
|
||||||
|
readOnly?: boolean;
|
||||||
|
path?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const fileStore = useFileStore();
|
||||||
|
const layoutStore = useLayoutStore();
|
||||||
|
|
||||||
|
const singleClick = computed(
|
||||||
|
() => !props.readOnly && authStore.user?.singleClick
|
||||||
|
);
|
||||||
|
const isSelected = computed(
|
||||||
|
() => fileStore.selected.indexOf(props.index) !== -1
|
||||||
|
);
|
||||||
|
const isDraggable = computed(
|
||||||
|
() => !props.readOnly && authStore.user?.perm.rename
|
||||||
|
);
|
||||||
|
|
||||||
|
const canDrop = computed(() => {
|
||||||
|
if (!props.isDir || props.readOnly) return false;
|
||||||
|
|
||||||
|
for (let i of fileStore.selected) {
|
||||||
|
if (fileStore.req?.items[i].url === props.url) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
});
|
||||||
thumbnailUrl() {
|
|
||||||
|
const thumbnailUrl = computed(() => {
|
||||||
const file = {
|
const file = {
|
||||||
path: this.path,
|
path: props.path,
|
||||||
modified: this.modified,
|
modified: props.modified,
|
||||||
};
|
};
|
||||||
|
|
||||||
return api.getPreviewURL(file, "thumb");
|
return api.getPreviewURL(file as Resource, "thumb");
|
||||||
},
|
});
|
||||||
isThumbsEnabled() {
|
|
||||||
|
const isThumbsEnabled = computed(() => {
|
||||||
return enableThumbs;
|
return enableThumbs;
|
||||||
},
|
});
|
||||||
},
|
|
||||||
methods: {
|
const humanSize = () => {
|
||||||
...mapMutations(["addSelected", "removeSelected", "resetSelected"]),
|
return props.type == "invalid_link" ? "invalid link" : filesize(props.size);
|
||||||
humanSize: function () {
|
};
|
||||||
return this.type == "invalid_link" ? "invalid link" : filesize(this.size);
|
|
||||||
},
|
const humanTime = () => {
|
||||||
humanTime: function () {
|
if (!props.readOnly && authStore.user?.dateFormat) {
|
||||||
if (this.readOnly == undefined && this.user.dateFormat) {
|
return dayjs(props.modified).format("L LT");
|
||||||
return moment(this.modified).format("L LT");
|
|
||||||
}
|
}
|
||||||
return moment(this.modified).fromNow();
|
return dayjs(props.modified).fromNow();
|
||||||
},
|
};
|
||||||
dragStart: function () {
|
|
||||||
if (this.selectedCount === 0) {
|
const dragStart = () => {
|
||||||
this.addSelected(this.index);
|
if (fileStore.selectedCount === 0) {
|
||||||
|
fileStore.selected.push(props.index);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isSelected) {
|
if (!isSelected.value) {
|
||||||
this.resetSelected();
|
fileStore.selected = [];
|
||||||
this.addSelected(this.index);
|
fileStore.selected.push(props.index);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
dragOver: function (event) {
|
|
||||||
if (!this.canDrop) return;
|
const dragOver = (event: Event) => {
|
||||||
|
if (!canDrop.value) return;
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let el = event.target;
|
let el = event.target as HTMLElement | null;
|
||||||
|
if (el !== null) {
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
if (!el.classList.contains("item")) {
|
if (!el?.classList.contains("item")) {
|
||||||
el = el.parentElement;
|
el = el?.parentElement ?? null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
el.style.opacity = 1;
|
if (el !== null) el.style.opacity = "1";
|
||||||
},
|
}
|
||||||
drop: async function (event) {
|
};
|
||||||
if (!this.canDrop) return;
|
|
||||||
|
const drop = async (event: Event) => {
|
||||||
|
if (!canDrop.value) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (this.selectedCount === 0) return;
|
if (fileStore.selectedCount === 0) return;
|
||||||
|
|
||||||
let el = event.target;
|
let el = event.target as HTMLElement | null;
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
if (el !== null && !el.classList.contains("item")) {
|
if (el !== null && !el.classList.contains("item")) {
|
||||||
el = el.parentElement;
|
el = el.parentElement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let items = [];
|
let items: any[] = [];
|
||||||
|
|
||||||
for (let i of this.selected) {
|
for (let i of fileStore.selected) {
|
||||||
|
if (fileStore.req) {
|
||||||
items.push({
|
items.push({
|
||||||
from: this.req.items[i].url,
|
from: fileStore.req?.items[i].url,
|
||||||
to: this.url + encodeURIComponent(this.req.items[i].name),
|
to: props.url + encodeURIComponent(fileStore.req?.items[i].name),
|
||||||
name: this.req.items[i].name,
|
name: fileStore.req?.items[i].name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get url from ListingItem instance
|
// Get url from ListingItem instance
|
||||||
|
if (el === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let path = el.__vue__.url;
|
let path = el.__vue__.url;
|
||||||
let baseItems = (await api.fetch(path)).items;
|
let baseItems = (await api.fetch(path)).items;
|
||||||
|
|
||||||
let action = (overwrite, rename) => {
|
let action = (overwrite: boolean, rename: boolean) => {
|
||||||
api
|
api
|
||||||
.move(items, overwrite, rename)
|
.move(items, overwrite, rename)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$store.commit("setReload", true);
|
fileStore.reload = true;
|
||||||
})
|
})
|
||||||
.catch(this.$showError);
|
.catch($showError);
|
||||||
};
|
};
|
||||||
|
|
||||||
let conflict = upload.checkConflict(items, baseItems);
|
let conflict = upload.checkConflict(items, baseItems);
|
||||||
@ -173,14 +189,14 @@ export default {
|
|||||||
let rename = false;
|
let rename = false;
|
||||||
|
|
||||||
if (conflict) {
|
if (conflict) {
|
||||||
this.$store.commit("showHover", {
|
layoutStore.showHover({
|
||||||
prompt: "replace-rename",
|
prompt: "replace-rename",
|
||||||
confirm: (event, option) => {
|
confirm: (event: Event, option: any) => {
|
||||||
overwrite = option == "overwrite";
|
overwrite = option == "overwrite";
|
||||||
rename = option == "rename";
|
rename = option == "rename";
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.$store.commit("closeHovers");
|
layoutStore.closeHovers();
|
||||||
action(overwrite, rename);
|
action(overwrite, rename);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -189,48 +205,51 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
action(overwrite, rename);
|
action(overwrite, rename);
|
||||||
},
|
};
|
||||||
itemClick: function (event) {
|
|
||||||
|
const itemClick = (event: Event | KeyboardEvent) => {
|
||||||
if (
|
if (
|
||||||
!(event.ctrlKey || event.metaKey) &&
|
!((event as KeyboardEvent).ctrlKey || (event as KeyboardEvent).metaKey) &&
|
||||||
this.singleClick &&
|
singleClick.value &&
|
||||||
!this.$store.state.multiple
|
!fileStore.multiple
|
||||||
)
|
)
|
||||||
this.open();
|
open();
|
||||||
else this.click(event);
|
else click(event);
|
||||||
},
|
};
|
||||||
click: function (event) {
|
|
||||||
if (!this.singleClick && this.selectedCount !== 0) event.preventDefault();
|
const click = (event: Event | KeyboardEvent) => {
|
||||||
|
if (!singleClick.value && fileStore.selectedCount !== 0)
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.touches = 0;
|
touches.value = 0;
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
this.touches++;
|
touches.value++;
|
||||||
if (this.touches > 1) {
|
if (touches.value > 1) {
|
||||||
this.open();
|
open();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.$store.state.selected.indexOf(this.index) !== -1) {
|
if (fileStore.selected.indexOf(props.index) !== -1) {
|
||||||
this.removeSelected(this.index);
|
fileStore.removeSelected(props.index);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.shiftKey && this.selected.length > 0) {
|
if ((event as KeyboardEvent).shiftKey && fileStore.selected.length > 0) {
|
||||||
let fi = 0;
|
let fi = 0;
|
||||||
let la = 0;
|
let la = 0;
|
||||||
|
|
||||||
if (this.index > this.selected[0]) {
|
if (props.index > fileStore.selected[0]) {
|
||||||
fi = this.selected[0] + 1;
|
fi = fileStore.selected[0] + 1;
|
||||||
la = this.index;
|
la = props.index;
|
||||||
} else {
|
} else {
|
||||||
fi = this.index;
|
fi = props.index;
|
||||||
la = this.selected[0] - 1;
|
la = fileStore.selected[0] - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; fi <= la; fi++) {
|
for (; fi <= la; fi++) {
|
||||||
if (this.$store.state.selected.indexOf(fi) == -1) {
|
if (fileStore.selected.indexOf(fi) == -1) {
|
||||||
this.addSelected(fi);
|
fileStore.selected.push(fi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,17 +257,17 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!this.singleClick &&
|
!singleClick.value &&
|
||||||
!event.ctrlKey &&
|
!(event as KeyboardEvent).ctrlKey &&
|
||||||
!event.metaKey &&
|
!(event as KeyboardEvent).metaKey &&
|
||||||
!this.$store.state.multiple
|
!fileStore.multiple
|
||||||
)
|
) {
|
||||||
this.resetSelected();
|
fileStore.selected = [];
|
||||||
this.addSelected(this.index);
|
}
|
||||||
},
|
fileStore.selected.push(props.index);
|
||||||
open: function () {
|
};
|
||||||
this.$router.push({ path: this.url });
|
|
||||||
},
|
const open = () => {
|
||||||
},
|
router.push({ path: props.url });
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
104
frontend/src/components/files/VideoPlayer.vue
Normal file
104
frontend/src/components/files/VideoPlayer.vue
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<template>
|
||||||
|
<video ref="videoPlayer" class="video-max video-js" controls>
|
||||||
|
<source :src="source" />
|
||||||
|
<track
|
||||||
|
kind="subtitles"
|
||||||
|
v-for="(sub, index) in subtitles"
|
||||||
|
:key="index"
|
||||||
|
:src="sub"
|
||||||
|
:label="subLabel(sub)"
|
||||||
|
:default="index === 0"
|
||||||
|
/>
|
||||||
|
<p class="vjs-no-js">
|
||||||
|
Sorry, your browser doesn't support embedded videos, but don't worry, you
|
||||||
|
can <a :href="source">download it</a>
|
||||||
|
and watch it with your favorite video player!
|
||||||
|
</p>
|
||||||
|
</video>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onBeforeUnmount } from "vue";
|
||||||
|
import videojs from "video.js";
|
||||||
|
import type Player from "video.js/dist/types/player";
|
||||||
|
import "videojs-mobile-ui";
|
||||||
|
import "videojs-hotkeys";
|
||||||
|
|
||||||
|
import "video.js/dist/video-js.min.css";
|
||||||
|
import "videojs-mobile-ui/dist/videojs-mobile-ui.css";
|
||||||
|
|
||||||
|
const videoPlayer = ref<HTMLElement | null>(null);
|
||||||
|
const player = ref<Player | null>(null);
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
source: string;
|
||||||
|
subtitles?: string[];
|
||||||
|
options?: any;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
options: {},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
player.value = videojs(
|
||||||
|
videoPlayer.value!,
|
||||||
|
{
|
||||||
|
html5: {
|
||||||
|
// needed for customizable subtitles
|
||||||
|
// TODO: add to user settings
|
||||||
|
nativeTextTracks: false,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
hotkeys: {
|
||||||
|
volumeStep: 0.1,
|
||||||
|
seekStep: 10,
|
||||||
|
enableModifiersForNumbers: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...props.options,
|
||||||
|
},
|
||||||
|
// onReady callback
|
||||||
|
async () => {
|
||||||
|
// player.value!.log("onPlayerReady", this);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// TODO: need to test on mobile
|
||||||
|
// @ts-ignore
|
||||||
|
player.value!.mobileUi();
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (player.value) {
|
||||||
|
player.value.dispose();
|
||||||
|
player.value = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const subLabel = (subUrl: string) => {
|
||||||
|
let url: URL;
|
||||||
|
try {
|
||||||
|
url = new URL(subUrl);
|
||||||
|
} catch (_) {
|
||||||
|
// treat it as a relative url
|
||||||
|
// we only need this for filename
|
||||||
|
url = new URL(subUrl, window.location.origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
const label = decodeURIComponent(
|
||||||
|
url.pathname
|
||||||
|
.split("/")
|
||||||
|
.pop()!
|
||||||
|
.replace(/\.[^/.]+$/, "")
|
||||||
|
);
|
||||||
|
|
||||||
|
return label;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.video-max {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
@ -2,24 +2,31 @@
|
|||||||
<button @click="action" :aria-label="label" :title="label" class="action">
|
<button @click="action" :aria-label="label" :title="label" class="action">
|
||||||
<i class="material-icons">{{ icon }}</i>
|
<i class="material-icons">{{ icon }}</i>
|
||||||
<span>{{ label }}</span>
|
<span>{{ label }}</span>
|
||||||
<span v-if="counter > 0" class="counter">{{ counter }}</span>
|
<span v-if="counter && counter > 0" class="counter">{{ counter }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
export default {
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
name: "action",
|
|
||||||
props: ["icon", "label", "counter", "show"],
|
const props = defineProps<{
|
||||||
methods: {
|
icon?: string;
|
||||||
action: function () {
|
label?: string;
|
||||||
if (this.show) {
|
counter?: number;
|
||||||
this.$store.commit("showHover", this.show);
|
show?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "action"): any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const layoutStore = useLayoutStore();
|
||||||
|
|
||||||
|
const action = () => {
|
||||||
|
if (props.show) {
|
||||||
|
layoutStore.showHover(props.show);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit("action");
|
emit("action");
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style></style>
|
|
||||||
|
@ -1,62 +1,59 @@
|
|||||||
<template>
|
<template>
|
||||||
<header>
|
<header>
|
||||||
<img v-if="showLogo !== undefined" :src="logoURL" />
|
<img v-if="showLogo" :src="logoURL" />
|
||||||
<action
|
<Action
|
||||||
v-if="showMenu !== undefined"
|
v-if="showMenu"
|
||||||
class="menu-button"
|
class="menu-button"
|
||||||
icon="menu"
|
icon="menu"
|
||||||
:label="$t('buttons.toggleSidebar')"
|
:label="t('buttons.toggleSidebar')"
|
||||||
@action="openSidebar()"
|
@action="layoutStore.showHover('sidebar')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
|
|
||||||
<div id="dropdown" :class="{ active: this.currentPromptName === 'more' }">
|
<div
|
||||||
|
id="dropdown"
|
||||||
|
:class="{ active: layoutStore.currentPromptName === 'more' }"
|
||||||
|
>
|
||||||
<slot name="actions" />
|
<slot name="actions" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<action
|
<Action
|
||||||
v-if="this.$slots.actions"
|
v-if="ifActionsSlot"
|
||||||
id="more"
|
id="more"
|
||||||
icon="more_vert"
|
icon="more_vert"
|
||||||
:label="$t('buttons.more')"
|
:label="t('buttons.more')"
|
||||||
@action="$store.commit('showHover', 'more')"
|
@action="layoutStore.showHover('more')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="overlay"
|
class="overlay"
|
||||||
v-show="this.currentPromptName == 'more'"
|
v-show="layoutStore.currentPromptName == 'more'"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="layoutStore.closeHovers"
|
||||||
/>
|
/>
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
|
||||||
import { logoURL } from "@/utils/constants";
|
import { logoURL } from "@/utils/constants";
|
||||||
|
|
||||||
import Action from "@/components/header/Action.vue";
|
import Action from "@/components/header/Action.vue";
|
||||||
import { mapGetters } from "vuex";
|
import { computed, useSlots } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
export default {
|
defineProps<{
|
||||||
name: "header-bar",
|
showLogo?: boolean;
|
||||||
props: ["showLogo", "showMenu"],
|
showMenu?: boolean;
|
||||||
components: {
|
}>();
|
||||||
Action,
|
|
||||||
},
|
const layoutStore = useLayoutStore();
|
||||||
data: function () {
|
const slots = useSlots();
|
||||||
return {
|
|
||||||
logoURL,
|
const { t } = useI18n();
|
||||||
};
|
|
||||||
},
|
const ifActionsSlot = computed(() => (slots.actions ? true : false));
|
||||||
methods: {
|
|
||||||
openSidebar() {
|
|
||||||
this.$store.commit("showHover", "sidebar");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapGetters(["currentPromptName"]),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style></style>
|
<style></style>
|
||||||
|
21
frontend/src/components/prompts/BaseModal.vue
Normal file
21
frontend/src/components/prompts/BaseModal.vue
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<template>
|
||||||
|
<VueFinalModal
|
||||||
|
overlay-transition="vfm-fade"
|
||||||
|
content-transition="vfm-fade"
|
||||||
|
@closed="layoutStore.closeHovers"
|
||||||
|
:focus-trap="{
|
||||||
|
initialFocus: '#focus-prompt',
|
||||||
|
fallbackFocus: 'div.vfm__content',
|
||||||
|
}"
|
||||||
|
style="z-index: 9999999"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</VueFinalModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { VueFinalModal } from "vue-final-modal";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
|
||||||
|
const layoutStore = useLayoutStore();
|
||||||
|
</script>
|
@ -6,8 +6,11 @@
|
|||||||
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p>{{ $t("prompts.copyMessage") }}</p>
|
<p>{{ $t("prompts.copyMessage") }}</p>
|
||||||
<file-list ref="fileList" @update:selected="(val) => (dest = val)">
|
<file-list
|
||||||
</file-list>
|
ref="fileList"
|
||||||
|
@update:selected="(val) => (dest = val)"
|
||||||
|
tabindex="1"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -28,17 +31,20 @@
|
|||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="$t('buttons.cancel')"
|
||||||
|
tabindex="3"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.cancel") }}
|
{{ $t("buttons.cancel") }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
id="focus-prompt"
|
||||||
class="button button--flat"
|
class="button button--flat"
|
||||||
@click="copy"
|
@click="copy"
|
||||||
:aria-label="$t('buttons.copy')"
|
:aria-label="$t('buttons.copy')"
|
||||||
:title="$t('buttons.copy')"
|
:title="$t('buttons.copy')"
|
||||||
|
tabindex="2"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.copy") }}
|
{{ $t("buttons.copy") }}
|
||||||
</button>
|
</button>
|
||||||
@ -48,7 +54,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from "vuex";
|
import { mapActions, mapState, mapWritableState } from "pinia";
|
||||||
|
import { useFileStore } from "@/stores/file";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
import FileList from "./FileList.vue";
|
import FileList from "./FileList.vue";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
@ -63,8 +72,14 @@ export default {
|
|||||||
dest: null,
|
dest: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: mapState(["req", "selected", "user"]),
|
inject: ["$showError"],
|
||||||
|
computed: {
|
||||||
|
...mapState(useFileStore, ["req", "selected"]),
|
||||||
|
...mapState(useAuthStore, ["user"]),
|
||||||
|
...mapWritableState(useFileStore, ["reload"]),
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
|
||||||
copy: async function (event) {
|
copy: async function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let items = [];
|
let items = [];
|
||||||
@ -87,7 +102,7 @@ export default {
|
|||||||
buttons.success("copy");
|
buttons.success("copy");
|
||||||
|
|
||||||
if (this.$route.path === this.dest) {
|
if (this.$route.path === this.dest) {
|
||||||
this.$store.commit("setReload", true);
|
this.reload = true;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -101,7 +116,7 @@ export default {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.$route.path === this.dest) {
|
if (this.$route.path === this.dest) {
|
||||||
this.$store.commit("closeHovers");
|
this.closeHovers();
|
||||||
action(false, true);
|
action(false, true);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -114,14 +129,14 @@ export default {
|
|||||||
let rename = false;
|
let rename = false;
|
||||||
|
|
||||||
if (conflict) {
|
if (conflict) {
|
||||||
this.$store.commit("showHover", {
|
this.showHover({
|
||||||
prompt: "replace-rename",
|
prompt: "replace-rename",
|
||||||
confirm: (event, option) => {
|
confirm: (event, option) => {
|
||||||
overwrite = option == "overwrite";
|
overwrite = option == "overwrite";
|
||||||
rename = option == "rename";
|
rename = option == "rename";
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.$store.commit("closeHovers");
|
this.closeHovers();
|
||||||
action(overwrite, rename);
|
action(overwrite, rename);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -10,18 +10,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="$t('buttons.cancel')"
|
||||||
|
tabindex="2"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.cancel") }}
|
{{ $t("buttons.cancel") }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
id="focus-prompt"
|
||||||
@click="submit"
|
@click="submit"
|
||||||
class="button button--flat button--red"
|
class="button button--flat button--red"
|
||||||
:aria-label="$t('buttons.delete')"
|
:aria-label="$t('buttons.delete')"
|
||||||
:title="$t('buttons.delete')"
|
:title="$t('buttons.delete')"
|
||||||
|
tabindex="1"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.delete") }}
|
{{ $t("buttons.delete") }}
|
||||||
</button>
|
</button>
|
||||||
@ -30,18 +33,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters, mapMutations, mapState } from "vuex";
|
import { mapActions, mapState, mapWritableState } from "pinia";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
|
import { useFileStore } from "@/stores/file";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "delete",
|
name: "delete",
|
||||||
|
inject: ["$showError"],
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(["isListing", "selectedCount", "currentPrompt"]),
|
...mapState(useFileStore, [
|
||||||
...mapState(["req", "selected"]),
|
"isListing",
|
||||||
|
"selectedCount",
|
||||||
|
"req",
|
||||||
|
"selected",
|
||||||
|
"currentPrompt",
|
||||||
|
]),
|
||||||
|
...mapWritableState(useFileStore, ["reload"]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations(["closeHovers"]),
|
...mapActions(useLayoutStore, ["closeHovers"]),
|
||||||
submit: async function () {
|
submit: async function () {
|
||||||
buttons.loading("delete");
|
buttons.loading("delete");
|
||||||
|
|
||||||
@ -69,11 +81,11 @@ export default {
|
|||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
buttons.success("delete");
|
buttons.success("delete");
|
||||||
this.$store.commit("setReload", true);
|
this.reload = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
buttons.done("delete");
|
buttons.done("delete");
|
||||||
this.$showError(e);
|
this.$showError(e);
|
||||||
if (this.isListing) this.$store.commit("setReload", true);
|
if (this.isListing) this.reload = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
40
frontend/src/components/prompts/DeleteUser.vue
Normal file
40
frontend/src/components/prompts/DeleteUser.vue
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card floating">
|
||||||
|
<div class="card-content">
|
||||||
|
<p>{{ t("prompts.deleteUser") }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-action">
|
||||||
|
<button
|
||||||
|
id="focus-prompt"
|
||||||
|
class="button button--flat button--grey"
|
||||||
|
@click="layoutStore.closeHovers"
|
||||||
|
:aria-label="t('buttons.cancel')"
|
||||||
|
:title="t('buttons.cancel')"
|
||||||
|
tabindex="1"
|
||||||
|
>
|
||||||
|
{{ t("buttons.cancel") }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="button button--flat"
|
||||||
|
@click="layoutStore.currentPrompt?.confirm()"
|
||||||
|
tabindex="2"
|
||||||
|
>
|
||||||
|
{{ t("buttons.delete") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const layoutStore = useLayoutStore();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
// const emit = defineEmits<{
|
||||||
|
// (e: "confirm"): void;
|
||||||
|
// }>();
|
||||||
|
</script>
|
@ -7,18 +7,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
@click="$store.commit('closeHovers')"
|
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
|
@click="closeHovers"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="$t('buttons.cancel')"
|
||||||
|
tabindex="2"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.cancel") }}
|
{{ $t("buttons.cancel") }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
id="focus-prompt"
|
||||||
@click="submit"
|
@click="submit"
|
||||||
class="button button--flat button--red"
|
class="button button--flat button--red"
|
||||||
:aria-label="$t('buttons.discardChanges')"
|
:aria-label="$t('buttons.discardChanges')"
|
||||||
:title="$t('buttons.discardChanges')"
|
:title="$t('buttons.discardChanges')"
|
||||||
|
tabindex="1"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.discardChanges") }}
|
{{ $t("buttons.discardChanges") }}
|
||||||
</button>
|
</button>
|
||||||
@ -27,15 +30,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapMutations } from "vuex";
|
import { mapActions } from "pinia";
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
import { useFileStore } from "@/stores/file";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "discardEditorChanges",
|
name: "discardEditorChanges",
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations(["closeHovers"]),
|
...mapActions(useLayoutStore, ["closeHovers"]),
|
||||||
|
...mapActions(useFileStore, ["updateRequest"]),
|
||||||
submit: async function () {
|
submit: async function () {
|
||||||
this.$store.commit("updateRequest", {});
|
this.updateRequest(null);
|
||||||
|
|
||||||
let uri = url.removeLastDir(this.$route.path) + "/";
|
let uri = url.removeLastDir(this.$route.path) + "/";
|
||||||
this.$router.push({ path: uri });
|
this.$router.push({ path: uri });
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="card floating" id="download">
|
<div class="card floating" id="download">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>{{ $t("prompts.download") }}</h2>
|
<h2>{{ t("prompts.download") }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p>{{ $t("prompts.downloadMessage") }}</p>
|
<p>{{ t("prompts.downloadMessage") }}</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
id="focus-prompt"
|
||||||
v-for="(ext, format) in formats"
|
v-for="(ext, format) in formats"
|
||||||
:key="format"
|
:key="format"
|
||||||
class="button button--block"
|
class="button button--block"
|
||||||
@click="currentPrompt.confirm(format)"
|
@click="layoutStore.currentPrompt?.confirm(format)"
|
||||||
v-focus
|
|
||||||
>
|
>
|
||||||
{{ ext }}
|
{{ ext }}
|
||||||
</button>
|
</button>
|
||||||
@ -20,14 +20,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { mapGetters } from "vuex";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
|
||||||
export default {
|
const layoutStore = useLayoutStore();
|
||||||
name: "download",
|
|
||||||
data: function () {
|
const { t } = useI18n();
|
||||||
return {
|
|
||||||
formats: {
|
const formats = {
|
||||||
zip: "zip",
|
zip: "zip",
|
||||||
tar: "tar",
|
tar: "tar",
|
||||||
targz: "tar.gz",
|
targz: "tar.gz",
|
||||||
@ -35,11 +36,5 @@ export default {
|
|||||||
tarxz: "tar.xz",
|
tarxz: "tar.xz",
|
||||||
tarlz4: "tar.lz4",
|
tarlz4: "tar.lz4",
|
||||||
tarsz: "tar.sz",
|
tarsz: "tar.sz",
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapGetters(["currentPrompt"]),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -25,7 +25,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from "vuex";
|
import { mapState } from "pinia";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
import { useFileStore } from "@/stores/file";
|
||||||
|
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
import { files } from "@/api";
|
import { files } from "@/api";
|
||||||
|
|
||||||
@ -42,8 +45,10 @@ export default {
|
|||||||
current: window.location.pathname,
|
current: window.location.pathname,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
inject: ["$showError"],
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["req", "user"]),
|
...mapState(useAuthStore, ["user"]),
|
||||||
|
...mapState(useFileStore, ["req"]),
|
||||||
nav() {
|
nav() {
|
||||||
return decodeURIComponent(this.current);
|
return decodeURIComponent(this.current);
|
||||||
},
|
},
|
||||||
|
@ -20,11 +20,13 @@
|
|||||||
|
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
|
id="focus-prompt"
|
||||||
type="submit"
|
type="submit"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
class="button button--flat"
|
class="button button--flat"
|
||||||
:aria-label="$t('buttons.ok')"
|
:aria-label="$t('buttons.ok')"
|
||||||
:title="$t('buttons.ok')"
|
:title="$t('buttons.ok')"
|
||||||
|
tabindex="1"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.ok") }}
|
{{ $t("buttons.ok") }}
|
||||||
</button>
|
</button>
|
||||||
@ -33,5 +35,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default { name: "help" };
|
import { mapActions } from "pinia";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "help",
|
||||||
|
methods: {
|
||||||
|
...mapActions(useLayoutStore, ["closeHovers"]),
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -40,33 +40,45 @@
|
|||||||
<p>
|
<p>
|
||||||
<strong>MD5: </strong
|
<strong>MD5: </strong
|
||||||
><code
|
><code
|
||||||
><a @click="checksum($event, 'md5')">{{
|
><a
|
||||||
$t("prompts.show")
|
@click="checksum($event, 'md5')"
|
||||||
}}</a></code
|
@keypress.enter="checksum($event, 'md5')"
|
||||||
|
tabindex="2"
|
||||||
|
>{{ $t("prompts.show") }}</a
|
||||||
|
></code
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>SHA1: </strong
|
<strong>SHA1: </strong
|
||||||
><code
|
><code
|
||||||
><a @click="checksum($event, 'sha1')">{{
|
><a
|
||||||
$t("prompts.show")
|
@click="checksum($event, 'sha1')"
|
||||||
}}</a></code
|
@keypress.enter="checksum($event, 'sha1')"
|
||||||
|
tabindex="3"
|
||||||
|
>{{ $t("prompts.show") }}</a
|
||||||
|
></code
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>SHA256: </strong
|
<strong>SHA256: </strong
|
||||||
><code
|
><code
|
||||||
><a @click="checksum($event, 'sha256')">{{
|
><a
|
||||||
$t("prompts.show")
|
@click="checksum($event, 'sha256')"
|
||||||
}}</a></code
|
@keypress.enter="checksum($event, 'sha256')"
|
||||||
|
tabindex="4"
|
||||||
|
>{{ $t("prompts.show") }}</a
|
||||||
|
></code
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>SHA512: </strong
|
<strong>SHA512: </strong
|
||||||
><code
|
><code
|
||||||
><a @click="checksum($event, 'sha512')">{{
|
><a
|
||||||
$t("prompts.show")
|
@click="checksum($event, 'sha512')"
|
||||||
}}</a></code
|
@keypress.enter="checksum($event, 'sha512')"
|
||||||
|
tabindex="5"
|
||||||
|
>{{ $t("prompts.show") }}</a
|
||||||
|
></code
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
@ -74,8 +86,9 @@
|
|||||||
|
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
|
id="focus-prompt"
|
||||||
type="submit"
|
type="submit"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
class="button button--flat"
|
class="button button--flat"
|
||||||
:aria-label="$t('buttons.ok')"
|
:aria-label="$t('buttons.ok')"
|
||||||
:title="$t('buttons.ok')"
|
:title="$t('buttons.ok')"
|
||||||
@ -87,16 +100,23 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapGetters } from "vuex";
|
import { mapActions, mapState } from "pinia";
|
||||||
|
import { useFileStore } from "@/stores/file";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
import { filesize } from "@/utils";
|
import { filesize } from "@/utils";
|
||||||
import moment from "moment/min/moment-with-locales";
|
import dayjs from "dayjs";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "info",
|
name: "info",
|
||||||
|
inject: ["$showError"],
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["req", "selected"]),
|
...mapState(useFileStore, [
|
||||||
...mapGetters(["selectedCount", "isListing"]),
|
"req",
|
||||||
|
"selected",
|
||||||
|
"selectedCount",
|
||||||
|
"isListing",
|
||||||
|
]),
|
||||||
humanSize: function () {
|
humanSize: function () {
|
||||||
if (this.selectedCount === 0 || !this.isListing) {
|
if (this.selectedCount === 0 || !this.isListing) {
|
||||||
return filesize(this.req.size);
|
return filesize(this.req.size);
|
||||||
@ -112,13 +132,19 @@ export default {
|
|||||||
},
|
},
|
||||||
humanTime: function () {
|
humanTime: function () {
|
||||||
if (this.selectedCount === 0) {
|
if (this.selectedCount === 0) {
|
||||||
return moment(this.req.modified).fromNow();
|
return dayjs(this.req.modified).fromNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
return moment(this.req.items[this.selected[0]].modified).fromNow();
|
return dayjs(this.req.items[this.selected[0]].modified).fromNow();
|
||||||
},
|
},
|
||||||
modTime: function () {
|
modTime: function () {
|
||||||
|
if (this.selectedCount === 0) {
|
||||||
return new Date(Date.parse(this.req.modified)).toLocaleString();
|
return new Date(Date.parse(this.req.modified)).toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date(
|
||||||
|
Date.parse(this.req.items[this.selected[0]].modified)
|
||||||
|
).toLocaleString();
|
||||||
},
|
},
|
||||||
name: function () {
|
name: function () {
|
||||||
return this.selectedCount === 0
|
return this.selectedCount === 0
|
||||||
@ -146,6 +172,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions(useLayoutStore, ["closeHovers"]),
|
||||||
checksum: async function (event, algo) {
|
checksum: async function (event, algo) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
@ -159,8 +186,7 @@ export default {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const hash = await api.checksum(link, algo);
|
const hash = await api.checksum(link, algo);
|
||||||
// eslint-disable-next-line
|
event.target.textContent = hash;
|
||||||
event.target.innerHTML = hash;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$showError(e);
|
this.$showError(e);
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<file-list ref="fileList" @update:selected="(val) => (dest = val)">
|
<file-list
|
||||||
</file-list>
|
ref="fileList"
|
||||||
|
@update:selected="(val) => (dest = val)"
|
||||||
|
tabindex="1"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -27,18 +30,21 @@
|
|||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="$t('buttons.cancel')"
|
||||||
|
tabindex="3"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.cancel") }}
|
{{ $t("buttons.cancel") }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
id="focus-prompt"
|
||||||
class="button button--flat"
|
class="button button--flat"
|
||||||
@click="move"
|
@click="move"
|
||||||
:disabled="$route.path === dest"
|
:disabled="$route.path === dest"
|
||||||
:aria-label="$t('buttons.move')"
|
:aria-label="$t('buttons.move')"
|
||||||
:title="$t('buttons.move')"
|
:title="$t('buttons.move')"
|
||||||
|
tabindex="2"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.move") }}
|
{{ $t("buttons.move") }}
|
||||||
</button>
|
</button>
|
||||||
@ -48,7 +54,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from "vuex";
|
import { mapActions, mapState } from "pinia";
|
||||||
|
import { useFileStore } from "@/stores/file";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
import FileList from "./FileList.vue";
|
import FileList from "./FileList.vue";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
@ -63,8 +72,13 @@ export default {
|
|||||||
dest: null,
|
dest: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: mapState(["req", "selected", "user"]),
|
inject: ["$showError"],
|
||||||
|
computed: {
|
||||||
|
...mapState(useFileStore, ["req", "selected"]),
|
||||||
|
...mapState(useAuthStore, ["user"]),
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
|
||||||
move: async function (event) {
|
move: async function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let items = [];
|
let items = [];
|
||||||
@ -99,14 +113,14 @@ export default {
|
|||||||
let rename = false;
|
let rename = false;
|
||||||
|
|
||||||
if (conflict) {
|
if (conflict) {
|
||||||
this.$store.commit("showHover", {
|
this.showHover({
|
||||||
prompt: "replace-rename",
|
prompt: "replace-rename",
|
||||||
confirm: (event, option) => {
|
confirm: (event, option) => {
|
||||||
overwrite = option == "overwrite";
|
overwrite = option == "overwrite";
|
||||||
rename = option == "rename";
|
rename = option == "rename";
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.$store.commit("closeHovers");
|
this.closeHovers();
|
||||||
action(overwrite, rename);
|
action(overwrite, rename);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,98 +1,104 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="card floating">
|
<div class="card floating">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>{{ $t("prompts.newDir") }}</h2>
|
<h2>{{ t("prompts.newDir") }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p>{{ $t("prompts.newDirMessage") }}</p>
|
<p>{{ t("prompts.newDirMessage") }}</p>
|
||||||
<input
|
<input
|
||||||
|
id="focus-prompt"
|
||||||
class="input input--block"
|
class="input input--block"
|
||||||
type="text"
|
type="text"
|
||||||
@keyup.enter="submit"
|
@keyup.enter="submit"
|
||||||
v-model.trim="name"
|
v-model.trim="name"
|
||||||
v-focus
|
tabindex="1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="layoutStore.closeHovers"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="t('buttons.cancel')"
|
||||||
|
tabindex="3"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.cancel") }}
|
{{ t("buttons.cancel") }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="button button--flat"
|
class="button button--flat"
|
||||||
:aria-label="$t('buttons.create')"
|
:aria-label="$t('buttons.create')"
|
||||||
:title="$t('buttons.create')"
|
:title="t('buttons.create')"
|
||||||
@click="submit"
|
@click="submit"
|
||||||
|
tabindex="2"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.create") }}
|
{{ t("buttons.create") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { mapGetters } from "vuex";
|
import { inject, ref } from "vue";
|
||||||
|
import { useFileStore } from "@/stores/file";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
export default {
|
const $showError = inject<IToastError>("$showError")!;
|
||||||
name: "new-dir",
|
|
||||||
props: {
|
const props = defineProps({
|
||||||
|
base: String,
|
||||||
redirect: {
|
redirect: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
base: {
|
});
|
||||||
type: [String, null],
|
|
||||||
default: null,
|
const fileStore = useFileStore();
|
||||||
},
|
const layoutStore = useLayoutStore();
|
||||||
},
|
|
||||||
data: function () {
|
const route = useRoute();
|
||||||
return {
|
const router = useRouter();
|
||||||
name: "",
|
const { t } = useI18n();
|
||||||
};
|
|
||||||
},
|
const name = ref<string>("");
|
||||||
computed: {
|
|
||||||
...mapGetters(["isFiles", "isListing"]),
|
const submit = async (event: Event) => {
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
submit: async function (event) {
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (this.new === "") return;
|
if (name.value === "") return;
|
||||||
|
|
||||||
// Build the path of the new directory.
|
// Build the path of the new directory.
|
||||||
let uri;
|
let uri: string;
|
||||||
|
if (props.base) uri = props.base;
|
||||||
if (this.base) uri = this.base;
|
else if (fileStore.isFiles) uri = route.path + "/";
|
||||||
else if (this.isFiles) uri = this.$route.path + "/";
|
|
||||||
else uri = "/";
|
else uri = "/";
|
||||||
|
|
||||||
if (!this.isListing) {
|
if (!fileStore.isListing) {
|
||||||
uri = url.removeLastDir(uri) + "/";
|
uri = url.removeLastDir(uri) + "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
uri += encodeURIComponent(this.name) + "/";
|
uri += encodeURIComponent(name.value) + "/";
|
||||||
uri = uri.replace("//", "/");
|
uri = uri.replace("//", "/");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.post(uri);
|
await api.post(uri);
|
||||||
if (this.redirect) {
|
if (props.redirect) {
|
||||||
this.$router.push({ path: uri });
|
router.push({ path: uri });
|
||||||
} else if (!this.base) {
|
} else if (!props.base) {
|
||||||
const res = await api.fetch(url.removeLastDir(uri) + "/");
|
const res = await api.fetch(url.removeLastDir(uri) + "/");
|
||||||
this.$store.commit("updateRequest", res);
|
fileStore.updateRequest(res);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$showError(e);
|
if (e instanceof Error) {
|
||||||
|
$showError(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.commit("closeHovers");
|
layoutStore.closeHovers();
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="card floating">
|
<div class="card floating">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>{{ $t("prompts.newFile") }}</h2>
|
<h2>{{ t("prompts.newFile") }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p>{{ $t("prompts.newFileMessage") }}</p>
|
<p>{{ t("prompts.newFileMessage") }}</p>
|
||||||
<input
|
<input
|
||||||
|
id="focus-prompt"
|
||||||
class="input input--block"
|
class="input input--block"
|
||||||
v-focus
|
|
||||||
type="text"
|
type="text"
|
||||||
@keyup.enter="submit"
|
@keyup.enter="submit"
|
||||||
v-model.trim="name"
|
v-model.trim="name"
|
||||||
@ -18,63 +18,68 @@
|
|||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="layoutStore.closeHovers"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="t('buttons.cancel')"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.cancel") }}
|
{{ t("buttons.cancel") }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="button button--flat"
|
class="button button--flat"
|
||||||
@click="submit"
|
@click="submit"
|
||||||
:aria-label="$t('buttons.create')"
|
:aria-label="t('buttons.create')"
|
||||||
:title="$t('buttons.create')"
|
:title="t('buttons.create')"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.create") }}
|
{{ t("buttons.create") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { mapGetters } from "vuex";
|
import { inject, ref } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import { useFileStore } from "@/stores/file";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
|
|
||||||
export default {
|
const $showError = inject<IToastError>("$showError")!;
|
||||||
name: "new-file",
|
|
||||||
data: function () {
|
const fileStore = useFileStore();
|
||||||
return {
|
const layoutStore = useLayoutStore();
|
||||||
name: "",
|
|
||||||
};
|
const route = useRoute();
|
||||||
},
|
const router = useRouter();
|
||||||
computed: {
|
const { t } = useI18n();
|
||||||
...mapGetters(["isFiles", "isListing"]),
|
|
||||||
},
|
const name = ref<string>("");
|
||||||
methods: {
|
|
||||||
submit: async function (event) {
|
const submit = async (event: Event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (this.new === "") return;
|
if (name.value === "") return;
|
||||||
|
|
||||||
// Build the path of the new directory.
|
// Build the path of the new directory.
|
||||||
let uri = this.isFiles ? this.$route.path + "/" : "/";
|
let uri = fileStore.isFiles ? route.path + "/" : "/";
|
||||||
|
|
||||||
if (!this.isListing) {
|
if (!fileStore.isListing) {
|
||||||
uri = url.removeLastDir(uri) + "/";
|
uri = url.removeLastDir(uri) + "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
uri += encodeURIComponent(this.name);
|
uri += encodeURIComponent(name.value);
|
||||||
uri = uri.replace("//", "/");
|
uri = uri.replace("//", "/");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.post(uri);
|
await api.post(uri);
|
||||||
this.$router.push({ path: uri });
|
router.push({ path: uri });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$showError(e);
|
if (e instanceof Error) {
|
||||||
|
$showError(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.commit("closeHovers");
|
layoutStore.closeHovers();
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,22 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<ModalsContainer />
|
||||||
<component
|
|
||||||
v-if="showOverlay"
|
|
||||||
:ref="currentPromptName"
|
|
||||||
:is="currentPromptName"
|
|
||||||
v-bind="currentPrompt.props"
|
|
||||||
>
|
|
||||||
</component>
|
|
||||||
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from "vue";
|
||||||
|
import { ModalsContainer, useModal } from "vue-final-modal";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
|
||||||
|
import BaseModal from "./BaseModal.vue";
|
||||||
import Help from "./Help.vue";
|
import Help from "./Help.vue";
|
||||||
import Info from "./Info.vue";
|
import Info from "./Info.vue";
|
||||||
import Delete from "./Delete.vue";
|
import Delete from "./Delete.vue";
|
||||||
import Rename from "./Rename.vue";
|
import DeleteUser from "./DeleteUser.vue";
|
||||||
import Download from "./Download.vue";
|
import Download from "./Download.vue";
|
||||||
|
import Rename from "./Rename.vue";
|
||||||
import Move from "./Move.vue";
|
import Move from "./Move.vue";
|
||||||
import Copy from "./Copy.vue";
|
import Copy from "./Copy.vue";
|
||||||
import NewFile from "./NewFile.vue";
|
import NewFile from "./NewFile.vue";
|
||||||
@ -24,87 +22,61 @@ import NewDir from "./NewDir.vue";
|
|||||||
import Replace from "./Replace.vue";
|
import Replace from "./Replace.vue";
|
||||||
import ReplaceRename from "./ReplaceRename.vue";
|
import ReplaceRename from "./ReplaceRename.vue";
|
||||||
import Share from "./Share.vue";
|
import Share from "./Share.vue";
|
||||||
import Upload from "./Upload.vue";
|
|
||||||
import ShareDelete from "./ShareDelete.vue";
|
import ShareDelete from "./ShareDelete.vue";
|
||||||
import Sidebar from "../Sidebar.vue";
|
import Upload from "./Upload.vue";
|
||||||
import DiscardEditorChanges from "./DiscardEditorChanges.vue";
|
import DiscardEditorChanges from "./DiscardEditorChanges.vue";
|
||||||
import { mapGetters, mapState } from "vuex";
|
|
||||||
import buttons from "@/utils/buttons";
|
|
||||||
|
|
||||||
export default {
|
const layoutStore = useLayoutStore();
|
||||||
name: "prompts",
|
|
||||||
components: {
|
const { currentPromptName } = storeToRefs(layoutStore);
|
||||||
Info,
|
|
||||||
Delete,
|
const closeModal = ref<() => Promise<string>>();
|
||||||
Rename,
|
|
||||||
Download,
|
const components = new Map<string, any>([
|
||||||
Move,
|
["info", Info],
|
||||||
Copy,
|
["help", Help],
|
||||||
Share,
|
["delete", Delete],
|
||||||
NewFile,
|
["rename", Rename],
|
||||||
NewDir,
|
["move", Move],
|
||||||
Help,
|
["copy", Copy],
|
||||||
Replace,
|
["newFile", NewFile],
|
||||||
ReplaceRename,
|
["newDir", NewDir],
|
||||||
Upload,
|
["download", Download],
|
||||||
ShareDelete,
|
["replace", Replace],
|
||||||
Sidebar,
|
["replace-rename", ReplaceRename],
|
||||||
DiscardEditorChanges,
|
["share", Share],
|
||||||
|
["upload", Upload],
|
||||||
|
["share-delete", ShareDelete],
|
||||||
|
["deleteUser", DeleteUser],
|
||||||
|
["discardEditorChanges", DiscardEditorChanges],
|
||||||
|
]);
|
||||||
|
|
||||||
|
watch(currentPromptName, (newValue) => {
|
||||||
|
if (closeModal.value) {
|
||||||
|
closeModal.value();
|
||||||
|
closeModal.value = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modal = components.get(newValue!);
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
const { open, close } = useModal({
|
||||||
|
component: BaseModal,
|
||||||
|
slots: {
|
||||||
|
default: modal,
|
||||||
},
|
},
|
||||||
data: function () {
|
});
|
||||||
return {
|
|
||||||
pluginData: {
|
closeModal.value = close;
|
||||||
buttons,
|
open();
|
||||||
store: this.$store,
|
});
|
||||||
router: this.$router,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
window.addEventListener("keydown", (event) => {
|
window.addEventListener("keydown", (event) => {
|
||||||
if (this.currentPrompt == null) return;
|
if (!layoutStore.currentPrompt) return;
|
||||||
|
|
||||||
const promptName = this.currentPrompt.prompt;
|
if (event.key === "Escape") {
|
||||||
const prompt = this.$refs[promptName];
|
|
||||||
|
|
||||||
if (event.code === "Escape") {
|
|
||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
this.$store.commit("closeHovers");
|
layoutStore.closeHovers();
|
||||||
}
|
|
||||||
|
|
||||||
if (event.code === "Enter") {
|
|
||||||
switch (promptName) {
|
|
||||||
case "delete":
|
|
||||||
prompt.submit();
|
|
||||||
break;
|
|
||||||
case "copy":
|
|
||||||
prompt.copy(event);
|
|
||||||
break;
|
|
||||||
case "move":
|
|
||||||
prompt.move(event);
|
|
||||||
break;
|
|
||||||
case "replace":
|
|
||||||
prompt.showConfirm(event);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(["plugins"]),
|
|
||||||
...mapGetters(["currentPrompt", "currentPromptName"]),
|
|
||||||
showOverlay: function () {
|
|
||||||
return (
|
|
||||||
this.currentPrompt !== null &&
|
|
||||||
this.currentPrompt.prompt !== "search" &&
|
|
||||||
this.currentPrompt.prompt !== "more"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
resetPrompts() {
|
|
||||||
this.$store.commit("closeHovers");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
>:
|
>:
|
||||||
</p>
|
</p>
|
||||||
<input
|
<input
|
||||||
|
id="focus-prompt"
|
||||||
class="input input--block"
|
class="input input--block"
|
||||||
v-focus
|
|
||||||
type="text"
|
type="text"
|
||||||
@keyup.enter="submit"
|
@keyup.enter="submit"
|
||||||
v-model.trim="name"
|
v-model.trim="name"
|
||||||
@ -21,7 +21,7 @@
|
|||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="$t('buttons.cancel')"
|
||||||
>
|
>
|
||||||
@ -41,7 +41,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapGetters } from "vuex";
|
import { mapActions, mapState, mapWritableState } from "pinia";
|
||||||
|
import { useFileStore } from "@/stores/file";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
import url from "@/utils/url";
|
import url from "@/utils/url";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
|
|
||||||
@ -55,13 +57,20 @@ export default {
|
|||||||
created() {
|
created() {
|
||||||
this.name = this.oldName();
|
this.name = this.oldName();
|
||||||
},
|
},
|
||||||
|
inject: ["$showError"],
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["req", "selected", "selectedCount"]),
|
...mapState(useFileStore, [
|
||||||
...mapGetters(["isListing"]),
|
"req",
|
||||||
|
"selected",
|
||||||
|
"selectedCount",
|
||||||
|
"isListing",
|
||||||
|
]),
|
||||||
|
...mapWritableState(useFileStore, ["reload"]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions(useLayoutStore, ["closeHovers"]),
|
||||||
cancel: function () {
|
cancel: function () {
|
||||||
this.$store.commit("closeHovers");
|
this.closeHovers();
|
||||||
},
|
},
|
||||||
oldName: function () {
|
oldName: function () {
|
||||||
if (!this.isListing) {
|
if (!this.isListing) {
|
||||||
@ -96,12 +105,12 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.commit("setReload", true);
|
this.reload = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$showError(e);
|
this.$showError(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.commit("closeHovers");
|
this.closeHovers();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -11,9 +11,10 @@
|
|||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="$t('buttons.cancel')"
|
||||||
|
tabindex="3"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.cancel") }}
|
{{ $t("buttons.cancel") }}
|
||||||
</button>
|
</button>
|
||||||
@ -22,14 +23,17 @@
|
|||||||
@click="currentPrompt.action"
|
@click="currentPrompt.action"
|
||||||
:aria-label="$t('buttons.continue')"
|
:aria-label="$t('buttons.continue')"
|
||||||
:title="$t('buttons.continue')"
|
:title="$t('buttons.continue')"
|
||||||
|
tabindex="2"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.continue") }}
|
{{ $t("buttons.continue") }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
id="focus-prompt"
|
||||||
class="button button--flat button--red"
|
class="button button--flat button--red"
|
||||||
@click="currentPrompt.confirm"
|
@click="currentPrompt.confirm"
|
||||||
:aria-label="$t('buttons.replace')"
|
:aria-label="$t('buttons.replace')"
|
||||||
:title="$t('buttons.replace')"
|
:title="$t('buttons.replace')"
|
||||||
|
tabindex="1"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.replace") }}
|
{{ $t("buttons.replace") }}
|
||||||
</button>
|
</button>
|
||||||
@ -38,10 +42,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from "vuex";
|
import { mapActions, mapState } from "pinia";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "replace",
|
name: "replace",
|
||||||
computed: mapGetters(["currentPrompt"]),
|
computed: {
|
||||||
|
...mapState(useLayoutStore, ["currentPrompt"]),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(useLayoutStore, ["closeHovers"]),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -11,9 +11,10 @@
|
|||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="$t('buttons.cancel')"
|
||||||
|
tabindex="3"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.cancel") }}
|
{{ $t("buttons.cancel") }}
|
||||||
</button>
|
</button>
|
||||||
@ -22,14 +23,17 @@
|
|||||||
@click="(event) => currentPrompt.confirm(event, 'rename')"
|
@click="(event) => currentPrompt.confirm(event, 'rename')"
|
||||||
:aria-label="$t('buttons.rename')"
|
:aria-label="$t('buttons.rename')"
|
||||||
:title="$t('buttons.rename')"
|
:title="$t('buttons.rename')"
|
||||||
|
tabindex="2"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.rename") }}
|
{{ $t("buttons.rename") }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
id="focus-prompt"
|
||||||
class="button button--flat button--red"
|
class="button button--flat button--red"
|
||||||
@click="(event) => currentPrompt.confirm(event, 'overwrite')"
|
@click="(event) => currentPrompt.confirm(event, 'overwrite')"
|
||||||
:aria-label="$t('buttons.replace')"
|
:aria-label="$t('buttons.replace')"
|
||||||
:title="$t('buttons.replace')"
|
:title="$t('buttons.replace')"
|
||||||
|
tabindex="1"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.replace") }}
|
{{ $t("buttons.replace") }}
|
||||||
</button>
|
</button>
|
||||||
@ -38,10 +42,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from "vuex";
|
import { mapActions, mapState } from "pinia";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "replace-rename",
|
name: "replace-rename",
|
||||||
computed: mapGetters(["currentPrompt"]),
|
computed: {
|
||||||
|
...mapState(useLayoutStore, ["currentPrompt"]),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(useLayoutStore, ["closeHovers"]),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="card floating share__promt__card" id="share">
|
<div class="card floating" id="share">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>{{ $t("buttons.share") }}</h2>
|
<h2>{{ $t("buttons.share") }}</h2>
|
||||||
</div>
|
</div>
|
||||||
@ -25,9 +25,9 @@
|
|||||||
<td class="small">
|
<td class="small">
|
||||||
<button
|
<button
|
||||||
class="action copy-clipboard"
|
class="action copy-clipboard"
|
||||||
:data-clipboard-text="buildLink(link)"
|
|
||||||
:aria-label="$t('buttons.copyToClipboard')"
|
:aria-label="$t('buttons.copyToClipboard')"
|
||||||
:title="$t('buttons.copyToClipboard')"
|
:title="$t('buttons.copyToClipboard')"
|
||||||
|
@click="copyToClipboard(buildLink(link))"
|
||||||
>
|
>
|
||||||
<i class="material-icons">content_paste</i>
|
<i class="material-icons">content_paste</i>
|
||||||
</button>
|
</button>
|
||||||
@ -35,9 +35,9 @@
|
|||||||
<td class="small" v-if="hasDownloadLink()">
|
<td class="small" v-if="hasDownloadLink()">
|
||||||
<button
|
<button
|
||||||
class="action copy-clipboard"
|
class="action copy-clipboard"
|
||||||
:data-clipboard-text="buildDownloadLink(link)"
|
|
||||||
:aria-label="$t('buttons.copyDownloadLinkToClipboard')"
|
:aria-label="$t('buttons.copyDownloadLinkToClipboard')"
|
||||||
:title="$t('buttons.copyDownloadLinkToClipboard')"
|
:title="$t('buttons.copyDownloadLinkToClipboard')"
|
||||||
|
@click="copyToClipboard(buildDownloadLink(link))"
|
||||||
>
|
>
|
||||||
<i class="material-icons">content_paste_go</i>
|
<i class="material-icons">content_paste_go</i>
|
||||||
</button>
|
</button>
|
||||||
@ -59,17 +59,20 @@
|
|||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
:aria-label="$t('buttons.close')"
|
:aria-label="$t('buttons.close')"
|
||||||
:title="$t('buttons.close')"
|
:title="$t('buttons.close')"
|
||||||
|
tabindex="2"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.close") }}
|
{{ $t("buttons.close") }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
id="focus-prompt"
|
||||||
class="button button--flat button--blue"
|
class="button button--flat button--blue"
|
||||||
@click="() => switchListing()"
|
@click="() => switchListing()"
|
||||||
:aria-label="$t('buttons.new')"
|
:aria-label="$t('buttons.new')"
|
||||||
:title="$t('buttons.new')"
|
:title="$t('buttons.new')"
|
||||||
|
tabindex="1"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.new") }}
|
{{ $t("buttons.new") }}
|
||||||
</button>
|
</button>
|
||||||
@ -80,15 +83,22 @@
|
|||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p>{{ $t("settings.shareDuration") }}</p>
|
<p>{{ $t("settings.shareDuration") }}</p>
|
||||||
<div class="input-group input">
|
<div class="input-group input">
|
||||||
<input
|
<vue-number-input
|
||||||
v-focus
|
center
|
||||||
type="number"
|
controls
|
||||||
max="2147483647"
|
size="small"
|
||||||
min="1"
|
:max="2147483647"
|
||||||
|
:min="0"
|
||||||
@keyup.enter="submit"
|
@keyup.enter="submit"
|
||||||
v-model.trim="time"
|
v-model="time"
|
||||||
|
tabindex="1"
|
||||||
/>
|
/>
|
||||||
<select class="right" v-model="unit" :aria-label="$t('time.unit')">
|
<select
|
||||||
|
class="right"
|
||||||
|
v-model="unit"
|
||||||
|
:aria-label="$t('time.unit')"
|
||||||
|
tabindex="2"
|
||||||
|
>
|
||||||
<option value="seconds">{{ $t("time.seconds") }}</option>
|
<option value="seconds">{{ $t("time.seconds") }}</option>
|
||||||
<option value="minutes">{{ $t("time.minutes") }}</option>
|
<option value="minutes">{{ $t("time.minutes") }}</option>
|
||||||
<option value="hours">{{ $t("time.hours") }}</option>
|
<option value="hours">{{ $t("time.hours") }}</option>
|
||||||
@ -100,6 +110,7 @@
|
|||||||
class="input input--block"
|
class="input input--block"
|
||||||
type="password"
|
type="password"
|
||||||
v-model.trim="password"
|
v-model.trim="password"
|
||||||
|
tabindex="3"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -109,14 +120,17 @@
|
|||||||
@click="() => switchListing()"
|
@click="() => switchListing()"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="$t('buttons.cancel')"
|
||||||
|
tabindex="5"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.cancel") }}
|
{{ $t("buttons.cancel") }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
id="focus-prompt"
|
||||||
class="button button--flat button--blue"
|
class="button button--flat button--blue"
|
||||||
@click="submit"
|
@click="submit"
|
||||||
:aria-label="$t('buttons.share')"
|
:aria-label="$t('buttons.share')"
|
||||||
:title="$t('buttons.share')"
|
:title="$t('buttons.share')"
|
||||||
|
tabindex="4"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.share") }}
|
{{ $t("buttons.share") }}
|
||||||
</button>
|
</button>
|
||||||
@ -126,16 +140,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapGetters } from "vuex";
|
import { mapActions, mapState } from "pinia";
|
||||||
|
import { useFileStore } from "@/stores/file";
|
||||||
import { share as api, pub as pub_api } from "@/api";
|
import { share as api, pub as pub_api } from "@/api";
|
||||||
import moment from "moment/min/moment-with-locales";
|
import dayjs from "dayjs";
|
||||||
import Clipboard from "clipboard";
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
import { copy } from "@/utils/clipboard";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "share",
|
name: "share",
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
time: "",
|
time: 0,
|
||||||
unit: "hours",
|
unit: "hours",
|
||||||
links: [],
|
links: [],
|
||||||
clip: null,
|
clip: null,
|
||||||
@ -143,9 +159,14 @@ export default {
|
|||||||
listing: true,
|
listing: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
inject: ["$showError", "$showSuccess"],
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["req", "selected", "selectedCount"]),
|
...mapState(useFileStore, [
|
||||||
...mapGetters(["isListing"]),
|
"req",
|
||||||
|
"selected",
|
||||||
|
"selectedCount",
|
||||||
|
"isListing",
|
||||||
|
]),
|
||||||
url() {
|
url() {
|
||||||
if (!this.isListing) {
|
if (!this.isListing) {
|
||||||
return this.$route.path;
|
return this.$route.path;
|
||||||
@ -172,23 +193,24 @@ export default {
|
|||||||
this.$showError(e);
|
this.$showError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
|
||||||
this.clip = new Clipboard(".copy-clipboard");
|
|
||||||
this.clip.on("success", () => {
|
|
||||||
this.$showSuccess(this.$t("success.linkCopied"));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.clip.destroy();
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions(useLayoutStore, ["closeHovers"]),
|
||||||
|
copyToClipboard: function (text) {
|
||||||
|
copy(text).then(
|
||||||
|
() => {
|
||||||
|
// clipboard successfully set
|
||||||
|
this.$showSuccess(this.$t("success.linkCopied"));
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
// clipboard write failed
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
submit: async function () {
|
submit: async function () {
|
||||||
let isPermanent = !this.time || this.time == 0;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let res = null;
|
let res = null;
|
||||||
|
|
||||||
if (isPermanent) {
|
if (!this.time) {
|
||||||
res = await api.create(this.url, this.password);
|
res = await api.create(this.url, this.password);
|
||||||
} else {
|
} else {
|
||||||
res = await api.create(this.url, this.password, this.time, this.unit);
|
res = await api.create(this.url, this.password, this.time, this.unit);
|
||||||
@ -197,7 +219,7 @@ export default {
|
|||||||
this.links.push(res);
|
this.links.push(res);
|
||||||
this.sort();
|
this.sort();
|
||||||
|
|
||||||
this.time = "";
|
this.time = 0;
|
||||||
this.unit = "hours";
|
this.unit = "hours";
|
||||||
this.password = "";
|
this.password = "";
|
||||||
|
|
||||||
@ -220,7 +242,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
humanTime(time) {
|
humanTime(time) {
|
||||||
return moment(time * 1000).fromNow();
|
return dayjs(time * 1000).fromNow();
|
||||||
},
|
},
|
||||||
buildLink(share) {
|
buildLink(share) {
|
||||||
return api.getShareURL(share);
|
return api.getShareURL(share);
|
||||||
@ -242,7 +264,7 @@ export default {
|
|||||||
},
|
},
|
||||||
switchListing() {
|
switchListing() {
|
||||||
if (this.links.length == 0 && !this.listing) {
|
if (this.links.length == 0 && !this.listing) {
|
||||||
this.$store.commit("closeHovers");
|
this.closeHovers();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.listing = !this.listing;
|
this.listing = !this.listing;
|
||||||
|
@ -5,18 +5,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button
|
<button
|
||||||
@click="$store.commit('closeHovers')"
|
@click="closeHovers"
|
||||||
class="button button--flat button--grey"
|
class="button button--flat button--grey"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')"
|
:title="$t('buttons.cancel')"
|
||||||
|
tabindex="2"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.cancel") }}
|
{{ $t("buttons.cancel") }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
id="focus-prompt"
|
||||||
@click="submit"
|
@click="submit"
|
||||||
class="button button--flat button--red"
|
class="button button--flat button--red"
|
||||||
:aria-label="$t('buttons.delete')"
|
:aria-label="$t('buttons.delete')"
|
||||||
:title="$t('buttons.delete')"
|
:title="$t('buttons.delete')"
|
||||||
|
tabindex="1"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.delete") }}
|
{{ $t("buttons.delete") }}
|
||||||
</button>
|
</button>
|
||||||
@ -25,14 +28,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from "vuex";
|
import { mapActions, mapState } from "pinia";
|
||||||
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "share-delete",
|
name: "share-delete",
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(["currentPrompt"]),
|
...mapState(useLayoutStore, ["currentPrompt"]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions(useLayoutStore, ["closeHovers"]),
|
||||||
submit: function () {
|
submit: function () {
|
||||||
this.currentPrompt?.confirm();
|
this.currentPrompt?.confirm();
|
||||||
},
|
},
|
||||||
|
@ -1,38 +1,111 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="card floating">
|
<div class="card floating">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>{{ $t("prompts.upload") }}</h2>
|
<h2>{{ t("prompts.upload") }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p>{{ $t("prompts.uploadMessage") }}</p>
|
<p>{{ t("prompts.uploadMessage") }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-action full">
|
<div class="card-action full">
|
||||||
<div @click="uploadFile" class="action">
|
<div
|
||||||
|
@click="uploadFile"
|
||||||
|
@keypress.enter="uploadFile"
|
||||||
|
class="action"
|
||||||
|
id="focus-prompt"
|
||||||
|
tabindex="1"
|
||||||
|
>
|
||||||
<i class="material-icons">insert_drive_file</i>
|
<i class="material-icons">insert_drive_file</i>
|
||||||
<div class="title">{{ $t("buttons.file") }}</div>
|
<div class="title">{{ t("buttons.file") }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div @click="uploadFolder" class="action">
|
<div
|
||||||
|
@click="uploadFolder"
|
||||||
|
@keypress.enter="uploadFolder"
|
||||||
|
class="action"
|
||||||
|
tabindex="2"
|
||||||
|
>
|
||||||
<i class="material-icons">folder</i>
|
<i class="material-icons">folder</i>
|
||||||
<div class="title">{{ $t("buttons.folder") }}</div>
|
<div class="title">{{ t("buttons.folder") }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
export default {
|
import { useI18n } from "vue-i18n";
|
||||||
name: "upload",
|
import { useRoute } from "vue-router";
|
||||||
methods: {
|
import { useFileStore } from "@/stores/file";
|
||||||
uploadFile: function () {
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
document.getElementById("upload-input").value = "";
|
|
||||||
document.getElementById("upload-input").click();
|
import * as upload from "@/utils/upload";
|
||||||
},
|
|
||||||
uploadFolder: function () {
|
const { t } = useI18n();
|
||||||
document.getElementById("upload-folder-input").value = "";
|
const route = useRoute();
|
||||||
document.getElementById("upload-folder-input").click();
|
|
||||||
|
const fileStore = useFileStore();
|
||||||
|
const layoutStore = useLayoutStore();
|
||||||
|
|
||||||
|
// TODO: this is a copy of the same function in FileListing.vue
|
||||||
|
const uploadInput = (event: Event) => {
|
||||||
|
layoutStore.closeHovers();
|
||||||
|
|
||||||
|
let files = (event.currentTarget as HTMLInputElement)?.files;
|
||||||
|
if (files === null) return;
|
||||||
|
|
||||||
|
let folder_upload = !!files[0].webkitRelativePath;
|
||||||
|
|
||||||
|
const uploadFiles: UploadList = [];
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const file = files[i];
|
||||||
|
const fullPath = folder_upload ? file.webkitRelativePath : undefined;
|
||||||
|
uploadFiles.push({
|
||||||
|
file,
|
||||||
|
name: file.name,
|
||||||
|
size: file.size,
|
||||||
|
isDir: false,
|
||||||
|
fullPath,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = route.path.endsWith("/") ? route.path : route.path + "/";
|
||||||
|
let conflict = upload.checkConflict(uploadFiles, fileStore.req!.items);
|
||||||
|
|
||||||
|
if (conflict) {
|
||||||
|
layoutStore.showHover({
|
||||||
|
prompt: "replace",
|
||||||
|
action: (event: Event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
layoutStore.closeHovers();
|
||||||
|
upload.handleFiles(uploadFiles, path, false);
|
||||||
},
|
},
|
||||||
|
confirm: (event: Event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
layoutStore.closeHovers();
|
||||||
|
upload.handleFiles(uploadFiles, path, true);
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
upload.handleFiles(uploadFiles, path);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openUpload = (isFolder: boolean) => {
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.type = "file";
|
||||||
|
input.multiple = true;
|
||||||
|
input.webkitdirectory = isFolder;
|
||||||
|
// TODO: call the function in FileListing.vue instead
|
||||||
|
input.onchange = uploadInput;
|
||||||
|
input.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadFile = () => {
|
||||||
|
openUpload(false);
|
||||||
|
};
|
||||||
|
const uploadFolder = () => {
|
||||||
|
openUpload(true);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -53,7 +53,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters, mapMutations } from "vuex";
|
import { mapState, mapWritableState, mapActions } from "pinia";
|
||||||
|
import { useUploadStore } from "@/stores/upload";
|
||||||
|
import { useFileStore } from "@/stores/file";
|
||||||
import { abortAllUploads } from "@/api/tus";
|
import { abortAllUploads } from "@/api/tus";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
|
|
||||||
@ -65,19 +67,20 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters([
|
...mapState(useUploadStore, [
|
||||||
"filesInUpload",
|
"filesInUpload",
|
||||||
"filesInUploadCount",
|
"filesInUploadCount",
|
||||||
"uploadSpeed",
|
"uploadSpeed",
|
||||||
"eta",
|
"getETA",
|
||||||
]),
|
]),
|
||||||
...mapMutations(["resetUpload"]),
|
...mapWritableState(useFileStore, ["reload"]),
|
||||||
|
...mapActions(useUploadStore, ["reset"]),
|
||||||
formattedETA() {
|
formattedETA() {
|
||||||
if (!this.eta || this.eta === Infinity) {
|
if (!this.getETA || this.getETA === Infinity) {
|
||||||
return "--:--:--";
|
return "--:--:--";
|
||||||
}
|
}
|
||||||
|
|
||||||
let totalSeconds = this.eta;
|
let totalSeconds = this.getETA;
|
||||||
const hours = Math.floor(totalSeconds / 3600);
|
const hours = Math.floor(totalSeconds / 3600);
|
||||||
totalSeconds %= 3600;
|
totalSeconds %= 3600;
|
||||||
const minutes = Math.floor(totalSeconds / 60);
|
const minutes = Math.floor(totalSeconds / 60);
|
||||||
@ -97,8 +100,8 @@ export default {
|
|||||||
abortAllUploads();
|
abortAllUploads();
|
||||||
buttons.done("upload");
|
buttons.done("upload");
|
||||||
this.open = false;
|
this.open = false;
|
||||||
this.$store.commit("resetUpload");
|
this.reset();
|
||||||
this.$store.commit("setReload", true);
|
this.reload = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<select v-on:change="change" :value="locale">
|
<select name="selectLanguage" v-on:change="change" :value="locale">
|
||||||
<option v-for="(language, value) in locales" :key="value" :value="value">
|
<option v-for="(language, value) in locales" :key="value" :value="value">
|
||||||
{{ $t("languages." + language) }}
|
{{ $t("languages." + language) }}
|
||||||
</option>
|
</option>
|
||||||
@ -7,12 +7,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { markRaw } from "vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "languages",
|
name: "languages",
|
||||||
props: ["locale"],
|
props: ["locale"],
|
||||||
data() {
|
data() {
|
||||||
let dataObj = {
|
let dataObj = {};
|
||||||
locales: {
|
const locales = {
|
||||||
he: "he",
|
he: "he",
|
||||||
hu: "hu",
|
hu: "hu",
|
||||||
ar: "ar",
|
ar: "ar",
|
||||||
@ -34,13 +36,16 @@ export default {
|
|||||||
sk: "sk",
|
sk: "sk",
|
||||||
"sv-se": "svSE",
|
"sv-se": "svSE",
|
||||||
tr: "tr",
|
tr: "tr",
|
||||||
ua: "ua",
|
uk: "uk",
|
||||||
"zh-cn": "zhCN",
|
"zh-cn": "zhCN",
|
||||||
"zh-tw": "zhTW",
|
"zh-tw": "zhTW",
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Vue3 reactivity breaks with this configuration
|
||||||
|
// so we need to use markRaw as a workaround
|
||||||
|
// https://github.com/vuejs/core/issues/3024
|
||||||
Object.defineProperty(dataObj, "locales", {
|
Object.defineProperty(dataObj, "locales", {
|
||||||
|
value: markRaw(locales),
|
||||||
configurable: false,
|
configurable: false,
|
||||||
writable: false,
|
writable: false,
|
||||||
});
|
});
|
||||||
|
@ -1,18 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<select v-on:change="change" :value="theme">
|
<select v-on:change="change" :value="theme">
|
||||||
<option value="">{{ $t("settings.themes.light") }}</option>
|
<option value="">{{ t("settings.themes.default") }}</option>
|
||||||
<option value="dark">{{ $t("settings.themes.dark") }}</option>
|
<option value="light">{{ t("settings.themes.light") }}</option>
|
||||||
|
<option value="dark">{{ t("settings.themes.dark") }}</option>
|
||||||
</select>
|
</select>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
export default {
|
import { SelectHTMLAttributes } from "vue";
|
||||||
name: "themes",
|
import { useI18n } from "vue-i18n";
|
||||||
props: ["theme"],
|
|
||||||
methods: {
|
const { t } = useI18n();
|
||||||
change(event) {
|
|
||||||
this.$emit("update:theme", event.target.value);
|
defineProps<{
|
||||||
},
|
theme: UserTheme;
|
||||||
},
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
(e: "update:theme", val: string | null): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const change = (event: Event) => {
|
||||||
|
emit("update:theme", (event.target as SelectHTMLAttributes)?.value);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<p v-if="!isDefault">
|
<p v-if="!isDefault && props.user !== null">
|
||||||
<label for="username">{{ $t("settings.username") }}</label>
|
<label for="username">{{ t("settings.username") }}</label>
|
||||||
<input
|
<input
|
||||||
class="input input--block"
|
class="input input--block"
|
||||||
type="text"
|
type="text"
|
||||||
@ -11,7 +11,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p v-if="!isDefault">
|
<p v-if="!isDefault">
|
||||||
<label for="password">{{ $t("settings.password") }}</label>
|
<label for="password">{{ t("settings.password") }}</label>
|
||||||
<input
|
<input
|
||||||
class="input input--block"
|
class="input input--block"
|
||||||
type="password"
|
type="password"
|
||||||
@ -22,9 +22,9 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<label for="scope">{{ $t("settings.scope") }}</label>
|
<label for="scope">{{ t("settings.scope") }}</label>
|
||||||
<input
|
<input
|
||||||
:disabled="createUserDirData"
|
:disabled="createUserDirData ?? false"
|
||||||
:placeholder="scopePlaceholder"
|
:placeholder="scopePlaceholder"
|
||||||
class="input input--block"
|
class="input input--block"
|
||||||
type="text"
|
type="text"
|
||||||
@ -34,86 +34,89 @@
|
|||||||
</p>
|
</p>
|
||||||
<p class="small" v-if="displayHomeDirectoryCheckbox">
|
<p class="small" v-if="displayHomeDirectoryCheckbox">
|
||||||
<input type="checkbox" v-model="createUserDirData" />
|
<input type="checkbox" v-model="createUserDirData" />
|
||||||
{{ $t("settings.createUserHomeDirectory") }}
|
{{ t("settings.createUserHomeDirectory") }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<label for="locale">{{ $t("settings.language") }}</label>
|
<label for="locale">{{ t("settings.language") }}</label>
|
||||||
<languages
|
<languages
|
||||||
class="input input--block"
|
class="input input--block"
|
||||||
id="locale"
|
id="locale"
|
||||||
:locale.sync="user.locale"
|
v-model:locale="user.locale"
|
||||||
></languages>
|
></languages>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p v-if="!isDefault">
|
<p v-if="!isDefault && user.perm">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:disabled="user.perm.admin"
|
:disabled="user.perm.admin"
|
||||||
v-model="user.lockPassword"
|
v-model="user.lockPassword"
|
||||||
/>
|
/>
|
||||||
{{ $t("settings.lockPassword") }}
|
{{ t("settings.lockPassword") }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<permissions :perm.sync="user.perm" />
|
<permissions v-model:perm="user.perm" />
|
||||||
<commands v-if="isExecEnabled" :commands.sync="user.commands" />
|
<commands v-if="enableExec" v-model:commands="user.commands" />
|
||||||
|
|
||||||
<div v-if="!isDefault">
|
<div v-if="!isDefault">
|
||||||
<h3>{{ $t("settings.rules") }}</h3>
|
<h3>{{ t("settings.rules") }}</h3>
|
||||||
<p class="small">{{ $t("settings.rulesHelp") }}</p>
|
<p class="small">{{ t("settings.rulesHelp") }}</p>
|
||||||
<rules :rules.sync="user.rules" />
|
<rules v-model:rules="user.rules" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import Languages from "./Languages.vue";
|
import Languages from "./Languages.vue";
|
||||||
import Rules from "./Rules.vue";
|
import Rules from "./Rules.vue";
|
||||||
import Permissions from "./Permissions.vue";
|
import Permissions from "./Permissions.vue";
|
||||||
import Commands from "./Commands.vue";
|
import Commands from "./Commands.vue";
|
||||||
import { enableExec } from "@/utils/constants";
|
import { enableExec } from "@/utils/constants";
|
||||||
|
import { computed, onMounted, ref, watch } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n();
|
||||||
name: "user",
|
|
||||||
data: () => {
|
const createUserDirData = ref<boolean | null>(null);
|
||||||
return {
|
const originalUserScope = ref<string | null>(null);
|
||||||
createUserDirData: false,
|
|
||||||
originalUserScope: "/",
|
const props = defineProps<{
|
||||||
};
|
user: IUserForm;
|
||||||
},
|
isNew: boolean;
|
||||||
components: {
|
isDefault: boolean;
|
||||||
Permissions,
|
createUserDir?: boolean;
|
||||||
Languages,
|
}>();
|
||||||
Rules,
|
|
||||||
Commands,
|
onMounted(() => {
|
||||||
},
|
if (props.user.scope) {
|
||||||
props: ["user", "createUserDir", "isNew", "isDefault"],
|
originalUserScope.value = props.user.scope;
|
||||||
created() {
|
createUserDirData.value = props.createUserDir;
|
||||||
this.originalUserScope = this.user.scope;
|
}
|
||||||
this.createUserDirData = this.createUserDir;
|
});
|
||||||
},
|
|
||||||
computed: {
|
const passwordPlaceholder = computed(() =>
|
||||||
passwordPlaceholder() {
|
props.isNew ? "" : t("settings.avoidChanges")
|
||||||
return this.isNew ? "" : this.$t("settings.avoidChanges");
|
);
|
||||||
},
|
const scopePlaceholder = computed(() =>
|
||||||
scopePlaceholder() {
|
createUserDirData.value ? t("settings.userScopeGenerationPlaceholder") : ""
|
||||||
return this.createUserDir
|
);
|
||||||
? this.$t("settings.userScopeGenerationPlaceholder")
|
const displayHomeDirectoryCheckbox = computed(
|
||||||
: "";
|
() => props.isNew && createUserDirData.value
|
||||||
},
|
);
|
||||||
displayHomeDirectoryCheckbox() {
|
|
||||||
return this.isNew && this.createUserDir;
|
watch(
|
||||||
},
|
() => props.user,
|
||||||
isExecEnabled: () => enableExec,
|
() => {
|
||||||
},
|
if (!props.user?.perm?.admin) return;
|
||||||
watch: {
|
props.user.lockPassword = false;
|
||||||
"user.perm.admin": function () {
|
}
|
||||||
if (!this.user.perm.admin) return;
|
);
|
||||||
this.user.lockPassword = false;
|
|
||||||
},
|
watch(createUserDirData, () => {
|
||||||
createUserDirData() {
|
if (props.user?.scope) {
|
||||||
this.user.scope = this.createUserDirData ? "" : this.originalUserScope;
|
props.user.scope = createUserDirData.value
|
||||||
},
|
? ""
|
||||||
},
|
: originalUserScope.value ?? "";
|
||||||
};
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
.button {
|
.button {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
padding: .5em 1em;
|
padding: 0.5em 1em;
|
||||||
border-radius: .1em;
|
border-radius: 0.1em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: var(--blue);
|
background: var(--blue);
|
||||||
color: white;
|
color: white;
|
||||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
border: 1px solid var(--divider);
|
||||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 0 5px var(--divider);
|
||||||
transition: .1s ease all;
|
transition: 0.1s ease all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:hover {
|
.button:hover {
|
||||||
@ -38,7 +38,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.button--flat:hover {
|
.button--flat:hover {
|
||||||
background: var(--moon-grey);
|
background: var(--surfaceSecondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.button--flat.button--red {
|
.button--flat.button--red {
|
||||||
@ -50,6 +50,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.button[disabled] {
|
.button[disabled] {
|
||||||
opacity: .5;
|
opacity: 0.5;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
.input {
|
.input {
|
||||||
border-radius: .1em;
|
background: var(--surfacePrimary);
|
||||||
padding: .5em 1em;
|
color: var(--textSecondary);
|
||||||
background: white;
|
border: 1px solid var(--borderPrimary);
|
||||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
border-radius: 0.1em;
|
||||||
transition: .2s ease all;
|
padding: 0.5em 1em;
|
||||||
color: #333;
|
transition: 0.2s ease all;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input:hover,
|
.input:hover,
|
||||||
.input:focus {
|
.input:focus {
|
||||||
border-color: rgba(0, 0, 0, 0.2);
|
border-color: var(--borderSecondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.input--block {
|
.input--block {
|
||||||
margin-bottom: .5em;
|
margin-bottom: 0.5em;
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@ -27,9 +27,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input--red {
|
.input--red {
|
||||||
background: #fcd0cd;
|
background: var(--input-red) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input--green {
|
.input--green {
|
||||||
background: #c9f2da;
|
background: var(--input-green) !important;
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.share__box {
|
.share__box {
|
||||||
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
|
box-shadow:
|
||||||
background: #fff;
|
rgba(0, 0, 0, 0.06) 0px 1px 3px,
|
||||||
|
rgba(0, 0, 0, 0.12) 0px 1px 2px;
|
||||||
|
background: var(--surfacePrimary);
|
||||||
|
color: var(--textPrimary);
|
||||||
border-radius: 0.2em;
|
border-radius: 0.2em;
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -39,7 +42,7 @@
|
|||||||
|
|
||||||
.share__box__element {
|
.share__box__element {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
border-top: 1px solid var(--borderPrimary);
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +65,7 @@
|
|||||||
border-left: 0;
|
border-left: 0;
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
border-top: 1px solid var(--borderPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.share__box__items #listing.list .item .name {
|
.share__box__items #listing.list .item .name {
|
||||||
@ -76,7 +79,7 @@
|
|||||||
.share__wrong__password {
|
.share__wrong__password {
|
||||||
background: var(--red);
|
background: var(--red);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: .5em;
|
padding: 0.5em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
animation: .2s opac forwards;
|
animation: 0.2s opac forwards;
|
||||||
}
|
}
|
@ -3,17 +3,8 @@
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
max-height: calc(100% - 4em);
|
max-height: calc(100% - 4em);
|
||||||
background: white;
|
background: var(--surfacePrimary);
|
||||||
color: #212121;
|
color: var(--textPrimary);
|
||||||
z-index: 9997;
|
|
||||||
width: 100%;
|
|
||||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
|
||||||
transition: .2s ease transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shell__divider {
|
|
||||||
position: relative;
|
|
||||||
height: 8px;
|
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
background: rgba(127, 127, 127, 0.1);
|
background: rgba(127, 127, 127, 0.1);
|
||||||
transition: 0.2s ease background;
|
transition: 0.2s ease background;
|
||||||
@ -32,6 +23,8 @@
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
cursor: text;
|
cursor: text;
|
||||||
|
box-shadow: 0 0 5px var(--borderPrimary);
|
||||||
|
transition: 0.2s ease transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shell__overlay {
|
.shell__overlay {
|
||||||
@ -52,7 +45,7 @@ body.rtl .shell-content {
|
|||||||
display: flex;
|
display: flex;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
border-top: 1px solid var(--divider);
|
||||||
}
|
}
|
||||||
|
|
||||||
.shell--hidden {
|
.shell--hidden {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
:root {
|
:root {
|
||||||
--blue: #2196f3;
|
--blue: #2196f3;
|
||||||
--dark-blue: #1E88E5;
|
--dark-blue: #1e88e5;
|
||||||
--red: #F44336;
|
--red: #f44336;
|
||||||
--dark-red: #D32F2F;
|
--dark-red: #d32f2f;
|
||||||
--moon-grey: #f2f2f2;
|
--moon-grey: #f2f2f2;
|
||||||
|
|
||||||
--icon-red: #da4453;
|
--icon-red: #da4453;
|
||||||
@ -11,4 +11,44 @@
|
|||||||
--icon-green: #2ecc71;
|
--icon-green: #2ecc71;
|
||||||
--icon-blue: #1d99f3;
|
--icon-blue: #1d99f3;
|
||||||
--icon-violet: #9b59b6;
|
--icon-violet: #9b59b6;
|
||||||
|
|
||||||
|
--input-red: rgb(252, 208, 205);
|
||||||
|
--input-green: rgb(201, 242, 218);
|
||||||
|
|
||||||
|
--item-selected: white;
|
||||||
|
|
||||||
|
--action: rgb(84, 110, 122);
|
||||||
|
|
||||||
|
--background: rgb(250, 250, 250);
|
||||||
|
--surfacePrimary: rgb(255, 255, 255);
|
||||||
|
--surfaceSecondary: rgb(230, 230, 230);
|
||||||
|
--divider: rgba(0, 0, 0, 0.05);
|
||||||
|
--iconPrimary: var(--icon-blue);
|
||||||
|
--iconSecondary: rgb(255, 255, 255);
|
||||||
|
--iconTertiary: rgb(204, 204, 204);
|
||||||
|
--textPrimary: rgb(111, 111, 111);
|
||||||
|
--textSecondary: rgb(51, 51, 51);
|
||||||
|
--hover: rgba(0, 0, 0, 0.1);
|
||||||
|
--borderPrimary: rgba(0, 0, 0, 0.1);
|
||||||
|
--borderSecondary: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark {
|
||||||
|
--input-red: rgb(115, 48, 45);
|
||||||
|
--input-green: rgb(20, 122, 65);
|
||||||
|
|
||||||
|
--action: rgb(255, 255, 255);
|
||||||
|
|
||||||
|
--background: rgb(20, 29, 36);
|
||||||
|
--surfacePrimary: rgb(32, 41, 47);
|
||||||
|
--surfaceSecondary: rgb(58, 65, 71);
|
||||||
|
--textPrimary: rgba(255, 255, 255, 0.6);
|
||||||
|
--textSecondary: rgba(255, 255, 255, 0.87);
|
||||||
|
--divider: rgba(255, 255, 255, 0.12);
|
||||||
|
--iconPrimary: rgb(255, 255, 255);
|
||||||
|
--iconSecondary: rgb(255, 255, 255);
|
||||||
|
--iconTertiary: rgb(255, 255, 255);
|
||||||
|
--hover: rgba(255, 255, 255, 0.1);
|
||||||
|
--borderPrimary: rgba(255, 255, 255, 0.05);
|
||||||
|
--borderSecondary: rgba(255, 255, 255, 0.15);
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
body {
|
body {
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Roboto", sans-serif;
|
||||||
padding-top: 4em;
|
padding-top: 4em;
|
||||||
background-color: #fafafa;
|
background: var(--background);
|
||||||
color: #333333;
|
color: var(--textSecondary);
|
||||||
}
|
|
||||||
|
|
||||||
body.rtl {
|
|
||||||
direction: rtl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@ -62,8 +58,8 @@ nav {
|
|||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl nav {
|
html[dir="rtl"] nav {
|
||||||
left: unset;
|
left: initial;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,13 +74,12 @@ nav .action {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl .action {
|
html[dir="rtl"] nav .action {
|
||||||
direction: rtl;
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav > div {
|
nav > div {
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
border-top: 1px solid var(--divider);
|
||||||
}
|
}
|
||||||
|
|
||||||
nav .action > * {
|
nav .action > * {
|
||||||
@ -99,14 +94,15 @@ main {
|
|||||||
|
|
||||||
.breadcrumbs {
|
.breadcrumbs {
|
||||||
height: 3em;
|
height: 3em;
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
background: var(--background);
|
||||||
|
border-bottom: 1px solid var(--divider);
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumbs span,
|
.breadcrumbs span,
|
||||||
.breadcrumbs {
|
.breadcrumbs {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: #6f6f6f;
|
color: var(--textPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumbs a {
|
.breadcrumbs a {
|
||||||
@ -115,12 +111,12 @@ main {
|
|||||||
border-radius: 0.125em;
|
border-radius: 0.125em;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl .breadcrumbs a {
|
html[dir="rtl"] .breadcrumbs a {
|
||||||
transform: translateX(-16em);
|
transform: translateX(-16em);
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumbs a:hover {
|
.breadcrumbs a:hover {
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
background-color: var(--divider);
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumbs span a {
|
.breadcrumbs span a {
|
||||||
@ -152,3 +148,33 @@ body.rtl .breadcrumbs a {
|
|||||||
.break-word {
|
.break-word {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vue-number-input > input {
|
||||||
|
background: var(--surfacePrimary) !important;
|
||||||
|
border-color: var(--surfaceSecondary) !important;
|
||||||
|
color: var(--textSecondary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-number-input--small > input {
|
||||||
|
height: 1rem !important;
|
||||||
|
font-size: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-number-input :hover,
|
||||||
|
.vue-number-input :focus {
|
||||||
|
border-color: var(--borderSecondary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-number-input__button {
|
||||||
|
background: var(--surfacePrimary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-number-input__button--minus,
|
||||||
|
.vue-number-input__button--plus {
|
||||||
|
border-color: var(--surfaceSecondary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-number-input__button::before,
|
||||||
|
.vue-number-input__button::after {
|
||||||
|
background: var(--textSecondary) !important;
|
||||||
|
}
|
||||||
|
@ -4,17 +4,17 @@
|
|||||||
|
|
||||||
.dashboard .row {
|
.dashboard .row {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0 -.5em;
|
margin: 0 -0.5em;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl .dashboard .row {
|
html[dir="rtl"] .dashboard .row {
|
||||||
margin-right: 16em;
|
margin-right: 16em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard .row .column {
|
.dashboard .row .column {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 0 .5em;
|
padding: 0 0.5em;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,26 +29,26 @@ body.rtl .dashboard .row {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: inherit
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard p label {
|
.dashboard p label {
|
||||||
margin-bottom: .2em;
|
margin-bottom: 0.2em;
|
||||||
display: block;
|
display: block;
|
||||||
font-size: .8em;
|
font-size: 0.8em;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: rgba(0, 0, 0, 0.57);
|
color: var(--textPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
li code,
|
li code,
|
||||||
p code {
|
p code {
|
||||||
background: rgba(0, 0, 0, 0.05);
|
background: var(--divider);
|
||||||
padding: .1em;
|
padding: 0.1em;
|
||||||
border-radius: .2em;
|
border-radius: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.small {
|
.small {
|
||||||
font-size: .8em;
|
font-size: 0.8em;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,21 +61,21 @@ p code {
|
|||||||
.dashboard #nav .wrapper {
|
.dashboard #nav .wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
border-bottom: 2px solid rgba(0, 0, 0, 0.05);
|
border-bottom: 2px solid var(--divider);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl #nav .wrapper {
|
html[dir="rtl"] .dashboard #nav .wrapper {
|
||||||
margin-right: 16em;
|
margin-right: 16em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard #nav ul {
|
.dashboard #nav ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
color: rgb(84, 110, 122);
|
color: var(--action);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0 0 -2px 0;
|
margin: 0 0 -2px 0;
|
||||||
font-size: .8em;
|
font-size: 0.8em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
justify-content: left;
|
justify-content: left;
|
||||||
}
|
}
|
||||||
@ -85,12 +85,11 @@ body.rtl #nav .wrapper {
|
|||||||
padding: 1.5em 2em;
|
padding: 1.5em 2em;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
border-bottom: 2px solid transparent;
|
border-bottom: 2px solid transparent;
|
||||||
transition: .1s ease-in-out all;
|
transition: 0.1s ease-in-out all;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard #nav ul li:hover {
|
.dashboard #nav ul li:hover {
|
||||||
background: var(--moon-grey);
|
background: var(--surfaceSecondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard #nav ul li.active {
|
.dashboard #nav ul li.active {
|
||||||
@ -120,7 +119,7 @@ table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
table tr {
|
table tr {
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid var(--iconTertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
table tr:last-child {
|
table tr:last-child {
|
||||||
@ -129,13 +128,13 @@ table tr:last-child {
|
|||||||
|
|
||||||
table th {
|
table th {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #757575;
|
color: var(--textSecondary);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
table th,
|
table th,
|
||||||
table td {
|
table td {
|
||||||
padding: .5em 0;
|
padding: 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
table td.small {
|
table td.small {
|
||||||
@ -146,7 +145,7 @@ table tr>*:first-child {
|
|||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl table tr>* {
|
html[dir="rtl"] table tr > * {
|
||||||
padding-left: unset;
|
padding-left: unset;
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@ -160,9 +159,13 @@ table tr>*:last-child {
|
|||||||
.card {
|
.card {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0 0 1rem 0;
|
margin: 0 0 1rem 0;
|
||||||
background-color: #fff;
|
background: var(--surfacePrimary);
|
||||||
|
color: var(--textSecondary);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
|
box-shadow:
|
||||||
|
0 2px 2px 0 rgba(0, 0, 0, 0.14),
|
||||||
|
0 1px 5px 0 rgba(0, 0, 0, 0.12),
|
||||||
|
0 3px 1px -2px rgba(0, 0, 0, 0.2);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,11 +174,11 @@ table tr>*:last-child {
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
z-index: 99999;
|
|
||||||
max-width: 25em;
|
max-width: 25em;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
max-height: 95%;
|
max-height: 95%;
|
||||||
animation: .1s show forwards;
|
/* animation-duration: 0.3s;
|
||||||
|
animation-fill-mode: forwards; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.card > * > *:first-child {
|
.card > * > *:first-child {
|
||||||
@ -195,7 +198,7 @@ table tr>*:last-child {
|
|||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl .card .card-title>*:first-child {
|
html[dir="rtl"] .card .card-title > *:first-child {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
@ -234,7 +237,7 @@ body.rtl .card .card-action {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card h3 {
|
.card h3 {
|
||||||
color: rgba(0, 0, 0, 0.53);
|
color: var(--textPrimary);
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin: 2em 0 1em;
|
margin: 2em 0 1em;
|
||||||
@ -253,6 +256,14 @@ body.rtl .card .card-action {
|
|||||||
max-width: 15em;
|
max-width: 15em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card#share input,
|
||||||
|
.card#share select,
|
||||||
|
.card#share input::-webkit-inner-spin-button,
|
||||||
|
.card#share input::-webkit-outer-spin-button {
|
||||||
|
background: var(--surfacePrimary);
|
||||||
|
color: var(--textSecondary);
|
||||||
|
}
|
||||||
|
|
||||||
.card#share ul {
|
.card#share ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -277,24 +288,24 @@ body.rtl .card .card-action {
|
|||||||
|
|
||||||
.card#share ul li input,
|
.card#share ul li input,
|
||||||
.card#share ul li select {
|
.card#share ul li select {
|
||||||
padding: .2em;
|
padding: 0.2em;
|
||||||
margin-right: .5em;
|
margin-right: 0.5em;
|
||||||
border: 1px solid #dadada;
|
border: 1px solid var(--borderPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card#share .action.copy-clipboard::after {
|
.card#share .action.copy-clipboard::after {
|
||||||
content: 'Copied!';
|
content: "Copied!";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -25%;
|
left: -25%;
|
||||||
width: 150%;
|
width: 150%;
|
||||||
font-size: .6em;
|
font-size: 0.6em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: #44a6f5;
|
background: #44a6f5;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: .5em .2em;
|
padding: 0.5em 0.2em;
|
||||||
border-radius: .4em;
|
border-radius: 0.4em;
|
||||||
top: -2em;
|
top: -2em;
|
||||||
transition: .1s ease opacity;
|
transition: 0.1s ease opacity;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,10 +335,9 @@ body.rtl .card .card-action {
|
|||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
animation: .1s show forwards;
|
animation: 0.1s show forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * *
|
||||||
* PROMPT - MOVE *
|
* PROMPT - MOVE *
|
||||||
* * * * * * * * * * * * * * * */
|
* * * * * * * * * * * * * * * */
|
||||||
@ -344,33 +354,33 @@ body.rtl .card .card-action {
|
|||||||
.file-list li {
|
.file-list li {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
border-radius: .2em;
|
border-radius: 0.2em;
|
||||||
padding: .3em;
|
padding: 0.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list li[aria-selected=true] {
|
.file-list li[aria-selected="true"] {
|
||||||
background: var(--blue) !important;
|
background: var(--blue) !important;
|
||||||
color: #fff !important;
|
color: var(--iconSecondary) !important;
|
||||||
transition: .1s ease all;
|
transition: 0.1s ease all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list li:hover {
|
.file-list li:hover {
|
||||||
background-color: #e9eaeb;
|
background: var(--surfaceSecondary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list li:before {
|
.file-list li:before {
|
||||||
content: "folder";
|
content: "folder";
|
||||||
color: #6f6f6f;
|
color: var(--textPrimary);
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
font-family: 'Material Icons';
|
font-family: "Material Icons";
|
||||||
font-size: 1.75em;
|
font-size: 1.75em;
|
||||||
margin-right: .25em;
|
margin-right: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list li[aria-selected=true]:before {
|
.file-list li[aria-selected="true"]:before {
|
||||||
color: white;
|
color: var(--iconSecondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.help {
|
.help {
|
||||||
@ -399,11 +409,11 @@ body.rtl .card .card-action {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.collapsible {
|
.collapsible {
|
||||||
border-top: 1px solid rgba(0,0,0,0.1);
|
border-top: 1px solid var(--borderPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsible:last-of-type {
|
.collapsible:last-of-type {
|
||||||
border-bottom: 1px solid rgba(0,0,0,0.1);
|
border-bottom: 1px solid var(--borderPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsible > input {
|
.collapsible > input {
|
||||||
@ -421,18 +431,18 @@ body.rtl .card .card-action {
|
|||||||
|
|
||||||
.collapsible > label * {
|
.collapsible > label * {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: rgba(0,0,0,0.57);
|
color: var(--textPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsible > label i {
|
.collapsible > label i {
|
||||||
transition: .2s ease transform;
|
transition: 0.2s ease transform;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsible .collapse {
|
.collapsible .collapse {
|
||||||
max-height: 0;
|
max-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: .2s ease all;
|
transition: 0.2s ease all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsible > input:checked ~ .collapse {
|
.collapsible > input:checked ~ .collapse {
|
||||||
@ -442,7 +452,7 @@ body.rtl .card .card-action {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.collapsible > input:checked ~ label i {
|
.collapsible > input:checked ~ label i {
|
||||||
transform: rotate(180deg)
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card .collapsible {
|
.card .collapsible {
|
||||||
@ -468,12 +478,12 @@ body.rtl .card .card-action {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 2em;
|
padding: 2em;
|
||||||
border-radius: 0.2em;
|
border-radius: 0.2em;
|
||||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
border: 1px solid var(--borderPrimary);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card .card-action.full .action {
|
.card .card-action.full .action {
|
||||||
margin: 0 0.25em 0.50em;
|
margin: 0 0.25em 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card .card-action.full .action i {
|
.card .card-action.full .action i {
|
||||||
@ -489,7 +499,7 @@ body.rtl .card .card-action {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*** RTL - Fix disk usage information (in english) ***/
|
/*** RTL - Fix disk usage information (in english) ***/
|
||||||
body.rtl .credits {
|
html[dir="rtl"] .credits {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
}
|
}
|
@ -1,8 +1,8 @@
|
|||||||
header {
|
header {
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
background-color: #fff;
|
background: var(--surfacePrimary);
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.075);
|
border-bottom: 1px solid var(--divider);
|
||||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 5px var(--borderPrimary);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -82,20 +82,25 @@ header .menu-button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#search #input {
|
#search #input {
|
||||||
background-color: #f5f5f5;
|
background: var(--surfaceSecondary);
|
||||||
|
border-color: var(--surfacePrimary);
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0em 0.75em;
|
padding: 0em 0.75em;
|
||||||
border-radius: 0.3em;
|
border-radius: 0.3em;
|
||||||
transition: .1s ease all;
|
transition: 0.1s ease all;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#search #input input::placeholder {
|
||||||
|
color: var(--textSecondary);
|
||||||
|
}
|
||||||
|
|
||||||
#search.active #input {
|
#search.active #input {
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.075);
|
border-bottom: 1px solid var(--borderPrimary);
|
||||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 5px var(--borderPrimary);
|
||||||
background-color: #fff;
|
background: var(--surfacePrimary);
|
||||||
height: 4em;
|
height: 4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +110,7 @@ header .menu-button {
|
|||||||
|
|
||||||
#search.active i,
|
#search.active i,
|
||||||
#search.active input {
|
#search.active input {
|
||||||
color: #212121;
|
color: var(--textPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
#search #input > .action,
|
#search #input > .action,
|
||||||
@ -124,18 +129,20 @@ header .menu-button {
|
|||||||
#search #result {
|
#search #result {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
max-height: none;
|
max-height: none;
|
||||||
background-color: #f8f8f8;
|
background: var(--background);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
color: rgba(0, 0, 0, 0.6);
|
color: var(--textPrimary);
|
||||||
height: 0;
|
height: 0;
|
||||||
transition: .1s ease height, .1s ease padding;
|
transition:
|
||||||
|
0.1s ease height,
|
||||||
|
0.1s ease padding;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl #search #result {
|
html[dir="rtl"] #search #result {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,19 +150,18 @@ body.rtl #search #result {
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl #search #result {
|
html[dir="rtl"] #search #result {
|
||||||
direction: rtl;
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** RTL - Keep search result LTR because it has paths (in english) ***/
|
/*** RTL - Keep search result LTR because it has paths (in english) ***/
|
||||||
body.rtl #search #result ul>* {
|
html[dir="rtl"] #search #result ul > * {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
#search.active #result {
|
#search.active #result {
|
||||||
padding: .5em;
|
padding: 0.5em;
|
||||||
height: calc(100% - 4em);
|
height: calc(100% - 4em);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +172,7 @@ body.rtl #search #result ul>* {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#search li {
|
#search li {
|
||||||
margin-bottom: .5em;
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#search #result > div {
|
#search #result > div {
|
||||||
@ -187,7 +193,7 @@ body.rtl #search #result ul>* {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#search.active #result i {
|
#search.active #result i {
|
||||||
color: #ccc;
|
color: var(--iconTertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
#search.active #result > p > i {
|
#search.active #result > p > i {
|
||||||
@ -199,35 +205,35 @@ body.rtl #search #result ul>* {
|
|||||||
#search.active #result ul li a {
|
#search.active #result ul li a {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: .3em 0;
|
padding: 0.3em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#search.active #result ul li a i {
|
#search.active #result ul li a i {
|
||||||
margin-right: .3em;
|
margin-right: 0.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#search::-webkit-input-placeholder {
|
/* I dont think we need these anymore */
|
||||||
color: rgba(255, 255, 255, .5);
|
/* #search::-webkit-input-placeholder {
|
||||||
}
|
color: var(--textPrimary);
|
||||||
|
|
||||||
#search:-moz-placeholder {
|
|
||||||
opacity: 1;
|
|
||||||
color: rgba(255, 255, 255, .5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#search::-moz-placeholder {
|
#search::-moz-placeholder {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
color: rgba(255, 255, 255, .5);
|
color: var(--textPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
#search:-ms-input-placeholder {
|
#search:-ms-input-placeholder {
|
||||||
color: rgba(255, 255, 255, .5);
|
color: var(--textPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#search #input input::placeholder {
|
||||||
|
color: var(--textPrimary);
|
||||||
|
} */
|
||||||
|
|
||||||
#search .boxes {
|
#search .boxes {
|
||||||
border: 1px solid rgba(0, 0, 0, 0.075);
|
border: 1px solid var(--borderPrimary);
|
||||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 5px var(--borderPrimary);
|
||||||
background: #fff;
|
background: var(--surfacePrimary);
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,11 +241,11 @@ body.rtl #search #result ul>* {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
color: #212121;
|
color: var(--textSecondary);
|
||||||
padding: .5em;
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl #search .boxes h3 {
|
html[dir="rtl"] #search .boxes h3 {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,30 +2,50 @@
|
|||||||
|
|
||||||
/* General */
|
/* General */
|
||||||
|
|
||||||
.file-icons [aria-label^="."] { opacity: 0.33 }
|
.file-icons [aria-label^="."] {
|
||||||
.file-icons [aria-label$=".bak"] { opacity: 0.33 }
|
opacity: 0.33;
|
||||||
|
}
|
||||||
|
.file-icons [aria-label$=".bak"] {
|
||||||
|
opacity: 0.33;
|
||||||
|
}
|
||||||
|
|
||||||
.file-icons [data-type=audio] i::before { content: 'volume_up' }
|
.file-icons [data-type="audio"] i::before {
|
||||||
.file-icons [data-type=blob] i::before { content: 'insert_drive_file' }
|
content: "volume_up";
|
||||||
.file-icons [data-type=image] i::before { content: 'image' }
|
}
|
||||||
.file-icons [data-type=pdf] i::before { content: 'description' }
|
.file-icons [data-type="blob"] i::before {
|
||||||
.file-icons [data-type=text] i::before { content: 'description' }
|
content: "insert_drive_file";
|
||||||
.file-icons [data-type=video] i::before { content: 'movie' }
|
}
|
||||||
.file-icons [data-type=invalid_link] i::before { content: 'link_off' }
|
.file-icons [data-type="image"] i::before {
|
||||||
|
content: "image";
|
||||||
|
}
|
||||||
|
.file-icons [data-type="pdf"] i::before {
|
||||||
|
content: "description";
|
||||||
|
}
|
||||||
|
.file-icons [data-type="text"] i::before {
|
||||||
|
content: "description";
|
||||||
|
}
|
||||||
|
.file-icons [data-type="video"] i::before {
|
||||||
|
content: "movie";
|
||||||
|
}
|
||||||
|
.file-icons [data-type="invalid_link"] i::before {
|
||||||
|
content: "link_off";
|
||||||
|
}
|
||||||
|
|
||||||
/* #f90 - Image */
|
/* #f90 - Image */
|
||||||
|
|
||||||
.file-icons [aria-label$=".ai"] i::before,
|
.file-icons [aria-label$=".ai"] i::before,
|
||||||
.file-icons [aria-label$=".odg"] i::before,
|
.file-icons [aria-label$=".odg"] i::before,
|
||||||
.file-icons [aria-label$=".xcf"] i::before
|
.file-icons [aria-label$=".xcf"] i::before {
|
||||||
{ content: 'image' }
|
content: "image";
|
||||||
|
}
|
||||||
|
|
||||||
/* #f90 - Presentation */
|
/* #f90 - Presentation */
|
||||||
|
|
||||||
.file-icons [aria-label$=".odp"] i::before,
|
.file-icons [aria-label$=".odp"] i::before,
|
||||||
.file-icons [aria-label$=".ppt"] i::before,
|
.file-icons [aria-label$=".ppt"] i::before,
|
||||||
.file-icons [aria-label$=".pptx"] i::before
|
.file-icons [aria-label$=".pptx"] i::before {
|
||||||
{ content: 'slideshow' }
|
content: "slideshow";
|
||||||
|
}
|
||||||
|
|
||||||
/* #0f0 - Spreadsheet/Database */
|
/* #0f0 - Spreadsheet/Database */
|
||||||
|
|
||||||
@ -34,8 +54,9 @@
|
|||||||
.file-icons [aria-label$=".odb"] i::before,
|
.file-icons [aria-label$=".odb"] i::before,
|
||||||
.file-icons [aria-label$=".ods"] i::before,
|
.file-icons [aria-label$=".ods"] i::before,
|
||||||
.file-icons [aria-label$=".xls"] i::before,
|
.file-icons [aria-label$=".xls"] i::before,
|
||||||
.file-icons [aria-label$=".xlsx"] i::before
|
.file-icons [aria-label$=".xlsx"] i::before {
|
||||||
{ content: 'border_all' }
|
content: "border_all";
|
||||||
|
}
|
||||||
|
|
||||||
/* #00f - Document */
|
/* #00f - Document */
|
||||||
|
|
||||||
@ -43,8 +64,9 @@
|
|||||||
.file-icons [aria-label$=".docx"] i::before,
|
.file-icons [aria-label$=".docx"] i::before,
|
||||||
.file-icons [aria-label$=".log"] i::before,
|
.file-icons [aria-label$=".log"] i::before,
|
||||||
.file-icons [aria-label$=".odt"] i::before,
|
.file-icons [aria-label$=".odt"] i::before,
|
||||||
.file-icons [aria-label$=".rtf"] i::before
|
.file-icons [aria-label$=".rtf"] i::before {
|
||||||
{ content: 'description' }
|
content: "description";
|
||||||
|
}
|
||||||
|
|
||||||
/* #999 - Code */
|
/* #999 - Code */
|
||||||
|
|
||||||
@ -65,8 +87,9 @@
|
|||||||
.file-icons [aria-label$=".rs"] i::before,
|
.file-icons [aria-label$=".rs"] i::before,
|
||||||
.file-icons [aria-label$=".vue"] i::before,
|
.file-icons [aria-label$=".vue"] i::before,
|
||||||
.file-icons [aria-label$=".xml"] i::before,
|
.file-icons [aria-label$=".xml"] i::before,
|
||||||
.file-icons [aria-label$=".yml"] i::before
|
.file-icons [aria-label$=".yml"] i::before {
|
||||||
{ content: 'code' }
|
content: "code";
|
||||||
|
}
|
||||||
|
|
||||||
/* #999 - Executable */
|
/* #999 - Executable */
|
||||||
|
|
||||||
@ -75,16 +98,18 @@
|
|||||||
.file-icons [aria-label$=".exe"] i::before,
|
.file-icons [aria-label$=".exe"] i::before,
|
||||||
.file-icons [aria-label$=".jar"] i::before,
|
.file-icons [aria-label$=".jar"] i::before,
|
||||||
.file-icons [aria-label$=".ps1"] i::before,
|
.file-icons [aria-label$=".ps1"] i::before,
|
||||||
.file-icons [aria-label$=".sh"] i::before
|
.file-icons [aria-label$=".sh"] i::before {
|
||||||
{ content: 'web_asset' }
|
content: "web_asset";
|
||||||
|
}
|
||||||
|
|
||||||
/* #999 - Installer */
|
/* #999 - Installer */
|
||||||
|
|
||||||
.file-icons [aria-label$=".deb"] i::before,
|
.file-icons [aria-label$=".deb"] i::before,
|
||||||
.file-icons [aria-label$=".msi"] i::before,
|
.file-icons [aria-label$=".msi"] i::before,
|
||||||
.file-icons [aria-label$=".pkg"] i::before,
|
.file-icons [aria-label$=".pkg"] i::before,
|
||||||
.file-icons [aria-label$=".rpm"] i::before
|
.file-icons [aria-label$=".rpm"] i::before {
|
||||||
{ content: 'archive' }
|
content: "archive";
|
||||||
|
}
|
||||||
|
|
||||||
/* #999 - Compressed */
|
/* #999 - Compressed */
|
||||||
|
|
||||||
@ -96,8 +121,9 @@
|
|||||||
.file-icons [aria-label$=".tar"] i::before,
|
.file-icons [aria-label$=".tar"] i::before,
|
||||||
.file-icons [aria-label$=".xz"] i::before,
|
.file-icons [aria-label$=".xz"] i::before,
|
||||||
.file-icons [aria-label$=".zip"] i::before,
|
.file-icons [aria-label$=".zip"] i::before,
|
||||||
.file-icons [aria-label$=".zst"] i::before
|
.file-icons [aria-label$=".zst"] i::before {
|
||||||
{ content: 'folder_zip' }
|
content: "folder_zip";
|
||||||
|
}
|
||||||
|
|
||||||
/* #999 - Disk */
|
/* #999 - Disk */
|
||||||
|
|
||||||
@ -108,25 +134,35 @@
|
|||||||
.file-icons [aria-label$=".vdi"] i::before,
|
.file-icons [aria-label$=".vdi"] i::before,
|
||||||
.file-icons [aria-label$=".vhd"] i::before,
|
.file-icons [aria-label$=".vhd"] i::before,
|
||||||
.file-icons [aria-label$=".vmdk"] i::before,
|
.file-icons [aria-label$=".vmdk"] i::before,
|
||||||
.file-icons [aria-label$=".wim"] i::before
|
.file-icons [aria-label$=".wim"] i::before {
|
||||||
{ content: 'album' }
|
content: "album";
|
||||||
|
}
|
||||||
|
|
||||||
/* #999 - Font */
|
/* #999 - Font */
|
||||||
|
|
||||||
.file-icons [aria-label$=".otf"] i::before,
|
.file-icons [aria-label$=".otf"] i::before,
|
||||||
.file-icons [aria-label$=".ttf"] i::before,
|
.file-icons [aria-label$=".ttf"] i::before,
|
||||||
.file-icons [aria-label$=".woff"] i::before,
|
.file-icons [aria-label$=".woff"] i::before,
|
||||||
.file-icons [aria-label$=".woff2"] i::before
|
.file-icons [aria-label$=".woff2"] i::before {
|
||||||
{ content: 'font_download' }
|
content: "font_download";
|
||||||
|
}
|
||||||
|
|
||||||
/* Colors */
|
/* Colors */
|
||||||
|
|
||||||
/* General */
|
/* General */
|
||||||
|
|
||||||
.file-icons [data-type=audio] i { color: var(--icon-yellow) }
|
.file-icons [data-type="audio"] i {
|
||||||
.file-icons [data-type=image] i { color: var(--icon-orange) }
|
color: var(--icon-yellow);
|
||||||
.file-icons [data-type=video] i { color: var(--icon-violet) }
|
}
|
||||||
.file-icons [data-type=invalid_link] i { color: var(--icon-red) }
|
.file-icons [data-type="image"] i {
|
||||||
|
color: var(--icon-orange);
|
||||||
|
}
|
||||||
|
.file-icons [data-type="video"] i {
|
||||||
|
color: var(--icon-violet);
|
||||||
|
}
|
||||||
|
.file-icons [data-type="invalid_link"] i {
|
||||||
|
color: var(--icon-red);
|
||||||
|
}
|
||||||
|
|
||||||
/* #f00 - Adobe/Oracle */
|
/* #f00 - Adobe/Oracle */
|
||||||
|
|
||||||
@ -135,8 +171,9 @@
|
|||||||
.file-icons [aria-label$=".jar"] i,
|
.file-icons [aria-label$=".jar"] i,
|
||||||
.file-icons [aria-label$=".psd"] i,
|
.file-icons [aria-label$=".psd"] i,
|
||||||
.file-icons [aria-label$=".rb"] i,
|
.file-icons [aria-label$=".rb"] i,
|
||||||
.file-icons [data-type=pdf] i
|
.file-icons [data-type="pdf"] i {
|
||||||
{ color: var(--icon-red) }
|
color: var(--icon-red);
|
||||||
|
}
|
||||||
|
|
||||||
/* #f90 - Image/Presentation */
|
/* #f90 - Image/Presentation */
|
||||||
|
|
||||||
@ -146,16 +183,18 @@
|
|||||||
.file-icons [aria-label$=".ppt"] i,
|
.file-icons [aria-label$=".ppt"] i,
|
||||||
.file-icons [aria-label$=".pptx"] i,
|
.file-icons [aria-label$=".pptx"] i,
|
||||||
.file-icons [aria-label$=".vue"] i,
|
.file-icons [aria-label$=".vue"] i,
|
||||||
.file-icons [aria-label$=".xcf"] i
|
.file-icons [aria-label$=".xcf"] i {
|
||||||
{ color: var(--icon-orange) }
|
color: var(--icon-orange);
|
||||||
|
}
|
||||||
|
|
||||||
/* #ff0 - Various */
|
/* #ff0 - Various */
|
||||||
|
|
||||||
.file-icons [aria-label$=".css"] i,
|
.file-icons [aria-label$=".css"] i,
|
||||||
.file-icons [aria-label$=".js"] i,
|
.file-icons [aria-label$=".js"] i,
|
||||||
.file-icons [aria-label$=".json"] i,
|
.file-icons [aria-label$=".json"] i,
|
||||||
.file-icons [aria-label$=".zip"] i
|
.file-icons [aria-label$=".zip"] i {
|
||||||
{ color: var(--icon-yellow) }
|
color: var(--icon-yellow);
|
||||||
|
}
|
||||||
|
|
||||||
/* #0f0 - Spreadsheet/Google */
|
/* #0f0 - Spreadsheet/Google */
|
||||||
|
|
||||||
@ -164,8 +203,9 @@
|
|||||||
.file-icons [aria-label$=".go"] i,
|
.file-icons [aria-label$=".go"] i,
|
||||||
.file-icons [aria-label$=".ods"] i,
|
.file-icons [aria-label$=".ods"] i,
|
||||||
.file-icons [aria-label$=".xls"] i,
|
.file-icons [aria-label$=".xls"] i,
|
||||||
.file-icons [aria-label$=".xlsx"] i
|
.file-icons [aria-label$=".xlsx"] i {
|
||||||
{ color: var(--icon-green) }
|
color: var(--icon-green);
|
||||||
|
}
|
||||||
|
|
||||||
/* #00f - Document/Microsoft/Apple/Closed */
|
/* #00f - Document/Microsoft/Apple/Closed */
|
||||||
|
|
||||||
@ -188,18 +228,26 @@
|
|||||||
.file-icons [aria-label$=".ps1"] i,
|
.file-icons [aria-label$=".ps1"] i,
|
||||||
.file-icons [aria-label$=".rtf"] i,
|
.file-icons [aria-label$=".rtf"] i,
|
||||||
.file-icons [aria-label$=".vob"] i,
|
.file-icons [aria-label$=".vob"] i,
|
||||||
.file-icons [aria-label$=".wim"] i
|
.file-icons [aria-label$=".wim"] i {
|
||||||
{ color: var(--icon-blue) }
|
color: var(--icon-blue);
|
||||||
|
}
|
||||||
|
|
||||||
/* #60f - Various */
|
/* #60f - Various */
|
||||||
|
|
||||||
.file-icons [aria-label$=".iso"] i,
|
.file-icons [aria-label$=".iso"] i,
|
||||||
.file-icons [aria-label$=".php"] i,
|
.file-icons [aria-label$=".php"] i,
|
||||||
.file-icons [aria-label$=".rar"] i
|
.file-icons [aria-label$=".rar"] i {
|
||||||
{ color: var(--icon-violet) }
|
color: var(--icon-violet);
|
||||||
|
}
|
||||||
|
|
||||||
/* Overrides */
|
/* Overrides */
|
||||||
|
|
||||||
.file-icons [data-dir=true] i { color: var(--icon-blue) }
|
.file-icons [data-dir="true"] i {
|
||||||
.file-icons [data-dir=true] i::before { content: 'folder' }
|
color: var(--icon-blue);
|
||||||
.file-icons [aria-selected=true] i { color: var(--item-selected) }
|
}
|
||||||
|
.file-icons [data-dir="true"] i::before {
|
||||||
|
content: "folder";
|
||||||
|
}
|
||||||
|
.file-icons [aria-selected="true"] i {
|
||||||
|
color: var(--iconSecondary);
|
||||||
|
}
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
#listing {
|
html[dir="rtl"] #listing {
|
||||||
--item-selected: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.rtl #listing {
|
|
||||||
margin-right: 16em;
|
margin-right: 16em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listing h2 {
|
#listing h2 {
|
||||||
margin: 0 0 0 0.5em;
|
margin: 0 0 0 0.5em;
|
||||||
font-size: .9em;
|
font-size: 0.9em;
|
||||||
color: rgba(0, 0, 0, 0.38);
|
color: var(--textPrimary);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,12 +21,15 @@ body.rtl #listing {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#listing .item {
|
#listing .item {
|
||||||
background-color: #fff;
|
background: var(--surfacePrimary);
|
||||||
|
border-color: var(--divider);
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
color: #6f6f6f;
|
color: var(--textPrimary);
|
||||||
transition: .1s ease background, .1s ease opacity;
|
transition:
|
||||||
|
0.1s ease background,
|
||||||
|
0.1s ease opacity;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
@ -75,13 +74,13 @@ body.rtl #listing {
|
|||||||
margin: 1em auto;
|
margin: 1em auto;
|
||||||
display: block !important;
|
display: block !important;
|
||||||
width: 95%;
|
width: 95%;
|
||||||
color: rgba(0, 0, 0, 0.3);
|
color: var(--textPrimary);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message i {
|
.message i {
|
||||||
font-size: 2.5em;
|
font-size: 2.5em;
|
||||||
margin-bottom: .2em;
|
margin-bottom: 0.2em;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,14 +91,18 @@ body.rtl #listing {
|
|||||||
|
|
||||||
#listing.mosaic .item {
|
#listing.mosaic .item {
|
||||||
width: calc(33% - 1em);
|
width: calc(33% - 1em);
|
||||||
margin: .5em;
|
margin: 0.5em;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
border-radius: 0.2em;
|
border-radius: 0.2em;
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 1px 2px rgba(0, 0, 0, .12);
|
box-shadow:
|
||||||
|
0 1px 3px rgba(0, 0, 0, 0.06),
|
||||||
|
0 1px 2px rgba(0, 0, 0, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
#listing.mosaic .item:hover {
|
#listing.mosaic .item:hover {
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24) !important;
|
box-shadow:
|
||||||
|
0 1px 3px rgba(0, 0, 0, 0.12),
|
||||||
|
0 1px 2px rgba(0, 0, 0, 0.24) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listing.mosaic .header {
|
#listing.mosaic .header {
|
||||||
@ -127,7 +130,7 @@ body.rtl #listing {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listing.mosaic.gallery .item[data-type=image] div:last-of-type {
|
#listing.mosaic.gallery .item[data-type="image"] div:last-of-type {
|
||||||
color: white;
|
color: white;
|
||||||
background: linear-gradient(#0000, #0009);
|
background: linear-gradient(#0000, #0009);
|
||||||
}
|
}
|
||||||
@ -159,7 +162,7 @@ body.rtl #listing {
|
|||||||
#listing.list .item {
|
#listing.list .item {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
border: 1px solid var(--borderPrimary);
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
}
|
}
|
||||||
@ -168,9 +171,9 @@ body.rtl #listing {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listing .item[aria-selected=true] {
|
#listing .item[aria-selected="true"] {
|
||||||
background: var(--blue) !important;
|
background: var(--blue) !important;
|
||||||
color: var(--item-selected) !important;
|
color: var(--iconSecondary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listing.list .item div:first-of-type {
|
#listing.list .item div:first-of-type {
|
||||||
@ -202,22 +205,22 @@ body.rtl #listing {
|
|||||||
|
|
||||||
#listing .item.header {
|
#listing .item.header {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
background-color: #ccc;
|
background-color: var(--iconTertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
#listing.list .header i {
|
#listing.list .header i {
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-left: .2em;
|
margin-left: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listing.list .item.header {
|
#listing.list .item.header {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
background: #fafafa;
|
background: var(--background);
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
padding: .85em;
|
padding: 0.85em;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
border-bottom: 1px solid var(--borderPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
#listing.list .item.header > div:first-child {
|
#listing.list .item.header > div:first-child {
|
||||||
@ -250,7 +253,7 @@ body.rtl #listing {
|
|||||||
|
|
||||||
#listing.list .header i {
|
#listing.list .header i {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: .1s ease all;
|
transition: 0.1s ease all;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listing.list .header p:hover i,
|
#listing.list .header p:hover i,
|
||||||
@ -272,7 +275,7 @@ body.rtl #listing {
|
|||||||
height: 4em;
|
height: 4em;
|
||||||
padding: 0.5em 0.5em 0.5em 1em;
|
padding: 0.5em 0.5em 0.5em 1em;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
transition: .2s ease bottom;
|
transition: 0.2s ease bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
#listing #multiple-selection.active {
|
#listing #multiple-selection.active {
|
||||||
@ -281,5 +284,5 @@ body.rtl #listing {
|
|||||||
|
|
||||||
#listing #multiple-selection p,
|
#listing #multiple-selection p,
|
||||||
#listing #multiple-selection i {
|
#listing #multiple-selection i {
|
||||||
color: var(--item-selected);
|
color: var(--iconSecondary);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#login {
|
#login {
|
||||||
background: #fff;
|
background: var(--surfacePrimary);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -17,7 +17,7 @@
|
|||||||
#login h1 {
|
#login h1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 2.5em;
|
font-size: 2.5em;
|
||||||
margin: .4em 0 .67em;
|
margin: 0.4em 0 0.67em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#login form {
|
#login form {
|
||||||
@ -34,15 +34,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#login #recaptcha {
|
#login #recaptcha {
|
||||||
margin: .5em 0 0;
|
margin: 0.5em 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#login .wrong {
|
#login .wrong {
|
||||||
background: var(--red);
|
background: var(--red);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: .5em;
|
padding: 0.5em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
animation: .2s opac forwards;
|
animation: 0.2s opac forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes opac {
|
@keyframes opac {
|
||||||
@ -61,5 +61,5 @@
|
|||||||
text-transform: lowercase;
|
text-transform: lowercase;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
margin: .5rem 0;
|
margin: 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
nav {
|
nav {
|
||||||
width: 10em
|
width: 10em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
main {
|
main {
|
||||||
width: calc(100% - 13em)
|
width: calc(100% - 13em);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,27 +21,27 @@
|
|||||||
width: 60%;
|
width: 60%;
|
||||||
}
|
}
|
||||||
#more {
|
#more {
|
||||||
display: inherit
|
display: inherit;
|
||||||
}
|
}
|
||||||
header .overlay {
|
header .overlay {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
background-color: var(--borderPrimary);
|
||||||
}
|
}
|
||||||
#dropdown {
|
#dropdown {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 1em;
|
top: 1em;
|
||||||
right: 1em;
|
right: 1em;
|
||||||
display: block;
|
display: block;
|
||||||
background-color: #fff;
|
background: var(--surfaceSecondary);
|
||||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 5px var(--borderPrimary);
|
||||||
transform: scale(0);
|
transform: scale(0);
|
||||||
transition: .1s ease-in-out transform;
|
transition: 0.1s ease-in-out transform;
|
||||||
transform-origin: top right;
|
transform-origin: top right;
|
||||||
z-index: 99999;
|
z-index: 99999;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl #dropdown {
|
html[dir="rtl"] #dropdown {
|
||||||
right: unset;
|
right: unset;
|
||||||
left: 1em;
|
left: 1em;
|
||||||
transform-origin: top left;
|
transform-origin: top left;
|
||||||
@ -61,7 +61,7 @@
|
|||||||
}
|
}
|
||||||
#dropdown .action span:not(.counter) {
|
#dropdown .action span:not(.counter) {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: .4em;
|
padding: 0.4em;
|
||||||
}
|
}
|
||||||
#dropdown .counter {
|
#dropdown .counter {
|
||||||
left: 2.25em;
|
left: 2.25em;
|
||||||
@ -73,8 +73,10 @@
|
|||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #fff;
|
background: var(--surfaceSecondary);
|
||||||
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
|
box-shadow:
|
||||||
|
rgba(0, 0, 0, 0.06) 0px 1px 3px,
|
||||||
|
rgba(0, 0, 0, 0.12) 0px 1px 2px;
|
||||||
width: 95%;
|
width: 95%;
|
||||||
max-width: 20em;
|
max-width: 20em;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@ -86,7 +88,7 @@
|
|||||||
#file-selection > span {
|
#file-selection > span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
color: #6f6f6f;
|
color: var(--textPrimary);
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
#file-selection .action span {
|
#file-selection .action span {
|
||||||
@ -95,15 +97,15 @@
|
|||||||
nav {
|
nav {
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 99999;
|
z-index: 99999;
|
||||||
background: #fff;
|
background: var(--surfaceSecondary);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 16em;
|
width: 16em;
|
||||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 5px var(--borderPrimary);
|
||||||
transition: .1s ease left;
|
transition: 0.1s ease left;
|
||||||
left: -17em;
|
left: -17em;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl nav {
|
html[dir="rtl"] nav {
|
||||||
left: unset;
|
left: unset;
|
||||||
right: -17em;
|
right: -17em;
|
||||||
}
|
}
|
||||||
@ -111,7 +113,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl nav.active {
|
html[dir="rtl"] nav.active {
|
||||||
left: unset;
|
left: unset;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
@ -131,19 +133,19 @@
|
|||||||
margin-bottom: 5em;
|
margin-bottom: 5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl #listing {
|
html[dir="rtl"] #listing {
|
||||||
margin-right: unset;
|
margin-right: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl .breadcrumbs {
|
html[dir="rtl"] .breadcrumbs {
|
||||||
transform: translateX(16em);
|
transform: translateX(16em);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl #nav .wrapper {
|
html[dir="rtl"] #nav .wrapper {
|
||||||
margin-right: unset;
|
margin-right: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl .dashboard .row {
|
html[dir="rtl"] .dashboard .row {
|
||||||
margin-right: unset;
|
margin-right: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
@import "normalize.css/normalize.css";
|
@import "normalize.css/normalize.css";
|
||||||
@import "noty/lib/noty.css";
|
@import "vue-toastification/dist/index.css";
|
||||||
@import "noty/lib/themes/mint.css";
|
@import "vue-final-modal/style.css";
|
||||||
@import "./_variables.css";
|
@import "./_variables.css";
|
||||||
@import "./_buttons.css";
|
@import "./_buttons.css";
|
||||||
@import "./_inputs.css";
|
@import "./_inputs.css";
|
||||||
@ -16,10 +16,23 @@
|
|||||||
@import "./login.css";
|
@import "./login.css";
|
||||||
@import "./mobile.css";
|
@import "./mobile.css";
|
||||||
|
|
||||||
|
/* For testing only
|
||||||
|
:focus {
|
||||||
|
outline: 2px solid crimson !important;
|
||||||
|
border-radius: 3px !important;
|
||||||
|
} */
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
color: var(--blue);
|
color: var(--blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#loading {
|
||||||
|
background: var(--background);
|
||||||
|
}
|
||||||
|
#loading .spinner > div {
|
||||||
|
background: var(--iconPrimary);
|
||||||
|
}
|
||||||
|
|
||||||
main .spinner {
|
main .spinner {
|
||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -32,7 +45,7 @@ main .spinner > div {
|
|||||||
height: 0.8em;
|
height: 0.8em;
|
||||||
margin: 0 0.1em;
|
margin: 0 0.1em;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
background-color: rgba(0, 0, 0, 0.3);
|
background: var(--iconPrimary);
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||||
@ -72,7 +85,7 @@ main .spinner .bounce2 {
|
|||||||
transition: 0.2s ease all;
|
transition: 0.2s ease all;
|
||||||
border: 0;
|
border: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #546e7a;
|
color: var(--action);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -94,7 +107,7 @@ main .spinner .bounce2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.action:hover {
|
.action:hover {
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
background-color: var(--hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.action ul {
|
.action ul {
|
||||||
@ -115,7 +128,7 @@ main .spinner .bounce2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.action ul li:hover {
|
.action ul li:hover {
|
||||||
background-color: rgba(0, 0, 0, 0.04);
|
background-color: var(--divider);
|
||||||
}
|
}
|
||||||
|
|
||||||
#click-overlay {
|
#click-overlay {
|
||||||
@ -138,7 +151,7 @@ main .spinner .bounce2 {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background: var(--blue);
|
background: var(--blue);
|
||||||
color: #fff;
|
color: var(--iconSecondary);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
width: 1.8em;
|
width: 1.8em;
|
||||||
@ -146,7 +159,7 @@ main .spinner .bounce2 {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1.55em;
|
line-height: 1.55em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border: 2px solid white;
|
border: 2px solid var(--borderPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* PREVIEWER */
|
/* PREVIEWER */
|
||||||
@ -217,7 +230,6 @@ main .spinner .bounce2 {
|
|||||||
height: 88%;
|
height: 88%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#previewer .preview video {
|
#previewer .preview video {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@ -302,7 +314,7 @@ main .spinner .bounce2 {
|
|||||||
#previewer .spinner > div {
|
#previewer .spinner > div {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
background-color: white;
|
background: var(--iconPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* EDITOR */
|
/* EDITOR */
|
||||||
@ -310,17 +322,21 @@ main .spinner .bounce2 {
|
|||||||
#editor-container {
|
#editor-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: #fafafa;
|
background-color: var(--background);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
padding-top: 4em;
|
padding-top: 4em;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 9999;
|
z-index: 9998;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#editor-container .bar {
|
||||||
|
background: var(--surfacePrimary);
|
||||||
|
}
|
||||||
|
|
||||||
#editor-container #editor {
|
#editor-container #editor {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
@ -331,7 +347,7 @@ main .spinner .bounce2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*** RTL - flip and position arrow of path ***/
|
/*** RTL - flip and position arrow of path ***/
|
||||||
body.rtl .breadcrumbs .chevron {
|
html[dir="rtl"] .breadcrumbs .chevron {
|
||||||
transform: scaleX(-1) translateX(16em);
|
transform: scaleX(-1) translateX(16em);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,22 +359,6 @@ body.rtl .breadcrumbs .chevron {
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * *
|
|
||||||
* PROMPT *
|
|
||||||
* * * * * * * * * * * * * * * */
|
|
||||||
|
|
||||||
.noty_buttons {
|
|
||||||
text-align: right;
|
|
||||||
padding: 0 10px 10px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.noty_buttons button {
|
|
||||||
background: rgba(0, 0, 0, 0.05);
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
||||||
box-shadow: 0 0 0 0;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * *
|
||||||
* FOOTER *
|
* FOOTER *
|
||||||
* * * * * * * * * * * * * * * */
|
* * * * * * * * * * * * * * * */
|
||||||
@ -436,17 +436,17 @@ body.rtl .breadcrumbs .chevron {
|
|||||||
* RTL overrides *
|
* RTL overrides *
|
||||||
* * * * * * * * * * * * * * * */
|
* * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
body.rtl .card-content textarea {
|
html[dir="rtl"] .card-content textarea {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl .card-content .small + input {
|
html[dir="rtl"] .card-content .small + input {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.rtl .card.floating .card-content .file-list {
|
html[dir="rtl"] .card.floating .card-content .file-list {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "إلغاء",
|
"cancel": "إلغاء",
|
||||||
|
"clear": "مسح",
|
||||||
"close": "إغلاق",
|
"close": "إغلاق",
|
||||||
|
"continue": "متابعة",
|
||||||
"copy": "نسخ",
|
"copy": "نسخ",
|
||||||
"copyFile": "نسخ الملف",
|
"copyFile": "نسخ الملف",
|
||||||
"copyToClipboard": "نسخ الى الحافظة",
|
"copyToClipboard": "نسخ الى الحافظة",
|
||||||
@ -11,6 +13,7 @@
|
|||||||
"download": "تحميل",
|
"download": "تحميل",
|
||||||
"file": "ملف",
|
"file": "ملف",
|
||||||
"folder": "مجلد",
|
"folder": "مجلد",
|
||||||
|
"fullScreen": "تكبير/تصغير الشاشة",
|
||||||
"hideDotfiles": "إخفاء ملفات النقطة",
|
"hideDotfiles": "إخفاء ملفات النقطة",
|
||||||
"info": "معلومات",
|
"info": "معلومات",
|
||||||
"more": "المزيد",
|
"more": "المزيد",
|
||||||
@ -38,7 +41,7 @@
|
|||||||
"update": "تحديث",
|
"update": "تحديث",
|
||||||
"upload": "رفع",
|
"upload": "رفع",
|
||||||
"openFile": "فتح الملف",
|
"openFile": "فتح الملف",
|
||||||
"continue": "متابعة"
|
"discardChanges": "إلغاء التغييرات"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "تحميل الملف",
|
"downloadFile": "تحميل الملف",
|
||||||
@ -56,7 +59,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "الصفحة",
|
"body": "الصفحة",
|
||||||
"clear": "مسح",
|
|
||||||
"closePreview": "إغلاق العرض",
|
"closePreview": "إغلاق العرض",
|
||||||
"files": "الملفات",
|
"files": "الملفات",
|
||||||
"folders": "المجلدات",
|
"folders": "المجلدات",
|
||||||
@ -133,6 +135,7 @@
|
|||||||
"deleteMessageMultiple": "هل تريد بالتأكيد حذف {count} ملف؟",
|
"deleteMessageMultiple": "هل تريد بالتأكيد حذف {count} ملف؟",
|
||||||
"deleteMessageSingle": "هل تريد بالتأكيد حذف هذا الملف/المجلد؟",
|
"deleteMessageSingle": "هل تريد بالتأكيد حذف هذا الملف/المجلد؟",
|
||||||
"deleteMessageShare": "هل تريد بالتأكيد إلغاء مشاركة هذا الملف/المجلد ({path})؟",
|
"deleteMessageShare": "هل تريد بالتأكيد إلغاء مشاركة هذا الملف/المجلد ({path})؟",
|
||||||
|
"deleteUser": "هل تريد بالتأكيد حذف هذا المستخدم؟",
|
||||||
"deleteTitle": "حذف الملفات",
|
"deleteTitle": "حذف الملفات",
|
||||||
"displayName": "عرض اﻹسم:",
|
"displayName": "عرض اﻹسم:",
|
||||||
"download": "تحميل الملفات",
|
"download": "تحميل الملفات",
|
||||||
@ -161,7 +164,9 @@
|
|||||||
"upload": "رفع",
|
"upload": "رفع",
|
||||||
"uploadFiles": "يتم رفع {files} ملفات.",
|
"uploadFiles": "يتم رفع {files} ملفات.",
|
||||||
"uploadMessage": "إختر الملفات التي تريد رفعها.",
|
"uploadMessage": "إختر الملفات التي تريد رفعها.",
|
||||||
"optionalPassword": "كلمة مرور إختيارية"
|
"optionalPassword": "كلمة مرور إختيارية",
|
||||||
|
"resolution": "الدقة",
|
||||||
|
"discardEditorChanges": "هل تريد بالتأكيد إلغاء التغييرات؟"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"images": "الصور",
|
"images": "الصور",
|
||||||
@ -243,6 +248,7 @@
|
|||||||
"shareDeleted": "تم حذف المشاركة!",
|
"shareDeleted": "تم حذف المشاركة!",
|
||||||
"singleClick": "استخدم النقرة الواحدة لفتح الملفات",
|
"singleClick": "استخدم النقرة الواحدة لفتح الملفات",
|
||||||
"themes": {
|
"themes": {
|
||||||
|
"default": "افتراضي (نظام التشغيل)",
|
||||||
"dark": "غامق",
|
"dark": "غامق",
|
||||||
"light": "فاتح",
|
"light": "فاتح",
|
||||||
"title": "موضوع"
|
"title": "موضوع"
|
||||||
@ -282,4 +288,3 @@
|
|||||||
"unit": "وحدة الوقت"
|
"unit": "وحدة الوقت"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
|
"clear": "Schließen",
|
||||||
"close": "Schließen",
|
"close": "Schließen",
|
||||||
"copy": "Kopieren",
|
"copy": "Kopieren",
|
||||||
"copyFile": "Kopiere Datei",
|
"copyFile": "Kopiere Datei",
|
||||||
@ -36,8 +37,7 @@
|
|||||||
"toggleSidebar": "Seitenleiste anzeigen",
|
"toggleSidebar": "Seitenleiste anzeigen",
|
||||||
"update": "Update",
|
"update": "Update",
|
||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
"openFile": "Datei öffnen",
|
"openFile": "Datei öffnen"
|
||||||
"continue": "Fortfahren"
|
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "Download Datei",
|
"downloadFile": "Download Datei",
|
||||||
@ -52,7 +52,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "Body",
|
"body": "Body",
|
||||||
"clear": "Schließen",
|
|
||||||
"closePreview": "Vorschau schließen",
|
"closePreview": "Vorschau schließen",
|
||||||
"files": "Dateien",
|
"files": "Dateien",
|
||||||
"folders": "Ordner",
|
"folders": "Ordner",
|
||||||
@ -88,6 +87,7 @@
|
|||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
"ar": "العربية",
|
"ar": "العربية",
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
|
"el": "Ελληνικά",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"es": "Español",
|
"es": "Español",
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
@ -104,7 +104,7 @@
|
|||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "Swedish (Sweden)",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文 (繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "Ακύρωση",
|
"cancel": "Ακύρωση",
|
||||||
|
"clear": "Καθαρισμός",
|
||||||
"close": "Κλείσιμο",
|
"close": "Κλείσιμο",
|
||||||
"copy": "Αντιγραφή",
|
"copy": "Αντιγραφή",
|
||||||
"copyFile": "Αντιγραφή αρχείου",
|
"copyFile": "Αντιγραφή αρχείου",
|
||||||
@ -36,8 +37,7 @@
|
|||||||
"toggleSidebar": "(Απ-)ενεργοποίησης της πλευρικής μπάρας",
|
"toggleSidebar": "(Απ-)ενεργοποίησης της πλευρικής μπάρας",
|
||||||
"update": "Ενημέρωση",
|
"update": "Ενημέρωση",
|
||||||
"upload": "Μεταφόρτωση",
|
"upload": "Μεταφόρτωση",
|
||||||
"openFile": "Άνοιγμα αρχείου",
|
"openFile": "Άνοιγμα αρχείου"
|
||||||
"continue": "Συνέχεια"
|
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"downloadFile": "Λήψη αρχείου",
|
"downloadFile": "Λήψη αρχείου",
|
||||||
@ -55,7 +55,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "Περιεχόμενο",
|
"body": "Περιεχόμενο",
|
||||||
"clear": "Καθαρισμός",
|
|
||||||
"closePreview": "Κλείσιμο προεπισκόπησης",
|
"closePreview": "Κλείσιμο προεπισκόπησης",
|
||||||
"files": "Αρχεία",
|
"files": "Αρχεία",
|
||||||
"folders": "Φάκελοι",
|
"folders": "Φάκελοι",
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
"clear": "Clear",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
|
"continue": "Continue",
|
||||||
"copy": "Copy",
|
"copy": "Copy",
|
||||||
"copyFile": "Copy file",
|
"copyFile": "Copy file",
|
||||||
"copyToClipboard": "Copy to clipboard",
|
"copyToClipboard": "Copy to clipboard",
|
||||||
@ -11,6 +13,7 @@
|
|||||||
"download": "Download",
|
"download": "Download",
|
||||||
"file": "File",
|
"file": "File",
|
||||||
"folder": "Folder",
|
"folder": "Folder",
|
||||||
|
"fullScreen": "Toggle full screen",
|
||||||
"hideDotfiles": "Hide dotfiles",
|
"hideDotfiles": "Hide dotfiles",
|
||||||
"info": "Info",
|
"info": "Info",
|
||||||
"more": "More",
|
"more": "More",
|
||||||
@ -38,7 +41,6 @@
|
|||||||
"update": "Update",
|
"update": "Update",
|
||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
"openFile": "Open file",
|
"openFile": "Open file",
|
||||||
"continue": "Continue",
|
|
||||||
"discardChanges": "Discard"
|
"discardChanges": "Discard"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
@ -57,7 +59,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "Body",
|
"body": "Body",
|
||||||
"clear": "Clear",
|
|
||||||
"closePreview": "Close preview",
|
"closePreview": "Close preview",
|
||||||
"files": "Files",
|
"files": "Files",
|
||||||
"folders": "Folders",
|
"folders": "Folders",
|
||||||
@ -110,7 +111,7 @@
|
|||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "Swedish (Sweden)",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文 (繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
@ -134,6 +135,7 @@
|
|||||||
"deleteMessageMultiple": "Are you sure you wish to delete {count} file(s)?",
|
"deleteMessageMultiple": "Are you sure you wish to delete {count} file(s)?",
|
||||||
"deleteMessageSingle": "Are you sure you wish to delete this file/folder?",
|
"deleteMessageSingle": "Are you sure you wish to delete this file/folder?",
|
||||||
"deleteMessageShare": "Are you sure you wish to delete this share({path})?",
|
"deleteMessageShare": "Are you sure you wish to delete this share({path})?",
|
||||||
|
"deleteUser": "Are you sure you want to delete this user?",
|
||||||
"deleteTitle": "Delete files",
|
"deleteTitle": "Delete files",
|
||||||
"displayName": "Display Name:",
|
"displayName": "Display Name:",
|
||||||
"download": "Download files",
|
"download": "Download files",
|
||||||
@ -246,6 +248,7 @@
|
|||||||
"shareDeleted": "Share deleted!",
|
"shareDeleted": "Share deleted!",
|
||||||
"singleClick": "Use single clicks to open files and directories",
|
"singleClick": "Use single clicks to open files and directories",
|
||||||
"themes": {
|
"themes": {
|
||||||
|
"default": "System default",
|
||||||
"dark": "Dark",
|
"dark": "Dark",
|
||||||
"light": "Light",
|
"light": "Light",
|
||||||
"title": "Theme"
|
"title": "Theme"
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
|
"clear": "Limpiar",
|
||||||
"close": "Cerrar",
|
"close": "Cerrar",
|
||||||
|
"continue": "Continuar",
|
||||||
"copy": "Copiar",
|
"copy": "Copiar",
|
||||||
"copyFile": "Copiar archivo",
|
"copyFile": "Copiar archivo",
|
||||||
"copyToClipboard": "Copiar al portapapeles",
|
"copyToClipboard": "Copiar al portapapeles",
|
||||||
@ -51,7 +53,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "Cuerpo",
|
"body": "Cuerpo",
|
||||||
"clear": "Limpiar",
|
|
||||||
"closePreview": "Cerrar vista previa",
|
"closePreview": "Cerrar vista previa",
|
||||||
"files": "Archivos",
|
"files": "Archivos",
|
||||||
"folders": "Carpetas",
|
"folders": "Carpetas",
|
||||||
@ -103,7 +104,7 @@
|
|||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "Swedish (Sweden)",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文 (繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
|
"clear": "Effacer",
|
||||||
"close": "Fermer",
|
"close": "Fermer",
|
||||||
|
"continue": "Continuer",
|
||||||
"copy": "Copier",
|
"copy": "Copier",
|
||||||
"copyFile": "Copier le fichier",
|
"copyFile": "Copier le fichier",
|
||||||
"copyToClipboard": "Copier dans le presse-papier",
|
"copyToClipboard": "Copier dans le presse-papier",
|
||||||
|
"copyDownloadLinkToClipboard": "Copier le lien de téléchargement dans le presse-papier",
|
||||||
"create": "Créer",
|
"create": "Créer",
|
||||||
"delete": "Supprimer",
|
"delete": "Supprimer",
|
||||||
"download": "Télécharger",
|
"download": "Télécharger",
|
||||||
"file": "Fichier",
|
"file": "Fichier",
|
||||||
"folder": "Dossier",
|
"folder": "Dossier",
|
||||||
|
"fullScreen": "Plein écran",
|
||||||
"hideDotfiles": "Masquer les dotfiles",
|
"hideDotfiles": "Masquer les dotfiles",
|
||||||
"info": "Info",
|
"info": "Info",
|
||||||
"more": "Plus",
|
"more": "Plus",
|
||||||
@ -51,7 +55,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "Corps",
|
"body": "Corps",
|
||||||
"clear": "Fermer",
|
|
||||||
"closePreview": "Fermer la prévisualisation",
|
"closePreview": "Fermer la prévisualisation",
|
||||||
"files": "Fichiers",
|
"files": "Fichiers",
|
||||||
"folders": "Dossiers",
|
"folders": "Dossiers",
|
||||||
@ -87,23 +90,24 @@
|
|||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
"ar": "العربية",
|
"ar": "العربية",
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
|
"el": "Ελληνικά",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"es": "Español",
|
"es": "Español",
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
"is": "",
|
"is": "Icelandic",
|
||||||
"it": "Italiano",
|
"it": "Italiano",
|
||||||
"ja": "日本語",
|
"ja": "日本語",
|
||||||
"ko": "한국어",
|
"ko": "한국어",
|
||||||
"nlBE": "",
|
"nlBE": "Dutch (Belgium)",
|
||||||
"pl": "Polski",
|
"pl": "Polski",
|
||||||
"pt": "Português",
|
"pt": "Português",
|
||||||
"ptBR": "Português (Brasil)",
|
"ptBR": "Português (Brasil)",
|
||||||
"ro": "",
|
"ro": "Romanian",
|
||||||
"ru": "Русский",
|
"ru": "Русский",
|
||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文 (繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "ביטול",
|
"cancel": "ביטול",
|
||||||
|
"clear": "נקה",
|
||||||
"close": "סגירה",
|
"close": "סגירה",
|
||||||
"copy": "העתקה",
|
"copy": "העתקה",
|
||||||
"copyFile": "העתק קובץ",
|
"copyFile": "העתק קובץ",
|
||||||
@ -93,6 +94,7 @@
|
|||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
"ar": "العربية",
|
"ar": "العربية",
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
|
"el": "Ελληνικά",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"es": "Español",
|
"es": "Español",
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
@ -109,7 +111,7 @@
|
|||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "Swedish (Sweden)",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文 (繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "Mégse",
|
"cancel": "Mégse",
|
||||||
|
"clear": "Törlése",
|
||||||
"close": "Bezárás",
|
"close": "Bezárás",
|
||||||
"copy": "Másolás",
|
"copy": "Másolás",
|
||||||
"copyFile": "Fájl másolása",
|
"copyFile": "Fájl másolása",
|
||||||
@ -51,7 +52,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "Törzs",
|
"body": "Törzs",
|
||||||
"clear": "Törlése",
|
|
||||||
"closePreview": "Előnézet bezárása",
|
"closePreview": "Előnézet bezárása",
|
||||||
"files": "Fájlok",
|
"files": "Fájlok",
|
||||||
"folders": "Mappák",
|
"folders": "Mappák",
|
||||||
@ -87,6 +87,7 @@
|
|||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
"ar": "العربية",
|
"ar": "العربية",
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
|
"el": "Ελληνικά",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"es": "Español",
|
"es": "Español",
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
@ -103,7 +104,7 @@
|
|||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "Swedish (Sweden)",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文 (繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
|
@ -1,143 +0,0 @@
|
|||||||
import Vue from "vue";
|
|
||||||
import VueI18n from "vue-i18n";
|
|
||||||
|
|
||||||
import he from "./he.json";
|
|
||||||
import hu from "./hu.json";
|
|
||||||
import ar from "./ar.json";
|
|
||||||
import de from "./de.json";
|
|
||||||
import el from "./el.json";
|
|
||||||
import en from "./en.json";
|
|
||||||
import es from "./es.json";
|
|
||||||
import fr from "./fr.json";
|
|
||||||
import is from "./is.json";
|
|
||||||
import it from "./it.json";
|
|
||||||
import ja from "./ja.json";
|
|
||||||
import ko from "./ko.json";
|
|
||||||
import nlBE from "./nl-be.json";
|
|
||||||
import pl from "./pl.json";
|
|
||||||
import pt from "./pt.json";
|
|
||||||
import ptBR from "./pt-br.json";
|
|
||||||
import ro from "./ro.json";
|
|
||||||
import ru from "./ru.json";
|
|
||||||
import sk from "./sk.json";
|
|
||||||
import ua from "./ua.json";
|
|
||||||
import svSE from "./sv-se.json";
|
|
||||||
import zhCN from "./zh-cn.json";
|
|
||||||
import zhTW from "./zh-tw.json";
|
|
||||||
|
|
||||||
Vue.use(VueI18n);
|
|
||||||
|
|
||||||
export function detectLocale() {
|
|
||||||
let locale = (navigator.language || navigator.browserLangugae).toLowerCase();
|
|
||||||
switch (true) {
|
|
||||||
case /^he.*/i.test(locale):
|
|
||||||
locale = "he";
|
|
||||||
break;
|
|
||||||
case /^hu.*/i.test(locale):
|
|
||||||
locale = "hu";
|
|
||||||
break;
|
|
||||||
case /^ar.*/i.test(locale):
|
|
||||||
locale = "ar";
|
|
||||||
break;
|
|
||||||
case /^el.*/i.test(locale):
|
|
||||||
locale = "el";
|
|
||||||
break;
|
|
||||||
case /^es.*/i.test(locale):
|
|
||||||
locale = "es";
|
|
||||||
break;
|
|
||||||
case /^en.*/i.test(locale):
|
|
||||||
locale = "en";
|
|
||||||
break;
|
|
||||||
case /^it.*/i.test(locale):
|
|
||||||
locale = "it";
|
|
||||||
break;
|
|
||||||
case /^fr.*/i.test(locale):
|
|
||||||
locale = "fr";
|
|
||||||
break;
|
|
||||||
case /^pt.*/i.test(locale):
|
|
||||||
locale = "pt";
|
|
||||||
break;
|
|
||||||
case /^pt-BR.*/i.test(locale):
|
|
||||||
locale = "pt-br";
|
|
||||||
break;
|
|
||||||
case /^ja.*/i.test(locale):
|
|
||||||
locale = "ja";
|
|
||||||
break;
|
|
||||||
case /^zh-CN/i.test(locale):
|
|
||||||
locale = "zh-cn";
|
|
||||||
break;
|
|
||||||
case /^zh-TW/i.test(locale):
|
|
||||||
locale = "zh-tw";
|
|
||||||
break;
|
|
||||||
case /^zh.*/i.test(locale):
|
|
||||||
locale = "zh-cn";
|
|
||||||
break;
|
|
||||||
case /^de.*/i.test(locale):
|
|
||||||
locale = "de";
|
|
||||||
break;
|
|
||||||
case /^ru.*/i.test(locale):
|
|
||||||
locale = "ru";
|
|
||||||
break;
|
|
||||||
case /^pl.*/i.test(locale):
|
|
||||||
locale = "pl";
|
|
||||||
break;
|
|
||||||
case /^ko.*/i.test(locale):
|
|
||||||
locale = "ko";
|
|
||||||
break;
|
|
||||||
case /^sk.*/i.test(locale):
|
|
||||||
locale = "sk";
|
|
||||||
break;
|
|
||||||
case /^ua.*/i.test(locale):
|
|
||||||
locale = "ua";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
locale = "en";
|
|
||||||
}
|
|
||||||
|
|
||||||
return locale;
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeEmpty = (obj) =>
|
|
||||||
Object.keys(obj)
|
|
||||||
.filter((k) => obj[k] !== null && obj[k] !== undefined && obj[k] !== "") // Remove undef. and null and empty.string.
|
|
||||||
.reduce(
|
|
||||||
(newObj, k) =>
|
|
||||||
typeof obj[k] === "object"
|
|
||||||
? Object.assign(newObj, { [k]: removeEmpty(obj[k]) }) // Recurse.
|
|
||||||
: Object.assign(newObj, { [k]: obj[k] }), // Copy value.
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const rtlLanguages = ["he", "ar"];
|
|
||||||
|
|
||||||
const i18n = new VueI18n({
|
|
||||||
locale: detectLocale(),
|
|
||||||
fallbackLocale: "en",
|
|
||||||
messages: {
|
|
||||||
he: removeEmpty(he),
|
|
||||||
hu: removeEmpty(hu),
|
|
||||||
ar: removeEmpty(ar),
|
|
||||||
de: removeEmpty(de),
|
|
||||||
el: removeEmpty(el),
|
|
||||||
en: en,
|
|
||||||
es: removeEmpty(es),
|
|
||||||
fr: removeEmpty(fr),
|
|
||||||
is: removeEmpty(is),
|
|
||||||
it: removeEmpty(it),
|
|
||||||
ja: removeEmpty(ja),
|
|
||||||
ko: removeEmpty(ko),
|
|
||||||
"nl-be": removeEmpty(nlBE),
|
|
||||||
pl: removeEmpty(pl),
|
|
||||||
"pt-br": removeEmpty(ptBR),
|
|
||||||
pt: removeEmpty(pt),
|
|
||||||
ru: removeEmpty(ru),
|
|
||||||
ro: removeEmpty(ro),
|
|
||||||
sk: removeEmpty(sk),
|
|
||||||
"sv-se": removeEmpty(svSE),
|
|
||||||
ua: removeEmpty(ua),
|
|
||||||
"zh-cn": removeEmpty(zhCN),
|
|
||||||
"zh-tw": removeEmpty(zhTW),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default i18n;
|
|
164
frontend/src/i18n/index.ts
Normal file
164
frontend/src/i18n/index.ts
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import dayjs from "dayjs";
|
||||||
|
import { createI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
import("dayjs/locale/ar");
|
||||||
|
import("dayjs/locale/de");
|
||||||
|
import("dayjs/locale/el");
|
||||||
|
import("dayjs/locale/en");
|
||||||
|
import("dayjs/locale/es");
|
||||||
|
import("dayjs/locale/fr");
|
||||||
|
import("dayjs/locale/he");
|
||||||
|
import("dayjs/locale/hu");
|
||||||
|
import("dayjs/locale/is");
|
||||||
|
import("dayjs/locale/it");
|
||||||
|
import("dayjs/locale/ja");
|
||||||
|
import("dayjs/locale/ko");
|
||||||
|
import("dayjs/locale/nl-be");
|
||||||
|
import("dayjs/locale/pl");
|
||||||
|
import("dayjs/locale/pt-br");
|
||||||
|
import("dayjs/locale/pt");
|
||||||
|
import("dayjs/locale/ro");
|
||||||
|
import("dayjs/locale/ru");
|
||||||
|
import("dayjs/locale/sk");
|
||||||
|
import("dayjs/locale/sv");
|
||||||
|
import("dayjs/locale/tr");
|
||||||
|
import("dayjs/locale/uk");
|
||||||
|
import("dayjs/locale/zh-cn");
|
||||||
|
import("dayjs/locale/zh-tw");
|
||||||
|
|
||||||
|
// All i18n resources specified in the plugin `include` option can be loaded
|
||||||
|
// at once using the import syntax
|
||||||
|
import messages from "@intlify/unplugin-vue-i18n/messages";
|
||||||
|
|
||||||
|
export function detectLocale() {
|
||||||
|
// locale is an RFC 5646 language tag
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/language
|
||||||
|
let locale = navigator.language.toLowerCase();
|
||||||
|
switch (true) {
|
||||||
|
case /^he\b/.test(locale):
|
||||||
|
locale = "he";
|
||||||
|
break;
|
||||||
|
case /^hu\b/.test(locale):
|
||||||
|
locale = "hu";
|
||||||
|
break;
|
||||||
|
case /^ar\b/.test(locale):
|
||||||
|
locale = "ar";
|
||||||
|
break;
|
||||||
|
case /^el.*/i.test(locale):
|
||||||
|
locale = "el";
|
||||||
|
break;
|
||||||
|
case /^es\b/.test(locale):
|
||||||
|
locale = "es";
|
||||||
|
break;
|
||||||
|
case /^en\b/.test(locale):
|
||||||
|
locale = "en";
|
||||||
|
break;
|
||||||
|
case /^is\b/.test(locale):
|
||||||
|
locale = "is";
|
||||||
|
break;
|
||||||
|
case /^it\b/.test(locale):
|
||||||
|
locale = "it";
|
||||||
|
break;
|
||||||
|
case /^fr\b/.test(locale):
|
||||||
|
locale = "fr";
|
||||||
|
break;
|
||||||
|
case /^pt-br\b/.test(locale):
|
||||||
|
locale = "pt-br";
|
||||||
|
break;
|
||||||
|
case /^pt\b/.test(locale):
|
||||||
|
locale = "pt";
|
||||||
|
break;
|
||||||
|
case /^ja\b/.test(locale):
|
||||||
|
locale = "ja";
|
||||||
|
break;
|
||||||
|
case /^zh-tw\b/.test(locale):
|
||||||
|
locale = "zh-tw";
|
||||||
|
break;
|
||||||
|
case /^zh-cn\b/.test(locale):
|
||||||
|
case /^zh\b/.test(locale):
|
||||||
|
locale = "zh-cn";
|
||||||
|
break;
|
||||||
|
case /^de\b/.test(locale):
|
||||||
|
locale = "de";
|
||||||
|
break;
|
||||||
|
case /^ro\b/.test(locale):
|
||||||
|
locale = "ro";
|
||||||
|
break;
|
||||||
|
case /^ru\b/.test(locale):
|
||||||
|
locale = "ru";
|
||||||
|
break;
|
||||||
|
case /^pl\b/.test(locale):
|
||||||
|
locale = "pl";
|
||||||
|
break;
|
||||||
|
case /^ko\b/.test(locale):
|
||||||
|
locale = "ko";
|
||||||
|
break;
|
||||||
|
case /^sk\b/.test(locale):
|
||||||
|
locale = "sk";
|
||||||
|
break;
|
||||||
|
case /^tr\b/.test(locale):
|
||||||
|
locale = "tr";
|
||||||
|
break;
|
||||||
|
// ua wasnt a valid locale for ukraine
|
||||||
|
case /^uk\b/.test(locale):
|
||||||
|
locale = "uk";
|
||||||
|
break;
|
||||||
|
case /^sv-se\b/.test(locale):
|
||||||
|
case /^sv\b/.test(locale):
|
||||||
|
locale = "sv";
|
||||||
|
break;
|
||||||
|
case /^nl-be\b/.test(locale):
|
||||||
|
locale = "nl-be";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
locale = "en";
|
||||||
|
}
|
||||||
|
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: was this really necessary?
|
||||||
|
// function removeEmpty(obj: Record<string, any>): void {
|
||||||
|
// Object.keys(obj)
|
||||||
|
// .filter((k) => obj[k] !== null && obj[k] !== undefined && obj[k] !== "") // Remove undef. and null and empty.string.
|
||||||
|
// .reduce(
|
||||||
|
// (newObj, k) =>
|
||||||
|
// typeof obj[k] === "object"
|
||||||
|
// ? Object.assign(newObj, { [k]: removeEmpty(obj[k]) }) // Recurse.
|
||||||
|
// : Object.assign(newObj, { [k]: obj[k] }), // Copy value.
|
||||||
|
// {}
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
export const rtlLanguages = ["he", "ar"];
|
||||||
|
|
||||||
|
export const i18n = createI18n({
|
||||||
|
locale: detectLocale(),
|
||||||
|
fallbackLocale: "en",
|
||||||
|
messages,
|
||||||
|
// expose i18n.global for outside components
|
||||||
|
legacy: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const isRtl = (locale?: string) => {
|
||||||
|
// see below
|
||||||
|
// @ts-ignore
|
||||||
|
return rtlLanguages.includes(locale || i18n.global.locale.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function setLocale(locale: string) {
|
||||||
|
dayjs.locale(locale);
|
||||||
|
// according to doc u only need .value if legacy: false but they lied
|
||||||
|
// https://vue-i18n.intlify.dev/guide/essentials/scope.html#local-scope-1
|
||||||
|
//@ts-ignore
|
||||||
|
i18n.global.locale.value = locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setHtmlLocale(locale: string) {
|
||||||
|
const html = document.documentElement;
|
||||||
|
html.lang = locale;
|
||||||
|
if (isRtl(locale)) html.dir = "rtl";
|
||||||
|
else html.dir = "ltr";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default i18n;
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "Hætta við",
|
"cancel": "Hætta við",
|
||||||
|
"clear": "Hreinsa",
|
||||||
"close": "Loka",
|
"close": "Loka",
|
||||||
"copy": "Afrita",
|
"copy": "Afrita",
|
||||||
"copyFile": "Afrita skjal",
|
"copyFile": "Afrita skjal",
|
||||||
@ -46,7 +47,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "Meginmál",
|
"body": "Meginmál",
|
||||||
"clear": "Hreinsa",
|
|
||||||
"closePreview": "Loka forskoðun",
|
"closePreview": "Loka forskoðun",
|
||||||
"files": "Skjöl",
|
"files": "Skjöl",
|
||||||
"folders": "Möppur",
|
"folders": "Möppur",
|
||||||
@ -81,23 +81,24 @@
|
|||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
"ar": "العربية",
|
"ar": "العربية",
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
|
"el": "Ελληνικά",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"es": "Español",
|
"es": "Español",
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
"is": "",
|
"is": "Icelandic",
|
||||||
"it": "Italiano",
|
"it": "Italiano",
|
||||||
"ja": "日本語",
|
"ja": "日本語",
|
||||||
"ko": "한국어",
|
"ko": "한국어",
|
||||||
"nlBE": "",
|
"nlBE": "Dutch (Belgium)",
|
||||||
"pl": "Polski",
|
"pl": "Polski",
|
||||||
"pt": "Português",
|
"pt": "Português",
|
||||||
"ptBR": "Português (Brasil)",
|
"ptBR": "Português (Brasil)",
|
||||||
"ro": "",
|
"ro": "Romanian",
|
||||||
"ru": "Русский",
|
"ru": "Русский",
|
||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文 (繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "Annulla",
|
"cancel": "Annulla",
|
||||||
|
"clear": "Cancella",
|
||||||
"close": "Chiudi",
|
"close": "Chiudi",
|
||||||
|
"continue": "Continua",
|
||||||
"copy": "Copia",
|
"copy": "Copia",
|
||||||
"copyFile": "Copia file",
|
"copyFile": "Copia file",
|
||||||
"copyToClipboard": "Copia negli appunti",
|
"copyToClipboard": "Copia negli appunti",
|
||||||
@ -46,7 +48,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "Contenuto",
|
"body": "Contenuto",
|
||||||
"clear": "Cancella",
|
|
||||||
"closePreview": "Chiudi anteprima",
|
"closePreview": "Chiudi anteprima",
|
||||||
"files": "File",
|
"files": "File",
|
||||||
"folders": "Cartelle",
|
"folders": "Cartelle",
|
||||||
@ -81,23 +82,24 @@
|
|||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
"ar": "العربية",
|
"ar": "العربية",
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
|
"el": "Ελληνικά",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"es": "Español",
|
"es": "Español",
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
"is": "",
|
"is": "Icelandic",
|
||||||
"it": "Italiano",
|
"it": "Italiano",
|
||||||
"ja": "日本語",
|
"ja": "日本語",
|
||||||
"ko": "한국어",
|
"ko": "한국어",
|
||||||
"nlBE": "",
|
"nlBE": "Dutch (Belgium)",
|
||||||
"pl": "Polski",
|
"pl": "Polski",
|
||||||
"pt": "Português",
|
"pt": "Português",
|
||||||
"ptBR": "Português (Brasil)",
|
"ptBR": "Português (Brasil)",
|
||||||
"ro": "",
|
"ro": "Romanian",
|
||||||
"ru": "Русский",
|
"ru": "Русский",
|
||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文 (繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "キャンセル",
|
"cancel": "キャンセル",
|
||||||
|
"clear": "クリアー",
|
||||||
"close": "閉じる",
|
"close": "閉じる",
|
||||||
"copy": "コピー",
|
"copy": "コピー",
|
||||||
"copyFile": "ファイルのコピー",
|
"copyFile": "ファイルのコピー",
|
||||||
@ -109,7 +110,7 @@
|
|||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "Swedish (Sweden)",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文 (繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "취소",
|
"cancel": "취소",
|
||||||
|
"clear": "지우기",
|
||||||
"close": "닫기",
|
"close": "닫기",
|
||||||
"copy": "복사",
|
"copy": "복사",
|
||||||
"copyFile": "파일 복사",
|
"copyFile": "파일 복사",
|
||||||
@ -46,7 +47,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "본문",
|
"body": "본문",
|
||||||
"clear": "지우기",
|
|
||||||
"closePreview": "미리보기 닫기",
|
"closePreview": "미리보기 닫기",
|
||||||
"files": "파일",
|
"files": "파일",
|
||||||
"folders": "폴더",
|
"folders": "폴더",
|
||||||
@ -81,23 +81,24 @@
|
|||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
"ar": "العربية",
|
"ar": "العربية",
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
|
"el": "Ελληνικά",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"es": "Español",
|
"es": "Español",
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
"is": "",
|
"is": "Icelandic",
|
||||||
"it": "Italiano",
|
"it": "Italiano",
|
||||||
"ja": "日本語",
|
"ja": "日本語",
|
||||||
"ko": "한국어",
|
"ko": "한국어",
|
||||||
"nlBE": "",
|
"nlBE": "Dutch (Belgium)",
|
||||||
"pl": "Polski",
|
"pl": "Polski",
|
||||||
"pt": "Português",
|
"pt": "Português",
|
||||||
"ptBR": "Português (Brasil)",
|
"ptBR": "Português (Brasil)",
|
||||||
"ro": "",
|
"ro": "Romanian",
|
||||||
"ru": "Русский",
|
"ru": "Русский",
|
||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文 (繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "Annuleren",
|
"cancel": "Annuleren",
|
||||||
|
"clear": "Wissen",
|
||||||
"close": "Sluiten",
|
"close": "Sluiten",
|
||||||
"copy": "Kopiëren",
|
"copy": "Kopiëren",
|
||||||
"copyFile": "Bestand kopiëren",
|
"copyFile": "Bestand kopiëren",
|
||||||
@ -46,7 +47,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "Body",
|
"body": "Body",
|
||||||
"clear": "Wissen",
|
|
||||||
"closePreview": "Voorvertoon sluiten",
|
"closePreview": "Voorvertoon sluiten",
|
||||||
"files": "Bestanden",
|
"files": "Bestanden",
|
||||||
"folders": "Mappen",
|
"folders": "Mappen",
|
||||||
@ -79,27 +79,28 @@
|
|||||||
"languages": {
|
"languages": {
|
||||||
"he": "עברית",
|
"he": "עברית",
|
||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
"ar": "Arabisch",
|
"ar": "العربية",
|
||||||
"de": "Duits",
|
"de": "Deutsch",
|
||||||
"en": "Engels",
|
"el": "Ελληνικά",
|
||||||
"es": "Spaans",
|
"en": "English",
|
||||||
"fr": "Frans",
|
"es": "Español",
|
||||||
"is": "",
|
"fr": "Français",
|
||||||
"it": "Italiaans",
|
"is": "Icelandic",
|
||||||
"ja": "Japans",
|
"it": "Italiano",
|
||||||
"ko": "Koreaans",
|
"ja": "日本語",
|
||||||
"nlBE": "",
|
"ko": "한국어",
|
||||||
"pl": "Pools",
|
"nlBE": "Dutch (Belgium)",
|
||||||
"pt": "Portugees",
|
"pl": "Polski",
|
||||||
"ptBR": "Portugees (Brazilië)",
|
"pt": "Português",
|
||||||
"ro": "",
|
"ptBR": "Português (Brasil)",
|
||||||
"ru": "Russisch",
|
"ro": "Romanian",
|
||||||
|
"ru": "Русский",
|
||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "Chinees (vereenvoudigd)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "Chinees (traditioneel)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"createAnAccount": "Account aanmaken",
|
"createAnAccount": "Account aanmaken",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "Anuluj",
|
"cancel": "Anuluj",
|
||||||
|
"clear": "Wyczyść",
|
||||||
"close": "Zamknij",
|
"close": "Zamknij",
|
||||||
"copy": "Kopiuj",
|
"copy": "Kopiuj",
|
||||||
"copyFile": "Kopiuj plik",
|
"copyFile": "Kopiuj plik",
|
||||||
@ -46,7 +47,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "Body",
|
"body": "Body",
|
||||||
"clear": "Wyczyść",
|
|
||||||
"closePreview": "Zamknij poprzednie",
|
"closePreview": "Zamknij poprzednie",
|
||||||
"files": "Pliki",
|
"files": "Pliki",
|
||||||
"folders": "Foldery",
|
"folders": "Foldery",
|
||||||
@ -81,23 +81,24 @@
|
|||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
"ar": "العربية",
|
"ar": "العربية",
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
|
"el": "Ελληνικά",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"es": "Español",
|
"es": "Español",
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
"is": "Íslenska",
|
"is": "Icelandic",
|
||||||
"it": "Italiano",
|
"it": "Italiano",
|
||||||
"ja": "日本語",
|
"ja": "日本語",
|
||||||
"ko": "한국어",
|
"ko": "한국어",
|
||||||
"nlBE": "Nederlands (België)",
|
"nlBE": "Dutch (Belgium)",
|
||||||
"pl": "Polski",
|
"pl": "Polski",
|
||||||
"pt": "Português",
|
"pt": "Português",
|
||||||
"ptBR": "Português (Brasil)",
|
"ptBR": "Português (Brasil)",
|
||||||
"ro": "Română",
|
"ro": "Romanian",
|
||||||
"ru": "Русский",
|
"ru": "Русский",
|
||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "Svenska (Sverige)",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文 (繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
@ -147,7 +148,6 @@
|
|||||||
"size": "Rozmiar",
|
"size": "Rozmiar",
|
||||||
"upload": "Prześlij",
|
"upload": "Prześlij",
|
||||||
"uploadMessage": "Proszę wybrać metodę przesyłania"
|
"uploadMessage": "Proszę wybrać metodę przesyłania"
|
||||||
|
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"images": "Zdjęcia",
|
"images": "Zdjęcia",
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
|
"clear": "Limpar",
|
||||||
"close": "Fechar",
|
"close": "Fechar",
|
||||||
|
"continue": "Continuar",
|
||||||
"copy": "Copiar",
|
"copy": "Copiar",
|
||||||
"copyFile": "Copiar arquivo",
|
"copyFile": "Copiar arquivo",
|
||||||
"copyToClipboard": "Copiar",
|
"copyToClipboard": "Copiar",
|
||||||
@ -51,7 +53,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "Corpo",
|
"body": "Corpo",
|
||||||
"clear": "Limpar",
|
|
||||||
"closePreview": "Fechar pré-visualização",
|
"closePreview": "Fechar pré-visualização",
|
||||||
"files": "Arquivos",
|
"files": "Arquivos",
|
||||||
"folders": "Pastas",
|
"folders": "Pastas",
|
||||||
@ -87,23 +88,24 @@
|
|||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
"ar": "العربية",
|
"ar": "العربية",
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
|
"el": "Ελληνικά",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"es": "Español",
|
"es": "Español",
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
"is": "",
|
"is": "Icelandic",
|
||||||
"it": "Italiano",
|
"it": "Italiano",
|
||||||
"ja": "日本語",
|
"ja": "日本語",
|
||||||
"ko": "한국어",
|
"ko": "한국어",
|
||||||
"nlBE": "",
|
"nlBE": "Dutch (Belgium)",
|
||||||
"pl": "Polski",
|
"pl": "Polski",
|
||||||
"pt": "Português",
|
"pt": "Português",
|
||||||
"ptBR": "Português (Brasil)",
|
"ptBR": "Português (Brasil)",
|
||||||
"ro": "",
|
"ro": "Romanian",
|
||||||
"ru": "Русский",
|
"ru": "Русский",
|
||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文 (繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
|
"clear": "Limpar",
|
||||||
"close": "Fechar",
|
"close": "Fechar",
|
||||||
|
"continue": "Continuar",
|
||||||
"copy": "Copiar",
|
"copy": "Copiar",
|
||||||
"copyFile": "Copiar ficheiro",
|
"copyFile": "Copiar ficheiro",
|
||||||
"copyToClipboard": "Copiar",
|
"copyToClipboard": "Copiar",
|
||||||
@ -46,7 +48,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "Corpo",
|
"body": "Corpo",
|
||||||
"clear": "Limpar",
|
|
||||||
"closePreview": "Fechar pré-visualização",
|
"closePreview": "Fechar pré-visualização",
|
||||||
"files": "Ficheiros",
|
"files": "Ficheiros",
|
||||||
"folders": "Pastas",
|
"folders": "Pastas",
|
||||||
@ -79,27 +80,28 @@
|
|||||||
"languages": {
|
"languages": {
|
||||||
"he": "עברית",
|
"he": "עברית",
|
||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
"ar": "Árabe",
|
"ar": "العربية",
|
||||||
"de": "Alemão",
|
"de": "Deutsch",
|
||||||
"en": "Inglês",
|
"el": "Ελληνικά",
|
||||||
"es": "Espanhol",
|
"en": "English",
|
||||||
"fr": "Francês",
|
"es": "Español",
|
||||||
"is": "",
|
"fr": "Français",
|
||||||
|
"is": "Icelandic",
|
||||||
"it": "Italiano",
|
"it": "Italiano",
|
||||||
"ja": "Japonês",
|
"ja": "日本語",
|
||||||
"ko": "Coreano",
|
"ko": "한국어",
|
||||||
"nlBE": "",
|
"nlBE": "Dutch (Belgium)",
|
||||||
"pl": "Polaco",
|
"pl": "Polski",
|
||||||
"pt": "Português",
|
"pt": "Português",
|
||||||
"ptBR": "Português (Brasil)",
|
"ptBR": "Português (Brasil)",
|
||||||
"ro": "",
|
"ro": "Romanian",
|
||||||
"ru": "Russo",
|
"ru": "Русский",
|
||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "Chinês simplificado",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "Chinês tradicional"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"createAnAccount": "Criar uma conta",
|
"createAnAccount": "Criar uma conta",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "Anulează",
|
"cancel": "Anulează",
|
||||||
|
"clear": "Curăță",
|
||||||
"close": "Închide",
|
"close": "Închide",
|
||||||
"copy": "Copiază",
|
"copy": "Copiază",
|
||||||
"copyFile": "Copiază fișier",
|
"copyFile": "Copiază fișier",
|
||||||
@ -46,7 +47,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "Corp",
|
"body": "Corp",
|
||||||
"clear": "Curăță",
|
|
||||||
"closePreview": "Închide previzualizarea",
|
"closePreview": "Închide previzualizarea",
|
||||||
"files": "Fișiere",
|
"files": "Fișiere",
|
||||||
"folders": "Directoare",
|
"folders": "Directoare",
|
||||||
@ -81,23 +81,24 @@
|
|||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
"ar": "العربية",
|
"ar": "العربية",
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
|
"el": "Ελληνικά",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"es": "Español",
|
"es": "Español",
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
"is": "",
|
"is": "Icelandic",
|
||||||
"it": "Italiano",
|
"it": "Italiano",
|
||||||
"ja": "日本語",
|
"ja": "日本語",
|
||||||
"ko": "한국어",
|
"ko": "한국어",
|
||||||
"nlBE": "",
|
"nlBE": "Dutch (Belgium)",
|
||||||
"pl": "Polski",
|
"pl": "Polski",
|
||||||
"pt": "Português",
|
"pt": "Português",
|
||||||
"ptBR": "Português (Brasil)",
|
"ptBR": "Português (Brasil)",
|
||||||
"ro": "",
|
"ro": "Romanian",
|
||||||
"ru": "Русский",
|
"ru": "Русский",
|
||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文 (繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "Отмена",
|
"cancel": "Отмена",
|
||||||
|
"clear": "Очистить",
|
||||||
"close": "Закрыть",
|
"close": "Закрыть",
|
||||||
"copy": "Копировать",
|
"copy": "Копировать",
|
||||||
"copyFile": "Скопировать файл",
|
"copyFile": "Скопировать файл",
|
||||||
@ -51,7 +52,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "Тело",
|
"body": "Тело",
|
||||||
"clear": "Очистить",
|
|
||||||
"closePreview": "Закрыть",
|
"closePreview": "Закрыть",
|
||||||
"files": "Файлы",
|
"files": "Файлы",
|
||||||
"folders": "Папки",
|
"folders": "Папки",
|
||||||
@ -87,6 +87,7 @@
|
|||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
"ar": "العربية",
|
"ar": "العربية",
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
|
"el": "Ελληνικά",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"es": "Español",
|
"es": "Español",
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
@ -103,7 +104,7 @@
|
|||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "Swedish (Sweden)",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文 (繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "Zrušiť",
|
"cancel": "Zrušiť",
|
||||||
|
"clear": "Zrušiť výber",
|
||||||
"close": "Zavrieť",
|
"close": "Zavrieť",
|
||||||
"copy": "Kopírovať",
|
"copy": "Kopírovať",
|
||||||
"copyFile": "Kopírovať súbor",
|
"copyFile": "Kopírovať súbor",
|
||||||
@ -51,7 +52,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "Telo",
|
"body": "Telo",
|
||||||
"clear": "Zrušiť výber",
|
|
||||||
"closePreview": "Zavrieť náhľad",
|
"closePreview": "Zavrieť náhľad",
|
||||||
"files": "Súbory",
|
"files": "Súbory",
|
||||||
"folders": "Priečinky",
|
"folders": "Priečinky",
|
||||||
@ -87,6 +87,7 @@
|
|||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
"ar": "العربية",
|
"ar": "العربية",
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
|
"el": "Ελληνικά",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"es": "Español",
|
"es": "Español",
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
@ -103,7 +104,7 @@
|
|||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "Swedish (Sweden)",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文 (繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "Avbryt",
|
"cancel": "Avbryt",
|
||||||
|
"clear": "Rensa",
|
||||||
"close": "Stäng",
|
"close": "Stäng",
|
||||||
"copy": "Kopiera",
|
"copy": "Kopiera",
|
||||||
"copyFile": "Kopiera fil",
|
"copyFile": "Kopiera fil",
|
||||||
@ -46,7 +47,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "Huvud",
|
"body": "Huvud",
|
||||||
"clear": "Rensa",
|
|
||||||
"closePreview": "Stäng förhands granskningen",
|
"closePreview": "Stäng förhands granskningen",
|
||||||
"files": "Filer",
|
"files": "Filer",
|
||||||
"folders": "Mappar",
|
"folders": "Mappar",
|
||||||
@ -81,23 +81,24 @@
|
|||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
"ar": "العربية",
|
"ar": "العربية",
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
|
"el": "Ελληνικά",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"es": "Español",
|
"es": "Español",
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
"is": "",
|
"is": "Icelandic",
|
||||||
"it": "Italiano",
|
"it": "Italiano",
|
||||||
"ja": "日本語",
|
"ja": "日本語",
|
||||||
"ko": "한국어",
|
"ko": "한국어",
|
||||||
"nlBE": "",
|
"nlBE": "Dutch (Belgium)",
|
||||||
"pl": "Polski",
|
"pl": "Polski",
|
||||||
"pt": "Português",
|
"pt": "Português",
|
||||||
"ptBR": "Português (Brasil)",
|
"ptBR": "Português (Brasil)",
|
||||||
"ro": "",
|
"ro": "Romanian",
|
||||||
"ru": "Русский",
|
"ru": "Русский",
|
||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文 (繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "Vazgeç",
|
"cancel": "Vazgeç",
|
||||||
|
"clear": "Temizle",
|
||||||
"close": "Kapat",
|
"close": "Kapat",
|
||||||
"copy": "Kopyala",
|
"copy": "Kopyala",
|
||||||
"copyFile": "Dosyayı kopyala",
|
"copyFile": "Dosyayı kopyala",
|
||||||
@ -49,7 +50,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "Sayfa",
|
"body": "Sayfa",
|
||||||
"clear": "Temizle",
|
|
||||||
"closePreview": "Önizlemeyi kapat",
|
"closePreview": "Önizlemeyi kapat",
|
||||||
"files": "Dosyalar",
|
"files": "Dosyalar",
|
||||||
"folders": "Klasörler",
|
"folders": "Klasörler",
|
||||||
@ -85,6 +85,7 @@
|
|||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
"ar": "العربية",
|
"ar": "العربية",
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
|
"el": "Ελληνικά",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"es": "Español",
|
"es": "Español",
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
@ -101,7 +102,7 @@
|
|||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "Swedish (Sweden)",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文 (繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
|
@ -1,270 +0,0 @@
|
|||||||
{
|
|
||||||
"buttons": {
|
|
||||||
"cancel": "Відмінити",
|
|
||||||
"close": "Закрити",
|
|
||||||
"copy": "Копіювати",
|
|
||||||
"copyFile": "Копіювати файл",
|
|
||||||
"copyToClipboard": "Копіювати в буфер обміну",
|
|
||||||
"create": "Створити",
|
|
||||||
"delete": "Видалити",
|
|
||||||
"download": "Завантажити",
|
|
||||||
"file": "Файл",
|
|
||||||
"folder": "Папка",
|
|
||||||
"hideDotfiles": "Приховати точкові файли",
|
|
||||||
"info": "Інфо",
|
|
||||||
"more": "Більше",
|
|
||||||
"move": "Перемістити",
|
|
||||||
"moveFile": "Перемістити файл",
|
|
||||||
"new": "Новий",
|
|
||||||
"next": "Далі",
|
|
||||||
"ok": "ОК",
|
|
||||||
"permalink": "Отримати постійне посилання",
|
|
||||||
"previous": "Назад",
|
|
||||||
"publish": "Опублікувати",
|
|
||||||
"rename": "Перейменувати",
|
|
||||||
"replace": "Замінити",
|
|
||||||
"reportIssue": "Повідомити про помилку",
|
|
||||||
"save": "Зберегти",
|
|
||||||
"schedule": "Планування",
|
|
||||||
"search": "Пошук",
|
|
||||||
"select": "Вибрати",
|
|
||||||
"selectMultiple": "Мультивибір",
|
|
||||||
"share": "Поділитися",
|
|
||||||
"shell": "Командний рядок",
|
|
||||||
"submit": "Відправити",
|
|
||||||
"switchView": "Вид",
|
|
||||||
"toggleSidebar": "Бічна панель",
|
|
||||||
"update": "Оновити",
|
|
||||||
"upload": "Завантажити",
|
|
||||||
"openFile": "Відкрити файл"
|
|
||||||
},
|
|
||||||
"download": {
|
|
||||||
"downloadFile": "Завантажити файл",
|
|
||||||
"downloadFolder": "Завантажити папку",
|
|
||||||
"downloadSelected": "Завантажити вибране"
|
|
||||||
},
|
|
||||||
"errors": {
|
|
||||||
"forbidden": "У вас немає прав доступу до цього.",
|
|
||||||
"internal": "Щось пішло не так.",
|
|
||||||
"notFound": "Неправильне посилання.",
|
|
||||||
"connection": "Немає підключення до сервера."
|
|
||||||
},
|
|
||||||
"files": {
|
|
||||||
"body": "Тіло",
|
|
||||||
"clear": "Очистити",
|
|
||||||
"closePreview": "Закрити",
|
|
||||||
"files": "Файли",
|
|
||||||
"folders": "Папки",
|
|
||||||
"home": "Домівка",
|
|
||||||
"lastModified": "Останній раз змінено",
|
|
||||||
"loading": "Завантаження...",
|
|
||||||
"lonely": "Тут пусто...",
|
|
||||||
"metadata": "Метадані",
|
|
||||||
"multipleSelectionEnabled": "Мультивибір включений",
|
|
||||||
"name": "Ім'я",
|
|
||||||
"size": "Розмір",
|
|
||||||
"sortByLastModified": "Сортувати за останнім зміненням",
|
|
||||||
"sortByName": "Сортувати за іменем",
|
|
||||||
"sortBySize": "Сортувати за розміром",
|
|
||||||
"noPreview": "Попередній перегляд для цього файлу недоступний."
|
|
||||||
},
|
|
||||||
"help": {
|
|
||||||
"click": "вибрати файл чи каталог",
|
|
||||||
"ctrl": {
|
|
||||||
"click": "вибрати кілька файлів чи каталогів",
|
|
||||||
"f": "відкрити пошук",
|
|
||||||
"s": "скачати файл або поточний каталог"
|
|
||||||
},
|
|
||||||
"del": "видалити вибрані елементи",
|
|
||||||
"doubleClick": "відкрити файл чи каталог",
|
|
||||||
"esc": "очистити виділення та/або закрити вікно",
|
|
||||||
"f1": "допомога",
|
|
||||||
"f2": "перейменувати файл",
|
|
||||||
"help": "Допомога"
|
|
||||||
},
|
|
||||||
"languages": {
|
|
||||||
"he": "עברית",
|
|
||||||
"hu": "Magyar",
|
|
||||||
"ar": "العربية",
|
|
||||||
"de": "Deutsch",
|
|
||||||
"en": "English",
|
|
||||||
"es": "Español",
|
|
||||||
"fr": "Français",
|
|
||||||
"is": "Icelandic",
|
|
||||||
"it": "Italiano",
|
|
||||||
"ja": "日本語",
|
|
||||||
"ko": "한국어",
|
|
||||||
"nlBE": "Dutch (Belgium)",
|
|
||||||
"pl": "Polski",
|
|
||||||
"pt": "Português",
|
|
||||||
"ptBR": "Português (Brasil)",
|
|
||||||
"ro": "Romanian",
|
|
||||||
"ru": "Русский",
|
|
||||||
"sk": "Slovenčina",
|
|
||||||
"svSE": "Swedish (Sweden)",
|
|
||||||
"tr": "Türkçe",
|
|
||||||
"ua": "Українська",
|
|
||||||
"zhCN": "中文 (简体)",
|
|
||||||
"zhTW": "中文 (繁體)"
|
|
||||||
},
|
|
||||||
"login": {
|
|
||||||
"createAnAccount": "Створити обліковий запис",
|
|
||||||
"loginInstead": "Вже є обліковий запис",
|
|
||||||
"password": "Пароль",
|
|
||||||
"passwordConfirm": "Підтвердження паролю",
|
|
||||||
"passwordsDontMatch": "Паролі не співпадають",
|
|
||||||
"signup": "Зареєструватися",
|
|
||||||
"submit": "Увійти",
|
|
||||||
"username": "Ім'я користувача",
|
|
||||||
"usernameTaken": "Ім'я користувача вже використовується",
|
|
||||||
"wrongCredentials": "Невірне ім'я користувача або пароль"
|
|
||||||
},
|
|
||||||
"permanent": "Постійний",
|
|
||||||
"prompts": {
|
|
||||||
"copy": "Копіювати",
|
|
||||||
"copyMessage": "Копіювати в:",
|
|
||||||
"currentlyNavigating": "Поточний каталог:",
|
|
||||||
"deleteMessageMultiple": "Видалити ці файли ({count})?",
|
|
||||||
"deleteMessageSingle": "Видалити цей файл/каталог?",
|
|
||||||
"deleteMessageShare": "Видалити цей спільний файл/каталог ({path})?",
|
|
||||||
"deleteTitle": "Видалити файлы",
|
|
||||||
"displayName": "Відображене ім'я:",
|
|
||||||
"download": "Завантажити файлы",
|
|
||||||
"downloadMessage": "Виберіть формат, в якому хочете завантажити.",
|
|
||||||
"error": "Помилка",
|
|
||||||
"fileInfo": "Інформація про файл",
|
|
||||||
"filesSelected": "Файлів вибрано: {count}.",
|
|
||||||
"lastModified": "Останній раз змінено",
|
|
||||||
"move": "Перемістити",
|
|
||||||
"moveMessage": "Перемістити в:",
|
|
||||||
"newArchetype": "Створіть новий запис на основі архетипу. Файл буде створено у каталозі.",
|
|
||||||
"newDir": "Новий каталог",
|
|
||||||
"newDirMessage": "Ім'я нового каталогу.",
|
|
||||||
"newFile": "Новий файл",
|
|
||||||
"newFileMessage": "Ім'я нового файлу.",
|
|
||||||
"numberDirs": "Кількість каталогів",
|
|
||||||
"numberFiles": "Кількість файлів",
|
|
||||||
"rename": "Перейменувати",
|
|
||||||
"renameMessage": "Нове ім'я",
|
|
||||||
"replace": "Замінити",
|
|
||||||
"replaceMessage": "Ім'я одного з файлів, що завантажуються, збігається з вже існуючим файлом. Ви бажаєте замінити існуючий?\n",
|
|
||||||
"schedule": "Планування",
|
|
||||||
"scheduleMessage": "Запланувати дату та час публікації.",
|
|
||||||
"show": "Показати",
|
|
||||||
"size": "Розмір",
|
|
||||||
"upload": "Завантажити",
|
|
||||||
"uploadMessage": "Виберіть варіант для завантаження.",
|
|
||||||
"optionalPassword": "Необов'язковий пароль"
|
|
||||||
},
|
|
||||||
"search": {
|
|
||||||
"images": "Зображення",
|
|
||||||
"music": "Музика",
|
|
||||||
"pdf": "PDF",
|
|
||||||
"pressToSearch": "Натисніть ENTER для пошуку",
|
|
||||||
"search": "Пошук...",
|
|
||||||
"typeToSearch": "Введіть ім'я файлу...",
|
|
||||||
"types": "Типи",
|
|
||||||
"video": "Відео"
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"admin": "Адмін",
|
|
||||||
"administrator": "Адміністратор",
|
|
||||||
"allowCommands": "Запуск команд",
|
|
||||||
"allowEdit": "Редагування, перейменування та видалення файлів чи каталогів",
|
|
||||||
"allowNew": "Створення нових файлів або каталогів",
|
|
||||||
"allowPublish": "Публікація нових записів та сторінок",
|
|
||||||
"allowSignup": "Дозволити користувачам реєструватися",
|
|
||||||
"avoidChanges": "(залишіть поле порожнім, щоб уникнути змін)",
|
|
||||||
"branding": "Брендинг",
|
|
||||||
"brandingDirectoryPath": "Шлях до каталогу брендів",
|
|
||||||
"brandingHelp": "Ви можете налаштувати зовнішній вигляд файлового браузера, змінивши його ім'я, замінивши логотип, додавши власні стилі та навіть відключивши зовнішні посилання на GitHub.\nДодаткову інформацію про персоналізований брендинг можна знайти на сторінці {0}.",
|
|
||||||
"changePassword": "Зміна пароля",
|
|
||||||
"commandRunner": "Запуск команд",
|
|
||||||
"commandRunnerHelp": "Тут ви можете встановити команди, які будуть виконуватися у зазначених подіях. Ви повинні вказати по одній команді в кожному рядку. Змінні середовища {0} та {1} будуть доступні, будучи {0} щодо {1}. Додаткові відомості про цю функцію та доступні змінні середовища див. у {2}.",
|
|
||||||
"commandsUpdated": "Команди оновлені!",
|
|
||||||
"createUserDir": "Автоматичне створення домашнього каталогу користувача при додаванні нового користувача",
|
|
||||||
"customStylesheet": "Свій стиль",
|
|
||||||
"defaultUserDescription": "Це налаштування за замовчуванням для нових користувачів.",
|
|
||||||
"disableExternalLinks": "Вимкнути зовнішні посилання (крім документації)",
|
|
||||||
"disableUsedDiskPercentage": "Disable used disk percentage graph",
|
|
||||||
"documentation": "документація",
|
|
||||||
"examples": "Приклади",
|
|
||||||
"executeOnShell": "Виконати в командному рядку",
|
|
||||||
"executeOnShellDescription": "За замовчуванням File Browser виконує команди, безпосередньо викликаючи їх бінарні файли. Якщо ви хочете замість цього запускати їх в оболонці (наприклад, Bash або PowerShell), ви можете визначити їх тут з необхідними аргументами та прапорами. Якщо встановлено, виконуєма вами команда буде додана як аргумент. Це стосується як користувацьких команд, так і обробників подій.",
|
|
||||||
"globalRules": "Це глобальний набір дозволяючих та забороняючих правил. Вони застосовні до кожного користувача. Ви можете визначити певні правила для налаштувань кожного користувача, щоб перевизначити їх.",
|
|
||||||
"globalSettings": "Глобальні налаштування",
|
|
||||||
"hideDotfiles": "Приховати точкові файли",
|
|
||||||
"insertPath": "Вставте шлях",
|
|
||||||
"insertRegex": "Вставити регулярний вираз",
|
|
||||||
"instanceName": "Поточна назва програми",
|
|
||||||
"language": "Мова",
|
|
||||||
"lockPassword": "Заборонити користувачеві змінювати пароль",
|
|
||||||
"newPassword": "Новий пароль",
|
|
||||||
"newPasswordConfirm": "Підтвердження нового пароля",
|
|
||||||
"newUser": "Новий користувач",
|
|
||||||
"password": "Пароль",
|
|
||||||
"passwordUpdated": "Пароль оновлено!",
|
|
||||||
"path": "Шлях",
|
|
||||||
"perm": {
|
|
||||||
"create": "Створювати файли та каталоги",
|
|
||||||
"delete": "Видаляти файли та каталоги",
|
|
||||||
"download": "Завантажувати",
|
|
||||||
"execute": "Виконувати команди",
|
|
||||||
"modify": "Редагувати файли",
|
|
||||||
"rename": "Перейменовувати або переміщувати файли та каталоги",
|
|
||||||
"share": "Ділітися файлами"
|
|
||||||
},
|
|
||||||
"permissions": "Дозволи",
|
|
||||||
"permissionsHelp": "Можна настроїти користувача як адміністратора або вибрати індивідуальні дозволи. При виборі \"Адміністратор\" всі інші параметри будуть автоматично вибрані. Керування користувачами - привілей адміністратора.\n",
|
|
||||||
"profileSettings": "Налаштування профілю",
|
|
||||||
"ruleExample1": "запобігти доступу до будь-якого прихованого файлу (наприклад: .git, .gitignore) у кожній папці.\n",
|
|
||||||
"ruleExample2": "блокує доступ до файлу з ім'ям Caddyfile у кореневій області.",
|
|
||||||
"rules": "Права",
|
|
||||||
"rulesHelp": "Тут ви можете визначити набір дозволяючих та забороняючих правил для цього конкретного користувача. Блоковані файли не відображатимуться у списках, і не будуть доступні для користувача. Є підтримка регулярних виразів та відносних шляхів.\n",
|
|
||||||
"scope": "Корінь",
|
|
||||||
"setDateFormat": "Встановити точний формат дати",
|
|
||||||
"settingsUpdated": "Налаштування застосовані!",
|
|
||||||
"shareDuration": "Тривалість спільного посилання",
|
|
||||||
"shareManagement": "Управління спільними посиланнями",
|
|
||||||
"shareDeleted": "Спільне посилання видалено!",
|
|
||||||
"singleClick": "Відкриття файлів та каталогів одним кліком",
|
|
||||||
"themes": {
|
|
||||||
"dark": "Темна",
|
|
||||||
"light": "Світла",
|
|
||||||
"title": "Тема"
|
|
||||||
},
|
|
||||||
"user": "Користувач",
|
|
||||||
"userCommands": "Команди",
|
|
||||||
"userCommandsHelp": "Список команд, доступних користувачу, розділений пробілами. Приклад:\n",
|
|
||||||
"userCreated": "Користувач створений!",
|
|
||||||
"userDefaults": "Налаштування користувача за замовчуванням",
|
|
||||||
"userDeleted": "Користувач видалений!",
|
|
||||||
"userManagement": "Керування користувачами",
|
|
||||||
"userUpdated": "Користувач змінений!",
|
|
||||||
"username": "Ім'я користувача",
|
|
||||||
"users": "Користувачі"
|
|
||||||
},
|
|
||||||
"sidebar": {
|
|
||||||
"help": "Допомога",
|
|
||||||
"hugoNew": "Hugo New",
|
|
||||||
"login": "Увійти",
|
|
||||||
"logout": "Вийти",
|
|
||||||
"myFiles": "Файли",
|
|
||||||
"newFile": "Новий файл",
|
|
||||||
"newFolder": "Новий каталог",
|
|
||||||
"preview": "Перегляд",
|
|
||||||
"settings": "Налаштування",
|
|
||||||
"signup": "Зареєструватися",
|
|
||||||
"siteSettings": "Налаштування сайту"
|
|
||||||
},
|
|
||||||
"success": {
|
|
||||||
"linkCopied": "Посилання скопійоване!"
|
|
||||||
},
|
|
||||||
"time": {
|
|
||||||
"days": "Дні",
|
|
||||||
"hours": "Години",
|
|
||||||
"minutes": "Хвилини",
|
|
||||||
"seconds": "Секунди",
|
|
||||||
"unit": "Одиниця часу"
|
|
||||||
}
|
|
||||||
}
|
|
271
frontend/src/i18n/uk.json
Normal file
271
frontend/src/i18n/uk.json
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
{
|
||||||
|
"buttons": {
|
||||||
|
"cancel": "Відмінити",
|
||||||
|
"clear": "Очистити",
|
||||||
|
"close": "Закрити",
|
||||||
|
"copy": "Копіювати",
|
||||||
|
"copyFile": "Копіювати файл",
|
||||||
|
"copyToClipboard": "Копіювати в буфер обміну",
|
||||||
|
"create": "Створити",
|
||||||
|
"delete": "Видалити",
|
||||||
|
"download": "Завантажити",
|
||||||
|
"file": "Файл",
|
||||||
|
"folder": "Папка",
|
||||||
|
"hideDotfiles": "Приховати точкові файли",
|
||||||
|
"info": "Інфо",
|
||||||
|
"more": "Більше",
|
||||||
|
"move": "Перемістити",
|
||||||
|
"moveFile": "Перемістити файл",
|
||||||
|
"new": "Новий",
|
||||||
|
"next": "Далі",
|
||||||
|
"ok": "ОК",
|
||||||
|
"permalink": "Отримати постійне посилання",
|
||||||
|
"previous": "Назад",
|
||||||
|
"publish": "Опублікувати",
|
||||||
|
"rename": "Перейменувати",
|
||||||
|
"replace": "Замінити",
|
||||||
|
"reportIssue": "Повідомити про помилку",
|
||||||
|
"save": "Зберегти",
|
||||||
|
"schedule": "Планування",
|
||||||
|
"search": "Пошук",
|
||||||
|
"select": "Вибрати",
|
||||||
|
"selectMultiple": "Мультивибір",
|
||||||
|
"share": "Поділитися",
|
||||||
|
"shell": "Командний рядок",
|
||||||
|
"submit": "Відправити",
|
||||||
|
"switchView": "Вид",
|
||||||
|
"toggleSidebar": "Бічна панель",
|
||||||
|
"update": "Оновити",
|
||||||
|
"upload": "Завантажити",
|
||||||
|
"openFile": "Відкрити файл"
|
||||||
|
},
|
||||||
|
"download": {
|
||||||
|
"downloadFile": "Завантажити файл",
|
||||||
|
"downloadFolder": "Завантажити папку",
|
||||||
|
"downloadSelected": "Завантажити вибране"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"forbidden": "У вас немає прав доступу до цього.",
|
||||||
|
"internal": "Щось пішло не так.",
|
||||||
|
"notFound": "Неправильне посилання.",
|
||||||
|
"connection": "Немає підключення до сервера."
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"body": "Тіло",
|
||||||
|
"closePreview": "Закрити",
|
||||||
|
"files": "Файли",
|
||||||
|
"folders": "Папки",
|
||||||
|
"home": "Домівка",
|
||||||
|
"lastModified": "Останній раз змінено",
|
||||||
|
"loading": "Завантаження...",
|
||||||
|
"lonely": "Тут пусто...",
|
||||||
|
"metadata": "Метадані",
|
||||||
|
"multipleSelectionEnabled": "Мультивибір включений",
|
||||||
|
"name": "Ім'я",
|
||||||
|
"size": "Розмір",
|
||||||
|
"sortByLastModified": "Сортувати за останнім зміненням",
|
||||||
|
"sortByName": "Сортувати за іменем",
|
||||||
|
"sortBySize": "Сортувати за розміром",
|
||||||
|
"noPreview": "Попередній перегляд для цього файлу недоступний."
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"click": "вибрати файл чи каталог",
|
||||||
|
"ctrl": {
|
||||||
|
"click": "вибрати кілька файлів чи каталогів",
|
||||||
|
"f": "відкрити пошук",
|
||||||
|
"s": "скачати файл або поточний каталог"
|
||||||
|
},
|
||||||
|
"del": "видалити вибрані елементи",
|
||||||
|
"doubleClick": "відкрити файл чи каталог",
|
||||||
|
"esc": "очистити виділення та/або закрити вікно",
|
||||||
|
"f1": "допомога",
|
||||||
|
"f2": "перейменувати файл",
|
||||||
|
"help": "Допомога"
|
||||||
|
},
|
||||||
|
"languages": {
|
||||||
|
"he": "עברית",
|
||||||
|
"hu": "Magyar",
|
||||||
|
"ar": "العربية",
|
||||||
|
"de": "Deutsch",
|
||||||
|
"el": "Ελληνικά",
|
||||||
|
"en": "English",
|
||||||
|
"es": "Español",
|
||||||
|
"fr": "Français",
|
||||||
|
"is": "Icelandic",
|
||||||
|
"it": "Italiano",
|
||||||
|
"ja": "日本語",
|
||||||
|
"ko": "한국어",
|
||||||
|
"nlBE": "Dutch (Belgium)",
|
||||||
|
"pl": "Polski",
|
||||||
|
"pt": "Português",
|
||||||
|
"ptBR": "Português (Brasil)",
|
||||||
|
"ro": "Romanian",
|
||||||
|
"ru": "Русский",
|
||||||
|
"sk": "Slovenčina",
|
||||||
|
"svSE": "Swedish (Sweden)",
|
||||||
|
"tr": "Türkçe",
|
||||||
|
"uk": "Українська",
|
||||||
|
"zhCN": "中文 (简体)",
|
||||||
|
"zhTW": "中文 (繁體)"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"createAnAccount": "Створити обліковий запис",
|
||||||
|
"loginInstead": "Вже є обліковий запис",
|
||||||
|
"password": "Пароль",
|
||||||
|
"passwordConfirm": "Підтвердження паролю",
|
||||||
|
"passwordsDontMatch": "Паролі не співпадають",
|
||||||
|
"signup": "Зареєструватися",
|
||||||
|
"submit": "Увійти",
|
||||||
|
"username": "Ім'я користувача",
|
||||||
|
"usernameTaken": "Ім'я користувача вже використовується",
|
||||||
|
"wrongCredentials": "Невірне ім'я користувача або пароль"
|
||||||
|
},
|
||||||
|
"permanent": "Постійний",
|
||||||
|
"prompts": {
|
||||||
|
"copy": "Копіювати",
|
||||||
|
"copyMessage": "Копіювати в:",
|
||||||
|
"currentlyNavigating": "Поточний каталог:",
|
||||||
|
"deleteMessageMultiple": "Видалити ці файли ({count})?",
|
||||||
|
"deleteMessageSingle": "Видалити цей файл/каталог?",
|
||||||
|
"deleteMessageShare": "Видалити цей спільний файл/каталог ({path})?",
|
||||||
|
"deleteTitle": "Видалити файлы",
|
||||||
|
"displayName": "Відображене ім'я:",
|
||||||
|
"download": "Завантажити файлы",
|
||||||
|
"downloadMessage": "Виберіть формат, в якому хочете завантажити.",
|
||||||
|
"error": "Помилка",
|
||||||
|
"fileInfo": "Інформація про файл",
|
||||||
|
"filesSelected": "Файлів вибрано: {count}.",
|
||||||
|
"lastModified": "Останній раз змінено",
|
||||||
|
"move": "Перемістити",
|
||||||
|
"moveMessage": "Перемістити в:",
|
||||||
|
"newArchetype": "Створіть новий запис на основі архетипу. Файл буде створено у каталозі.",
|
||||||
|
"newDir": "Новий каталог",
|
||||||
|
"newDirMessage": "Ім'я нового каталогу.",
|
||||||
|
"newFile": "Новий файл",
|
||||||
|
"newFileMessage": "Ім'я нового файлу.",
|
||||||
|
"numberDirs": "Кількість каталогів",
|
||||||
|
"numberFiles": "Кількість файлів",
|
||||||
|
"rename": "Перейменувати",
|
||||||
|
"renameMessage": "Нове ім'я",
|
||||||
|
"replace": "Замінити",
|
||||||
|
"replaceMessage": "Ім'я одного з файлів, що завантажуються, збігається з вже існуючим файлом. Ви бажаєте замінити існуючий?\n",
|
||||||
|
"schedule": "Планування",
|
||||||
|
"scheduleMessage": "Запланувати дату та час публікації.",
|
||||||
|
"show": "Показати",
|
||||||
|
"size": "Розмір",
|
||||||
|
"upload": "Завантажити",
|
||||||
|
"uploadMessage": "Виберіть варіант для завантаження.",
|
||||||
|
"optionalPassword": "Необов'язковий пароль"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"images": "Зображення",
|
||||||
|
"music": "Музика",
|
||||||
|
"pdf": "PDF",
|
||||||
|
"pressToSearch": "Натисніть ENTER для пошуку",
|
||||||
|
"search": "Пошук...",
|
||||||
|
"typeToSearch": "Введіть ім'я файлу...",
|
||||||
|
"types": "Типи",
|
||||||
|
"video": "Відео"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"admin": "Адмін",
|
||||||
|
"administrator": "Адміністратор",
|
||||||
|
"allowCommands": "Запуск команд",
|
||||||
|
"allowEdit": "Редагування, перейменування та видалення файлів чи каталогів",
|
||||||
|
"allowNew": "Створення нових файлів або каталогів",
|
||||||
|
"allowPublish": "Публікація нових записів та сторінок",
|
||||||
|
"allowSignup": "Дозволити користувачам реєструватися",
|
||||||
|
"avoidChanges": "(залишіть поле порожнім, щоб уникнути змін)",
|
||||||
|
"branding": "Брендинг",
|
||||||
|
"brandingDirectoryPath": "Шлях до каталогу брендів",
|
||||||
|
"brandingHelp": "Ви можете налаштувати зовнішній вигляд файлового браузера, змінивши його ім'я, замінивши логотип, додавши власні стилі та навіть відключивши зовнішні посилання на GitHub.\nДодаткову інформацію про персоналізований брендинг можна знайти на сторінці {0}.",
|
||||||
|
"changePassword": "Зміна пароля",
|
||||||
|
"commandRunner": "Запуск команд",
|
||||||
|
"commandRunnerHelp": "Тут ви можете встановити команди, які будуть виконуватися у зазначених подіях. Ви повинні вказати по одній команді в кожному рядку. Змінні середовища {0} та {1} будуть доступні, будучи {0} щодо {1}. Додаткові відомості про цю функцію та доступні змінні середовища див. у {2}.",
|
||||||
|
"commandsUpdated": "Команди оновлені!",
|
||||||
|
"createUserDir": "Автоматичне створення домашнього каталогу користувача при додаванні нового користувача",
|
||||||
|
"customStylesheet": "Свій стиль",
|
||||||
|
"defaultUserDescription": "Це налаштування за замовчуванням для нових користувачів.",
|
||||||
|
"disableExternalLinks": "Вимкнути зовнішні посилання (крім документації)",
|
||||||
|
"disableUsedDiskPercentage": "Disable used disk percentage graph",
|
||||||
|
"documentation": "документація",
|
||||||
|
"examples": "Приклади",
|
||||||
|
"executeOnShell": "Виконати в командному рядку",
|
||||||
|
"executeOnShellDescription": "За замовчуванням File Browser виконує команди, безпосередньо викликаючи їх бінарні файли. Якщо ви хочете замість цього запускати їх в оболонці (наприклад, Bash або PowerShell), ви можете визначити їх тут з необхідними аргументами та прапорами. Якщо встановлено, виконуєма вами команда буде додана як аргумент. Це стосується як користувацьких команд, так і обробників подій.",
|
||||||
|
"globalRules": "Це глобальний набір дозволяючих та забороняючих правил. Вони застосовні до кожного користувача. Ви можете визначити певні правила для налаштувань кожного користувача, щоб перевизначити їх.",
|
||||||
|
"globalSettings": "Глобальні налаштування",
|
||||||
|
"hideDotfiles": "Приховати точкові файли",
|
||||||
|
"insertPath": "Вставте шлях",
|
||||||
|
"insertRegex": "Вставити регулярний вираз",
|
||||||
|
"instanceName": "Поточна назва програми",
|
||||||
|
"language": "Мова",
|
||||||
|
"lockPassword": "Заборонити користувачеві змінювати пароль",
|
||||||
|
"newPassword": "Новий пароль",
|
||||||
|
"newPasswordConfirm": "Підтвердження нового пароля",
|
||||||
|
"newUser": "Новий користувач",
|
||||||
|
"password": "Пароль",
|
||||||
|
"passwordUpdated": "Пароль оновлено!",
|
||||||
|
"path": "Шлях",
|
||||||
|
"perm": {
|
||||||
|
"create": "Створювати файли та каталоги",
|
||||||
|
"delete": "Видаляти файли та каталоги",
|
||||||
|
"download": "Завантажувати",
|
||||||
|
"execute": "Виконувати команди",
|
||||||
|
"modify": "Редагувати файли",
|
||||||
|
"rename": "Перейменовувати або переміщувати файли та каталоги",
|
||||||
|
"share": "Ділітися файлами"
|
||||||
|
},
|
||||||
|
"permissions": "Дозволи",
|
||||||
|
"permissionsHelp": "Можна настроїти користувача як адміністратора або вибрати індивідуальні дозволи. При виборі \"Адміністратор\" всі інші параметри будуть автоматично вибрані. Керування користувачами - привілей адміністратора.\n",
|
||||||
|
"profileSettings": "Налаштування профілю",
|
||||||
|
"ruleExample1": "запобігти доступу до будь-якого прихованого файлу (наприклад: .git, .gitignore) у кожній папці.\n",
|
||||||
|
"ruleExample2": "блокує доступ до файлу з ім'ям Caddyfile у кореневій області.",
|
||||||
|
"rules": "Права",
|
||||||
|
"rulesHelp": "Тут ви можете визначити набір дозволяючих та забороняючих правил для цього конкретного користувача. Блоковані файли не відображатимуться у списках, і не будуть доступні для користувача. Є підтримка регулярних виразів та відносних шляхів.\n",
|
||||||
|
"scope": "Корінь",
|
||||||
|
"setDateFormat": "Встановити точний формат дати",
|
||||||
|
"settingsUpdated": "Налаштування застосовані!",
|
||||||
|
"shareDuration": "Тривалість спільного посилання",
|
||||||
|
"shareManagement": "Управління спільними посиланнями",
|
||||||
|
"shareDeleted": "Спільне посилання видалено!",
|
||||||
|
"singleClick": "Відкриття файлів та каталогів одним кліком",
|
||||||
|
"themes": {
|
||||||
|
"dark": "Темна",
|
||||||
|
"light": "Світла",
|
||||||
|
"title": "Тема"
|
||||||
|
},
|
||||||
|
"user": "Користувач",
|
||||||
|
"userCommands": "Команди",
|
||||||
|
"userCommandsHelp": "Список команд, доступних користувачу, розділений пробілами. Приклад:\n",
|
||||||
|
"userCreated": "Користувач створений!",
|
||||||
|
"userDefaults": "Налаштування користувача за замовчуванням",
|
||||||
|
"userDeleted": "Користувач видалений!",
|
||||||
|
"userManagement": "Керування користувачами",
|
||||||
|
"userUpdated": "Користувач змінений!",
|
||||||
|
"username": "Ім'я користувача",
|
||||||
|
"users": "Користувачі"
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"help": "Допомога",
|
||||||
|
"hugoNew": "Hugo New",
|
||||||
|
"login": "Увійти",
|
||||||
|
"logout": "Вийти",
|
||||||
|
"myFiles": "Файли",
|
||||||
|
"newFile": "Новий файл",
|
||||||
|
"newFolder": "Новий каталог",
|
||||||
|
"preview": "Перегляд",
|
||||||
|
"settings": "Налаштування",
|
||||||
|
"signup": "Зареєструватися",
|
||||||
|
"siteSettings": "Налаштування сайту"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"linkCopied": "Посилання скопійоване!"
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"days": "Дні",
|
||||||
|
"hours": "Години",
|
||||||
|
"minutes": "Хвилини",
|
||||||
|
"seconds": "Секунди",
|
||||||
|
"unit": "Одиниця часу"
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
|
"clear": "清空",
|
||||||
"close": "关闭",
|
"close": "关闭",
|
||||||
"copy": "复制",
|
"copy": "复制",
|
||||||
"copyFile": "复制文件",
|
"copyFile": "复制文件",
|
||||||
@ -56,7 +57,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "内容",
|
"body": "内容",
|
||||||
"clear": "清空",
|
|
||||||
"closePreview": "关闭预览",
|
"closePreview": "关闭预览",
|
||||||
"files": "文件",
|
"files": "文件",
|
||||||
"folders": "文件夹",
|
"folders": "文件夹",
|
||||||
@ -103,15 +103,15 @@
|
|||||||
"nlBE": "Dutch (Belgium)",
|
"nlBE": "Dutch (Belgium)",
|
||||||
"pl": "Polski",
|
"pl": "Polski",
|
||||||
"pt": "Português",
|
"pt": "Português",
|
||||||
"ptBR": "Português(Brasil)",
|
"ptBR": "Português (Brasil)",
|
||||||
"ro": "Romanian",
|
"ro": "Romanian",
|
||||||
"ru": "Русский",
|
"ru": "Русский",
|
||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "Swedish(Sweden)",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文(简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文(繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"createAnAccount": "创建用户",
|
"createAnAccount": "创建用户",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
|
"clear": "清空",
|
||||||
"close": "關閉",
|
"close": "關閉",
|
||||||
"copy": "複製",
|
"copy": "複製",
|
||||||
"copyFile": "複製檔案",
|
"copyFile": "複製檔案",
|
||||||
@ -46,7 +47,6 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"body": "内容",
|
"body": "内容",
|
||||||
"clear": "清空",
|
|
||||||
"closePreview": "關閉預覽",
|
"closePreview": "關閉預覽",
|
||||||
"files": "檔案",
|
"files": "檔案",
|
||||||
"folders": "資料夾",
|
"folders": "資料夾",
|
||||||
@ -81,6 +81,7 @@
|
|||||||
"hu": "Magyar",
|
"hu": "Magyar",
|
||||||
"ar": "العربية",
|
"ar": "العربية",
|
||||||
"de": "Deutsch",
|
"de": "Deutsch",
|
||||||
|
"el": "Ελληνικά",
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"es": "Español",
|
"es": "Español",
|
||||||
"fr": "Français",
|
"fr": "Français",
|
||||||
@ -88,16 +89,16 @@
|
|||||||
"it": "Italiano",
|
"it": "Italiano",
|
||||||
"ja": "日本語",
|
"ja": "日本語",
|
||||||
"ko": "한국어",
|
"ko": "한국어",
|
||||||
"nlBE": "Dutch(Belgium)",
|
"nlBE": "Dutch (Belgium)",
|
||||||
"pl": "Polski",
|
"pl": "Polski",
|
||||||
"pt": "Português",
|
"pt": "Português",
|
||||||
"ptBR": "Português (Brasil)",
|
"ptBR": "Português (Brasil)",
|
||||||
"ro": "Romanian",
|
"ro": "Romanian",
|
||||||
"ru": "Русский",
|
"ru": "Русский",
|
||||||
"sk": "Slovenčina",
|
"sk": "Slovenčina",
|
||||||
"svSE": "Swedish(Sweden)",
|
"svSE": "Swedish (Sweden)",
|
||||||
"tr": "Türkçe",
|
"tr": "Türkçe",
|
||||||
"ua": "Українська",
|
"uk": "Українська",
|
||||||
"zhCN": "中文 (简体)",
|
"zhCN": "中文 (简体)",
|
||||||
"zhTW": "中文 (繁體)"
|
"zhTW": "中文 (繁體)"
|
||||||
},
|
},
|
||||||
|
1
frontend/src/index.d.ts
vendored
Normal file
1
frontend/src/index.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
declare module "*.vue";
|
@ -1,51 +0,0 @@
|
|||||||
import "whatwg-fetch";
|
|
||||||
import cssVars from "css-vars-ponyfill";
|
|
||||||
import { sync } from "vuex-router-sync";
|
|
||||||
import store from "@/store";
|
|
||||||
import router from "@/router";
|
|
||||||
import i18n from "@/i18n";
|
|
||||||
import Vue from "@/utils/vue";
|
|
||||||
import { recaptcha, loginPage } from "@/utils/constants";
|
|
||||||
import { login, validateLogin } from "@/utils/auth";
|
|
||||||
import App from "@/App.vue";
|
|
||||||
|
|
||||||
cssVars();
|
|
||||||
|
|
||||||
sync(store, router);
|
|
||||||
|
|
||||||
async function start() {
|
|
||||||
try {
|
|
||||||
if (loginPage) {
|
|
||||||
await validateLogin();
|
|
||||||
} else {
|
|
||||||
await login("", "", "");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recaptcha) {
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
const check = () => {
|
|
||||||
if (typeof window.grecaptcha === "undefined") {
|
|
||||||
setTimeout(check, 100);
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
check();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
el: "#app",
|
|
||||||
store,
|
|
||||||
router,
|
|
||||||
i18n,
|
|
||||||
template: "<App/>",
|
|
||||||
components: { App },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
start();
|
|
109
frontend/src/main.ts
Normal file
109
frontend/src/main.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import { disableExternal } from "@/utils/constants";
|
||||||
|
import { createApp } from "vue";
|
||||||
|
import VueNumberInput from "@chenfengyuan/vue-number-input";
|
||||||
|
import VueLazyload from "vue-lazyload";
|
||||||
|
import { createVfm } from "vue-final-modal";
|
||||||
|
import Toast, { POSITION, useToast } from "vue-toastification";
|
||||||
|
import {
|
||||||
|
ToastOptions,
|
||||||
|
PluginOptions,
|
||||||
|
} from "vue-toastification/dist/types/types";
|
||||||
|
import createPinia from "@/stores";
|
||||||
|
import router from "@/router";
|
||||||
|
import i18n, { isRtl } from "@/i18n";
|
||||||
|
import App from "@/App.vue";
|
||||||
|
import CustomToast from "@/components/CustomToast.vue";
|
||||||
|
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||||
|
import relativeTime from "dayjs/plugin/relativeTime";
|
||||||
|
import duration from "dayjs/plugin/duration";
|
||||||
|
|
||||||
|
import "./css/styles.css";
|
||||||
|
|
||||||
|
// register dayjs plugins globally
|
||||||
|
dayjs.extend(localizedFormat);
|
||||||
|
dayjs.extend(relativeTime);
|
||||||
|
dayjs.extend(duration);
|
||||||
|
|
||||||
|
const pinia = createPinia(router);
|
||||||
|
const vfm = createVfm();
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
|
||||||
|
app.component(VueNumberInput.name || "vue-number-input", VueNumberInput);
|
||||||
|
app.use(VueLazyload);
|
||||||
|
app.use(Toast, {
|
||||||
|
transition: "Vue-Toastification__bounce",
|
||||||
|
maxToasts: 10,
|
||||||
|
newestOnTop: true,
|
||||||
|
} satisfies PluginOptions);
|
||||||
|
|
||||||
|
app.use(vfm);
|
||||||
|
app.use(i18n);
|
||||||
|
app.use(pinia);
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
|
app.mixin({
|
||||||
|
mounted() {
|
||||||
|
// expose vue instance to components
|
||||||
|
this.$el.__vue__ = this;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// provide v-focus for components
|
||||||
|
app.directive("focus", {
|
||||||
|
mounted: async (el) => {
|
||||||
|
// initiate focus for the element
|
||||||
|
el.focus();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const toastConfig = {
|
||||||
|
position: POSITION.BOTTOM_CENTER,
|
||||||
|
timeout: 4000,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnFocusLoss: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
draggablePercent: 0.6,
|
||||||
|
showCloseButtonOnHover: false,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeButton: "button",
|
||||||
|
icon: true,
|
||||||
|
} satisfies ToastOptions;
|
||||||
|
|
||||||
|
app.provide("$showSuccess", (message: string) => {
|
||||||
|
const $toast = useToast();
|
||||||
|
$toast.success(
|
||||||
|
{
|
||||||
|
component: CustomToast,
|
||||||
|
props: {
|
||||||
|
message: message,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ ...toastConfig, rtl: isRtl() }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.provide("$showError", (error: Error | string, displayReport = true) => {
|
||||||
|
const $toast = useToast();
|
||||||
|
$toast.error(
|
||||||
|
{
|
||||||
|
component: CustomToast,
|
||||||
|
props: {
|
||||||
|
message: (error as Error).message || error,
|
||||||
|
isReport: !disableExternal && displayReport,
|
||||||
|
// TODO: could you add this to the component itself?
|
||||||
|
reportText: i18n.global.t("buttons.reportIssue"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...toastConfig,
|
||||||
|
timeout: 0,
|
||||||
|
rtl: isRtl(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.isReady().then(() => app.mount("#app"));
|
@ -1,194 +0,0 @@
|
|||||||
import Vue from "vue";
|
|
||||||
import Router from "vue-router";
|
|
||||||
import Login from "@/views/Login.vue";
|
|
||||||
import Layout from "@/views/Layout.vue";
|
|
||||||
import Files from "@/views/Files.vue";
|
|
||||||
import Share from "@/views/Share.vue";
|
|
||||||
import Users from "@/views/settings/Users.vue";
|
|
||||||
import User from "@/views/settings/User.vue";
|
|
||||||
import Settings from "@/views/Settings.vue";
|
|
||||||
import GlobalSettings from "@/views/settings/Global.vue";
|
|
||||||
import ProfileSettings from "@/views/settings/Profile.vue";
|
|
||||||
import Shares from "@/views/settings/Shares.vue";
|
|
||||||
import Errors from "@/views/Errors.vue";
|
|
||||||
import store from "@/store";
|
|
||||||
import { baseURL, name } from "@/utils/constants";
|
|
||||||
import i18n, { rtlLanguages } from "@/i18n";
|
|
||||||
|
|
||||||
Vue.use(Router);
|
|
||||||
|
|
||||||
const titles = {
|
|
||||||
Login: "sidebar.login",
|
|
||||||
Share: "buttons.share",
|
|
||||||
Files: "files.files",
|
|
||||||
Settings: "sidebar.settings",
|
|
||||||
ProfileSettings: "settings.profileSettings",
|
|
||||||
Shares: "settings.shareManagement",
|
|
||||||
GlobalSettings: "settings.globalSettings",
|
|
||||||
Users: "settings.users",
|
|
||||||
User: "settings.user",
|
|
||||||
Forbidden: "errors.forbidden",
|
|
||||||
NotFound: "errors.notFound",
|
|
||||||
InternalServerError: "errors.internal",
|
|
||||||
};
|
|
||||||
|
|
||||||
const router = new Router({
|
|
||||||
base: import.meta.env.PROD ? baseURL : "",
|
|
||||||
mode: "history",
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
path: "/login",
|
|
||||||
name: "Login",
|
|
||||||
component: Login,
|
|
||||||
beforeEnter: (to, from, next) => {
|
|
||||||
if (store.getters.isLogged) {
|
|
||||||
return next({ path: "/files" });
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/*",
|
|
||||||
component: Layout,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: "/share/*",
|
|
||||||
name: "Share",
|
|
||||||
component: Share,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/files/*",
|
|
||||||
name: "Files",
|
|
||||||
component: Files,
|
|
||||||
meta: {
|
|
||||||
requiresAuth: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/settings",
|
|
||||||
name: "Settings",
|
|
||||||
component: Settings,
|
|
||||||
redirect: {
|
|
||||||
path: "/settings/profile",
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
requiresAuth: true,
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: "/settings/profile",
|
|
||||||
name: "ProfileSettings",
|
|
||||||
component: ProfileSettings,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/settings/shares",
|
|
||||||
name: "Shares",
|
|
||||||
component: Shares,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/settings/global",
|
|
||||||
name: "GlobalSettings",
|
|
||||||
component: GlobalSettings,
|
|
||||||
meta: {
|
|
||||||
requiresAdmin: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/settings/users",
|
|
||||||
name: "Users",
|
|
||||||
component: Users,
|
|
||||||
meta: {
|
|
||||||
requiresAdmin: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/settings/users/*",
|
|
||||||
name: "User",
|
|
||||||
component: User,
|
|
||||||
meta: {
|
|
||||||
requiresAdmin: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/403",
|
|
||||||
name: "Forbidden",
|
|
||||||
component: Errors,
|
|
||||||
props: {
|
|
||||||
errorCode: 403,
|
|
||||||
showHeader: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/404",
|
|
||||||
name: "NotFound",
|
|
||||||
component: Errors,
|
|
||||||
props: {
|
|
||||||
errorCode: 404,
|
|
||||||
showHeader: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/500",
|
|
||||||
name: "InternalServerError",
|
|
||||||
component: Errors,
|
|
||||||
props: {
|
|
||||||
errorCode: 500,
|
|
||||||
showHeader: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/files",
|
|
||||||
redirect: {
|
|
||||||
path: "/files/",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/*",
|
|
||||||
redirect: (to) => `/files${to.path}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
|
||||||
const title = i18n.t(titles[to.name]);
|
|
||||||
document.title = title + " - " + name;
|
|
||||||
|
|
||||||
/*** RTL related settings per route ****/
|
|
||||||
const rtlSet = document.querySelector("body").classList.contains("rtl");
|
|
||||||
const shouldSetRtl = rtlLanguages.includes(i18n.locale);
|
|
||||||
switch (true) {
|
|
||||||
case shouldSetRtl && !rtlSet:
|
|
||||||
document.querySelector("body").classList.add("rtl");
|
|
||||||
break;
|
|
||||||
case !shouldSetRtl && rtlSet:
|
|
||||||
document.querySelector("body").classList.remove("rtl");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (to.matched.some((record) => record.meta.requiresAuth)) {
|
|
||||||
if (!store.getters.isLogged) {
|
|
||||||
next({
|
|
||||||
path: "/login",
|
|
||||||
query: { redirect: to.fullPath },
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (to.matched.some((record) => record.meta.requiresAdmin)) {
|
|
||||||
if (!store.state.user.perm.admin) {
|
|
||||||
next({ path: "/403" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
220
frontend/src/router/index.ts
Normal file
220
frontend/src/router/index.ts
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
import { RouteLocation, createRouter, createWebHistory } from "vue-router";
|
||||||
|
import Login from "@/views/Login.vue";
|
||||||
|
import Layout from "@/views/Layout.vue";
|
||||||
|
import Files from "@/views/Files.vue";
|
||||||
|
import Share from "@/views/Share.vue";
|
||||||
|
import Users from "@/views/settings/Users.vue";
|
||||||
|
import User from "@/views/settings/User.vue";
|
||||||
|
import Settings from "@/views/Settings.vue";
|
||||||
|
import GlobalSettings from "@/views/settings/Global.vue";
|
||||||
|
import ProfileSettings from "@/views/settings/Profile.vue";
|
||||||
|
import Shares from "@/views/settings/Shares.vue";
|
||||||
|
import Errors from "@/views/Errors.vue";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
import { baseURL, name } from "@/utils/constants";
|
||||||
|
import i18n from "@/i18n";
|
||||||
|
import { recaptcha, loginPage } from "@/utils/constants";
|
||||||
|
import { login, validateLogin } from "@/utils/auth";
|
||||||
|
|
||||||
|
const titles = {
|
||||||
|
Login: "sidebar.login",
|
||||||
|
Share: "buttons.share",
|
||||||
|
Files: "files.files",
|
||||||
|
Settings: "sidebar.settings",
|
||||||
|
ProfileSettings: "settings.profileSettings",
|
||||||
|
Shares: "settings.shareManagement",
|
||||||
|
GlobalSettings: "settings.globalSettings",
|
||||||
|
Users: "settings.users",
|
||||||
|
User: "settings.user",
|
||||||
|
Forbidden: "errors.forbidden",
|
||||||
|
NotFound: "errors.notFound",
|
||||||
|
InternalServerError: "errors.internal",
|
||||||
|
};
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: "/login",
|
||||||
|
name: "Login",
|
||||||
|
component: Login,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/share",
|
||||||
|
component: Layout,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: ":path*",
|
||||||
|
name: "Share",
|
||||||
|
component: Share,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/files",
|
||||||
|
component: Layout,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: ":path*",
|
||||||
|
name: "Files",
|
||||||
|
component: Files,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/settings",
|
||||||
|
component: Layout,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
name: "Settings",
|
||||||
|
component: Settings,
|
||||||
|
redirect: {
|
||||||
|
path: "/settings/profile",
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "profile",
|
||||||
|
name: "ProfileSettings",
|
||||||
|
component: ProfileSettings,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "shares",
|
||||||
|
name: "Shares",
|
||||||
|
component: Shares,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "global",
|
||||||
|
name: "GlobalSettings",
|
||||||
|
component: GlobalSettings,
|
||||||
|
meta: {
|
||||||
|
requiresAdmin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "users",
|
||||||
|
name: "Users",
|
||||||
|
component: Users,
|
||||||
|
meta: {
|
||||||
|
requiresAdmin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "users/:id",
|
||||||
|
name: "User",
|
||||||
|
component: User,
|
||||||
|
meta: {
|
||||||
|
requiresAdmin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/403",
|
||||||
|
name: "Forbidden",
|
||||||
|
component: Errors,
|
||||||
|
props: {
|
||||||
|
errorCode: 403,
|
||||||
|
showHeader: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/404",
|
||||||
|
name: "NotFound",
|
||||||
|
component: Errors,
|
||||||
|
props: {
|
||||||
|
errorCode: 404,
|
||||||
|
showHeader: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/500",
|
||||||
|
name: "InternalServerError",
|
||||||
|
component: Errors,
|
||||||
|
props: {
|
||||||
|
errorCode: 500,
|
||||||
|
showHeader: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/:catchAll(.*)*",
|
||||||
|
redirect: (to: RouteLocation) =>
|
||||||
|
`/files/${[...to.params.catchAll].join("/")}`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
async function initAuth() {
|
||||||
|
if (loginPage) {
|
||||||
|
await validateLogin();
|
||||||
|
} else {
|
||||||
|
await login("", "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recaptcha) {
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
const check = () => {
|
||||||
|
if (typeof window.grecaptcha === "undefined") {
|
||||||
|
setTimeout(check, 100);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
check();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(baseURL),
|
||||||
|
routes,
|
||||||
|
});
|
||||||
|
|
||||||
|
router.beforeResolve(async (to, from, next) => {
|
||||||
|
const title = i18n.global.t(titles[to.name as keyof typeof titles]);
|
||||||
|
document.title = title + " - " + name;
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
// this will only be null on first route
|
||||||
|
if (from.name == null) {
|
||||||
|
try {
|
||||||
|
await initAuth();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to.path.endsWith("/login") && authStore.isLoggedIn) {
|
||||||
|
next({ path: "/files/" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to.matched.some((record) => record.meta.requiresAuth)) {
|
||||||
|
if (!authStore.isLoggedIn) {
|
||||||
|
next({
|
||||||
|
path: "/login",
|
||||||
|
query: { redirect: to.fullPath },
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to.matched.some((record) => record.meta.requiresAdmin)) {
|
||||||
|
if (authStore.user === null || !authStore.user.perm.admin) {
|
||||||
|
next({ path: "/403" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
export { router, router as default };
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user