Merge pull request #4 from gtsteffaniak/fix-frontend-deps

- 15% RAM reduction
- more search keywords
- context bar
This commit is contained in:
Graham Steffaniak 2023-07-17 00:29:45 -05:00 committed by GitHub
commit 08b4b03831
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 3969 additions and 23608 deletions

2
.gitignore vendored
View File

@ -6,7 +6,7 @@ rice-box.go
/filebrowser /filebrowser
/filebrowser.exe /filebrowser.exe
/dist /dist
/src/backend/vendor/* /src/backend/vendor
.DS_Store .DS_Store
node_modules node_modules

View File

@ -1,7 +1,8 @@
FROM node:14.21-slim as nbuild FROM node:slim as nbuild
WORKDIR /app WORKDIR /app
COPY ./src/frontend ./ COPY ./src/frontend/package*.json ./
RUN npm i RUN npm i
COPY ./src/frontend/ ./
RUN npm run build RUN npm run build
FROM golang:alpine as base FROM golang:alpine as base

Binary file not shown.

View File

@ -8,18 +8,18 @@ import (
var searchHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { var searchHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
response := []map[string]interface{}{} response := []map[string]interface{}{}
query := r.URL.Query().Get("query") query := r.URL.Query().Get("query")
files, dirs := search.SearchAllIndexes(query, r.URL.Path) indexInfo, fileTypes := search.SearchAllIndexes(query, r.URL.Path)
for _,v := range(files){ for _,path := range(indexInfo){
response = append(response, map[string]interface{}{ f := fileTypes[path]
"dir": false, responseObj := map[string]interface{}{
"path": v, "path" : path,
}) }
} for filterType,_ := range(f) {
for _,v := range(dirs){ if f[filterType] {
response = append(response, map[string]interface{}{ responseObj[filterType] = f[filterType]
"dir": true, }
"path": v, }
}) response = append(response,responseObj)
} }
return renderJSON(w, r, response) return renderJSON(w, r, response)
}) })

View File

@ -11,6 +11,8 @@ var documentTypes = []string{
".word", ".word",
".pdf", ".pdf",
".txt", ".txt",
".doc",
".docx",
} }
var compressedFile = []string{ var compressedFile = []string{
@ -45,7 +47,6 @@ func ParseSearch(value string) *searchOptions {
if len(filterType) == 1 { if len(filterType) == 1 {
continue continue
} }
switch filterType[1] { switch filterType[1] {
case "image": case "image":
opts.Conditions["image"] = true opts.Conditions["image"] = true
@ -55,8 +56,12 @@ func ParseSearch(value string) *searchOptions {
opts.Conditions["video"] = true opts.Conditions["video"] = true
case "doc": case "doc":
opts.Conditions["doc"] = true opts.Conditions["doc"] = true
case "zip": case "archive":
opts.Conditions["zip"] = true opts.Conditions["archive"] = true
case "folder":
opts.Conditions["dir"] = true
case "file":
opts.Conditions["dir"] = false
} }
} }

View File

@ -13,7 +13,7 @@ import (
var ( var (
rootPath string = "/srv" rootPath string = "/srv"
indexes map[string][]string indexes = map[string][]string{}
mutex sync.RWMutex mutex sync.RWMutex
lastIndexed time.Time lastIndexed time.Time
) )
@ -21,6 +21,8 @@ var (
func InitializeIndex(intervalMinutes uint32) { func InitializeIndex(intervalMinutes uint32) {
// Initialize the indexes map // Initialize the indexes map
indexes = make(map[string][]string) indexes = make(map[string][]string)
indexes["dirs"] = []string{}
indexes["files"] = []string{}
var numFiles, numDirs int var numFiles, numDirs int
log.Println("Indexing files...") log.Println("Indexing files...")
lastIndexedStart := time.Now() lastIndexedStart := time.Now()
@ -79,92 +81,88 @@ func indexFiles(path string, numFiles *int, numDirs *int) (int, int, error) {
for _, file := range files { for _, file := range files {
if file.IsDir() { if file.IsDir() {
*numDirs++ *numDirs++
indexFiles(path+"/"+file.Name(), numFiles, numDirs) addToIndex(path, file.Name(), true)
indexFiles(path+"/"+file.Name(), numFiles, numDirs) // recursive
} else {
*numFiles++
addToIndex(path, file.Name(), false)
} }
*numFiles++
addToIndex(path, file.Name())
} }
return *numFiles, *numDirs, nil return *numFiles, *numDirs, nil
} }
func addToIndex(path string, fileName string) { func addToIndex(path string, fileName string, isDir bool) {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
path = strings.TrimPrefix(path, rootPath+"/") path = strings.TrimPrefix(path, rootPath+"/")
path = strings.TrimSuffix(path, "/") path = strings.TrimSuffix(path, "/")
adjustedPath := path + "/" + fileName
if path == rootPath { if path == rootPath {
path = "/" adjustedPath = fileName
} }
info, exists := indexes[path] if isDir {
if !exists { indexes["dirs"] = append(indexes["dirs"], adjustedPath)
info = []string{} }else{
indexes["files"] = append(indexes["files"], adjustedPath)
} }
info = append(info, fileName)
indexes[path] = info
} }
func SearchAllIndexes(search string, scope string) ([]string, []string) { func SearchAllIndexes(search string, scope string) ([]string, map[string]map[string]bool) {
searchOptions := ParseSearch(search) searchOptions := ParseSearch(search)
mutex.RLock() mutex.RLock()
defer mutex.RUnlock() defer mutex.RUnlock()
var matchingFiles []string fileListTypes := make(map[string]map[string]bool)
var matchingDirs []string var matching []string
maximum := 100 maximum := 125
count := 0
for _, searchTerm := range searchOptions.Terms { for _, searchTerm := range searchOptions.Terms {
if searchTerm == "" { if searchTerm == "" {
continue continue
} }
count := 0
// Iterate over the indexes // Iterate over the indexes
for dirName, v := range indexes { for _, dirName := range indexes["dirs"] {
if count > maximum { if count > maximum {
break break
} }
searchItems := v
// Iterate over the path names
for _, pathName := range searchItems {
if count > maximum {
break
}
if dirName != "/" {
pathName = dirName + "/" + pathName
}
// Check if the path name contains the search term
if !containsSearchTerm(pathName, searchTerm, searchOptions.Conditions) {
continue
}
pathName = scopedPathNameFilter(pathName, scope)
if pathName == "" {
continue
}
count++
matchingFiles = append(matchingFiles, pathName)
}
// Check if the path name contains the search term
if !containsSearchTerm(dirName, searchTerm, searchOptions.Conditions) {
continue
}
pathName := scopedPathNameFilter(dirName, scope) pathName := scopedPathNameFilter(dirName, scope)
if pathName == "" { if pathName == "" {
continue continue
} }
matches, fileType := containsSearchTerm(pathName, searchTerm, searchOptions.Conditions, true)
if !matches {
continue
}
count++
matching = append(matching, pathName+"/")
fileListTypes[pathName+"/"] = fileType
}
count = 0
for _, fileName := range indexes["files"] {
if count > maximum {
break
}
pathName := scopedPathNameFilter(fileName, scope)
if pathName == "" {
continue
}
// Check if the path name contains the search term
matches, fileType := containsSearchTerm(pathName, searchTerm, searchOptions.Conditions, false)
if !matches {
continue
}
matching = append(matching, pathName)
fileListTypes[pathName] = fileType
count++ count++
matchingDirs = append(matchingDirs, pathName)
} }
} }
// Sort the strings based on the number of elements after splitting by "/" // Sort the strings based on the number of elements after splitting by "/"
sort.Slice(matchingFiles, func(i, j int) bool { sort.Slice(matching, func(i, j int) bool {
parts1 := strings.Split(matchingFiles[i], "/") parts1 := strings.Split(matching[i], "/")
parts2 := strings.Split(matchingFiles[j], "/") parts2 := strings.Split(matching[j], "/")
return len(parts1) < len(parts2) return len(parts1) < len(parts2)
}) })
// Sort the strings based on the number of elements after splitting by "/" return matching, fileListTypes
sort.Slice(matchingDirs, func(i, j int) bool {
parts1 := strings.Split(matchingDirs[i], "/")
parts2 := strings.Split(matchingDirs[j], "/")
return len(parts1) < len(parts2)
})
return matchingFiles, matchingDirs
} }
func scopedPathNameFilter(pathName string, scope string) string { func scopedPathNameFilter(pathName string, scope string) string {
@ -177,51 +175,52 @@ func scopedPathNameFilter(pathName string, scope string) string {
return pathName return pathName
} }
func containsSearchTerm(pathName string, searchTerm string, conditions map[string]bool) bool { func containsSearchTerm(pathName string, searchTerm string, conditions map[string]bool, isDir bool) (bool, map[string]bool) {
path := getLastPathComponent(pathName) path := getLastPathComponent(pathName)
fileTypes := map[string]bool{}
matchesCondition := false
extension := filepath.Ext(strings.ToLower(path))
mimetype := mime.TypeByExtension(extension)
fileTypes["audio"] = strings.HasPrefix(mimetype, "audio")
fileTypes["image"] = strings.HasPrefix(mimetype, "image")
fileTypes["video"] = strings.HasPrefix(mimetype, "video")
fileTypes["doc"] = isDoc(extension)
fileTypes["archive"] = isArchive(extension)
fileTypes["dir"] = isDir
anyFilter := false
for t,v := range conditions {
if t == "exact" {
continue
}
matchesCondition = v == fileTypes[t]
anyFilter = true
}
if !anyFilter {
matchesCondition = true
}
if !conditions["exact"] { if !conditions["exact"] {
path = strings.ToLower(path) path = strings.ToLower(path)
searchTerm = strings.ToLower(searchTerm) searchTerm = strings.ToLower(searchTerm)
} }
matchesCondition := true return strings.Contains(path, searchTerm) && matchesCondition, fileTypes
if conditions["audio"] { }
extension := filepath.Ext(path)
mimetype := mime.TypeByExtension(extension) func isDoc(extension string) bool {
matchesCondition = strings.HasPrefix(mimetype, "audio") for _, typefile := range documentTypes {
} if extension == typefile {
if conditions["video"] { return true
extension := filepath.Ext(path)
mimetype := mime.TypeByExtension(extension)
matchesCondition = strings.HasPrefix(mimetype, "video")
}
if conditions["image"] {
extension := filepath.Ext(path)
mimetype := mime.TypeByExtension(extension)
matchesCondition = strings.HasPrefix(mimetype, "image")
}
if conditions["doc"] {
extension := filepath.Ext(path)
for _, typefile := range documentTypes {
if extension == typefile {
matchesCondition = true
continue
} else {
matchesCondition = false
}
} }
} }
if conditions["zip"] { return false
extension := filepath.Ext(path) }
for _, typefile := range compressedFile {
if extension == typefile { func isArchive(extension string) bool {
matchesCondition = true for _, typefile := range compressedFile {
continue if extension == typefile {
} else { return true
matchesCondition = false
}
} }
} }
return strings.Contains(path, searchTerm) && matchesCondition return false
} }
func getLastPathComponent(path string) string { func getLastPathComponent(path string) string {

View File

@ -2,7 +2,7 @@ package version
var ( var (
// Version is the current File Browser version. // Version is the current File Browser version.
Version = "(0.1.0)" Version = "(0.1.2)"
// CommitSHA is the commmit sha. // CommitSHA is the commmit sha.
CommitSHA = "(unknown)" CommitSHA = "(unknown)"
) )

View File

@ -1,13 +0,0 @@
//go:build !dev
// +build !dev
package frontend
import "embed"
//go:embed dist/*
var assets embed.FS
func Assets() embed.FS {
return assets
}

View File

@ -1,15 +0,0 @@
//go:build dev
// +build dev
package frontend
import (
"io/fs"
"os"
)
var assets fs.FS = os.DirFS("frontend")
func Assets() fs.FS {
return assets
}

File diff suppressed because it is too large Load Diff

View File

@ -5,21 +5,20 @@
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
"build": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --no-clean", "build": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --no-clean",
"lint": "npx vue-cli-service lint --no-fix --max-warnings=0",
"fix": "npx vue-cli-service lint", "fix": "npx vue-cli-service lint",
"watch": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --watch --no-clean" "watch": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --watch --no-clean"
}, },
"dependencies": { "dependencies": {
"normalize.css": "^8.0.1",
"file-loader": "^6.2.0",
"ace-builds": "^1.4.7", "ace-builds": "^1.4.7",
"clipboard": "^2.0.4", "clipboard": "^2.0.4",
"core-js": "^3.9.1",
"css-vars-ponyfill": "^2.4.3", "css-vars-ponyfill": "^2.4.3",
"js-base64": "^2.5.1", "js-base64": "^2.5.1",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"material-icons": "^1.10.5", "material-icons": "^1.10.5",
"moment": "^2.29.4", "moment": "^2.29.4",
"normalize.css": "^8.0.1",
"noty": "^3.2.0-beta", "noty": "^3.2.0-beta",
"pretty-bytes": "^6.0.0", "pretty-bytes": "^6.0.0",
"qrcode.vue": "^1.7.0", "qrcode.vue": "^1.7.0",
@ -35,33 +34,10 @@
"whatwg-fetch": "^3.6.2" "whatwg-fetch": "^3.6.2"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "^4.1.2", "@vue/cli-service": "^5.0.8",
"@vue/cli-plugin-eslint": "~4.5.0", "compression-webpack-plugin": "^10.0.0",
"@vue/cli-service": "^4.1.2",
"@vue/eslint-config-prettier": "^6.0.0",
"babel-eslint": "^10.1.0",
"compression-webpack-plugin": "^6.0.3",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^6.2.2",
"prettier": "^2.2.1",
"vue-template-compiler": "^2.6.10" "vue-template-compiler": "^2.6.10"
}, },
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended",
"@vue/prettier"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},
"postcss": { "postcss": {
"plugins": { "plugins": {
"autoprefixer": {} "autoprefixer": {}

View File

@ -0,0 +1,32 @@
import vue from 'rollup-plugin-vue'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import { terser } from "rollup-plugin-terser"
import postcss from 'rollup-plugin-postcss'
import babel from '@rollup/plugin-babel'
import replace from '@rollup/plugin-replace'
import livereload from 'rollup-plugin-livereload'
import css from 'rollup-plugin-css-only'
import autoprefixer from 'autoprefixer'
export default {
input: 'src/main.js', // Entry file
output: {
file: 'dist/build.js', // Output file
format: 'iife', // Immediately Invoked Function Expression format suitable for <script> tag
},
plugins: [
replace({
'process.env.NODE_ENV': JSON.stringify('production'),
'process.env.VUE_ENV': '"client"'
}),
nodeResolve({ browser: true, jsnext: true }), // Resolve modules from node_modules
commonjs(), // Convert CommonJS modules to ES6
vue({ css: false }), // Handle .vue files
css({ output: 'bundle.css' }), // css to separate file
postcss({ plugins: [autoprefixer()]}),
babel({ babelHelpers: 'bundled' }), // Transpile to ES5
terser(), // Minify the build
livereload('dist') // Live reload for development
],
}

View File

@ -1,65 +1,50 @@
<template> <template>
<div id="search" @click="open" v-bind:class="{ active, ongoing }"> <div id="search" @click="open" v-bind:class="{ active, ongoing }">
<div id="input"> <div id="input">
<button <button v-if="active" class="action" @click="close" :aria-label="$t('buttons.close')" :title="$t('buttons.close')">
v-if="active"
class="action"
@click="close"
:aria-label="$t('buttons.close')"
:title="$t('buttons.close')"
>
<i class="material-icons">arrow_back</i> <i class="material-icons">arrow_back</i>
</button> </button>
<i v-else class="material-icons">search</i> <i v-else class="material-icons">search</i>
<input <input type="text" @keyup.exact="keyup" @input="submit" ref="input" :autofocus="active" v-model.trim="value"
type="text" :aria-label="$t('search.search')" :placeholder="$t('search.search')" />
@keyup.exact="keyup"
@input="submit"
ref="input"
:autofocus="active"
v-model.trim="value"
:aria-label="$t('search.search')"
:placeholder="$t('search.search')"
/>
</div> </div>
<div id="result" ref="result"> <div id="result" ref="result">
<div> <div id="result-list">
<br>
<br>
<div class="button" style="width:100%">Search Context: {{ getContext(this.$route.path) }}</div>
<template v-if="isEmpty"> <template v-if="isEmpty">
<p>{{ text }}</p> <p>{{ text }}</p>
<template v-if="value.length === 0"> <template v-if="value.length === 0">
<div class="boxes"> <div class="boxes">
<h3>{{ $t("search.types") }}</h3> <h3>{{ $t("search.types") }}</h3>
<div> <div>
<div <div tabindex="0" v-for="(v, k) in boxes" :key="k" role="button" @click="init('type:' + k)"
tabindex="0" :aria-label="(v.label)">
v-for="(v, k) in boxes"
:key="k"
role="button"
@click="init('type:' + k)"
:aria-label="$t('search.' + v.label)"
>
<i class="material-icons">{{ v.icon }}</i> <i class="material-icons">{{ v.icon }}</i>
<p>{{ $t("search." + v.label) }}</p> <p>{{ v.label }}</p>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
</template> </template>
<ul v-show="filteredResults.length > 0"> <ul v-show="results.length > 0">
<li v-for="(s, k) in filteredResults" :key="k"> <li v-for="(s, k) in results" :key="k" @click.stop.prevent="navigateTo(s.url)" style="cursor: pointer">
<router-link @click.native="close" :to="s.url"> <router-link to="#" event="">
<i v-if="s.dir" class="material-icons">folder</i> <i v-if="s.dir" class="material-icons folder-icons"> folder </i>
<i v-else class="material-icons">insert_drive_file</i> <i v-else-if="s.audio" class="material-icons audio-icons"> volume_up </i>
<span>./{{ s.path }}</span> <i v-else-if="s.image" class="material-icons image-icons"> photo </i>
<i v-else-if="s.video" class="material-icons video-icons"> movie </i>
<i v-else-if="s.archive" class="material-icons archive-icons"> archive </i>
<i v-else class="material-icons file-icons"> insert_drive_file </i>
<span class="text-container">
{{ basePath(s.path) }}<b>{{ baseName(s.path) }}</b>
</span>
</router-link> </router-link>
</li> </li>
</ul> </ul>
</div> </div>
<p id="renew">
<i class="material-icons spin">autorenew</i>
</p>
</div> </div>
</div> </div>
</template> </template>
@ -70,10 +55,13 @@ import url from "@/utils/url";
import { search } from "@/api"; import { search } from "@/api";
var boxes = { var boxes = {
image: { label: "images", icon: "insert_photo" }, folder: { label: "folders", icon: "folder" },
audio: { label: "music", icon: "volume_up" }, file: { label: "files", icon: "insert_drive_file" },
video: { label: "video", icon: "movie" }, archive: { label: "archives", icon: "archive" },
pdf: { label: "pdf", icon: "picture_as_pdf" }, image: { label: "images", icon: "photo" },
audio: { label: "audio files", icon: "volume_up" },
video: { label: "videos", icon: "movie" },
doc: { label: "documents", icon: "picture_as_pdf" },
}; };
export default { export default {
@ -92,7 +80,6 @@ export default {
watch: { watch: {
show(val, old) { show(val, old) {
this.active = val === "search"; this.active = val === "search";
if (old === "search" && !this.active) { if (old === "search" && !this.active) {
if (this.reload) { if (this.reload) {
this.setReload(true); this.setReload(true);
@ -133,9 +120,6 @@ export default {
? this.$t("search.typeToSearch") ? this.$t("search.typeToSearch")
: this.$t("search.pressToSearch"); : this.$t("search.pressToSearch");
}, },
filteredResults() {
return this.results.slice(0, this.resultsCount);
},
}, },
mounted() { mounted() {
this.$refs.result.addEventListener("scroll", (event) => { this.$refs.result.addEventListener("scroll", (event) => {
@ -148,13 +132,31 @@ export default {
}); });
}, },
methods: { methods: {
async navigateTo(url) {
this.closeHovers();
await this.$nextTick();
setTimeout(() => this.$router.push(url), 0);
},
getContext(url) {
url = url.slice(1)
let path = "./" + url.substring(url.indexOf('/') + 1);
return path.replace(/\/+$/, '') + "/"
},
basePath(str) {
let parts = str.replace(/\/$/, '').split("/")
parts.pop()
return parts.join("/") + "/"
},
baseName(str) {
let parts = str.replace(/\/$/, '').split("/")
return parts.pop();
},
...mapMutations(["showHover", "closeHovers", "setReload"]), ...mapMutations(["showHover", "closeHovers", "setReload"]),
open() { open() {
this.showHover("search"); this.showHover("search");
}, },
close(event) { close(event) {
event.stopPropagation(); event.stopPropagation();
event.preventDefault();
this.closeHovers(); this.closeHovers();
}, },
keyup(event) { keyup(event) {
@ -162,7 +164,6 @@ export default {
this.close(event); this.close(event);
return; return;
} }
this.results.length === 0; this.results.length === 0;
}, },
init(string) { init(string) {

View File

@ -37,7 +37,6 @@
<script> <script>
import { enableThumbs } from "@/utils/constants"; import { enableThumbs } from "@/utils/constants";
import { mapMutations, mapGetters, mapState } from "vuex"; import { mapMutations, mapGetters, mapState } from "vuex";
import filesize from "filesize";
import moment from "moment"; import moment from "moment";
import { files as api } from "@/api"; import { files as api } from "@/api";
import * as upload from "@/utils/upload"; import * as upload from "@/utils/upload";
@ -98,7 +97,7 @@ export default {
methods: { methods: {
...mapMutations(["addSelected", "removeSelected", "resetSelected"]), ...mapMutations(["addSelected", "removeSelected", "resetSelected"]),
humanSize: function () { humanSize: function () {
return this.type == "invalid_link" ? "invalid link" : filesize(this.size); return this.type == "invalid_link" ? "invalid link" : this.size;
}, },
humanTime: function () { humanTime: function () {
if (this.readOnly == undefined && this.user.dateFormat) { if (this.readOnly == undefined && this.user.dateFormat) {

View File

@ -81,7 +81,6 @@
<script> <script>
import { mapState, mapGetters } from "vuex"; import { mapState, mapGetters } from "vuex";
import filesize from "filesize";
import moment from "moment"; import moment from "moment";
import { files as api } from "@/api"; import { files as api } from "@/api";
@ -92,7 +91,7 @@ export default {
...mapGetters(["selectedCount", "isListing"]), ...mapGetters(["selectedCount", "isListing"]),
humanSize: function () { humanSize: function () {
if (this.selectedCount === 0 || !this.isListing) { if (this.selectedCount === 0 || !this.isListing) {
return filesize(this.req.size); return this.req.size;
} }
let sum = 0; let sum = 0;
@ -101,7 +100,7 @@ export default {
sum += this.req.items[selected].size; sum += this.req.items[selected].size;
} }
return filesize(sum); return sum;
}, },
humanTime: function () { humanTime: function () {
if (this.selectedCount === 0) { if (this.selectedCount === 0) {

View File

@ -1,6 +1,5 @@
header { header {
z-index: 1000; z-index: 1000;
background-color: #fff;
border-bottom: 1px solid rgba(0, 0, 0, 0.075); border-bottom: 1px solid rgba(0, 0, 0, 0.075);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
position: fixed; position: fixed;
@ -12,6 +11,7 @@ header {
display: flex; display: flex;
padding: 0.5em 0.5em 0.5em 1em; padding: 0.5em 0.5em 0.5em 1em;
align-items: center; align-items: center;
backdrop-filter: blur(6px);
} }
header > * { header > * {
@ -82,7 +82,7 @@ header .menu-button {
} }
#search #input { #search #input {
background-color: #f5f5f5; background-color: rgba(100, 100, 100, 0.2);
display: flex; display: flex;
height: 100%; height: 100%;
padding: 0em 0.75em; padding: 0em 0.75em;
@ -93,9 +93,9 @@ header .menu-button {
} }
#search.active #input { #search.active #input {
border-bottom: 1px solid rgba(0, 0, 0, 0.075); border-bottom: 3px solid rgba(0, 0, 0, 0.075);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
background-color: #fff; backdrop-filter: blur(6px);
height: 4em; height: 4em;
} }
@ -103,11 +103,6 @@ header .menu-button {
border-radius: 0 !important; border-radius: 0 !important;
} }
#search.active i,
#search.active input {
color: #212121;
}
#search #input>.action, #search #input>.action,
#search #input>i { #search #input>i {
margin-right: 0.3em; margin-right: 0.3em;
@ -121,20 +116,44 @@ header .menu-button {
padding: 0; padding: 0;
} }
#result-list {
width: 60em;
max-width: 100%;
padding-top: 3em;
overflow-x: hidden;
overflow-y: auto;
}
.text-container {
white-space: nowrap; /* Prevents the text from wrapping */
overflow: hidden; /* Hides the content that exceeds the div size */
text-overflow: ellipsis; /* Adds "..." when the text overflows */
width: 100%; /* Ensures the content takes the full width available */
text-align: left;
direction: rtl;
}
#search #result { #search #result {
visibility: visible; overflow: hidden;
max-height: none; background: white;
background-color: #f8f8f8; display: flex;
top: -4em;
flex-direction: column;
align-items: center;
text-align: left; text-align: left;
padding: 0; padding: 0;
color: rgba(0, 0, 0, 0.6); color: rgba(0, 0, 0, 0.6);
height: 0; height: 0;
transition: .1s ease height, .1s ease padding; transition: .2s ease height, .2s ease padding;
overflow-x: hidden;
overflow-y: auto;
z-index: 1; z-index: 1;
} }
@media screen and (min-width: 800px) {
#search #result {
background: linear-gradient(to right, white 15%,lightgray 25%,lightgray 75%,white 85%);
}
}
body.rtl #search #result { body.rtl #search #result {
direction: ltr; direction: ltr;
} }
@ -155,23 +174,17 @@ body.rtl #search #result ul>* {
} }
#search.active #result { #search.active #result {
padding: .5em; height: 100vh
height: calc(100% - 4em);
} }
#search ul { #search ul {
margin-top: 1em;
padding: 0; padding: 0;
margin: 0;
list-style: none; list-style: none;
} }
#search li { #search li {
margin-bottom: .5em; margin: .5em;
}
#search #result>div {
max-width: 45em;
margin: 0 auto;
} }
#search #result #renew { #search #result #renew {
@ -186,8 +199,20 @@ body.rtl #search #result ul>* {
display: block; display: block;
} }
#search.active #result i { .folder-icons {
color: #ccc; color: var(--icon-blue);
}
.video-icons {
color: lightskyblue;
}
.image-icons {
color: lightcoral;
}
.archive-icons {
color: tan;
}
.audio-icons {
color: plum;
} }
#search.active #result>p>i { #search.active #result>p>i {

