diff --git a/backend/libs/handlers/app.go b/backend/libs/handlers/app.go index 824829f..c982e72 100644 --- a/backend/libs/handlers/app.go +++ b/backend/libs/handlers/app.go @@ -20,9 +20,14 @@ type App struct { banService *services.BanService rateLimiter *rateLimiter uploadGroups *uploadGrouper + fileIcons *fileIconSet } func NewApp(cfg config.Config, logger *slog.Logger, renderer *web.Renderer, uploadService *services.UploadService, authService *services.AuthService, settingsService *services.SettingsService, reactionService *services.ReactionService, banService *services.BanService) *App { + fileIcons, err := loadFileIcons(cfg.StaticDir) + if err != nil { + logger.Warn("failed to load file icon map", "source", "handlers", "severity", "warn", "error", err.Error()) + } return &App{ cfg: cfg, logger: logger, @@ -34,6 +39,7 @@ func NewApp(cfg config.Config, logger *slog.Logger, renderer *web.Renderer, uplo banService: banService, rateLimiter: newRateLimiter(), uploadGroups: newUploadGrouper(), + fileIcons: fileIcons, } } diff --git a/backend/libs/handlers/download.go b/backend/libs/handlers/download.go index 780e616..7751c4b 100644 --- a/backend/libs/handlers/download.go +++ b/backend/libs/handlers/download.go @@ -45,6 +45,9 @@ type fileView struct { URL string DownloadURL string ThumbnailURL string + HasThumbnail bool + IconURL string + IconRetroURL string ReactURL string Reactions []reactionView Reacted bool @@ -350,6 +353,7 @@ func (a *App) fileView(box services.Box, file services.File) fileView { } func (a *App) fileViewWithReactions(box services.Box, file services.File, reactions []services.ReactionSummary, reacted bool) fileView { + icon := a.fileIcons.lookup(file.Name, file.ContentType) return fileView{ ID: file.ID, Name: file.Name, @@ -359,6 +363,9 @@ func (a *App) fileViewWithReactions(box services.Box, file services.File, reacti URL: fmt.Sprintf("/d/%s/f/%s", box.ID, file.ID), DownloadURL: fmt.Sprintf("/d/%s/f/%s/download", box.ID, file.ID), ThumbnailURL: fmt.Sprintf("/d/%s/thumb/%s", box.ID, file.ID), + HasThumbnail: file.Thumbnail != "", + IconURL: fileIconURL("standard", icon.Standard), + IconRetroURL: fileIconURL("retro", icon.Retro), ReactURL: fmt.Sprintf("/d/%s/f/%s/react", box.ID, file.ID), Reactions: a.reactionViews(reactions), Reacted: reacted, diff --git a/backend/libs/handlers/icons.go b/backend/libs/handlers/icons.go new file mode 100644 index 0000000..b1e6415 --- /dev/null +++ b/backend/libs/handlers/icons.go @@ -0,0 +1,152 @@ +package handlers + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" +) + +// fileIcon holds the two icon filenames for a file type: the standard (modern) +// icon and the retro (Win98) icon. The filenames are resolved against +// static/file-icons/standard and static/file-icons/retro respectively. +type fileIcon struct { + Standard string `json:"standard"` + Retro string `json:"retro"` +} + +type iconType struct { + Mime string `json:"mime"` + Standard string `json:"standard"` + Retro string `json:"retro"` + Extensions []string `json:"extensions"` +} + +type iconMapFile struct { + Default iconType `json:"default"` + Types []iconType `json:"types"` +} + +type mimeRule struct { + pattern string // exact mime ("application/pdf") or major prefix ("image/") + prefix bool + icon fileIcon +} + +// fileIconSet is the loaded icon map: an extension lookup plus content-type +// rules and a fallback. It is built once at startup from icon-map.json. +type fileIconSet struct { + byExt map[string]fileIcon + byMime []mimeRule + fallback fileIcon +} + +// loadFileIcons reads static/file-icons/icon-map.json and indexes it by +// extension and content type so icons can be assigned at render time. +func loadFileIcons(staticDir string) (*fileIconSet, error) { + data, err := os.ReadFile(filepath.Join(staticDir, "file-icons", "icon-map.json")) + if err != nil { + return nil, err + } + var raw iconMapFile + if err := json.Unmarshal(data, &raw); err != nil { + return nil, err + } + + set := &fileIconSet{ + byExt: make(map[string]fileIcon), + fallback: fileIcon{Standard: raw.Default.Standard, Retro: raw.Default.Retro}, + } + if err := validateFileIcon(staticDir, set.fallback); err != nil { + return nil, err + } + for _, t := range raw.Types { + icon := fileIcon{Standard: t.Standard, Retro: t.Retro} + if err := validateFileIcon(staticDir, icon); err != nil { + return nil, err + } + for _, ext := range t.Extensions { + set.byExt[strings.ToLower(strings.TrimPrefix(ext, "."))] = icon + } + if t.Mime == "" { + continue + } + if strings.HasSuffix(t.Mime, "/*") { + set.byMime = append(set.byMime, mimeRule{pattern: strings.TrimSuffix(t.Mime, "*"), prefix: true, icon: icon}) + } else { + set.byMime = append(set.byMime, mimeRule{pattern: strings.ToLower(t.Mime), icon: icon}) + } + } + return set, nil +} + +func validateFileIcon(staticDir string, icon fileIcon) error { + if icon.Standard != "" { + if err := validateFileIconPath(staticDir, "standard", icon.Standard); err != nil { + return err + } + } + if icon.Retro != "" { + if err := validateFileIconPath(staticDir, "retro", icon.Retro); err != nil { + return err + } + } + return nil +} + +func validateFileIconPath(staticDir, theme, name string) error { + if strings.Contains(name, "/") || strings.Contains(name, "\\") || strings.Contains(name, "..") { + return fmt.Errorf("invalid %s file icon path %q", theme, name) + } + path := filepath.Join(staticDir, "file-icons", theme, name) + info, err := os.Stat(path) + if err != nil { + return fmt.Errorf("missing %s file icon %q: %w", theme, name, err) + } + if info.IsDir() { + return fmt.Errorf("%s file icon %q is a directory", theme, name) + } + return nil +} + +// lookup resolves a file's icon from its name (extension) first, falling back to +// its content type, then to the default icon. Extension wins because stored +// content types are often the generic application/octet-stream. +func (s *fileIconSet) lookup(name, contentType string) fileIcon { + if s == nil { + return fileIcon{} + } + if ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(name), ".")); ext != "" { + if icon, ok := s.byExt[ext]; ok { + return icon + } + } + + ct := strings.ToLower(strings.TrimSpace(contentType)) + if i := strings.IndexByte(ct, ';'); i >= 0 { + ct = strings.TrimSpace(ct[:i]) + } + if ct != "" && ct != "application/octet-stream" { + for _, rule := range s.byMime { // exact matches first + if !rule.prefix && rule.pattern == ct { + return rule.icon + } + } + for _, rule := range s.byMime { // then major-type prefixes + if rule.prefix && strings.HasPrefix(ct, rule.pattern) { + return rule.icon + } + } + } + return s.fallback +} + +// fileIconURL builds the /static URL for an icon filename in the given theme +// directory ("standard" or "retro"). +func fileIconURL(theme, name string) string { + if name == "" { + return "" + } + return "/static/file-icons/" + theme + "/" + name +} diff --git a/backend/libs/handlers/icons_test.go b/backend/libs/handlers/icons_test.go new file mode 100644 index 0000000..039566f --- /dev/null +++ b/backend/libs/handlers/icons_test.go @@ -0,0 +1,54 @@ +package handlers + +import ( + "path/filepath" + "testing" +) + +func TestFileIconMapLoadsAndResolvesCommonTypes(t *testing.T) { + icons, err := loadFileIcons(filepath.Join("..", "..", "static")) + if err != nil { + t.Fatalf("loadFileIcons returned error: %v", err) + } + + tests := []struct { + name string + contentType string + wantStandard string + wantRetro string + }{ + { + name: "photo.jpg", + contentType: "application/octet-stream", + wantStandard: "image-document-svgrepo-com.svg", + wantRetro: "shimgvw.dll_14_1-2.png", + }, + { + name: "movie.mkv", + contentType: "", + wantStandard: "video-document-svgrepo-com.svg", + wantRetro: "wmploc.dll_14_504-2.png", + }, + { + name: "archive.7z", + contentType: "", + wantStandard: "zip-document-svgrepo-com.svg", + wantRetro: "zipfldr.dll_14_101-2.png", + }, + { + name: "unknown.bin", + contentType: "application/octet-stream", + wantStandard: "txt-document-svgrepo-com.svg", + wantRetro: "shell32.dll_14_152-2.png", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := icons.lookup(tt.name, tt.contentType) + if got.Standard != tt.wantStandard || got.Retro != tt.wantRetro { + t.Fatalf("lookup returned %+v, want standard=%q retro=%q", got, tt.wantStandard, tt.wantRetro) + } + }) + } +} diff --git a/backend/static/css/30-download.css b/backend/static/css/30-download.css index 31ca5dd..21755aa 100644 --- a/backend/static/css/30-download.css +++ b/backend/static/css/30-download.css @@ -65,7 +65,7 @@ .file-card { position: relative; - padding-bottom: 2.6rem; + padding-block: 0.65rem 2.6rem; } .file-reaction-dock { @@ -303,24 +303,60 @@ html.reaction-picker-open body { object-fit: contain; } -.thumb-link { - display: block; +/* A file row behaves like an entry in a desktop file explorer: a small + thumbnail/icon followed by the name and metadata. The whole row is the click + target (raw view of the file). */ +.file-open { + min-width: 0; + flex: 1; + display: flex; + align-items: center; + gap: 0.8rem; + color: var(--foreground); + text-decoration: none; +} + +.file-media { + flex: 0 0 3rem; + width: 3rem; + height: 3rem; + display: grid; + place-items: center; overflow: hidden; - flex: 0 0 4.75rem; - width: 4.75rem; - aspect-ratio: 16 / 10; border: 1px solid var(--border); border-radius: calc(var(--radius) - 0.125rem); background: var(--muted); } -.thumb-link img { +.file-thumb { width: 100%; height: 100%; display: block; object-fit: cover; } +.file-icon { + width: 2.1rem; + height: 2.1rem; + display: block; + object-fit: contain; +} + +/* Retro (Win98) icons are tiny pixel art — keep them crisp and swap them in + only when the retro theme is active. */ +.file-icon-retro { + display: none; + image-rendering: pixelated; +} + +[data-theme="retro"] .file-icon-standard { + display: none; +} + +[data-theme="retro"] .file-icon-retro { + display: block; +} + .file-main { min-width: 0; max-width: 100%; @@ -329,46 +365,47 @@ html.reaction-picker-open body { text-decoration: none; } -.file-actions { - display: inline-flex; - align-items: center; - gap: 0.5rem; -} - -.preview-action [hidden] { - display: none; +.file-main small { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .file-browser.is-thumbs { - grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); } .file-browser.is-thumbs .file-card { display: grid; min-width: 0; align-content: start; - gap: 0.7rem; + gap: 0.5rem; +} + +.file-browser.is-thumbs .file-open { + display: grid; + gap: 0.55rem; + text-align: center; + justify-items: center; +} + +.file-browser.is-thumbs .file-media { + width: 100%; + height: auto; + flex-basis: auto; + aspect-ratio: 16 / 10; +} + +.file-browser.is-thumbs .file-icon { + width: 45%; + height: auto; } .file-browser.is-thumbs .file-main { width: 100%; } -.file-browser.is-thumbs .thumb-link { - width: 100%; - flex-basis: auto; -} - -.file-browser.is-thumbs .button { - width: 100%; -} - -.file-browser.is-thumbs .file-actions { - width: 100%; - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); -} - .file-browser.images-only .file-card:not([data-kind="image"]) { display: none; } diff --git a/backend/static/file-icons/icon-map.json b/backend/static/file-icons/icon-map.json new file mode 100644 index 0000000..e5e783b --- /dev/null +++ b/backend/static/file-icons/icon-map.json @@ -0,0 +1,112 @@ +{ + "_comment": "Maps a file's type (resolved from its extension / content type) to a file-type icon. 'standard' icons live in file-icons/standard, 'retro' (Win98) icons in file-icons/retro. The server reads this at startup and picks the icon per file; thumbnails always win over icons when present.", + "default": { + "mime": "application/octet-stream", + "standard": "txt-document-svgrepo-com.svg", + "retro": "shell32.dll_14_152-2.png" + }, + "types": [ + { + "mime": "image/*", + "standard": "image-document-svgrepo-com.svg", + "retro": "shimgvw.dll_14_1-2.png", + "extensions": ["png", "jpg", "jpeg", "gif", "webp", "bmp", "svg", "ico", "tif", "tiff", "heic", "heif", "avif", "jfif"] + }, + { + "mime": "image/vnd.adobe.photoshop", + "standard": "psd-document-svgrepo-com.svg", + "retro": "shimgvw.dll_14_1-2.png", + "extensions": ["psd"] + }, + { + "mime": "audio/*", + "standard": "audio-document-svgrepo-com.svg", + "retro": "wmploc.dll_14_610-2.png", + "extensions": ["mp3", "wav", "flac", "aac", "ogg", "oga", "m4a", "wma", "opus", "aiff", "aif", "mid", "midi"] + }, + { + "mime": "video/mp4", + "standard": "mp4-document-svgrepo-com.svg", + "retro": "wmploc.dll_14_504-2.png", + "extensions": ["mp4", "m4v"] + }, + { + "mime": "video/*", + "standard": "video-document-svgrepo-com.svg", + "retro": "wmploc.dll_14_504-2.png", + "extensions": ["mkv", "mov", "avi", "webm", "wmv", "flv", "mpg", "mpeg", "3gp", "ogv", "ts", "m2ts"] + }, + { + "mime": "application/zip", + "standard": "zip-document-svgrepo-com.svg", + "retro": "zipfldr.dll_14_101-2.png", + "extensions": ["zip", "rar", "7z", "gz", "tar", "bz2", "xz", "tgz", "zst", "lz", "lzma", "cab", "iso"] + }, + { + "mime": "application/pdf", + "standard": "pdf-document-svgrepo-com.svg", + "retro": "shell32.dll_14_152-2.png", + "extensions": ["pdf"] + }, + { + "mime": "text/html", + "standard": "html-document-svgrepo-com.svg", + "retro": "mshtml.dll_14_2660-2.png", + "extensions": ["html", "htm", "xhtml", "mhtml"] + }, + { + "mime": "application/x-shockwave-flash", + "standard": "flash-document-svgrepo-com.svg", + "retro": "shell32.dll_14_152-2.png", + "extensions": ["swf", "fla"] + }, + { + "mime": "application/vnd.ms-excel", + "standard": "excel-document-svgrepo-com.svg", + "retro": "shell32.dll_14_151-2.png", + "extensions": ["xls", "xlsx", "xlsm", "ods"] + }, + { + "mime": "text/csv", + "standard": "csv-document-svgrepo-com.svg", + "retro": "shell32.dll_14_151-2.png", + "extensions": ["csv", "tsv"] + }, + { + "mime": "application/msword", + "standard": "word-document-svgrepo-com.svg", + "retro": "shell32.dll_14_2-0.png", + "extensions": ["doc", "docx", "odt"] + }, + { + "mime": "application/rtf", + "standard": "rtf-document-svgrepo-com.svg", + "retro": "shell32.dll_14_2-0.png", + "extensions": ["rtf"] + }, + { + "mime": "application/vnd.apple.pages", + "standard": "pages-document-svgrepo-com.svg", + "retro": "shell32.dll_14_2-0.png", + "extensions": ["pages"] + }, + { + "mime": "application/vnd.visio", + "standard": "visio-document-svgrepo-com.svg", + "retro": "shell32.dll_14_152-2.png", + "extensions": ["vsd", "vsdx"] + }, + { + "mime": "application/x-msdownload", + "standard": "exe-document-svgrepo-com.svg", + "retro": "shell32.dll_14_3-0.png", + "extensions": ["exe", "msi", "bat", "cmd", "com", "app", "dmg", "apk", "deb", "rpm", "appimage"] + }, + { + "mime": "text/plain", + "standard": "txt-document-svgrepo-com.svg", + "retro": "shell32.dll_14_151-2.png", + "extensions": ["txt", "text", "log", "md", "markdown", "ini", "cfg", "conf", "json", "xml", "yaml", "yml", "toml", "js", "ts", "jsx", "tsx", "go", "py", "rb", "php", "java", "c", "h", "cpp", "cc", "cs", "rs", "sh", "bash", "css", "scss", "sql"] + } + ] +} diff --git a/backend/static/file-icons/retro/mshtml.dll_14_2660-2.png b/backend/static/file-icons/retro/mshtml.dll_14_2660-2.png new file mode 100644 index 0000000..f91d751 Binary files /dev/null and b/backend/static/file-icons/retro/mshtml.dll_14_2660-2.png differ diff --git a/backend/static/file-icons/retro/shell32.dll_14_151-2.png b/backend/static/file-icons/retro/shell32.dll_14_151-2.png new file mode 100644 index 0000000..d59b1f9 Binary files /dev/null and b/backend/static/file-icons/retro/shell32.dll_14_151-2.png differ diff --git a/backend/static/file-icons/retro/shell32.dll_14_152-2.png b/backend/static/file-icons/retro/shell32.dll_14_152-2.png new file mode 100644 index 0000000..1af6c1e Binary files /dev/null and b/backend/static/file-icons/retro/shell32.dll_14_152-2.png differ diff --git a/backend/static/file-icons/retro/shell32.dll_14_2-0.png b/backend/static/file-icons/retro/shell32.dll_14_2-0.png new file mode 100644 index 0000000..ed458f1 Binary files /dev/null and b/backend/static/file-icons/retro/shell32.dll_14_2-0.png differ diff --git a/backend/static/file-icons/retro/shell32.dll_14_3-0.png b/backend/static/file-icons/retro/shell32.dll_14_3-0.png new file mode 100644 index 0000000..d3e9146 Binary files /dev/null and b/backend/static/file-icons/retro/shell32.dll_14_3-0.png differ diff --git a/backend/static/file-icons/retro/shimgvw.dll_14_1-2.png b/backend/static/file-icons/retro/shimgvw.dll_14_1-2.png new file mode 100644 index 0000000..817c4d5 Binary files /dev/null and b/backend/static/file-icons/retro/shimgvw.dll_14_1-2.png differ diff --git a/backend/static/file-icons/retro/wmploc.dll_14_504-2.png b/backend/static/file-icons/retro/wmploc.dll_14_504-2.png new file mode 100644 index 0000000..4437160 Binary files /dev/null and b/backend/static/file-icons/retro/wmploc.dll_14_504-2.png differ diff --git a/backend/static/file-icons/retro/wmploc.dll_14_610-2.png b/backend/static/file-icons/retro/wmploc.dll_14_610-2.png new file mode 100644 index 0000000..b8cd622 Binary files /dev/null and b/backend/static/file-icons/retro/wmploc.dll_14_610-2.png differ diff --git a/backend/static/file-icons/retro/zipfldr.dll_14_101-2.png b/backend/static/file-icons/retro/zipfldr.dll_14_101-2.png new file mode 100644 index 0000000..576f74d Binary files /dev/null and b/backend/static/file-icons/retro/zipfldr.dll_14_101-2.png differ diff --git a/backend/static/file-icons/standard/audio-document-svgrepo-com.svg b/backend/static/file-icons/standard/audio-document-svgrepo-com.svg new file mode 100644 index 0000000..09c3084 --- /dev/null +++ b/backend/static/file-icons/standard/audio-document-svgrepo-com.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/static/file-icons/standard/csv-document-svgrepo-com.svg b/backend/static/file-icons/standard/csv-document-svgrepo-com.svg new file mode 100644 index 0000000..d38147f --- /dev/null +++ b/backend/static/file-icons/standard/csv-document-svgrepo-com.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/static/file-icons/standard/excel-document-svgrepo-com.svg b/backend/static/file-icons/standard/excel-document-svgrepo-com.svg new file mode 100644 index 0000000..329e6bf --- /dev/null +++ b/backend/static/file-icons/standard/excel-document-svgrepo-com.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/backend/static/file-icons/standard/exe-document-svgrepo-com.svg b/backend/static/file-icons/standard/exe-document-svgrepo-com.svg new file mode 100644 index 0000000..13325fa --- /dev/null +++ b/backend/static/file-icons/standard/exe-document-svgrepo-com.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/static/file-icons/standard/flash-document-svgrepo-com.svg b/backend/static/file-icons/standard/flash-document-svgrepo-com.svg new file mode 100644 index 0000000..678f126 --- /dev/null +++ b/backend/static/file-icons/standard/flash-document-svgrepo-com.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/static/file-icons/standard/html-document-svgrepo-com.svg b/backend/static/file-icons/standard/html-document-svgrepo-com.svg new file mode 100644 index 0000000..b7e91fb --- /dev/null +++ b/backend/static/file-icons/standard/html-document-svgrepo-com.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/static/file-icons/standard/image-document-svgrepo-com.svg b/backend/static/file-icons/standard/image-document-svgrepo-com.svg new file mode 100644 index 0000000..72b904c --- /dev/null +++ b/backend/static/file-icons/standard/image-document-svgrepo-com.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/static/file-icons/standard/mp4-document-svgrepo-com.svg b/backend/static/file-icons/standard/mp4-document-svgrepo-com.svg new file mode 100644 index 0000000..a6a9d51 --- /dev/null +++ b/backend/static/file-icons/standard/mp4-document-svgrepo-com.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/static/file-icons/standard/pages-document-svgrepo-com.svg b/backend/static/file-icons/standard/pages-document-svgrepo-com.svg new file mode 100644 index 0000000..739d626 --- /dev/null +++ b/backend/static/file-icons/standard/pages-document-svgrepo-com.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/static/file-icons/standard/pdf-document-svgrepo-com.svg b/backend/static/file-icons/standard/pdf-document-svgrepo-com.svg new file mode 100644 index 0000000..87e0779 --- /dev/null +++ b/backend/static/file-icons/standard/pdf-document-svgrepo-com.svg @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/static/file-icons/standard/psd-document-svgrepo-com.svg b/backend/static/file-icons/standard/psd-document-svgrepo-com.svg new file mode 100644 index 0000000..a69f6b0 --- /dev/null +++ b/backend/static/file-icons/standard/psd-document-svgrepo-com.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/static/file-icons/standard/rtf-document-svgrepo-com.svg b/backend/static/file-icons/standard/rtf-document-svgrepo-com.svg new file mode 100644 index 0000000..b7a2c08 --- /dev/null +++ b/backend/static/file-icons/standard/rtf-document-svgrepo-com.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/static/file-icons/standard/txt-document-svgrepo-com.svg b/backend/static/file-icons/standard/txt-document-svgrepo-com.svg new file mode 100644 index 0000000..b32a9f9 --- /dev/null +++ b/backend/static/file-icons/standard/txt-document-svgrepo-com.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/static/file-icons/standard/video-document-svgrepo-com.svg b/backend/static/file-icons/standard/video-document-svgrepo-com.svg new file mode 100644 index 0000000..2f3ef35 --- /dev/null +++ b/backend/static/file-icons/standard/video-document-svgrepo-com.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/static/file-icons/standard/visio-document-svgrepo-com.svg b/backend/static/file-icons/standard/visio-document-svgrepo-com.svg new file mode 100644 index 0000000..5eb1556 --- /dev/null +++ b/backend/static/file-icons/standard/visio-document-svgrepo-com.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/static/file-icons/standard/word-document-svgrepo-com.svg b/backend/static/file-icons/standard/word-document-svgrepo-com.svg new file mode 100644 index 0000000..4278239 --- /dev/null +++ b/backend/static/file-icons/standard/word-document-svgrepo-com.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/static/file-icons/standard/zip-document-svgrepo-com.svg b/backend/static/file-icons/standard/zip-document-svgrepo-com.svg new file mode 100644 index 0000000..73fc64c --- /dev/null +++ b/backend/static/file-icons/standard/zip-document-svgrepo-com.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/templates/pages/download.html b/backend/templates/pages/download.html index b54451e..189eb34 100644 --- a/backend/templates/pages/download.html +++ b/backend/templates/pages/download.html @@ -24,16 +24,25 @@ {{end}} {{if .Data.Files}} + {{$single := eq (len .Data.Files) 1}}
Expires {{.Data.ExpiresLabel}} {{if .Data.MaxDownloads}}{{.Data.DownloadCount}} / {{.Data.MaxDownloads}} downloads{{end}}
{{if not .Data.Locked}} - - - Download zip - + {{if $single}} + {{$first := index .Data.Files 0}} + + + Download + + {{else}} + + + Download zip + + {{end}} {{end}}
@@ -45,25 +54,21 @@
{{range .Data.Files}}