View File

@ -182,7 +182,6 @@
<script> <script>
import { mapState, mapMutations, mapGetters } from "vuex"; import { mapState, mapMutations, mapGetters } from "vuex";
import { pub as api } from "@/api"; import { pub as api } from "@/api";
import filesize from "filesize";
import moment from "moment"; import moment from "moment";
import HeaderBar from "@/components/header/HeaderBar"; import HeaderBar from "@/components/header/HeaderBar";
@ -255,8 +254,7 @@ export default {
if (this.req.isDir) { if (this.req.isDir) {
return this.req.items.length; return this.req.items.length;
} }
return this.req.size;
return filesize(this.req.size);
}, },
humanTime: function () { humanTime: function () {
return moment(this.req.modified).fromNow(); return moment(this.req.modified).fromNow();

View File

@ -805,9 +805,7 @@ export default {
prompt: "download", prompt: "download",
confirm: (format) => { confirm: (format) => {
this.$store.commit("closeHovers"); this.$store.commit("closeHovers");
let files = []; let files = [];
if (this.selectedCount > 0) { if (this.selectedCount > 0) {
for (let i of this.selected) { for (let i of this.selected) {
files.push(this.req.items[i].url); files.push(this.req.items[i].url);
@ -822,7 +820,6 @@ export default {
}, },
switchView: async function () { switchView: async function () {
this.$store.commit("closeHovers"); this.$store.commit("closeHovers");
const modes = { const modes = {
list: "mosaic", list: "mosaic",
mosaic: "mosaic gallery", mosaic: "mosaic gallery",