Compare commits

..

No commits in common. "develop" and "230719-73fa7bbe8" have entirely different histories.

527 changed files with 22417 additions and 42132 deletions

View file

@ -14,7 +14,7 @@ Feel free to [contact us](https://www.photoprism.app/contact) with anything that
You can also contribute by…
* answering questions in the [Community Chat](https://link.photoprism.app/chat), on [Reddit](https://link.photoprism.app/reddit) and in [GitHub Discussions](https://link.photoprism.app/discussions)
* helping us [translate](https://docs.photoprism.app/developer-guide/translations-weblate/) the Web UI on [Weblate](https://translate.photoprism.app/)
* helping us [translate](https://docs.photoprism.app/developer-guide/translations-weblate/) the Web UI
* [conducting research](https://github.com/photoprism/photoprism/issues?q=is%3Aopen+is%3Aissue+label%3Aresearch) and [improving the documentation](https://github.com/photoprism/photoprism/issues?q=is%3Aopen+is%3Aissue+label%3Adocs)
* publishing tutorials, blog posts, and podcasts
* voting for us on pages like:

View file

@ -1,9 +1,7 @@
# Ubuntu 23.10 (Mantic Minotaur)
FROM photoprism/develop:231206-mantic
# Ubuntu 23.04 (Lunar Lobster)
FROM photoprism/develop:230715-lunar
## Alternative Environments:
# FROM photoprism/develop:armv7 # ARMv7 (32bit)
# FROM photoprism/develop:lunar # Ubuntu 23.04 (Lunar Lobster)
# FROM photoprism/develop:jammy # Ubuntu 22.04 LTS (Jammy Jellyfish)
# FROM photoprism/develop:impish # Ubuntu 21.10 (Impish Indri)
# FROM photoprism/develop:bookworm # Debian 12 (Bookworm)
@ -15,4 +13,4 @@ WORKDIR "/go/src/github.com/photoprism/photoprism"
# Copy source to image.
COPY . .
COPY --chown=root:root /scripts/dist/ /scripts/
COPY --chown=root:root /scripts/dist/ /scripts/

113
Makefile
View file

@ -22,7 +22,7 @@ BUILD_ARCH ?= $(shell scripts/dist/arch.sh)
JS_BUILD_PATH ?= $(shell realpath "./assets/static/build")
# Install parameters.
INSTALL_PATH ?= $(BUILD_PATH)/photoprism-ce_$(BUILD_TAG)-$(shell echo $(BUILD_OS) | tr '[:upper:]' '[:lower:]')-$(BUILD_ARCH)
INSTALL_PATH ?= $(BUILD_PATH)/photoprism-$(BUILD_TAG)-$(shell echo $(BUILD_OS) | tr '[:upper:]' '[:lower:]')-$(BUILD_ARCH)
DESTDIR ?= $(INSTALL_PATH)
DESTUID ?= 1000
DESTGID ?= 1000
@ -54,7 +54,6 @@ all: dep build-js
dep: dep-tensorflow dep-js
biuld: build
build: build-go
build-all: build-go build-js
pull: docker-pull
test: test-js test-go
test-go: reset-sqlite run-test-go
@ -108,23 +107,19 @@ clean:
[ ! -d "$(JS_BUILD_PATH)" ] || rm -rf --preserve-root $(JS_BUILD_PATH)
tar.gz:
$(info Creating tar.gz archives from the directories in "$(BUILD_PATH)"...)
find "$(BUILD_PATH)" -maxdepth 1 -mindepth 1 -type d -name "photoprism*" -exec tar --exclude='.[^/]*' -C {} -czf {}.tar.gz . \;
pkg: pkg-amd64 pkg-arm64
pkg-amd64:
docker run --rm -u $(UID) --platform=amd64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" --entrypoint "" photoprism/develop:jammy make all install tar.gz
pkg-arm64:
docker run --rm -u $(UID) --platform=arm64 --pull=always -v ".:/go/src/github.com/photoprism/photoprism" --entrypoint "" photoprism/develop:jammy make all install tar.gz
find "$(BUILD_PATH)" -maxdepth 1 -mindepth 1 -type d -exec tar --exclude='.[^/]*' -C {} -czf {}.tar.gz . \;
install:
$(info Installing in "$(DESTDIR)"...)
@[ ! -d "$(DESTDIR)" ] || (echo "ERROR: Install path '$(DESTDIR)' already exists!"; exit 1)
mkdir --mode=$(INSTALL_MODE) -p $(DESTDIR)
env TMPDIR="$(BUILD_PATH)" ./scripts/dist/install-tensorflow.sh $(DESTDIR)
rm -rf --preserve-root $(DESTDIR)/include
(cd $(DESTDIR) && mkdir -p bin lib assets)
(cd $(DESTDIR) && mkdir -p bin lib assets config config/examples)
./scripts/build.sh prod "$(DESTDIR)/bin/$(BINARY_NAME)"
rsync -r -l --safe-links --exclude-from=assets/.buildignore --chmod=a+r,u+rw ./assets/ $(DESTDIR)/assets
wget -O $(DESTDIR)/assets/static/img/wallpaper/welcome.jpg https://cdn.photoprism.app/wallpaper/welcome.jpg
wget -O $(DESTDIR)/assets/static/img/preview.jpg https://cdn.photoprism.app/img/preview.jpg
cp internal/config/testdata/*.yml $(DESTDIR)/config/examples
chown -R $(INSTALL_USER) $(DESTDIR)
chmod -R $(INSTALL_MODE) $(DESTDIR)
chmod -R $(INSTALL_MODE_BIN) $(DESTDIR)/bin $(DESTDIR)/lib
@ -146,9 +141,9 @@ acceptance-sqlite-restart:
rm -rf storage/acceptance/originals/2011
rm -rf storage/acceptance/originals/2013
rm -rf storage/acceptance/originals/2017
./photoprism --auth-mode="public" -c "./storage/acceptance/config-sqlite" --test start -d
./photoprism -p -c "./storage/acceptance/config-sqlite" --test start -d
acceptance-sqlite-stop:
./photoprism --auth-mode="public" -c "./storage/acceptance/config-sqlite" --test stop
./photoprism -p -c "./storage/acceptance/config-sqlite" --test stop
acceptance-auth-sqlite-restart:
cp -f storage/acceptance/backup.db storage/acceptance/index.db
cp -f storage/acceptance/config-sqlite/settingsBackup.yml storage/acceptance/config-sqlite/settings.yml
@ -321,7 +316,7 @@ docker-develop: docker-develop-latest
docker-develop-all: docker-develop-latest docker-develop-other
docker-develop-latest: docker-develop-ubuntu
docker-develop-debian: docker-develop-bookworm docker-develop-bookworm-slim
docker-develop-ubuntu: docker-develop-mantic docker-develop-mantic-slim
docker-develop-ubuntu: docker-develop-lunar docker-develop-lunar-slim
docker-develop-other: docker-develop-debian docker-develop-bullseye docker-develop-bullseye-slim docker-develop-buster
docker-develop-bookworm:
docker pull --platform=amd64 debian:bookworm-slim
@ -339,9 +334,8 @@ docker-develop-bullseye-slim:
docker pull --platform=amd64 debian:bullseye-slim
docker pull --platform=arm64 debian:bullseye-slim
scripts/docker/buildx-multi.sh develop linux/amd64,linux/arm64 bullseye-slim /bullseye-slim
develop-armv7: docker-develop-armv7
docker-develop-armv7:
docker pull --platform=arm ubuntu:mantic
docker pull --platform=arm ubuntu:jammy
scripts/docker/buildx.sh develop linux/arm armv7 /armv7
docker-develop-buster:
docker pull --platform=amd64 golang:1-buster
@ -362,21 +356,13 @@ docker-develop-jammy-slim:
docker-develop-lunar:
docker pull --platform=amd64 ubuntu:lunar
docker pull --platform=arm64 ubuntu:lunar
scripts/docker/buildx-multi.sh develop linux/amd64,linux/arm64 lunar /lunar
scripts/docker/buildx-multi.sh develop linux/amd64,linux/arm64 lunar /lunar "-t photoprism/develop:latest -t photoprism/develop:ubuntu"
docker-develop-lunar-slim:
docker pull --platform=amd64 ubuntu:lunar
docker pull --platform=arm64 ubuntu:lunar
scripts/docker/buildx-multi.sh develop linux/amd64,linux/arm64 lunar-slim /lunar-slim
docker-develop-mantic:
docker pull --platform=amd64 ubuntu:mantic
docker pull --platform=arm64 ubuntu:mantic
scripts/docker/buildx-multi.sh develop linux/amd64,linux/arm64 mantic /mantic "-t photoprism/develop:latest -t photoprism/develop:ubuntu"
docker-develop-mantic-slim:
docker pull --platform=amd64 ubuntu:mantic
docker pull --platform=arm64 ubuntu:mantic
scripts/docker/buildx-multi.sh develop linux/amd64,linux/arm64 mantic-slim /mantic-slim
unstable: docker-unstable
docker-unstable: docker-unstable-mantic
docker-unstable: docker-unstable-lunar
docker-unstable-jammy:
docker pull --platform=amd64 photoprism/develop:jammy
docker pull --platform=amd64 photoprism/develop:jammy-slim
@ -385,17 +371,13 @@ docker-unstable-lunar:
docker pull --platform=amd64 photoprism/develop:lunar
docker pull --platform=amd64 photoprism/develop:lunar-slim
scripts/docker/buildx-multi.sh photoprism linux/amd64 unstable-ce /lunar
docker-unstable-mantic:
docker pull --platform=amd64 photoprism/develop:mantic
docker pull --platform=amd64 photoprism/develop:mantic-slim
scripts/docker/buildx-multi.sh photoprism linux/amd64 unstable-ce /mantic
preview: docker-preview-ce
docker-preview: docker-preview-ce
docker-preview-all: docker-preview-latest docker-preview-other
docker-preview-ce: docker-preview-mantic
docker-preview-ce: docker-preview-lunar
docker-preview-latest: docker-preview-ubuntu
docker-preview-debian: docker-preview-bookworm
docker-preview-ubuntu: docker-preview-mantic
docker-preview-ubuntu: docker-preview-lunar
docker-preview-other: docker-preview-debian docker-preview-bullseye
docker-preview-arm: docker-preview-arm64 docker-preview-armv7
docker-preview-bookworm:
@ -406,7 +388,7 @@ docker-preview-bookworm:
scripts/docker/buildx-multi.sh photoprism linux/amd64,linux/arm64 preview-bookworm /bookworm "-t photoprism/photoprism:preview-ce-debian"
docker-preview-armv7:
docker pull --platform=arm photoprism/develop:armv7
docker pull --platform=arm ubuntu:mantic
docker pull --platform=arm ubuntu:jammy
scripts/docker/buildx.sh photoprism linux/arm preview-armv7 /armv7
docker-preview-arm64:
docker pull --platform=arm64 photoprism/develop:lunar
@ -424,12 +406,6 @@ docker-preview-buster:
docker pull --platform=amd64 debian:buster-slim
docker pull --platform=arm64 debian:buster-slim
scripts/docker/buildx-multi.sh photoprism linux/amd64,linux/arm64 preview-buster /buster
docker-preview-impish:
docker pull --platform=amd64 photoprism/develop:impish
docker pull --platform=arm64 photoprism/develop:impish
docker pull --platform=amd64 ubuntu:impish
docker pull --platform=arm64 ubuntu:impish
scripts/docker/buildx-multi.sh photoprism linux/amd64,linux/arm64 preview-impish /impish
docker-preview-jammy:
docker pull --platform=amd64 photoprism/develop:jammy
docker pull --platform=amd64 photoprism/develop:jammy-slim
@ -442,18 +418,18 @@ docker-preview-lunar:
docker pull --platform=arm64 photoprism/develop:lunar
docker pull --platform=arm64 photoprism/develop:lunar-slim
scripts/docker/buildx-multi.sh photoprism linux/amd64,linux/arm64 preview-ce /lunar
docker-preview-mantic:
docker pull --platform=amd64 photoprism/develop:mantic
docker pull --platform=amd64 photoprism/develop:mantic-slim
docker pull --platform=arm64 photoprism/develop:mantic
docker pull --platform=arm64 photoprism/develop:mantic-slim
scripts/docker/buildx-multi.sh photoprism linux/amd64,linux/arm64 preview-ce /mantic
docker-preview-impish:
docker pull --platform=amd64 photoprism/develop:impish
docker pull --platform=arm64 photoprism/develop:impish
docker pull --platform=amd64 ubuntu:impish
docker pull --platform=arm64 ubuntu:impish
scripts/docker/buildx-multi.sh photoprism linux/amd64,linux/arm64 preview-impish /impish
release: docker-release
docker-release: docker-release-latest
docker-release-all: docker-release-latest docker-release-other
docker-release-latest: docker-release-ubuntu
docker-release-debian: docker-release-bookworm
docker-release-ubuntu: docker-release-mantic
docker-release-ubuntu: docker-release-lunar
docker-release-other: docker-release-debian docker-release-bullseye
docker-release-arm: docker-release-arm64 docker-release-armv7
docker-release-bookworm:
@ -464,7 +440,7 @@ docker-release-bookworm:
scripts/docker/buildx-multi.sh photoprism linux/amd64,linux/arm64 ce-bookworm /bookworm "-t photoprism/photoprism:ce-debian"
docker-release-armv7:
docker pull --platform=arm photoprism/develop:armv7
docker pull --platform=arm ubuntu:mantic
docker pull --platform=arm ubuntu:jammy
scripts/docker/buildx.sh photoprism linux/arm armv7 /armv7
docker-release-arm64:
docker pull --platform=arm64 photoprism/develop:lunar
@ -482,12 +458,6 @@ docker-release-buster:
docker pull --platform=amd64 debian:buster-slim
docker pull --platform=arm64 debian:buster-slim
scripts/docker/buildx-multi.sh photoprism linux/amd64,linux/arm64 ce-buster /buster
docker-release-impish:
docker pull --platform=amd64 photoprism/develop:impish
docker pull --platform=arm64 photoprism/develop:impish
docker pull --platform=amd64 ubuntu:impish
docker pull --platform=arm64 ubuntu:impish
scripts/docker/buildx-multi.sh photoprism linux/amd64,linux/arm64 ce-impish /impish
docker-release-jammy:
docker pull --platform=amd64 photoprism/develop:jammy
docker pull --platform=amd64 photoprism/develop:jammy-slim
@ -500,12 +470,12 @@ docker-release-lunar:
docker pull --platform=arm64 photoprism/develop:lunar
docker pull --platform=arm64 photoprism/develop:lunar-slim
scripts/docker/buildx-multi.sh photoprism linux/amd64,linux/arm64 ce /lunar
docker-release-mantic:
docker pull --platform=amd64 photoprism/develop:mantic
docker pull --platform=amd64 photoprism/develop:mantic-slim
docker pull --platform=arm64 photoprism/develop:mantic
docker pull --platform=arm64 photoprism/develop:mantic-slim
scripts/docker/buildx-multi.sh photoprism linux/amd64,linux/arm64 ce /mantic
docker-release-impish:
docker pull --platform=amd64 photoprism/develop:impish
docker pull --platform=arm64 photoprism/develop:impish
docker pull --platform=amd64 ubuntu:impish
docker pull --platform=arm64 ubuntu:impish
scripts/docker/buildx-multi.sh photoprism linux/amd64,linux/arm64 ce-impish /impish
start-local:
$(DOCKER_COMPOSE) -f docker-compose.local.yml up -d --wait
stop-local:
@ -532,8 +502,8 @@ terminal-latest:
$(DOCKER_COMPOSE) -f docker-compose.latest.yml exec photoprism-latest bash
logs-latest:
$(DOCKER_COMPOSE) -f docker-compose.latest.yml logs -f photoprism-latest
docker-local: docker-local-mantic
docker-local-all: docker-local-mantic docker-local-lunar docker-local-jammy docker-local-bookworm docker-local-bullseye docker-local-buster
docker-local: docker-local-lunar
docker-local-all: docker-local-lunar docker-local-jammy docker-local-bookworm docker-local-bullseye docker-local-buster
docker-local-bookworm:
docker pull photoprism/develop:bookworm
docker pull photoprism/develop:bookworm-slim
@ -546,10 +516,6 @@ docker-local-buster:
docker pull photoprism/develop:buster
docker pull debian:buster-slim
scripts/docker/build.sh photoprism ce-buster /buster "-t photoprism/photoprism:local"
docker-local-impish:
docker pull photoprism/develop:impish
docker pull ubuntu:impish
scripts/docker/build.sh photoprism ce-impish /impish "-t photoprism/photoprism:local"
docker-local-jammy:
docker pull photoprism/develop:jammy
docker pull ubuntu:jammy
@ -558,12 +524,12 @@ docker-local-lunar:
docker pull photoprism/develop:lunar
docker pull ubuntu:lunar
scripts/docker/build.sh photoprism ce-lunar /lunar "-t photoprism/photoprism:local"
docker-local-mantic:
docker pull photoprism/develop:mantic
docker pull ubuntu:mantic
scripts/docker/build.sh photoprism ce-mantic /mantic "-t photoprism/photoprism:local"
docker-local-develop: docker-local-develop-mantic
docker-local-develop-all: docker-local-develop-mantic docker-local-develop-lunar docker-local-develop-jammy docker-local-develop-bookworm docker-local-develop-bullseye docker-local-develop-buster docker-local-develop-impish
docker-local-impish:
docker pull photoprism/develop:impish
docker pull ubuntu:impish
scripts/docker/build.sh photoprism ce-impish /impish "-t photoprism/photoprism:local"
docker-local-develop: docker-local-develop-lunar
docker-local-develop-all: docker-local-develop-lunar docker-local-develop-jammy docker-local-develop-bookworm docker-local-develop-bullseye docker-local-develop-buster docker-local-develop-impish
docker-local-develop-bookworm:
docker pull debian:bookworm-slim
scripts/docker/build.sh develop bookworm /bookworm
@ -573,18 +539,15 @@ docker-local-develop-bullseye:
docker-local-develop-buster:
docker pull golang:1-buster
scripts/docker/build.sh develop buster /buster
docker-local-develop-impish:
docker pull ubuntu:impish
scripts/docker/build.sh develop impish /impish
docker-local-develop-jammy:
docker pull ubuntu:jammy
scripts/docker/build.sh develop jammy /jammy
docker-local-develop-lunar:
docker pull ubuntu:lunar
scripts/docker/build.sh develop lunar /lunar
docker-local-develop-mantic:
docker pull ubuntu:mantic
scripts/docker/build.sh develop mantic /mantic
docker-local-develop-impish:
docker pull ubuntu:impish
scripts/docker/build.sh develop impish /impish
docker-ddns:
docker pull golang:alpine
scripts/docker/buildx-multi.sh ddns linux/amd64,linux/arm64 $(BUILD_DATE)

View file

@ -2,19 +2,20 @@ PhotoPrism: Browse Your Life in Pictures
========================================
[![License: AGPL](https://img.shields.io/badge/license-AGPL-blue.svg)](https://docs.photoprism.app/license/agpl/)
[![GitHub contributors](https://img.shields.io/github/contributors/photoprism/photoprism.svg)](https://www.photoprism.app/about/team)
[![Documentation](https://img.shields.io/badge/read-the%20docs-4aa087.svg)](https://docs.photoprism.app/)
[![Community Chat](https://img.shields.io/badge/chat-on%20gitter-4aa087.svg)](https://link.photoprism.app/chat)
[![GitHub Discussions](https://img.shields.io/badge/ask-%20on%20github-4d6a91.svg)](https://link.photoprism.app/discussions)
[![Bluesky Social](https://dl.photoprism.app/img/badges/badge-follow-photoprism-bsky-social.svg)](https://photoprism.bsky.social/)
[![Mastodon](https://dl.photoprism.app/img/badges/badge-floss-social.svg)](https://floss.social/@photoprism)
[![Mastodon](https://dl.photoprism.app/img/badges/badge-mastodon.svg)](https://floss.social/@photoprism)
[![Twitter](https://dl.photoprism.app/img/badges/badge-twitter.svg)](https://link.photoprism.app/twitter)
PhotoPrism® is an AI-Powered Photos App for the [Decentralized Web](https://en.wikipedia.org/wiki/Decentralized_web).
It makes use of the latest technologies to tag and find pictures automatically without getting in your way.
You can run it at home, on a private server, or in the cloud.
![](https://dl.photoprism.app/img/ui/search-cards-view.jpg)
![](https://dl.photoprism.app/img/ui/desktop-1000px.jpg)
To get a first impression, you are welcome to play with our [public demo](https://try.photoprism.app/). Please be careful not to upload any private, unlawful or offensive pictures.
To get a first impression, you are welcome to play with our [public demo](https://try.photoprism.app/). Be careful not to upload any private pictures.
## Feature Overview ##
@ -38,12 +39,20 @@ Being completely [**self-funded and independent**](https://link.photoprism.app/m
## Getting Started ##
<img align="right" width="25%" src="https://www.photoprism.app/user/pages/01.home/03._screenshots/iphone-maps-hybrid-540px.png">
Step-by-step [installation instructions](https://docs.photoprism.app/getting-started/) for our self-hosted [community edition](https://link.photoprism.app/personal-editions) can be found on [docs.photoprism.app](https://docs.photoprism.app/getting-started/) - all you need is a Web browser and [Docker](https://docs.docker.com/get-docker/) to run the server. It is available for Mac, Linux, and Windows.
Step-by-step installation instructions for our self-hosted [community edition](https://www.photoprism.app/get) can be found
on [docs.photoprism.app](https://docs.photoprism.app/getting-started/) -
all you need is a Web browser and [Docker](https://docs.docker.com/get-docker/) to run the server.
It is available for Mac, Linux, and Windows.
The [stable releases](https://docs.photoprism.app/release-notes/) and [development preview](https://docs.photoprism.app/getting-started/updates/#development-preview) are available as a [multi-arch image](https://link.photoprism.app/docker-hub) for 64-bit AMD, Intel, and ARM processors.
That means, [Raspberry Pi](https://docs.photoprism.app/getting-started/raspberry-pi/) and Apple Silicon users enjoy the exact same functionality and can follow the same [installation steps](https://docs.photoprism.app/getting-started/docker-compose/).
The [stable version](https://docs.photoprism.app/release-notes/) and development
preview have been built into a single [multi-arch image](https://link.photoprism.app/docker-hub) for 64-bit AMD, Intel,
and ARM processors. That means, [Raspberry Pi](https://docs.photoprism.app/getting-started/raspberry-pi/) 3 / 4 owners can pull
from the same repository, enjoy the exact same functionality, and can follow the regular
[installation instructions](https://docs.photoprism.app/getting-started/docker-compose/)
after going through a short list of [requirements](https://docs.photoprism.app/getting-started/raspberry-pi/).
See our [Getting Started FAQ](https://docs.photoprism.app/getting-started/faq/#how-can-i-install-photoprism-without-docker) for alternative installation methods, for example using the [*tar.gz* packages](https://dl.photoprism.app/pkg/linux/README.html) we provide.
Existing users are advised to update their `docker-compose.yml` config based on our examples
available at [dl.photoprism.app/docker](https://dl.photoprism.app/docker/).
## Support Our Mission 💎 ##
@ -103,7 +112,7 @@ Feel free to contact us at [hello@photoprism.app](mailto:hello@photoprism.app) w
## Every Contribution Makes a Difference ##
We welcome [contributions](CONTRIBUTING.md) of any kind, including blog posts, tutorials, translations, testing, writing documentation, and pull requests. Our [Developer Guide](https://docs.photoprism.app/developer-guide/) contains all the information necessary for you to get started.
We welcome [contributions](CONTRIBUTING.md) of any kind, including blog posts, tutorials, testing, writing documentation, and pull requests. Our [Developer Guide](https://docs.photoprism.app/developer-guide/) contains all the information necessary for you to get started.
----

View file

@ -20,10 +20,6 @@ You are [welcome to contact us](https://www.photoprism.app/contact) for change r
**Vitold Romanovski** (May 2023)
**Aaron C. de Bruyn** (September 2023)
[**Patrick Kvaksrud**](https://github.com/Kvaksrud) (October 2023)
## Gold Sponsors ##
[**Simen Eriksen**](https://github.com/dennorske) (GitHub Sponsors, December 2019)
@ -72,10 +68,6 @@ You are [welcome to contact us](https://www.photoprism.app/contact) for change r
[**Yongho Lee**](https://github.com/lyh16) (Patreon, May 2023)
**Albert R** (Patreon, August 2023)
**Peter Galbavy** (Patreon, November 2023)
## Infrastructure Sponsors ##
Our project infrastructure is provided by the following companies:

View file

@ -1,70 +0,0 @@
[{
"SourceFile": "beach_sand.jpg",
"ExifToolVersion": 12.56,
"FileName": "beach_sand.jpg",
"Directory": ".",
"FileSize": 105321,
"FileModifyDate": "2023:06:20 04:43:41+00:00",
"FileAccessDate": "2023:08:06 16:38:50+00:00",
"FileInodeChangeDate": "2023:08:04 05:10:37+00:00",
"FilePermissions": 100644,
"FileType": "JPEG",
"FileTypeExtension": "JPG",
"MIMEType": "image/jpeg",
"JFIFVersion": "1 2",
"ExifByteOrder": "MM",
"Make": "Apple",
"Model": "iPhone SE",
"XResolution": 72,
"YResolution": 72,
"ResolutionUnit": 2,
"Software": "10.2.1",
"ModifyDate": "2017:02:15 14:13:40",
"YCbCrPositioning": 1,
"ExposureTime": 0.0004940711462,
"ExposureProgram": 2,
"ISO": 25,
"ExifVersion": "0231",
"DateTimeOriginal": "2017:02:15 14:13:40",
"CreateDate": "2017:02:15 14:13:40",
"ComponentsConfiguration": "1 2 3 0",
"ApertureValue": 2.19999999733148,
"MeteringMode": 5,
"Flash": 16,
"FocalLength": 4.2,
"SubSecTimeOriginal": 249,
"SubSecTimeDigitized": 249,
"FlashpixVersion": "0100",
"ColorSpace": 65535,
"SensingMethod": 2,
"ExposureMode": 0,
"WhiteBalance": 0,
"SceneCaptureType": 0,
"LensModel": "iPhone SE back camera 4.15mm f/2.2",
"GPSVersionID": "2 3 0 0",
"GPSLatitudeRef": "S",
"GPSLongitudeRef": "E",
"GPSAltitudeRef": 1,
"CurrentIPTCDigest": "804bedc723e0e6cd3a41d0a44b074d19",
"DocumentNotes": "https://flickr.com/e/Rl7qi7oH%2BSEGDuwWBbZYaBQMEB5oNfPvQ6m3aMrPQ64%3D",
"ApplicationRecordVersion": 4,
"ImageWidth": 640,
"ImageHeight": 480,
"EncodingProcess": 2,
"BitsPerSample": 8,
"ColorComponents": 3,
"YCbCrSubSampling": "2 2",
"Aperture": 2.19999999733148,
"ImageSize": "640 480",
"Megapixels": 0.3072,
"ShutterSpeed": 0.0004940711462,
"SubSecCreateDate": "2017:02:15 14:13:40.249",
"SubSecDateTimeOriginal": "2017:02:15 14:13:40.249",
"GPSAltitude": -1.990417522,
"GPSLatitude": -29.2824777777778,
"GPSLongitude": 31.4436361111111,
"FocalLength35efl": 4.2,
"GPSPosition": "-29.2824777777778 31.4436361111111",
"LightValue": 15.2580006188259,
"LensID": "iPhone SE back camera 4.15mm f/2.2"
}]

Binary file not shown.

View file

@ -1,153 +0,0 @@
[{
"SourceFile": "iphone_15_pro.heic",
"ExifToolVersion": 12.40,
"FileName": "iphone_15_pro.heic",
"Directory": ".",
"FileSize": 886825,
"FileModifyDate": "2023:10:31 11:48:48+01:00",
"FileAccessDate": "2023:10:31 11:51:22+01:00",
"FileInodeChangeDate": "2023:10:31 11:50:11+01:00",
"FilePermissions": 100664,
"FileType": "HEIC",
"FileTypeExtension": "HEIC",
"MIMEType": "image/heic",
"MajorBrand": "heic",
"MinorVersion": "0.0.0",
"CompatibleBrands": ["mif1","MiHE","MiPr","miaf","MiHB","heic"],
"HandlerType": "pict",
"PrimaryItemReference": 49,
"MetaImageSize": "0 1287 4032 3024",
"XMPToolkit": "XMP Core 6.0.0",
"HDRGainMapHeadroom": 3.847906,
"HDRGainMapVersion": 131072,
"ExifByteOrder": "MM",
"Make": "Apple",
"Model": "iPhone 15 Pro",
"Orientation": 6,
"XResolution": 72,
"YResolution": 72,
"ResolutionUnit": 2,
"Software": 17.1,
"ModifyDate": "2023:10:31 11:44:43",
"HostComputer": "iPhone 15 Pro",
"ExposureTime": 0.01666666667,
"FNumber": 2.2,
"ExposureProgram": 2,
"ISO": 400,
"ExifVersion": "0232",
"DateTimeOriginal": "2023:10:31 11:44:43",
"CreateDate": "2023:10:31 11:44:43",
"OffsetTime": "+01:00",
"OffsetTimeOriginal": "+01:00",
"OffsetTimeDigitized": "+01:00",
"ShutterSpeedValue": 0.0165679999782223,
"ApertureValue": 2.20000000038133,
"BrightnessValue": 2.15000162,
"ExposureCompensation": 0,
"MeteringMode": 5,
"Flash": 16,
"FocalLength": 2.22,
"SubjectArea": "2015 1511 2323 1330",
"RunTimeFlags": 1,
"RunTimeValue": 154455128730208,
"RunTimeScale": 1000000000,
"RunTimeEpoch": 0,
"AccelerationVector": "-0.004213878416 -1.002046108 -0.01470538881",
"Warning": "[minor] Bad format (16) for MakerNotes entry 10",
"SubSecTimeOriginal": 432,
"SubSecTimeDigitized": 432,
"ColorSpace": 65535,
"ExifImageWidth": 4032,
"ExifImageHeight": 3024,
"SensingMethod": 2,
"SceneType": 1,
"ExposureMode": 0,
"WhiteBalance": 0,
"FocalLengthIn35mmFormat": 14,
"LensInfo": "2.22 9 1.779999971 2.8",
"LensMake": "Apple",
"LensModel": "iPhone 15 Pro back triple camera 2.22mm f/2.2",
"CompositeImage": 2,
"GPSLatitudeRef": "N",
"GPSLongitudeRef": "E",
"GPSAltitudeRef": 0,
"GPSTimeStamp": "10:44:43",
"GPSSpeedRef": "K",
"GPSSpeed": 0,
"GPSImgDirectionRef": "T",
"GPSImgDirection": 101.3112946,
"GPSDestBearingRef": "T",
"GPSDestBearing": 101.3112946,
"GPSDateStamp": "2023:10:31",
"GPSHPositioningError": 14.275587,
"ProfileCMMType": "appl",
"ProfileVersion": 1024,
"ProfileClass": "mntr",
"ColorSpaceData": "RGB ",
"ProfileConnectionSpace": "XYZ ",
"ProfileDateTime": "2022:01:01 00:00:00",
"ProfileFileSignature": "acsp",
"PrimaryPlatform": "APPL",
"CMMFlags": 0,
"DeviceManufacturer": "APPL",
"DeviceModel": "",
"DeviceAttributes": "0 0",
"RenderingIntent": 0,
"ConnectionSpaceIlluminant": "0.9642 1 0.82491",
"ProfileCreator": "appl",
"ProfileID": "236 253 163 142 56 133 71 195 109 180 189 79 122 218 24 47",
"ProfileDescription": "Display P3",
"ProfileCopyright": "Copyright Apple Inc., 2022",
"MediaWhitePoint": "0.96419 1 0.82489",
"RedMatrixColumn": "0.51512 0.2412 -0.00105",
"GreenMatrixColumn": "0.29198 0.69225 0.04189",
"BlueMatrixColumn": "0.1571 0.06657 0.78407",
"RedTRC": "(Binary data 32 bytes, use -b option to extract)",
"ChromaticAdaptation": "1.04788 0.02292 -0.0502 0.02959 0.99048 -0.01706 -0.00923 0.01508 0.75168",
"BlueTRC": "(Binary data 32 bytes, use -b option to extract)",
"GreenTRC": "(Binary data 32 bytes, use -b option to extract)",
"HEVCConfigurationVersion": 1,
"GeneralProfileSpace": 0,
"GeneralTierFlag": 0,
"GeneralProfileIDC": 3,
"GenProfileCompatibilityFlags": 1879048192,
"ConstraintIndicatorFlags": "176 0 0 0 0 0",
"GeneralLevelIDC": 90,
"MinSpatialSegmentationIDC": 0,
"ParallelismType": 0,
"ChromaFormat": 1,
"BitDepthLuma": 8,
"BitDepthChroma": 8,
"AverageFrameRate": 0,
"ConstantFrameRate": 0,
"NumTemporalLayers": 1,
"TemporalIDNested": 0,
"ImageWidth": 4032,
"ImageHeight": 3024,
"ImageSpatialExtent": "4032 3024",
"Rotation": 270,
"ImagePixelDepth": 8,
"AuxiliaryImageType": "urn:com:apple:photo:2020:aux:hdrgainmap",
"MediaDataSize": 882139,
"MediaDataOffset": 4686,
"RunTimeSincePowerUp": 154455.128730208,
"Aperture": 2.2,
"ImageSize": "4032 3024",
"Megapixels": 12.192768,
"ScaleFactor35efl": 6.30630630630631,
"ShutterSpeed": 0.01666666667,
"SubSecCreateDate": "2023:10:31 11:44:43.432+01:00",
"SubSecDateTimeOriginal": "2023:10:31 11:44:43.432+01:00",
"SubSecModifyDate": "2023:10:31 11:44:43+01:00",
"GPSAltitude": 50.15664187,
"GPSDateTime": "2023:10:31 10:44:43Z",
"GPSLatitude": 52.4596055555556,
"GPSLongitude": 13.3218416666667,
"CircleOfConfusion": "0.00476447847114884",
"FOV": 104.250120754113,
"FocalLength35efl": 14,
"GPSPosition": "52.4596055555556 13.3218416666667",
"HyperfocalDistance": 0.470184057236731,
"LightValue": 6.18189764281985,
"LensID": "iPhone 15 Pro back triple camera 2.22mm f/2.2"
}]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 MiB

View file

@ -1,82 +0,0 @@
[{
"SourceFile": "/go/src/github.com/photoprism/photoprism/storage/import/samsung-motion-photo.jpg",
"ExifToolVersion": 12.56,
"FileName": "samsung-motion-photo.jpg",
"Directory": "/go/src/github.com/photoprism/photoprism/storage/import",
"FileSize": 7221645,
"FileModifyDate": "2023:07:27 02:03:13+00:00",
"FileAccessDate": "2023:08:13 17:11:04+00:00",
"FileInodeChangeDate": "2023:08:13 17:11:02+00:00",
"FilePermissions": 100644,
"FileType": "JPEG",
"FileTypeExtension": "JPG",
"MIMEType": "image/jpeg",
"ExifByteOrder": "II",
"Make": "samsung",
"Model": "SM-G973F",
"Orientation": 1,
"XResolution": 72,
"YResolution": 72,
"ResolutionUnit": 2,
"Software": "G973FXXU4CTC9",
"ModifyDate": "2020:04:23 18:33:41",
"YCbCrPositioning": 1,
"ExposureTime": 0.0007575757576,
"FNumber": 2.4,
"ExposureProgram": 2,
"ISO": 50,
"ExifVersion": "0220",
"DateTimeOriginal": "2020:04:23 18:33:41",
"CreateDate": "2020:04:23 18:33:41",
"ShutterSpeedValue": 0.999475026346474,
"ApertureValue": 2.39495740923786,
"BrightnessValue": 22.58,
"ExposureCompensation": 0,
"MaxApertureValue": 2.39495740923786,
"MeteringMode": 2,
"Flash": 0,
"FocalLength": 4.32,
"ColorSpace": 1,
"ExifImageWidth": 4032,
"ExifImageHeight": 3024,
"ExposureMode": 0,
"WhiteBalance": 0,
"DigitalZoomRatio": 1,
"FocalLengthIn35mmFormat": 26,
"SceneCaptureType": 0,
"ImageUniqueID": "L12XLLD01VM",
"GPSLatitudeRef": "N",
"GPSLongitudeRef": "W",
"Compression": 6,
"ThumbnailOffset": 888,
"ThumbnailLength": 50555,
"XMPToolkit": "Adobe XMP Core 5.1.0-jc003",
"MicroVideo": 1,
"MicroVideoVersion": 1,
"MicroVideoOffset": 4535831,
"MicroVideoPresentationTimestampUs": -1,
"ImageWidth": 4032,
"ImageHeight": 3024,
"EncodingProcess": 0,
"BitsPerSample": 8,
"ColorComponents": 3,
"YCbCrSubSampling": "2 2",
"TimeStamp": "2020:04:23 17:33:41.809+00:00",
"MCCData": 234,
"EmbeddedVideoType": "MotionPhoto_Data",
"EmbeddedVideoFile": "(Binary data 4535775 bytes, use -b option to extract)",
"Aperture": 2.4,
"ImageSize": "4032 3024",
"Megapixels": 12.192768,
"ScaleFactor35efl": 6.01851851851852,
"ShutterSpeed": 0.0007575757576,
"ThumbnailImage": "(Binary data 50555 bytes, use -b option to extract)",
"GPSLatitude": 51.5049828,
"GPSLongitude": -0.0787347997222222,
"CircleOfConfusion": "0.00499230176602706",
"FOV": 69.3903656740024,
"FocalLength35efl": 26,
"GPSPosition": "51.5049828 -0.0787347997222222",
"HyperfocalDistance": 1.55759815100044,
"LightValue": 13.8923910258672
}]

View file

@ -2,16 +2,16 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-03-09 13:14+0000\n"
"PO-Revision-Date: 2023-10-16 16:35+0000\n"
"Last-Translator: dtsolakis <dtsola@eranet.gr>\n"
"POT-Creation-Date: 2023-02-09 12:51+0000\n"
"PO-Revision-Date: 2023-02-09 13:13+0000\n"
"Last-Translator: Anonymous <noreply@weblate.org>\n"
"Language-Team: none\n"
"Language: el\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.0.2\n"
"X-Generator: Weblate 4.15.2\n"
#: messages.go:94
msgid "Something went wrong, try again"
@ -19,7 +19,7 @@ msgstr "Κάτι πήγε στραβά, δοκιμάστε ξανά"
#: messages.go:95
msgid "Unable to do that"
msgstr "Αυτό δεν είναι εφικτό"
msgstr "Δεν είναι δυνατόν να το κάνετε αυτό"
#: messages.go:96
msgid "Changes could not be saved"
@ -334,7 +334,7 @@ msgstr "Επεξεργασία μεταφόρτωσης..."
#: messages.go:172
msgid "Upload has been processed"
msgstr "Η φόρτωση έχει ολοκληρωθεί"
msgstr "%s έχει αποκατασταθεί"
#: messages.go:173
msgid "Selection approved"

View file

@ -26,8 +26,8 @@
<div class="splash-logo">
{{template "logo.gohtml" .}}
</div>
<progress id="progress" class="html-progress" max="100" style="accent-color: #c8c2e8; color-scheme: dark;"></progress>
<progress id="progress" class="html-progress" max="100"></progress>
</div>
</div>
<div id="busy-overlay"><div class="splash-center"><progress id="busy-progress" class="html-progress" max="100" style="accent-color: #c8c2e8; color-scheme: dark;"></progress></div></div>
<div id="busy-overlay"><div class="splash-center"><progress id="busy-progress" class="html-progress" max="100"></progress></div></div>

View file

@ -1,128 +0,0 @@
version: '3.5'
## FOR ARMv7 TEST AND DEVELOPMENT ONLY, DO NOT USE IN PRODUCTION ##
## Setup: https://docs.photoprism.app/developer-guide/setup/ ##
services:
## App Server
photoprism:
build: .
image: photoprism/photoprism:develop
platform: "arm"
depends_on:
- mariadb
- dummy-webdav
security_opt:
- seccomp:unconfined
- apparmor:unconfined
ports:
- "2342:2342" # Default HTTP port (host:container)
- "2343:2343" # Acceptance Test HTTP port (host:container)
shm_size: "2gb"
environment:
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # The initial admin password (min 4 characters)
PHOTOPRISM_UID: ${UID:-1000}
PHOTOPRISM_GID: ${GID:-1000}
PHOTOPRISM_SITE_URL: "http://localhost:2342/"
PHOTOPRISM_SITE_TITLE: "PhotoPrism"
PHOTOPRISM_SITE_CAPTION: "Browse Your Life"
PHOTOPRISM_SITE_DESCRIPTION: "AI-powered app for browsing, organizing & sharing your photo collection."
PHOTOPRISM_SITE_AUTHOR: "@photoprism_app"
PHOTOPRISM_DEBUG: "true"
PHOTOPRISM_READONLY: "false"
PHOTOPRISM_PUBLIC: "true"
PHOTOPRISM_EXPERIMENTAL: "true"
PHOTOPRISM_SERVER_MODE: "debug"
PHOTOPRISM_HTTP_HOST: "0.0.0.0"
PHOTOPRISM_HTTP_PORT: 2342
PHOTOPRISM_HTTP_COMPRESSION: "gzip" # Improves transfer speed and bandwidth utilization (none or gzip)
PHOTOPRISM_DATABASE_DRIVER: "mysql"
PHOTOPRISM_DATABASE_SERVER: "mariadb:4001"
PHOTOPRISM_DATABASE_NAME: "photoprism"
PHOTOPRISM_DATABASE_USER: "root"
PHOTOPRISM_DATABASE_PASSWORD: "photoprism"
PHOTOPRISM_TEST_DRIVER: "sqlite"
PHOTOPRISM_TEST_DSN: ".test.db"
PHOTOPRISM_TEST_DSN_MYSQL8: "root:photoprism@tcp(mysql:4001)/photoprism?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true"
PHOTOPRISM_ASSETS_PATH: "/go/src/github.com/photoprism/photoprism/assets"
PHOTOPRISM_STORAGE_PATH: "/go/src/github.com/photoprism/photoprism/storage"
PHOTOPRISM_ORIGINALS_PATH: "/go/src/github.com/photoprism/photoprism/storage/originals"
PHOTOPRISM_IMPORT_PATH: "/go/src/github.com/photoprism/photoprism/storage/import"
PHOTOPRISM_DISABLE_CHOWN: "false" # Disables storage permission updates on startup
PHOTOPRISM_DISABLE_BACKUPS: "false" # Don't backup photo and album metadata to YAML files
PHOTOPRISM_DISABLE_WEBDAV: "false" # Disables built-in WebDAV server
PHOTOPRISM_DISABLE_SETTINGS: "false" # Disables Settings in Web UI
PHOTOPRISM_DISABLE_PLACES: "false" # Disables reverse geocoding and maps
PHOTOPRISM_DISABLE_EXIFTOOL: "false" # Don't create ExifTool JSON files for improved metadata extraction
PHOTOPRISM_DISABLE_TENSORFLOW: "false" # Don't use TensorFlow for image classification
PHOTOPRISM_DETECT_NSFW: "false" # Flag photos as private that MAY be offensive (requires TensorFlow)
PHOTOPRISM_UPLOAD_NSFW: "false" # Allows uploads that may be offensive
PHOTOPRISM_DARKTABLE_PRESETS: "false" # Enables Darktable presets and disables concurrent RAW conversion
PHOTOPRISM_THUMB_FILTER: "lanczos" # Resample filter, best to worst: blackman, lanczos, cubic, linear
PHOTOPRISM_THUMB_UNCACHED: "true" # Enables on-demand thumbnail rendering (high memory and cpu usage)
PHOTOPRISM_THUMB_SIZE: 2048 # Pre-rendered thumbnail size limit (default 2048, min 720, max 7680)
# PHOTOPRISM_THUMB_SIZE: 4096 # Retina 4K, DCI 4K (requires more storage); 7680 for 8K Ultra HD
PHOTOPRISM_THUMB_SIZE_UNCACHED: 7680 # On-demand rendering size limit (default 7680, min 720, max 7680)
PHOTOPRISM_JPEG_SIZE: 7680 # Size limit for converted image files in pixels (720-30000)
PHOTOPRISM_JPEG_QUALITY: 92 # Set to 95 for high-quality thumbnails (25-100)
TF_CPP_MIN_LOG_LEVEL: 0 # Show TensorFlow log messages for development
## Enable TensorFlow AVX2 support for modern Intel CPUs (requires starting the container as root):
# PHOTOPRISM_INIT: "tensorflow-amd64-avx2"
## Hardware video transcoding config (optional):
# PHOTOPRISM_FFMPEG_BUFFERS: "64" # FFmpeg capture buffers (default: 32)
# PHOTOPRISM_FFMPEG_BITRATE: "32" # FFmpeg encoding bitrate limit in Mbit/s (default: 50)
# PHOTOPRISM_FFMPEG_ENCODER: "h264_v4l2m2m" # Use Video4Linux for AVC transcoding (default: libx264)
# PHOTOPRISM_FFMPEG_ENCODER: "h264_qsv" # Use Intel Quick Sync Video for AVC transcoding (default: libx264)
# PHOTOPRISM_INIT: "intel-graphics tensorflow-amd64-avx2" # Enable TensorFlow AVX2 & Intel Graphics support
# PHOTOPRISM_INIT: "install-updates" # Installs general operating system updates
## Hardware devices for video transcoding and machine learning (optional):
# devices:
# - "/dev/video11:/dev/video11" # Video4Linux (h264_v4l2m2m)
# - "/dev/dri/renderD128:/dev/dri/renderD128" # Intel GPU
# - "/dev/dri/card0:/dev/dri/card0"
working_dir: "/go/src/github.com/photoprism/photoprism"
volumes:
- ".:/go/src/github.com/photoprism/photoprism"
- "go-mod:/go/pkg/mod"
## MariaDB Database Server
## Docs: https://mariadb.com/docs/reference/
## Release Notes: https://mariadb.com/kb/en/changes-improvements-in-mariadb-1011/
mariadb:
image: mariadb:10.11
security_opt: # see https://github.com/MariaDB/mariadb-docker/issues/434#issuecomment-1136151239
- seccomp:unconfined
- apparmor:unconfined
command: --port=4001 --innodb-strict-mode=1 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
expose:
- "4001"
ports:
- "4001:4001" # database port (host:container)
volumes:
- "./scripts/sql/mariadb-init.sql:/docker-entrypoint-initdb.d/init.sql"
environment:
MARIADB_AUTO_UPGRADE: "1"
MARIADB_INITDB_SKIP_TZINFO: "1"
MARIADB_DATABASE: "photoprism"
MARIADB_USER: "photoprism"
MARIADB_PASSWORD: "photoprism"
MARIADB_ROOT_PASSWORD: "photoprism"
## Dummy WebDAV Server
dummy-webdav:
image: photoprism/dummy-webdav:231015
container_name: dummy-webdav
environment:
WEBDAV_USERNAME: admin
WEBDAV_PASSWORD: photoprism
## Create named volume for Go module cache
volumes:
go-mod:
driver: local
## Create shared "photoprism-develop" network for connecting with services in other docker-compose.yml files
networks:
default:
name: shared
driver: bridge

View file

@ -150,7 +150,7 @@ services:
security_opt: # see https://github.com/MariaDB/mariadb-docker/issues/434#issuecomment-1136151239
- seccomp:unconfined
- apparmor:unconfined
command: --port=4001 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
command: mariadbd --port=4001 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
expose:
- "4001" # database port (internal)
volumes:
@ -165,11 +165,11 @@ services:
## Dummy OpenID Connect Provider
dummy-oidc:
image: photoprism/dummy-oidc:231015
image: photoprism/dummy-oidc:220405
## Dummy WebDAV Server
dummy-webdav:
image: photoprism/dummy-webdav:231015
image: photoprism/dummy-webdav:20211109
environment:
WEBDAV_USERNAME: admin
WEBDAV_PASSWORD: photoprism

View file

@ -4,15 +4,15 @@ version: '3.5'
## Setup: https://docs.photoprism.app/developer-guide/setup/ ##
services:
## MariaDB 11.2 Database Server
## MariaDB 11.0 Database Server
## Docs: https://mariadb.com/docs/reference/
## Release Notes: https://mariadb.com/kb/en/release-notes-mariadb-11-2-series/
mariadb-11-2:
image: mariadb:11.2
## Release Notes: https://mariadb.com/kb/en/mariadb-11-0-0-release-notes/
mariadb-11-0:
image: mariadb:11.0-rc
security_opt: # see https://github.com/MariaDB/mariadb-docker/issues/434#issuecomment-1136151239
- seccomp:unconfined
- apparmor:unconfined
command: --port=4001 --innodb-strict-mode=1 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
command: mariadbd --port=4001 --innodb-strict-mode=1 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
expose:
- "4001"
ports:
@ -27,14 +27,14 @@ services:
MARIADB_PASSWORD: "photoprism"
MARIADB_ROOT_PASSWORD: "photoprism"
## MariaDB 10.11 Database Server
## Docs: https://mariadb.com/kb/en/release-notes-mariadb-1011-series/
mariadb-10-11:
image: mariadb:10.11
## MariaDB 10.8 Database Server
## Docs: https://mariadb.com/kb/en/release-notes-mariadb-108-series/
mariadb-10-8:
image: mariadb:10.8
security_opt: # see https://github.com/MariaDB/mariadb-docker/issues/434#issuecomment-1136151239
- seccomp:unconfined
- apparmor:unconfined
command: --port=4001 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
command: mariadbd --port=4001 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
expose:
- "4001"
ports:
@ -49,12 +49,28 @@ services:
MARIADB_PASSWORD: "photoprism"
MARIADB_ROOT_PASSWORD: "photoprism"
## MariaDB 10.7 Database Server
mariadb-10-7:
image: mariadb:10.7
command: mysqld --port=4001 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
expose:
- "4001" # database port (internal)
volumes:
- "./scripts/sql/mariadb-init.sql:/docker-entrypoint-initdb.d/init.sql"
environment:
MARIADB_AUTO_UPGRADE: "1"
MARIADB_INITDB_SKIP_TZINFO: "1"
MARIADB_DATABASE: "photoprism"
MARIADB_USER: "photoprism"
MARIADB_PASSWORD: "photoprism"
MARIADB_ROOT_PASSWORD: "photoprism"
## MariaDB 10.5.5 Database Server
## Affected by MDEV-25362: Incorrect name resolution for subqueries in ON expressions
## see https://jira.mariadb.org/browse/MDEV-25362
mariadb-10-5-5:
image: mariadb:10.5.5
command: --port=4001 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
command: mysqld --port=4001 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
expose:
- "4001" # database port (internal)
volumes:
@ -69,7 +85,7 @@ services:
## Docs: https://mariadb.com/docs/reference/cs10.3/
mariadb-10-3:
image: mariadb:10.3
command: --port=4001 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
command: mysqld --port=4001 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
expose:
- "4001" # database port (internal)
volumes:
@ -84,7 +100,7 @@ services:
## Docs: https://mariadb.com/docs/reference/cs10.2/
mariadb-10-2:
image: mariadb:10.2
command: --port=4001 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
command: mysqld --port=4001 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
expose:
- "4001" # database port (internal)
volumes:
@ -98,7 +114,7 @@ services:
## MariaDB 10.1 Database Server
mariadb-10-1:
image: mariadb:10.1
command: --port=4001 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
command: mysqld --port=4001 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
expose:
- "4001" # database port (internal)
volumes:

View file

@ -8,7 +8,7 @@ services:
## Docs: https://dev.mysql.com/doc/refman/8.0/en/
mysql:
image: mysql:8
command: --port=4001 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
command: mysqld --port=4001 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
expose:
- "4001" # database port (internal)
environment:

View file

@ -83,7 +83,7 @@ services:
## Dummy WebDAV Server
dummy-webdav:
image: photoprism/dummy-webdav:231015
image: photoprism/dummy-webdav:20211109
environment:
WEBDAV_USERNAME: admin
WEBDAV_PASSWORD: photoprism

View file

@ -133,7 +133,7 @@ services:
security_opt: # see https://github.com/MariaDB/mariadb-docker/issues/434#issuecomment-1136151239
- seccomp:unconfined
- apparmor:unconfined
command: --port=4001 --innodb-strict-mode=1 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
command: mariadbd --port=4001 --innodb-strict-mode=1 --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
expose:
- "4001"
ports:
@ -165,7 +165,7 @@ services:
## Login: user / photoprism
## Admin: admin / photoprism
keycloak:
image: quay.io/keycloak/keycloak:22.0
image: quay.io/keycloak/keycloak:20.0
command: "start-dev" # development mode, do not use this in production!
container_name: keycloak
links:
@ -211,7 +211,7 @@ services:
## Dummy OpenID Connect Provider
dummy-oidc:
image: photoprism/dummy-oidc:231015
image: photoprism/dummy-oidc:220405
container_name: dummy-oidc
labels:
- "traefik.enable=true"
@ -224,7 +224,7 @@ services:
## Dummy WebDAV Server
dummy-webdav:
image: photoprism/dummy-webdav:231015
image: photoprism/dummy-webdav:220405
container_name: dummy-webdav
environment:
WEBDAV_USERNAME: admin

View file

@ -1,21 +1,64 @@
# Dockerfiles for Development and Production
# Dockerfiles and Docker Compose Examples
[**Dockerfiles**](https://docs.docker.com/engine/reference/builder/) are text documents that contain all commands a user could call in a terminal to assemble an application image.
[**Dockerfiles**](https://docs.docker.com/engine/reference/builder/) are text documents that contain all commands a user
could call in a terminal to assemble an application image.
[**Docker Compose**](https://docs.docker.com/compose/) uses [human-friendly YAML files](https://docs.photoprism.app/developer-guide/technologies/yaml/) to configure all application services so you can easily start them with a single command.
[**Docker Compose**](https://docs.docker.com/compose/) uses [human-friendly YAML files](https://docs.photoprism.app/developer-guide/technologies/yaml/)
to configure all application services so you can easily start them with a single command.
See our [Getting Started FAQ](https://docs.photoprism.app/getting-started/faq/#how-can-i-install-photoprism-without-docker) for alternative installation methods, for example using the [*tar.gz* packages](https://github.com/photoprism/photoprism/blob/develop/setup/pkg/linux/README.md) we provide for download at [dl.photoprism.app/pkg/linux/](https://dl.photoprism.app/pkg/linux/README.html).
## Why are we using Docker? ##
## What are the benefits of using Docker? ##
Containers are nothing new; [Solaris Zones](https://en.wikipedia.org/wiki/Solaris_Containers) have been around for
about 15 years, first released publicly in 2004. The chroot system call was introduced during
[development of Version 7 Unix in 1979](https://en.wikipedia.org/wiki/Chroot). It is used ever since for hosting
applications exposed to the public Internet.
**(1) Docker uses standard features of the Linux kernel.** Containers are nothing new; [Solaris Zones](https://en.wikipedia.org/wiki/Solaris_Containers) were released about 20 years ago and the chroot system call was introduced during [development of Version 7 Unix in 1979](https://en.wikipedia.org/wiki/Chroot). It is used ever since for hosting applications exposed to the public Internet. Modern Linux containers are an incremental improvement of this, based on standard functionality that is part of the kernel.
Modern Linux containers are an incremental enhancement. A main advantage of Docker is that application images
can be easily made available to users via Internet. It provides a common standard across most operating
systems and devices, which saves our team a lot of time that we can then spend [more effectively](https://docs.photoprism.app/developer-guide/issues/#effectiveness-efficiency), for example,
providing support and developing one of the many features that users are waiting for.
**(2) Docker saves time through simplified deployment and testing.** A main advantage of Docker is that application images can be [easily made available](https://hub.docker.com/r/photoprism/photoprism) to users via Internet. It provides a common standard across most operating systems and devices, which saves our team a lot of time that we can then spend [more effectively](https://docs.photoprism.app/developer-guide/code-quality/#effectiveness-efficiency), for example, providing support and developing one of the many features that users are waiting for.
Human-readable and versioned Dockerfiles as part of our public source code also help avoid "works for me" moments and
other unwelcome surprises by enabling teams to have the exact same environment everywhere in
[development](https://github.com/photoprism/photoprism/blob/develop/docker/develop/bookworm/Dockerfile), staging,
and [production](https://github.com/photoprism/photoprism/blob/develop/docker/photoprism/bookworm/Dockerfile).
**(3) Dockerfiles are part of the source code repository.** [Human-readable](https://docs.docker.com/engine/reference/builder/) and [versioned Dockerfiles](https://github.com/photoprism/photoprism/tree/develop/docker) that are part of our public source code help avoid "works for me" moments and other unwelcome surprises by enabling us to have the exact [same environment](http://localhost:8000/developer-guide/setup/) everywhere in [development](https://github.com/photoprism/photoprism/tree/develop/docker/develop), [staging, and production](https://github.com/photoprism/photoprism/tree/develop/docker/photoprism).
Last but not least, virtually all file format parsers have vulnerabilities that just haven't been discovered yet.
This is a known risk that can affect you even if your computer is not directly connected to the Internet.
Running apps in a container with limited host access is an easy way to improve security without
compromising performance and usability.
**(4) Running applications in containers is more secure.** Last but not least, virtually all file format parsers have vulnerabilities that just haven't been discovered yet. This is a known risk that can affect you even if your computer is not directly connected to the Internet. Running apps in a container with limited host access is an easy way to improve security without compromising performance and usability.
## What about Virtual Machines? ##
## Why not use virtual machines instead? ##
A virtual machine running its own operating system provides more security, but typically has side effects
such as lower performance and more difficult handling. You can also run Docker in a VM to get the best of
both worlds. It's essentially what happens when you run dockerized applications on [virtual cloud servers](https://docs.photoprism.app/getting-started/cloud/digitalocean/)
and operating systems other than Linux.
A virtual machine with a dedicated operating system environment provides even more security, but usually has side effects such as lower performance and more difficult handling. Using a VM, however, doesn't prevent you from running containerized apps to get the best of both worlds. This is essentially what happens when you install Docker on [virtual cloud servers](https://docs.photoprism.app/getting-started/cloud/digitalocean/) and operating systems other than Linux.
## Alternatives ##
### Building From Source ###
You can build and install PhotoPrism from the publicly available [source code](https://docs.photoprism.app/developer-guide/setup/):
```bash
git clone https://github.com/photoprism/photoprism.git
cd photoprism
make all install DESTDIR=/opt/photoprism
```
Missing build dependencies must be installed manually as shown in our human-readable and versioned
[Dockerfile](https://github.com/photoprism/photoprism/blob/develop/docker/develop/Dockerfile). You often don't
need to use the exact same versions, so it's possible to replace packages with what is available in your environment.
Please note that we do not have the resources to provide private users with dependencies and
[TensorFlow libraries](https://dl.photoprism.app/tensorflow/) for their personal environments. We recommend giving
Docker a try if you use Linux as it saves developers a lot of time when building, testing, and deploying complex
applications like PhotoPrism. It also effectively helps avoid "works for me" moments and missing dependencies.
### Installation Packages ###
An [unofficial port](https://docs.photoprism.app/getting-started/freebsd/) is available for FreeBSD / FreeNAS users.
You are invited to contribute by [building and testing standalone packages](https://docs.photoprism.app/developer-guide/) for Linux distributions and other operating systems.
Updates are [released several times a month](https://docs.photoprism.app/release-notes/), so maintaining the long list of dependencies for additional environments would currently consume too many of [our resources](https://link.photoprism.app/membership).

View file

@ -1,5 +1,5 @@
#### Base Image: Ubuntu 23.10 (Mantic Minotaur)
FROM ubuntu:mantic
#### Base Image: Ubuntu 22.04 LTS (Jammy Jellyfish)
FROM ubuntu:jammy
# Copyright © 2018 - 2023 PhotoPrism UG. All rights reserved.
#
@ -9,7 +9,7 @@ FROM ubuntu:mantic
# Add Open Container Initiative (OCI) annotations.
# See: https://github.com/opencontainers/image-spec/blob/main/annotations.md
LABEL org.opencontainers.image.title="PhotoPrism® Build Image (ARMv7)"
LABEL org.opencontainers.image.description="Ubuntu 23.10 (Mantic Minotaur)"
LABEL org.opencontainers.image.description="Ubuntu 22.04 LTS (Jammy Jellyfish)"
LABEL org.opencontainers.image.url="https://hub.docker.com/repository/docker/photoprism/develop"
LABEL org.opencontainers.image.source="https://github.com/photoprism/photoprism"
LABEL org.opencontainers.image.documentation="https://docs.photoprism.app/developer-guide/setup/"
@ -48,19 +48,19 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
echo 'APT::Get::Assume-Yes "true";' > /etc/apt/apt.conf.d/80forceyes && \
echo 'APT::Get::Fix-Missing "true";' > /etc/apt/apt.conf.d/80fixmissing && \
echo 'force-confold' > /etc/dpkg/dpkg.cfg.d/force-confold && \
apt-get update && apt-get -qq dist-upgrade && apt-get -qq install --no-install-recommends \
apt-get update && apt-get -qq upgrade && apt-get -qq install --no-install-recommends \
apt-utils gpg pkg-config software-properties-common ca-certificates avahi-utils \
build-essential gcc g++ sudo bash make nano lsof lshw git jq \
autoconf automake cmake libtool libjpeg-dev libpng-dev libwebp-dev \
autoconf automake cmake libtool libjpeg8 libjpeg8-dev \
zip unzip wget curl rsync sqlite3 chrpath gettext libc6-dev \
libssl-dev libxft-dev libfreetype6 libfreetype6-dev libfontconfig1 \
libfontconfig1-dev libhdf5-serial-dev libzmq3-dev \
libfontconfig1-dev libhdf5-serial-dev libpng-dev libzmq3-dev \
libx264-dev libx265-dev libde265-dev libaom-dev libnss3 libxtst6 librsvg2-bin tzdata \
exiftool ffmpeg libavcodec-extra \
&& \
/scripts/install-nodejs.sh && \
/scripts/install-libheif.sh && \
/scripts/install-tensorflow.sh && \
/scripts/install-libheif.sh && \
/scripts/install-go.sh && \
/scripts/install-go-tools.sh && \
echo 'alias ll="ls -alh"' >> /etc/skel/.bashrc && \
@ -79,14 +79,14 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
/photoprism/storage/cache && \
/scripts/cleanup.sh
# Download models and testdata.
# download models and testdata
RUN mkdir /tmp/photoprism && \
wget "https://dl.photoprism.app/tensorflow/nsfw.zip?${BUILD_TAG}" -O /tmp/photoprism/nsfw.zip && \
wget "https://dl.photoprism.app/tensorflow/nasnet.zip?${BUILD_TAG}" -O /tmp/photoprism/nasnet.zip && \
wget "https://dl.photoprism.app/tensorflow/facenet.zip?${BUILD_TAG}" -O /tmp/photoprism/facenet.zip && \
wget "https://dl.photoprism.app/qa/testdata.zip?${BUILD_TAG}" -O /tmp/photoprism/testdata.zip
# Default working directory.
# set up project directory
WORKDIR "/go/src/github.com/photoprism/photoprism"
# Expose the following container ports:
@ -103,4 +103,3 @@ ENTRYPOINT ["/scripts/entrypoint.sh"]
# Keep container running.
CMD ["tail", "-f", "/dev/null"]

View file

@ -43,7 +43,7 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
echo 'APT::Get::Assume-Yes "true";' > /etc/apt/apt.conf.d/80forceyes && \
echo 'APT::Get::Fix-Missing "true";' > /etc/apt/apt.conf.d/80fixmissing && \
echo 'force-confold' > /etc/dpkg/dpkg.cfg.d/force-confold && \
apt-get update && apt-get -qq dist-upgrade && \
apt-get update && apt-get -qq upgrade && \
apt-get -qq install \
libc6 ca-certificates sudo bash tzdata \
gpg zip unzip wget curl rsync make nano \

View file

@ -49,7 +49,7 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
echo 'APT::Get::Assume-Yes "true";' > /etc/apt/apt.conf.d/80forceyes && \
echo 'APT::Get::Fix-Missing "true";' > /etc/apt/apt.conf.d/80fixmissing && \
echo 'force-confold' > /etc/dpkg/dpkg.cfg.d/force-confold && \
apt-get update && apt-get -qq dist-upgrade && \
apt-get update && apt-get -qq upgrade && \
apt-get -qq install \
libc6 ca-certificates sudo bash tzdata \
gpg zip unzip wget curl rsync make nano \

View file

@ -42,7 +42,7 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
echo 'APT::Get::Assume-Yes "true";' > /etc/apt/apt.conf.d/80forceyes && \
echo 'APT::Get::Fix-Missing "true";' > /etc/apt/apt.conf.d/80fixmissing && \
echo 'force-confold' > /etc/dpkg/dpkg.cfg.d/force-confold && \
apt-get update && apt-get -qq dist-upgrade && \
apt-get update && apt-get -qq upgrade && \
apt-get -qq install \
libc6 ca-certificates bash sudo nano avahi-utils jq lsof lshw \
exiftool sqlite3 tzdata gpg make zip unzip wget curl rsync \

View file

@ -48,7 +48,7 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
echo 'APT::Get::Assume-Yes "true";' > /etc/apt/apt.conf.d/80forceyes && \
echo 'APT::Get::Fix-Missing "true";' > /etc/apt/apt.conf.d/80fixmissing && \
echo 'force-confold' > /etc/dpkg/dpkg.cfg.d/force-confold && \
apt-get update && apt-get -qq dist-upgrade && \
apt-get update && apt-get -qq upgrade && \
apt-get -qq install \
libc6 ca-certificates bash sudo nano avahi-utils jq lsof lshw \
exiftool sqlite3 tzdata gpg make zip unzip wget curl rsync \
@ -58,11 +58,11 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
apt-get -qq install \
apt-utils pkg-config software-properties-common \
build-essential gcc g++ git gettext davfs2 chrpath apache2-utils \
autoconf automake cmake libtool libjpeg-dev libpng-dev libwebp-dev \
libx264-dev libx265-dev libaom-dev libvpx-dev libwebm-dev libxft-dev \
autoconf automake cmake libtool libjpeg8-dev \
libx264-dev libx265-dev libaom-dev libvpx-dev libwebm-dev libpng-dev libxft-dev \
libc6-dev libhdf5-serial-dev libzmq3-dev libssl-dev libnss3 \
libfreetype6 libfreetype6-dev libfontconfig1 libfontconfig1-dev fonts-roboto \
librsvg2-bin ghostscript gsfonts pdf2svg ps2eps \
librsvg2-bin ghostscript gsfonts \
&& \
/scripts/install-nodejs.sh && \
/scripts/install-mariadb.sh mariadb-client && \

View file

@ -42,7 +42,7 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
echo 'APT::Get::Assume-Yes "true";' > /etc/apt/apt.conf.d/80forceyes && \
echo 'APT::Get::Fix-Missing "true";' > /etc/apt/apt.conf.d/80fixmissing && \
echo 'force-confold' > /etc/dpkg/dpkg.cfg.d/force-confold && \
apt-get update && apt-get -qq dist-upgrade && \
apt-get update && apt-get -qq upgrade && \
apt-get -qq install \
libc6 ca-certificates bash sudo nano avahi-utils jq lsof lshw libebml5 libgav1-bin libatomic1 \
exiftool sqlite3 tzdata gpg make zip unzip wget curl rsync imagemagick libvips-dev rawtherapee \
@ -75,4 +75,4 @@ WORKDIR /photoprism
EXPOSE 2342 2442 2443
# Keep container running.
CMD ["tail", "-f", "/dev/null"]
CMD ["tail", "-f", "/dev/null"]

View file

@ -48,7 +48,7 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
echo 'APT::Get::Assume-Yes "true";' > /etc/apt/apt.conf.d/80forceyes && \
echo 'APT::Get::Fix-Missing "true";' > /etc/apt/apt.conf.d/80fixmissing && \
echo 'force-confold' > /etc/dpkg/dpkg.cfg.d/force-confold && \
apt-get update && apt-get -qq dist-upgrade && \
apt-get update && apt-get -qq upgrade && \
apt-get -qq install \
libc6 ca-certificates bash sudo nano avahi-utils jq lsof lshw libebml5 libgav1-bin libatomic1 \
exiftool sqlite3 tzdata gpg make zip unzip wget curl rsync imagemagick libvips-dev rawtherapee \
@ -58,11 +58,11 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
apt-get -qq install \
apt-utils pkg-config software-properties-common \
build-essential gcc g++ git gettext davfs2 chrpath apache2-utils \
autoconf automake cmake libtool libjpeg-dev libpng-dev libwebp-dev \
libx264-dev libx265-dev libaom-dev libvpx-dev libwebm-dev libxft-dev \
autoconf automake cmake libtool libjpeg8-dev \
libx264-dev libx265-dev libaom-dev libvpx-dev libwebm-dev libpng-dev libxft-dev \
libc6-dev libhdf5-serial-dev libzmq3-dev libssl-dev libnss3 \
libfreetype6 libfreetype6-dev libfontconfig1 libfontconfig1-dev fonts-roboto \
librsvg2-bin ghostscript gsfonts pdf2svg ps2eps \
librsvg2-bin ghostscript gsfonts \
&& \
/scripts/install-nodejs.sh && \
/scripts/install-mariadb.sh mariadb-client && \

View file

@ -1,78 +0,0 @@
#### Base Image: Ubuntu 23.10 (Mantic Minotaur)
FROM ubuntu:mantic
# Copyright © 2018 - 2023 PhotoPrism UG. All rights reserved.
#
# Questions? Email us at hello@photoprism.app or visit our website to learn
# more about our team, products and services: https://www.photoprism.app/
# Add Open Container Initiative (OCI) annotations.
# See: https://github.com/opencontainers/image-spec/blob/main/annotations.md
LABEL org.opencontainers.image.title="PhotoPrism® Base Image (Ubuntu 23.10)"
LABEL org.opencontainers.image.description="Ubuntu 23.10 (Mantic Minotaur)"
LABEL org.opencontainers.image.url="https://hub.docker.com/repository/docker/photoprism/develop"
LABEL org.opencontainers.image.source="https://github.com/photoprism/photoprism"
LABEL org.opencontainers.image.documentation="https://docs.photoprism.app/developer-guide/setup/"
LABEL org.opencontainers.image.authors="PhotoPrism UG <hello@photoprism.app>"
LABEL org.opencontainers.image.vendor="PhotoPrism UG"
# Declare build parameters.
ARG TARGETARCH
ARG BUILD_TAG
# Set environment variables, see https://docs.photoprism.app/getting-started/config-options/.
ENV PHOTOPRISM_ARCH=$TARGETARCH \
DOCKER_TAG=$BUILD_TAG \
DOCKER_ENV="prod" \
PS1="\u@$DOCKER_TAG:\w\$ " \
PATH="/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin:/scripts:/opt/photoprism/bin" \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL="2" \
PROG="photoprism"
# Copy scripts and package sources config.
COPY --chown=root:root --chmod=755 /scripts/dist/ /scripts/
# Update base image and add dependencies.
RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
echo 'APT::Install-Recommends "false";' > /etc/apt/apt.conf.d/80recommends && \
echo 'APT::Install-Suggests "false";' > /etc/apt/apt.conf.d/80suggests && \
echo 'APT::Get::Assume-Yes "true";' > /etc/apt/apt.conf.d/80forceyes && \
echo 'APT::Get::Fix-Missing "true";' > /etc/apt/apt.conf.d/80fixmissing && \
echo 'force-confold' > /etc/dpkg/dpkg.cfg.d/force-confold && \
apt-get update && apt-get -qq dist-upgrade && \
apt-get -qq install \
libc6 ca-certificates bash sudo nano avahi-utils jq lsof lshw libebml5 libgav1-bin libatomic1 \
exiftool sqlite3 tzdata gpg make zip unzip wget curl rsync imagemagick libvips-dev rawtherapee \
ffmpeg libffmpeg-nvenc-dev libswscale-dev libavfilter-extra libavformat-extra libavcodec-extra \
x264 x265 libde265-dev libaom3 libvpx7 libwebm1 libjpeg8 libmatroska7 libdvdread8 \
&& \
/scripts/install-mariadb.sh mariadb-client && \
/scripts/install-darktable.sh && \
/scripts/install-libheif.sh && \
echo 'alias ll="ls -alh"' >> /etc/skel/.bashrc && \
echo 'export PS1="\u@$DOCKER_TAG:\w\$ "' >> /etc/skel/.bashrc && \
echo "ALL ALL=(ALL) NOPASSWD:SETENV: /scripts/entrypoint-init.sh" >> /etc/sudoers.d/init && \
cp /etc/skel/.bashrc /root/.bashrc && \
/scripts/create-users.sh && \
install -d -m 0777 -o 1000 -g 1000 \
/photoprism/originals \
/photoprism/import \
/photoprism/storage \
/photoprism/storage/sidecar \
/photoprism/storage/albums \
/photoprism/storage/backups \
/photoprism/storage/config \
/photoprism/storage/cache && \
/scripts/cleanup.sh
# Default working directory.
WORKDIR /photoprism
# Expose HTTP and HTTPS ports.
EXPOSE 2342 2442 2443
# Keep container running.
CMD ["tail", "-f", "/dev/null"]

View file

@ -1,115 +0,0 @@
#### Base Image: Ubuntu 23.10 (Mantic Minotaur)
FROM ubuntu:mantic
# Copyright © 2018 - 2023 PhotoPrism UG. All rights reserved.
#
# Questions? Email us at hello@photoprism.app or visit our website to learn
# more about our team, products and services: https://www.photoprism.app/
# Add Open Container Initiative (OCI) annotations.
# See: https://github.com/opencontainers/image-spec/blob/main/annotations.md
LABEL org.opencontainers.image.title="PhotoPrism® Build Image (Ubuntu 23.10)"
LABEL org.opencontainers.image.description="Ubuntu 23.10 (Mantic Minotaur)"
LABEL org.opencontainers.image.url="https://hub.docker.com/repository/docker/photoprism/develop"
LABEL org.opencontainers.image.source="https://github.com/photoprism/photoprism"
LABEL org.opencontainers.image.documentation="https://docs.photoprism.app/developer-guide/setup/"
LABEL org.opencontainers.image.authors="PhotoPrism UG <hello@photoprism.app>"
LABEL org.opencontainers.image.vendor="PhotoPrism UG"
# Declare build parameters.
ARG TARGETARCH
ARG BUILD_TAG
# Set environment variables, see https://docs.photoprism.app/getting-started/config-options/.
ENV PHOTOPRISM_ARCH=$TARGETARCH \
DOCKER_TAG=$BUILD_TAG \
DOCKER_ENV="develop" \
NODE_ENV="production" \
PS1="\u@$DOCKER_TAG:\w\$ " \
PATH="/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin:/scripts:/usr/local/go/bin:/go/bin:/opt/photoprism/bin" \
LD_LIBRARY_PATH="/usr/local/lib:/usr/lib" \
DEBIAN_FRONTEND="noninteractive" \
TMPDIR="/tmp" \
TF_CPP_MIN_LOG_LEVEL="0" \
GOPATH="/go" \
GOBIN="/usr/local/bin" \
GO111MODULE="on" \
CGO_CFLAGS="-g -O2 -Wno-return-local-addr" \
PROG="photoprism"
# Copy scripts and package sources config.
COPY --chown=root:root --chmod=755 /scripts/dist/ /scripts/
COPY --chown=root:root --chmod=644 /.my.cnf /etc/my.cnf
# Update base image and add dependencies.
RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
echo 'APT::Install-Recommends "false";' > /etc/apt/apt.conf.d/80recommends && \
echo 'APT::Install-Suggests "false";' > /etc/apt/apt.conf.d/80suggests && \
echo 'APT::Get::Assume-Yes "true";' > /etc/apt/apt.conf.d/80forceyes && \
echo 'APT::Get::Fix-Missing "true";' > /etc/apt/apt.conf.d/80fixmissing && \
echo 'force-confold' > /etc/dpkg/dpkg.cfg.d/force-confold && \
apt-get update && apt-get -qq dist-upgrade && \
apt-get -qq install \
libc6 ca-certificates bash sudo nano avahi-utils jq lsof lshw libebml5 libgav1-bin libatomic1 \
exiftool sqlite3 tzdata gpg make zip unzip wget curl rsync imagemagick libvips-dev rawtherapee \
ffmpeg libffmpeg-nvenc-dev libswscale-dev libavfilter-extra libavformat-extra libavcodec-extra \
x264 x265 libde265-dev libaom3 libvpx7 libwebm1 libjpeg8 libmatroska7 libdvdread8 \
&& \
apt-get -qq install \
apt-utils pkg-config software-properties-common \
build-essential gcc g++ git gettext davfs2 chrpath apache2-utils \
autoconf automake cmake libtool libjpeg-dev libpng-dev libwebp-dev \
libx264-dev libx265-dev libaom-dev libvpx-dev libwebm-dev libxft-dev \
libc6-dev libhdf5-serial-dev libzmq3-dev libssl-dev libnss3 \
libfreetype6 libfreetype6-dev libfontconfig1 libfontconfig1-dev fonts-roboto \
librsvg2-bin ghostscript gsfonts pdf2svg ps2eps \
&& \
/scripts/install-nodejs.sh && \
/scripts/install-mariadb.sh mariadb-client && \
/scripts/install-tensorflow.sh && \
/scripts/install-darktable.sh && \
/scripts/install-libheif.sh && \
/scripts/install-chrome.sh && \
/scripts/install-go.sh && \
/scripts/install-go-tools.sh && \
echo 'alias go=richgo ll="ls -alh"' >> /etc/skel/.bashrc && \
echo 'export PS1="\u@$DOCKER_TAG:\w\$ "' >> /etc/skel/.bashrc && \
echo "ALL ALL=(ALL) NOPASSWD:SETENV: ALL" >> /etc/sudoers.d/all && \
cp /etc/skel/.bashrc /root/.bashrc && \
cp /scripts/convert/policy.xml /etc/ImageMagick-6/policy.xml && \
/scripts/create-users.sh && \
install -d -m 0777 -o 1000 -g 1000 \
/photoprism/originals \
/photoprism/import \
/photoprism/storage \
/photoprism/storage/sidecar \
/photoprism/storage/albums \
/photoprism/storage/backups \
/photoprism/storage/config \
/photoprism/storage/cache && \
/scripts/cleanup.sh
# Download models and testdata.
RUN mkdir /tmp/photoprism && \
wget "https://dl.photoprism.app/tensorflow/nsfw.zip?${BUILD_TAG}" -O /tmp/photoprism/nsfw.zip && \
wget "https://dl.photoprism.app/tensorflow/nasnet.zip?${BUILD_TAG}" -O /tmp/photoprism/nasnet.zip && \
wget "https://dl.photoprism.app/tensorflow/facenet.zip?${BUILD_TAG}" -O /tmp/photoprism/facenet.zip && \
wget "https://dl.photoprism.app/qa/testdata.zip?${BUILD_TAG}" -O /tmp/photoprism/testdata.zip
# Default working directory.
WORKDIR "/go/src/github.com/photoprism/photoprism"
# Expose the following container ports:
# - 2342 (HTTP)
# - 2343 (Acceptance Tests)
# - 2442 (HTTP)
# - 2443 (HTTPS)
# - 9515 (Chromedriver)
# - 40000 (Go Debugger)
EXPOSE 2342 2343 2442 2443 9515 40000
# Declare container entrypoint script.
ENTRYPOINT ["/scripts/entrypoint.sh"]
# Keep container running.
CMD ["tail", "-f", "/dev/null"]

View file

@ -8,10 +8,9 @@ FROM photoprism/develop:armv7 as build
# Declare build parameters.
ARG TARGETARCH
ARG TARGETPLATFORM
ARG BUILD_TAG
# Copy project files.
# Copy source to image.
WORKDIR "/go/src/github.com/photoprism/photoprism"
COPY . .
@ -19,19 +18,18 @@ COPY . .
RUN make all install DESTDIR=/opt/photoprism
################################################## PRODUCTION STAGE ####################################################
#### Base Image: Ubuntu 23.10 (Mantic Minotaur)
FROM ubuntu:mantic
#### Base Image: Ubuntu 22.04 LTS (Jammy Jellyfish)
FROM ubuntu:jammy
# Add Open Container Initiative (OCI) annotations.
# See: https://github.com/opencontainers/image-spec/blob/main/annotations.md
LABEL org.opencontainers.image.title="PhotoPrism® (ARMv7)"
LABEL org.opencontainers.image.description="Ubuntu 23.10 (Mantic Minotaur)"
LABEL org.opencontainers.image.description="Ubuntu 22.04 LTS (Jammy Jellyfish)"
LABEL org.opencontainers.image.url="https://hub.docker.com/r/photoprism/photoprism"
LABEL org.opencontainers.image.source="https://github.com/photoprism/photoprism"
LABEL org.opencontainers.image.documentation="https://docs.photoprism.app/getting-started/"
LABEL org.opencontainers.image.authors="PhotoPrism UG <hello@photoprism.app>"
LABEL org.opencontainers.image.vendor="PhotoPrism UG"
LABEL org.opencontainers.image.ref.name="photoprism"
# Declare build parameters.
ARG TARGETARCH
@ -105,7 +103,7 @@ RUN echo 'APT::Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
echo 'APT::Get::Assume-Yes "true";' > /etc/apt/apt.conf.d/80forceyes && \
echo 'APT::Get::Fix-Missing "true";' > /etc/apt/apt.conf.d/80fixmissing && \
echo 'force-confold' > /etc/dpkg/dpkg.cfg.d/force-confold && \
apt-get update && apt-get -qq dist-upgrade && \
apt-get update && apt-get -qq upgrade && \
apt-get -qq install --no-install-recommends \
libc6 ca-certificates bash sudo nano avahi-utils jq lsof lshw \
exiftool mariadb-client sqlite3 tzdata gpg make zip unzip wget curl rsync \

View file

@ -94,7 +94,7 @@ COPY --chown=root:root --chmod=755 /scripts/dist/ /scripts/
# Update pre-installed packages.
RUN apt-get update && \
apt-get -qq dist-upgrade && \
apt-get -qq upgrade && \
/scripts/cleanup.sh
# Default working directory.

View file

@ -95,7 +95,7 @@ COPY --chown=root:root --chmod=755 /scripts/dist/ /scripts/
# Update pre-installed packages.
RUN apt-get update && \
apt-get -qq dist-upgrade && \
apt-get -qq upgrade && \
/scripts/cleanup.sh
# Default working directory.

View file

@ -95,7 +95,7 @@ COPY --chown=root:root --chmod=755 /scripts/dist/ /scripts/
# Update pre-installed packages.
RUN apt-get update && \
apt-get -qq dist-upgrade && \
apt-get -qq upgrade && \
/scripts/cleanup.sh
# Default working directory.

View file

@ -1,114 +0,0 @@
##################################################### BUILD STAGE ######################################################
FROM photoprism/develop:mantic as build
# Copyright © 2018 - 2023 PhotoPrism UG. All rights reserved.
#
# Questions? Email us at hello@photoprism.app or visit our website to learn
# more about our team, products and services: https://www.photoprism.app/
# Declare build parameters.
ARG TARGETARCH
ARG TARGETPLATFORM
ARG BUILD_TAG
# Copy source to image.
WORKDIR "/go/src/github.com/photoprism/photoprism"
COPY . .
# Build app.
RUN make all install DESTDIR=/opt/photoprism
################################################## PRODUCTION STAGE ####################################################
#### Base Image: Ubuntu 23.10 (Mantic Minotaur)
FROM photoprism/develop:mantic-slim
# Add Open Container Initiative (OCI) annotations.
# See: https://github.com/opencontainers/image-spec/blob/main/annotations.md
LABEL org.opencontainers.image.title="PhotoPrism® (Ubuntu 23.10)"
LABEL org.opencontainers.image.description="Ubuntu 23.10 (Mantic Minotaur)"
LABEL org.opencontainers.image.url="https://hub.docker.com/r/photoprism/photoprism"
LABEL org.opencontainers.image.source="https://github.com/photoprism/photoprism"
LABEL org.opencontainers.image.documentation="https://docs.photoprism.app/getting-started/"
LABEL org.opencontainers.image.authors="PhotoPrism UG <hello@photoprism.app>"
LABEL org.opencontainers.image.vendor="PhotoPrism UG"
# Declare build parameters.
ARG TARGETARCH
ARG BUILD_TAG
# Set environment variables, see https://docs.photoprism.app/getting-started/config-options/.
ENV PHOTOPRISM_ARCH=$TARGETARCH \
DOCKER_TAG=$BUILD_TAG \
DOCKER_ENV="prod" \
TMPDIR="/tmp" \
DEBIAN_FRONTEND="noninteractive" \
TF_CPP_MIN_LOG_LEVEL="2" \
PROG="photoprism" \
PHOTOPRISM_ASSETS_PATH="/opt/photoprism/assets" \
PHOTOPRISM_IMPORT_PATH="/photoprism/import" \
PHOTOPRISM_ORIGINALS_PATH="/photoprism/originals" \
PHOTOPRISM_STORAGE_PATH="/photoprism/storage" \
PHOTOPRISM_BACKUP_PATH="/photoprism/storage/backups" \
PHOTOPRISM_LOG_FILENAME="/photoprism/storage/photoprism.log" \
PHOTOPRISM_PID_FILENAME="/photoprism/storage/photoprism.pid" \
PHOTOPRISM_DEBUG="false" \
PHOTOPRISM_PUBLIC="false" \
PHOTOPRISM_READONLY="false" \
PHOTOPRISM_UPLOAD_NSFW="true" \
PHOTOPRISM_DETECT_NSFW="false" \
PHOTOPRISM_EXPERIMENTAL="false" \
PHOTOPRISM_SITE_URL="http://localhost:2342/" \
PHOTOPRISM_SITE_CAPTION="AI-Powered Photos App" \
PHOTOPRISM_SITE_DESCRIPTION="" \
PHOTOPRISM_SITE_AUTHOR="" \
PHOTOPRISM_HTTP_HOST="0.0.0.0" \
PHOTOPRISM_HTTP_PORT=2342 \
PHOTOPRISM_DATABASE_DRIVER="sqlite" \
PHOTOPRISM_DATABASE_SERVER="" \
PHOTOPRISM_DATABASE_NAME="photoprism" \
PHOTOPRISM_DATABASE_USER="photoprism" \
PHOTOPRISM_DATABASE_PASSWORD="" \
PHOTOPRISM_DISABLE_CHOWN="false" \
PHOTOPRISM_DISABLE_WEBDAV="false" \
PHOTOPRISM_DISABLE_SETTINGS="false" \
PHOTOPRISM_DISABLE_BACKUPS="false" \
PHOTOPRISM_DISABLE_EXIFTOOL="false" \
PHOTOPRISM_DISABLE_PLACES="false" \
PHOTOPRISM_DISABLE_TENSORFLOW="false" \
PHOTOPRISM_DISABLE_FACES="false" \
PHOTOPRISM_DISABLE_CLASSIFICATION="false" \
PHOTOPRISM_RAW_PRESETS="false" \
PHOTOPRISM_THUMB_FILTER="lanczos" \
PHOTOPRISM_THUMB_UNCACHED="false" \
PHOTOPRISM_THUMB_SIZE=2048 \
PHOTOPRISM_THUMB_SIZE_UNCACHED=7680 \
PHOTOPRISM_JPEG_SIZE=7680 \
PHOTOPRISM_JPEG_QUALITY=85 \
PHOTOPRISM_WORKERS=0 \
PHOTOPRISM_WAKEUP_INTERVAL=900 \
PHOTOPRISM_AUTO_INDEX=300 \
PHOTOPRISM_AUTO_IMPORT=300 \
PHOTOPRISM_INIT="https"
# Copy scripts.
COPY --chown=root:root --chmod=755 /scripts/dist/ /scripts/
# Update pre-installed packages.
RUN apt-get update && \
apt-get -qq dist-upgrade && \
/scripts/cleanup.sh
# Default working directory.
WORKDIR /photoprism
# Expose HTTP(S) ports.
EXPOSE 2342 2443
# Copy app files.
COPY --from=build --chown=root:root --chmod=755 /opt/photoprism/ /opt/photoprism
# Declare container entrypoint script.
ENTRYPOINT ["/scripts/entrypoint.sh"]
# Start app.
CMD ["/opt/photoprism/bin/photoprism", "start"]

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "photoprism",
"version": "1",
"version": "1.0.0",
"description": "AI-Powered Photos App",
"author": "PhotoPrism UG",
"license": "AGPL-3.0",
@ -20,45 +20,45 @@
"gettext-compile": "gettext-compile --output src/locales/translations.json src/locales/*.po"
},
"dependencies": {
"@babel/cli": "^7.23.0",
"@babel/core": "^7.23.2",
"@babel/eslint-parser": "^7.22.15",
"@babel/cli": "^7.21.5",
"@babel/core": "^7.21.8",
"@babel/eslint-parser": "^7.21.8",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
"@babel/plugin-transform-runtime": "^7.23.2",
"@babel/preset-env": "^7.23.2",
"@babel/register": "^7.22.15",
"@babel/runtime": "^7.23.2",
"@babel/plugin-transform-runtime": "^7.21.4",
"@babel/preset-env": "^7.21.5",
"@babel/register": "^7.21.0",
"@babel/runtime": "^7.21.5",
"@lcdp/offline-plugin": "^5.1.1",
"@vvo/tzdb": "^6.109.0",
"axios": "^1.6.0",
"axios-mock-adapter": "^1.22.0",
"babel-loader": "^9.1.3",
"@vvo/tzdb": "^6.107.0",
"axios": "^1.4.0",
"axios-mock-adapter": "^1.21.4",
"babel-loader": "^9.1.2",
"babel-plugin-istanbul": "^6.1.1",
"browserslist": "^4.22.1",
"chai": "^4.3.10",
"browserslist": "^4.21.5",
"chai": "^4.3.7",
"cheerio": "1.0.0-rc.10",
"chrome-finder": "^1.0.7",
"core-js": "^3.33.1",
"core-js": "^3.30.2",
"cross-env": "^7.0.3",
"css-loader": "^6.8.1",
"css-loader": "^6.7.3",
"cssnano": "^6.0.1",
"easygettext": "^2.17.0",
"eslint": "^8.51.0",
"eslint-config-prettier": "^9.0.0",
"eslint-config-standard": "^17.1.0",
"eslint": "^8.40.0",
"eslint-config-prettier": "^8.8.0",
"eslint-config-standard": "^17.0.0",
"eslint-formatter-pretty": "^5.0.0",
"eslint-plugin-html": "^7.1.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier-vue": "^5.0.0",
"eslint-plugin-prettier-vue": "^4.2.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-vue": "^9.17.0",
"eslint-plugin-vue": "^9.13.0",
"eslint-webpack-plugin": "^4.0.1",
"eventsource-polyfill": "^0.9.6",
"file-loader": "^6.2.0",
"file-saver": "^2.0.5",
"hls.js": "^1.4.12",
"hls.js": "^1.2.9",
"i": "^0.3.7",
"karma": "^6.4.2",
"karma-chrome-launcher": "^3.2.0",
@ -67,31 +67,31 @@
"karma-mocha": "^2.0.1",
"karma-verbose-reporter": "^0.0.8",
"karma-webpack": "^5.0.0",
"luxon": "^3.4.3",
"maplibre-gl": "^3.5.1",
"luxon": "^3.3.0",
"maplibre-gl": "^3.2.0",
"memoize-one": "^6.0.0",
"mini-css-extract-plugin": "^2.7.6",
"mini-css-extract-plugin": "^2.7.2",
"minimist": ">=1.2.5",
"mocha": "^10.2.0",
"node-storage-shim": "^2.0.1",
"photoswipe": "^4.1.3",
"postcss": "^8.4.31",
"postcss": "^8.4.20",
"postcss-import": "^15.1.0",
"postcss-loader": "^7.3.3",
"postcss-preset-env": "^9.2.0",
"postcss-loader": "^7.0.2",
"postcss-preset-env": "^9.0.0",
"postcss-reporter": "^7.0.5",
"postcss-url": "^10.1.3",
"prettier": "^3.0.3",
"prettier": "^2.8.8",
"pubsub-js": "^1.9.4",
"regenerator-runtime": "^0.14.0",
"regenerator-runtime": "^0.13.11",
"resolve-url-loader": "^5.0.0",
"sass": "^1.69.4",
"sass-loader": "^13.3.2",
"sass": "^1.62.1",
"sass-loader": "^13.2.0",
"server": "^1.0.38",
"sockette": "^2.0.6",
"style-loader": "^3.3.3",
"style-loader": "^3.3.1",
"svg-url-loader": "^8.0.0",
"tar": "^6.2.0",
"tar": "^6.1.13",
"url-loader": "^4.1.1",
"util": "^0.12.5",
"vue": "^2.7.14",
@ -106,17 +106,17 @@
"vue-template-compiler": "^2.7.13",
"vue2-filters": "^0.14.0",
"vuetify": "^1.5.24",
"webpack": "^5.89.0",
"webpack-bundle-analyzer": "^4.9.1",
"webpack-cli": "^5.1.4",
"webpack-hot-middleware": "^2.25.4",
"webpack": "^5.83.0",
"webpack-bundle-analyzer": "^4.8.0",
"webpack-cli": "^5.1.1",
"webpack-hot-middleware": "^2.25.3",
"webpack-manifest-plugin": "^5.0.0",
"webpack-md5-hash": "^0.0.6",
"webpack-merge": "^5.10.0"
"webpack-merge": "^5.8.0"
},
"engines": {
"node": ">= 18.0.0",
"npm": ">= 9.0.0",
"node": ">= 16.0.0",
"npm": ">= 8.0.0",
"yarn": "please use npm"
},
"browserslist": ">0.25% and last 2 years"

View file

@ -164,9 +164,9 @@ export default [
},
{
name: "moment",
path: "/moments/:album/:slug",
path: "/moments/:uid/:slug",
component: AlbumPhotos,
meta: { collectionTitle: "Moments", collectionRoute: "moments", auth: true },
meta: { collName: "Moments", collRoute: "moments", auth: true },
},
{
name: "albums",
@ -177,9 +177,9 @@ export default [
},
{
name: "album",
path: "/albums/:album/:slug",
path: "/albums/:uid/:slug",
component: AlbumPhotos,
meta: { collectionTitle: "Albums", collectionRoute: "albums", auth: true },
meta: { collName: "Albums", collRoute: "albums", auth: true },
},
{
name: "calendar",
@ -190,9 +190,9 @@ export default [
},
{
name: "month",
path: "/calendar/:album/:slug",
path: "/calendar/:uid/:slug",
component: AlbumPhotos,
meta: { collectionTitle: "Calendar", collectionRoute: "calendar", auth: true },
meta: { collName: "Calendar", collRoute: "calendar", auth: true },
},
{
name: "folders",
@ -203,9 +203,9 @@ export default [
},
{
name: "folder",
path: "/folders/:album/:slug",
path: "/folders/:uid/:slug",
component: AlbumPhotos,
meta: { collectionTitle: "Folders", collectionRoute: "folders", auth: true },
meta: { collName: "Folders", collRoute: "folders", auth: true },
},
{
name: "unsorted",
@ -263,25 +263,16 @@ export default [
meta: { title: $gettext("Places"), auth: true },
},
{
name: "places_view",
path: "/places/view/:s",
name: "places_query",
path: "/places/:q",
component: Places,
meta: { title: $gettext("Places"), auth: true },
},
{
name: "places_browse",
path: "/places/browse",
component: Photos,
name: "places_scope",
path: "/places/:s/:q",
component: Places,
meta: { title: $gettext("Places"), auth: true },
beforeEnter: (to, from, next) => {
if (session.loginRequired()) {
next({ name: "login" });
} else if (config.deny("photos", "search")) {
next({ name: "albums" });
} else {
next();
}
},
},
{
name: "states",
@ -292,9 +283,9 @@ export default [
},
{
name: "state",
path: "/states/:album/:slug",
path: "/states/:uid/:slug",
component: AlbumPhotos,
meta: { collectionTitle: "Places", collectionRoute: "states", auth: true },
meta: { collName: "Places", collRoute: "states", auth: true },
},
{
name: "files",

View file

@ -327,12 +327,6 @@ export default class Config {
case "files":
this.values.count.files += data.count;
break;
case "hidden":
this.values.count.hidden += data.count;
break;
case "archived":
this.values.count.archived += data.count;
break;
case "favorites":
this.values.count.favorites += data.count;
break;

View file

@ -84,14 +84,11 @@ export default class Session {
this.config.progress(80);
this.redeemToken(shared.token).finally(() => {
this.config.progress(99);
// Redirect URL.
const location = shared.uri ? shared.uri : this.config.baseUri + "/";
// Redirect to URL after one second.
setTimeout(() => {
window.location = location;
}, 1000);
if (shared.uri) {
window.location = shared.uri;
} else {
window.location = this.config.baseUri + "/";
}
});
} else {
this.config.progress(80);

View file

@ -153,14 +153,6 @@ export default class Util {
return s.replace(/\w\S*/g, (w) => w.replace(/^\w/, (c) => c.toUpperCase()));
}
static ucFirst(s) {
if (!s || s === "") {
return "";
}
return s.charAt(0).toUpperCase() + s.slice(1);
}
static generateToken() {
return (Math.random() + 1).toString(36).substring(6);
}
@ -179,7 +171,6 @@ export default class Util {
return "Unprocessed Sensor Data (RAW)";
case "mov":
case "qt":
case "qt ":
return "Apple QuickTime";
case "bmp":
return "Bitmap";
@ -202,9 +193,8 @@ export default class Util {
return "AOMedia Video 1 (AV1)";
case "avifs":
return "AVIF Image Sequence";
case "hvc":
case "hevc":
case "hev1":
case "hvc":
case "hvc1":
return "High Efficiency Video Coding (HEVC) / H.265";
case "m4v":
@ -245,31 +235,6 @@ export default class Util {
}
}
static formatCodec(codec) {
if (!codec) {
return "";
}
switch (codec) {
case "webp":
case "extended webp":
return "WebP";
case "webm":
return "WebM";
case "av1c":
case "av01":
return "AV1";
case "avc1":
return "AVC";
case "hvc":
case "hev1":
case "hvc1":
return "HEVC";
default:
return codec.toUpperCase();
}
}
static codecName(value) {
if (!value || typeof value !== "string") {
return "";
@ -280,19 +245,16 @@ export default class Util {
return "Unprocessed Sensor Data (RAW)";
case "mov":
case "qt":
case "qt ":
return "Apple QuickTime (MOV)";
case "avc":
case "avc1":
return "Advanced Video Coding (AVC) / H.264";
case "hvc":
case "hevc":
case "hev1":
case "hvc":
case "hvc1":
return "High Efficiency Video Coding (HEVC) / H.265";
case "vvc":
return "Versatile Video Coding (VVC) / H.266";
case "av1c":
case "av01":
return "AOMedia Video 1 (AV1)";
case "gif":
@ -301,8 +263,6 @@ export default class Util {
return "Matroska Multimedia Container (MKV)";
case "webp":
return "Google WebP";
case "extended webp":
return "Extended WebP";
case "webm":
return "Google WebM";
case "mpeg":

View file

@ -5,8 +5,8 @@
<v-toolbar flat :dense="$vuetify.breakpoint.smAndDown" class="page-toolbar" color="secondary">
<v-toolbar-title :title="album.Title">
<span class="hidden-xs-only">
<router-link :to="{ name: collectionRoute }">
{{ T(collectionTitle) }}
<router-link :to="{ name: collRoute }">
{{ T(collName) }}
</router-link>
<v-icon>{{ navIcon }}</v-icon>
</span>
@ -33,12 +33,10 @@
<v-icon>get_app</v-icon>
</v-btn>
<v-btn v-if="settings.view === 'cards'" icon class="action-view-list" :title="$gettext('Toggle View')"
@click.stop="setView('list')">
<v-btn v-if="settings.view === 'cards'" icon class="action-view-list" :title="$gettext('Toggle View')" @click.stop="setView('list')">
<v-icon>view_list</v-icon>
</v-btn>
<v-btn v-else-if="settings.view === 'list'" icon class="action-view-mosaic" :title="$gettext('Toggle View')"
@click.stop="setView('mosaic')">
<v-btn v-else-if="settings.view === 'list'" icon class="action-view-mosaic" :title="$gettext('Toggle View')" @click.stop="setView('mosaic')">
<v-icon>view_comfy</v-icon>
</v-btn>
<v-btn v-else icon class="action-view-cards" :title="$gettext('Toggle View')" @click.stop="setView('cards')">
@ -70,8 +68,7 @@
<p-share-dialog :show="dialog.share" :model="album" @upload="webdavUpload"
@close="dialog.share = false"></p-share-dialog>
<p-share-upload-dialog :show="dialog.upload" :items="{albums: album.getId()}" :model="album"
@cancel="dialog.upload = false"
<p-share-upload-dialog :show="dialog.upload" :items="{albums: album.getId()}" :model="album" @cancel="dialog.upload = false"
@confirm="dialog.upload = false"></p-share-upload-dialog>
<p-album-edit-dialog :show="dialog.edit" :album="album" @close="dialog.edit = false"></p-album-edit-dialog>
</v-form>
@ -80,40 +77,34 @@
import Event from "pubsub-js";
import Notify from "common/notify";
import download from "common/download";
import {T} from "common/vm";
import { T } from "common/vm";
export default {
name: 'PAlbumToolbar',
props: {
album: {
type: Object,
default: () => {
},
default: () => {},
},
filter: {
type: Object,
default: () => {
},
default: () => {},
},
updateFilter: {
type: Function,
default: () => {
},
default: () => {},
},
updateQuery: {
type: Function,
default: () => {
},
default: () => {},
},
settings: {
type: Object,
default: () => {
},
default: () => {},
},
refresh: {
type: Function,
default: () => {
},
default: () => {},
},
},
data() {
@ -134,8 +125,8 @@ export default {
experimental: this.$config.get("experimental"),
isFullScreen: !!document.fullscreenElement,
categories: this.$config.albumCategories(),
collectionTitle: this.$route.meta?.collectionTitle ? this.$route.meta.collectionTitle : this.$gettext("Albums"),
collectionRoute: this.$route.meta?.collectionRoute ? this.$route.meta.collectionRoute : "albums",
collName: this.$route.meta && this.$route.meta.collName ? this.$route.meta.collName : this.$gettext("Albums"),
collRoute: this.$route.meta && this.$route.meta.collRoute ? this.$route.meta.collRoute : "albums",
navIcon: this.$rtl ? 'navigate_before' : 'navigate_next',
searchExpanded: false,
options: {
@ -176,12 +167,7 @@ export default {
this.dialog.upload = true;
},
showUpload() {
// Pre-select manually managed albums in upload dialog.
if(this.album.Type === "album") {
Event.publish("dialog.upload", {albums: [this.album]});
} else {
Event.publish("dialog.upload", {albums: []});
}
Event.publish("dialog.upload");
},
expand() {
this.searchExpanded = !this.searchExpanded;

View file

@ -33,7 +33,6 @@ import PPhotoToolbar from "component/photo/toolbar.vue";
import PPhotoCards from "component/photo/cards.vue";
import PPhotoMosaic from "component/photo/mosaic.vue";
import PPhotoList from "component/photo/list.vue";
import PPhotoPreview from "component/photo/preview.vue";
import PPhotoClipboard from "component/photo/clipboard.vue";
import PAlbumClipboard from "component/album/clipboard.vue";
import PAlbumToolbar from "component/album/toolbar.vue";
@ -60,7 +59,6 @@ components.install = (Vue) => {
Vue.component("PPhotoCards", PPhotoCards);
Vue.component("PPhotoMosaic", PPhotoMosaic);
Vue.component("PPhotoList", PPhotoList);
Vue.component("PPhotoPreview", PPhotoPreview);
Vue.component("PPhotoClipboard", PPhotoClipboard);
Vue.component("PAlbumClipboard", PAlbumClipboard);
Vue.component("PAlbumToolbar", PAlbumToolbar);

View file

@ -65,7 +65,7 @@
</v-list>
</v-toolbar>
<v-list class="pt-3 p-flex-menu navigation-menu">
<v-list class="pt-3 p-flex-menu">
<v-list-tile v-if="isMini && !isRestricted" class="nav-expand" @click.stop="toggleIsMini()">
<v-list-tile-action :title="$gettext('Expand')">
<v-icon v-if="!rtl">chevron_right</v-icon>
@ -165,8 +165,6 @@
<v-list-tile-content>
<v-list-tile-title :class="`p-flex-menuitem menu-item ${rtl ? '--rtl' : ''}`">
<translate>Archive</translate>
<span v-show="config.count.archived > 0"
:class="`nav-count ${rtl ? '--rtl' : ''}`">{{ config.count.archived | abbreviateCount }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
@ -524,18 +522,6 @@
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile v-show="featMembership" :to="{ name: 'upgrade' }" class="nav-membership" @click.stop="">
<v-list-tile-action :title="$gettext('Support Our Mission')">
<v-icon>diamond</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
<translate key="Support Our Mission">Support Our Mission</translate>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list>
<v-list class="p-user-box">
@ -553,7 +539,7 @@
</v-list-tile-content>
</v-list-tile>
<v-list-tile v-show="auth && !isPublic" class="p-profile" @click.stop="onAccountSettings">
<v-list-tile v-show="auth && !isPublic && $config.feature('account')" class="p-profile" @click.stop="onAccount">
<v-list-tile-avatar size="36">
<img :src="userAvatarURL" :alt="accountInfo" :title="accountInfo">
</v-list-tile-avatar>
@ -678,7 +664,7 @@
<span v-else>{{ config.legalInfo }}</span>
</div>
<p-reload-dialog :show="reload.dialog" @close="reload.dialog = false"></p-reload-dialog>
<p-upload-dialog :show="upload.dialog" :data="upload.data" @cancel="upload.dialog = false"
<p-upload-dialog :show="upload.dialog" @cancel="upload.dialog = false"
@confirm="upload.dialog = false"></p-upload-dialog>
<p-photo-edit-dialog :show="edit.dialog" :selection="edit.selection" :index="edit.index" :album="edit.album"
@close="edit.dialog = false"></p-photo-edit-dialog>
@ -687,7 +673,6 @@
<script>
import Event from "pubsub-js";
import Album from "model/album";
export default {
name: "PNavigation",
@ -717,7 +702,6 @@ export default {
const isReadOnly = this.$config.get("readonly");
const isRestricted = this.$config.deny("photos", "access_library");
const isSuperAdmin = this.$session.isSuperAdmin();
const tier = this.$config.getTier();
return {
canSearchPlaces: this.$config.allow("places", "search"),
@ -731,8 +715,7 @@ export default {
appIcon: this.$config.getIcon(),
indexing: false,
drawer: null,
featUpgrade: tier < 6 && isSuperAdmin && !isPublic && !isDemo,
featMembership: tier < 3 && isSuperAdmin && !isPublic && !isDemo,
featUpgrade: this.$config.getTier() < 8 && isSuperAdmin && !isPublic && !isDemo,
isRestricted: isRestricted,
isMini: localStorage.getItem('last_navigation_mode') !== 'false' || isRestricted,
isDemo: isDemo,
@ -751,7 +734,6 @@ export default {
},
upload: {
dialog: false,
data: {},
},
edit: {
dialog: false,
@ -795,14 +777,7 @@ export default {
this.subscriptions.push(Event.subscribe('index', this.onIndex));
this.subscriptions.push(Event.subscribe('import', this.onIndex));
this.subscriptions.push(Event.subscribe("dialog.reload", () => this.reload.dialog = true));
this.subscriptions.push(Event.subscribe("dialog.upload", (ev, data) => {
if(data) {
this.upload.data = data;
} else {
this.upload.data = {};
}
this.upload.dialog = true;
}));
this.subscriptions.push(Event.subscribe("dialog.upload", () => this.upload.dialog = true));
this.subscriptions.push(Event.subscribe("dialog.edit", (ev, data) => {
if (!this.edit.dialog) {
this.edit.dialog = true;
@ -832,18 +807,7 @@ export default {
},
openUpload() {
if (this.auth && !this.isReadOnly && this.$config.feature('upload')) {
if (this.$route.name === 'album' && this.$route.params?.album) {
return new Album().find(this.$route.params?.album).then(m => {
this.upload.dialog = true;
this.upload.data = {albums: [m]};
}).catch(() => {
this.upload.dialog = true;
this.upload.data = {albums: []};
});
} else {
this.upload.dialog = true;
this.upload.data = {albums: []};
}
this.upload.dialog = true;
} else {
this.goHome();
}
@ -863,12 +827,8 @@ export default {
this.isMini = !this.isMini;
localStorage.setItem('last_navigation_mode', `${this.isMini}`);
},
onAccountSettings: function () {
if (this.$config.feature('account')) {
this.$router.push({name: "settings_account"});
} else {
this.$router.push({name: "settings"});
}
onAccount: function () {
this.$router.push({name: "settings_account"});
},
onInfo() {
if (this.isSponsor && this.config.legalUrl) {

View file

@ -31,7 +31,7 @@
ref="items"
:key="photo.ID"
:data-index="index"
class="flex xs12 sm6 md4 lg3 xlg2 ul1 d-flex"
class="flex xs12 sm6 md4 lg3 xlg2 xxxl1 d-flex"
>
<div v-if="index < firstVisibleElementIndex || index > lastVisibileElementIndex"
:data-uid="photo.UID"
@ -92,7 +92,7 @@
>
<v-layout v-if="photo.Type === 'live' || photo.Type === 'animated'" class="live-player">
<video :id="'live-player-' + photo.ID" :key="photo.ID" width="500" height="500" preload="none"
loop muted playsinline>
loop muted playsinline>
<source :src="photo.videoUrl()">
</video>
</v-layout>
@ -203,11 +203,6 @@
<i>movie</i>
{{ photo.getVideoInfo() }}
</button>
<button v-else-if="photo.Type === 'live'" :title="$gettext('Live')"
@click.exact="openPhoto(index)">
<i>play_circle</i>
{{ photo.getVideoInfo() }}
</button>
<button v-else-if="photo.Type === 'animated'" :title="$gettext('Animated')+' GIF'"
@click.exact="openPhoto(index)">
<i>gif_box</i>
@ -223,11 +218,6 @@
<i>photo_camera</i>
{{ photo.getPhotoInfo() }}
</button>
<button v-if="photo.LensID > 1 || photo.FocalLength" :title="$gettext('Lens')" class="action-lens-edit"
:data-uid="photo.UID" @click.exact="editPhoto(index)">
<i>camera</i>
{{ photo.getLensInfo() }}
</button>
<template v-if="filter.order === 'name' && $config.feature('download')">
<br>
<button :title="$gettext('Name')"

View file

@ -163,10 +163,6 @@ import Photo from "model/photo";
export default {
name: 'PPhotoClipboard',
props: {
context: {
type: String,
default: 'photos',
},
selection: {
type: Array,
default: () => [],
@ -179,6 +175,10 @@ export default {
type: Object,
default: () => {},
},
context: {
type: String,
default: '',
},
},
data() {
const features = this.$config.settings().features;

View file

@ -1,6 +1,6 @@
<template>
<v-container grid-list-xs fluid class="pa-2 p-photos p-photo-mosaic">
<div v-if="photos.length === 0" class="pa-0">
<template v-if="photos.length === 0">
<v-alert
:value="true"
color="secondary-dark"
@ -24,7 +24,7 @@
</template>
</p>
</v-alert>
</div>
</template>
<v-layout row wrap class="search-results photo-results mosaic-view" :class="{'select-results': selectMode}">
<div
v-for="(photo, index) in photos"
@ -60,7 +60,7 @@
@mouseleave="pauseLive(photo)">
<v-layout v-if="photo.Type === 'live' || photo.Type === 'animated'" class="live-player">
<video :id="'live-player-' + photo.ID" :key="photo.ID" width="224" height="224" preload="none"
loop muted playsinline>
loop muted playsinline>
<source :src="photo.videoUrl()">
</video>
</v-layout>

View file

@ -1,49 +0,0 @@
<template>
<div class="p-photo-preview pa-0 ma-0 elevation-0 v-card v-sheet v-sheet--tile no-transition" :title="title">
<div class="v-responsive v-image card darken-1 elevation-0 clickable" @click.prevent.stop="openPhoto">
<div class="v-responsive__sizer" style="padding-bottom: 100%;"></div>
<div class="v-image__image v-image__image--cover" :style="cover"></div>
<div class="v-responsive__content"></div>
</div>
</div>
</template>
<script>
import Photo from "model/photo";
import Thumb from "model/thumb";
export default {
name: 'PPhotoPreview',
props: {
model: {
type: Object,
default: () => new Photo(false),
},
},
data() {
return {
url: this.model.thumbnailUrl('tile_500'),
title: this.model.Title ? this.model.Title : '',
};
},
computed: {
cover() {
return `background-image: url('${this.url}'); background-position: center center;`;
},
},
watch: {
model() {
this.url = this.model.thumbnailUrl('tile_500');
this.title = this.model.Title ? this.model.Title : '';
},
},
methods: {
openPhoto() {
if (!this.$viewer || !this.model) {
return;
}
this.$viewer.show(Thumb.fromFiles([this.model]), 0);
},
},
};
</script>

View file

@ -1,68 +1,46 @@
<template>
<v-form ref="form" lazy-validation
dense autocomplete="off" class="p-photo-toolbar" accept-charset="UTF-8"
:class="{'embedded': embedded}"
@submit.prevent="updateQuery()">
<v-toolbar flat :dense="$vuetify.breakpoint.smAndDown" :height="embedded ? 45 : undefined"
class="page-toolbar" color="secondary">
<template v-if="!embedded">
<v-text-field :value="filter.q"
class="input-search background-inherit elevation-0"
solo hide-details clearable overflow single-line validate-on-blur
autocorrect="off"
autocapitalize="none"
browser-autocomplete="off"
:label="$gettext('Search')"
prepend-inner-icon="search"
color="secondary-dark"
@change="(v) => {updateFilter({'q': v})}"
@keyup.enter.native="(e) => updateQuery({'q': e.target.value})"
@click:clear="() => {updateQuery({'q': ''})}"
></v-text-field>
<v-toolbar flat :dense="$vuetify.breakpoint.smAndDown" class="page-toolbar" color="secondary">
<v-text-field :value="filter.q"
class="input-search background-inherit elevation-0"
solo hide-details clearable overflow single-line
validate-on-blur
autocorrect="off"
autocapitalize="none"
browser-autocomplete="off"
:label="$gettext('Search')"
prepend-inner-icon="search"
color="secondary-dark"
@change="(v) => {updateFilter({'q': v})}"
@keyup.enter.native="(e) => updateQuery({'q': e.target.value})"
@click:clear="() => {updateQuery({'q': ''})}"
></v-text-field>
<v-btn v-if="filter.latlng" icon :title="$gettext('Show more')" class="action-clear-location" @click.stop="clearLocation()">
<v-icon>location_off</v-icon>
</v-btn>
<v-btn icon class="hidden-xs-only action-reload" :title="$gettext('Reload')" @click.stop="refresh()">
<v-icon>refresh</v-icon>
</v-btn>
<v-btn icon class="hidden-xs-only action-reload" :title="$gettext('Reload')" @click.stop="refresh()">
<v-icon>refresh</v-icon>
</v-btn>
<v-btn v-if="settings.view === 'cards'" icon :title="$gettext('Toggle View')" @click.stop="setView('list')">
<v-icon>view_list</v-icon>
</v-btn>
<v-btn v-else-if="settings.view === 'list'" icon :title="$gettext('Toggle View')" @click.stop="setView('mosaic')">
<v-icon>view_comfy</v-icon>
</v-btn>
<v-btn v-else icon :title="$gettext('Toggle View')" @click.stop="setView('cards')">
<v-icon>view_column</v-icon>
</v-btn>
<v-btn v-if="settings.view === 'cards'" icon :title="$gettext('Toggle View')" @click.stop="setView('list')">
<v-icon>view_list</v-icon>
</v-btn>
<v-btn v-else-if="settings.view === 'list'" icon :title="$gettext('Toggle View')"
@click.stop="setView('mosaic')">
<v-icon>view_comfy</v-icon>
</v-btn>
<v-btn v-else icon :title="$gettext('Toggle View')" @click.stop="setView('cards')">
<v-icon>view_column</v-icon>
</v-btn>
<v-btn v-if="!$config.values.readonly && $config.feature('upload')" icon class="hidden-sm-and-down action-upload"
:title="$gettext('Upload')" @click.stop="showUpload()">
<v-icon>cloud_upload</v-icon>
</v-btn>
<v-btn v-if="canDelete && context === 'archive' && config.count.archived > 0" icon
class="hidden-sm-and-down action-delete-all"
:title="$gettext('Delete All')" @click.stop="deleteAll()">
<v-icon>delete_sweep</v-icon>
</v-btn>
<v-btn v-else-if="canUpload" icon class="hidden-sm-and-down action-upload"
:title="$gettext('Upload')" @click.stop="showUpload()">
<v-icon>cloud_upload</v-icon>
</v-btn>
<v-btn icon class="p-expand-search" :title="$gettext('Expand Search')"
@click.stop="searchExpanded = !searchExpanded">
<v-icon>{{ searchExpanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}</v-icon>
</v-btn>
</template>
<template v-else>
<v-spacer></v-spacer>
<v-btn v-if="canAccessLibrary" icon :title="$gettext('Browse')" class="action-browse" @click.stop="onBrowse">
<v-icon size="20">tab</v-icon>
</v-btn>
<v-btn v-if="onClose !== undefined" icon :title="$gettext('Close')" class="action-close" @click.stop="onClose">
<v-icon>close</v-icon>
</v-btn>
</template>
<v-btn icon class="p-expand-search" :title="$gettext('Expand Search')"
@click.stop="searchExpanded = !searchExpanded">
<v-icon>{{ searchExpanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}</v-icon>
</v-btn>
</v-toolbar>
<v-card v-show="searchExpanded"
@ -188,77 +166,41 @@
</v-layout>
</v-card-text>
</v-card>
<p-photo-delete-dialog
:show="dialog.delete"
:text="$gettext('Are you sure you want to delete all archived pictures?')"
:action="$gettext('Delete All')"
@cancel="dialog.delete = false" @confirm="batchDelete">
</p-photo-delete-dialog>
</v-form>
</template>
<script>
import Event from "pubsub-js";
import * as options from "options/options";
import Api from "common/api";
import Notify from "common/notify";
export default {
name: 'PPhotoToolbar',
props: {
context: {
type: String,
default: 'photos',
},
filter: {
type: Object,
default: () => {
},
},
staticFilter: {
type: Object,
default: () => {
},
default: () => {},
},
updateFilter: {
type: Function,
default: () => {
},
default: () => {},
},
updateQuery: {
type: Function,
default: () => {
},
default: () => {},
},
settings: {
type: Object,
default: () => {
},
default: () => {},
},
refresh: {
type: Function,
default: () => {
},
},
onClose: {
type: Function,
default: undefined,
},
embedded: {
type: Boolean,
default: false
default: () => {},
},
},
data() {
const features = this.$config.settings().features;
const readonly = this.$config.get("readonly");
return {
experimental: this.$config.get("experimental"),
isFullScreen: !!document.fullscreenElement,
config: this.$config.values,
readonly: readonly,
canUpload: !readonly && !this.embedded && this.$config.allow("files", "upload") && features.upload,
canDelete: !readonly && !this.embedded && this.$config.allow("photos", "delete") && features.delete,
canAccessLibrary: this.$config.allow("photos", "access_library"),
searchExpanded: false,
all: {
countries: [{ID: "", Name: this.$gettext("All Countries")}],
@ -287,9 +229,6 @@ export default {
{value: 'similar', text: this.$gettext('Visual Similarity')},
],
},
dialog: {
delete: false,
},
};
},
computed: {
@ -323,37 +262,7 @@ export default {
},
showUpload() {
Event.publish("dialog.upload");
},
deleteAll() {
if (!this.canDelete) {
return;
}
this.dialog.delete = true;
},
clearLocation() {
this.$router.push({ name: "browse" });
},
onBrowse() {
const route = {name: 'places_browse', query: this.staticFilter};
const routeUrl = this.$router.resolve(route).href;
if (routeUrl) {
window.open(routeUrl, '_blank');
}
},
batchDelete() {
if (!this.canDelete) {
return;
}
this.dialog.delete = false;
Api.post("batch/photos/delete", {"all": true}).then(() => this.onDeleted());
},
onDeleted() {
Notify.success(this.$gettext("Permanently deleted"));
this.$clipboard.clear();
},
}
},
};
</script>

View file

@ -1,102 +0,0 @@
export default class MapStyleControl {
constructor(styles, defaultStyle, setStyle) {
this.styles = styles || MapStyleControl.DEFAULT_STYLES;
this.defaultStyle = defaultStyle || MapStyleControl.DEFAULT_STYLE;
this.setStyle = setStyle;
this.onDocumentClick = this.onDocumentClick.bind(this);
}
getDefaultPosition() {
return "top-right";
}
onAdd(map) {
this.map = map;
this.controlContainer = document.createElement("div");
this.controlContainer.classList.add("maplibregl-ctrl");
this.controlContainer.classList.add("maplibregl-ctrl-group");
this.mapStyleContainer = document.createElement("div");
this.styleButton = document.createElement("button");
this.styleButton.type = "button";
this.mapStyleContainer.classList.add("maplibregl-style-list");
for (const style of this.styles) {
const styleElement = document.createElement("button");
styleElement.type = "button";
styleElement.innerText = style.title;
styleElement.classList.add(style.style, "_");
styleElement.dataset.style = JSON.stringify(style.style);
styleElement.addEventListener("click", (event) => {
const srcElement = event.srcElement;
if (srcElement.classList.contains("active")) {
this.mapStyleContainer.style.display = "none";
this.styleButton.style.display = "block";
return;
}
// Set new map style.
if (typeof this.setStyle === "function") {
this.setStyle(JSON.parse(srcElement.dataset.style));
}
/*
this.mapStyleContainer.style.display = "none";
this.styleButton.style.display = "block";
const elms = this.mapStyleContainer.getElementsByClassName("active");
while (elms[0]) {
elms[0].classList.remove("active");
}
srcElement.classList.add("active");
*/
});
if (style.style === this.defaultStyle) {
styleElement.classList.add("active");
}
this.mapStyleContainer.appendChild(styleElement);
}
this.styleButton.classList.add("maplibregl-ctrl-icon");
this.styleButton.classList.add("maplibregl-style-switcher");
this.styleButton.addEventListener("click", () => {
this.styleButton.style.display = "none";
this.mapStyleContainer.style.display = "block";
});
document.addEventListener("click", this.onDocumentClick);
this.controlContainer.appendChild(this.styleButton);
this.controlContainer.appendChild(this.mapStyleContainer);
return this.controlContainer;
}
onRemove() {
if (
!this.controlContainer ||
!this.controlContainer.parentNode ||
!this.map ||
!this.styleButton
) {
return;
}
this.styleButton.removeEventListener("click", this.onDocumentClick);
this.controlContainer.parentNode.removeChild(this.controlContainer);
document.removeEventListener("click", this.onDocumentClick);
this.map = undefined;
}
onDocumentClick(event) {
if (
this.controlContainer &&
!this.controlContainer.contains(event.target) &&
this.mapStyleContainer &&
this.styleButton
) {
this.mapStyleContainer.style.display = "none";
this.styleButton.style.display = "block";
}
}
}
MapStyleControl.DEFAULT_STYLE = "default";
MapStyleControl.DEFAULT_STYLES = [
{
title: "Default",
style: "default",
},
];

View file

@ -1,8 +1,8 @@
<template>
<div class="video-wrapper" :style="style">
<video :key="source" ref="video" class="video-player" :height="height" :width="width" :autoplay="autoplay"
:style="style" :poster="poster" :loop="loop" preload="auto" controls playsinline
@click.stop @keydown.esc.stop.prevent="$emit('close')">
:style="style" :poster="poster" :loop="loop" preload="auto" controls playsinline @click.stop
@keydown.esc.stop.prevent="$emit('close')">
<source :src="source">
</video>
</div>

View file

@ -38,7 +38,7 @@ Additional information can be found in our Developer Guide:
@import url("viewer.css");
@import url("tables.css");
@import url("results.css");
@import url("places.css");
@import url("maps.css");
@import url("labels.css");
@import url("files.css");
@import url("pages.css");
@ -68,7 +68,7 @@ main {
z-index: 1;
}
/* Loading Animations */
/* Page Overlay */
#busy-overlay {
display: none;

View file

@ -1,49 +1,32 @@
/* Layout Breakpoints */
@media (min-width: 1750px) {
/* Extra Large (XL) */
.flex.xlg2 {
flex-basis: 16.666666666666664%;
flex-grow: 0;
max-width: 16.666666666666664%;
}
.flex.xlg10 {
flex-basis: 83.333333333333332%;
flex-grow: 0;
max-width: 83.333333333333332%;
}
}
@media (min-width: 2400px) {
/* Extra Extra Large (XXL) */
.flex.xxl1 {
flex-basis: 8.333333333333332%;
flex-grow: 0;
max-width: 8.333333333333332%;
}
.flex.xxl11 {
flex-basis: 91.66666666666667%;
flex-grow: 0;
max-width: 91.66666666666667%;
}
}
@media (min-width: 2800px) {
/* Ultra Large (UL) */
.flex.ul1 {
@media (min-width: 2550px) {
.flex.xxxl1 {
flex-basis: 8.333333333333332%;
flex-grow: 0;
max-width: 8.333333333333332%;
}
.flex.ul11 {
flex-basis: 91.66666666666667%;
flex-grow: 0;
max-width: 91.66666666666667%;
}
}
/* Layout Widths */
.width-sm, .width-md, .width-lg {
margin-left: auto;
margin-right: auto;
@ -112,13 +95,6 @@
border-radius: 6px;
}
.ra-2,
.ra-2 > a,
.rounded-2,
.rounded-2 > a {
border-radius: 2px !important;
}
.ra-3,
.ra-3 > a,
.rounded-3,
@ -161,13 +137,6 @@
border-radius: 10px !important;
}
.ra-24,
.ra-24 > a,
.rounded-24,
.rounded-24 > a {
border-radius: 24px !important;
}
#photoprism div.v-dialog.v-dialog--fullscreen > div.v-card {
border-radius: 0;
}
@ -181,30 +150,6 @@
border-radius: 5px 5px 0 0;
}
/* Responsive Layout Styles */
@media only screen and (min-width: 960px) {
.pa-1-md-and-up {
padding: 4px!important;
}
.pa-2-md-and-up {
padding: 8px!important;
}
.pa-3-md-and-up {
padding: 16px!important;
}
.pa-3-md-and-up {
padding: 24px!important;
}
.ra-4-table-md-and-up table.v-table {
border-radius: 4px !important;
}
}
/* Page Footer */
footer {

View file

@ -1,4 +1,3 @@
/* Event Logs */
#photoprism .p-logs {
color: white;

26
frontend/src/css/maps.css Normal file
View file

@ -0,0 +1,26 @@
#photoprism .map-control {
position: absolute;
background: transparent;
bottom: 35px;
right: 30px;
z-index: 2;
}
#photoprism.is-rtl .map-control {
right: auto;
left: 30px;
}
#photoprism #map .marker {
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12) !important;
background-color: rgba(0, 0, 0, 0.3);
border-color: #fff;
color: rgba(0, 0, 0, 0.87);
display: block;
border-radius: 50%;
cursor: pointer;
}
#photoprism .maplibregl-ctrl-attrib-inner a {
color: #333 !important;
}

View file

@ -1,5 +1,3 @@
/* App Navigation */
#p-navigation {
z-index: 10;
}
@ -122,12 +120,6 @@ nav .v-list__tile__title.title {
overflow: hidden;
}
#photoprism .p-photo-toolbar.embedded {
position: sticky;
top: 0;
z-index: 1;
}
.p-user-box {
width: 100%;
padding: 0;
@ -315,4 +307,4 @@ nav .v-list__tile__title.title {
#photoprism #mobile-menu .menu-action:hover a i{
fill: #ffffff;
color: #ffffff;
}
}

View file

@ -1,148 +0,0 @@
/* Maps & Places */
#photoprism .map-control {
position: absolute;
background: transparent;
top: 10px;
left: 10px;
z-index: 2;
}
#photoprism.is-rtl .map-control {
left: auto;
right: 10px;
}
#photoprism .map-control .map-control-search {
overflow: hidden;
border-radius: 24px;
}
#photoprism #map .marker {
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12) !important;
background-color: rgba(23, 23, 23, 0.23);
color: rgba(0, 0, 0, 0.87);
display: block;
border-radius: 50%;
cursor: pointer;
border: 1px solid #ffffff99;
}
#photoprism #map .cluster-marker {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 1px;
overflow: hidden;
/* background-color: #ffffff99; */
width: 100%;
height: 100%
}
#photoprism #map .counter-bubble {
border-radius: 100%;
position: absolute;
width: 24px;
height: 24px;
font-size: 12px;
display: flex;
color: #ffffff;
top: -5px;
right: -5px;
align-items: center;
justify-content: center;
box-shadow: 0px 3px 1px -2px rgb(0 0 0 / 20%), 0px 2px 2px 0px rgb(0 0 0 / 14%), 0px 1px 5px 0px rgb(0 0 0 / 12%);
}
#photoprism #map .cluster-marker > div {
width: 100%;
height: 100%;
background-size: cover;
}
#photoprism .maplibregl-ctrl-attrib-inner a {
color: #333 !important;
}
#photoprism .maplibregl-style-list {
display: none;
}
#photoprism .maplibregl-ctrl-group .maplibregl-style-list button {
background: none;
border: none;
cursor: pointer;
display: block;
font-size: 14px;
padding: 8px 8px 6px;
text-align: right;
width: 100%;
height: auto;
color: #000000;
}
#photoprism .maplibregl-style-list button.active {
font-weight: bold;
}
#photoprism .maplibregl-style-list button:hover {
background-color: rgba(0, 0, 0, 0.05);
}
#photoprism .maplibregl-style-list button + button {
border-top: 1px solid #ddd;
}
#photoprism .maplibregl-style-switcher {
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNTQuODQ5cHgiIGhlaWdodD0iNTQuODQ5cHgiIHZpZXdCb3g9IjAgMCA1NC44NDkgNTQuODQ5IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1NC44NDkgNTQuODQ5OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PGc+PGc+PGc+PHBhdGggZD0iTTU0LjQ5NywzOS42MTRsLTEwLjM2My00LjQ5bC0xNC45MTcsNS45NjhjLTAuNTM3LDAuMjE0LTEuMTY1LDAuMzE5LTEuNzkzLDAuMzE5Yy0wLjYyNywwLTEuMjU0LTAuMTA0LTEuNzktMC4zMThsLTE0LjkyMS01Ljk2OEwwLjM1MSwzOS42MTRjLTAuNDcyLDAuMjAzLTAuNDY3LDAuNTI0LDAuMDEsMC43MTZMMjYuNTYsNTAuODFjMC40NzcsMC4xOTEsMS4yNTEsMC4xOTEsMS43MjksMEw1NC40ODgsNDAuMzNDNTQuOTY0LDQwLjEzOSw1NC45NjksMzkuODE3LDU0LjQ5NywzOS42MTR6Ii8+PHBhdGggZD0iTTU0LjQ5NywyNy41MTJsLTEwLjM2NC00LjQ5MWwtMTQuOTE2LDUuOTY2Yy0wLjUzNiwwLjIxNS0xLjE2NSwwLjMyMS0xLjc5MiwwLjMyMWMtMC42MjgsMC0xLjI1Ni0wLjEwNi0xLjc5My0wLjMyMWwtMTQuOTE4LTUuOTY2TDAuMzUxLDI3LjUxMmMtMC40NzIsMC4yMDMtMC40NjcsMC41MjMsMC4wMSwwLjcxNkwyNi41NiwzOC43MDZjMC40NzcsMC4xOSwxLjI1MSwwLjE5LDEuNzI5LDBsMjYuMTk5LTEwLjQ3OUM1NC45NjQsMjguMDM2LDU0Ljk2OSwyNy43MTYsNTQuNDk3LDI3LjUxMnoiLz48cGF0aCBkPSJNMC4zNjEsMTYuMTI1bDEzLjY2Miw1LjQ2NWwxMi41MzcsNS4wMTVjMC40NzcsMC4xOTEsMS4yNTEsMC4xOTEsMS43MjksMGwxMi41NDEtNS4wMTZsMTMuNjU4LTUuNDYzYzAuNDc3LTAuMTkxLDAuNDgtMC41MTEsMC4wMS0wLjcxNkwyOC4yNzcsNC4wNDhjLTAuNDcxLTAuMjA0LTEuMjM2LTAuMjA0LTEuNzA4LDBMMC4zNTEsMTUuNDFDLTAuMTIxLDE1LjYxNC0wLjExNiwxNS45MzUsMC4zNjEsMTYuMTI1eiIvPjwvZz48L2c+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjwvc3ZnPg==);
background-position: center;
background-repeat: no-repeat;
background-size: 70%;
}
#photoprism .cluster-control {
background: #2f3031;
position: absolute;
top: auto;
z-index: 2;
bottom: 0;
margin: 0;
padding: 0;
left: 4px;
right: 4px;
border-top-left-radius: 16px;
border-top-right-radius: 16px;
max-height: 90vh;
overflow: hidden;
box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;
transition: all 650ms cubic-bezier(0.32, 1, 0.23, 1) 0ms;
}
#photoprism .cluster-control-container {
min-height: 40vh;
}
#photoprism .cluster-control .p-photos {
position: absolute;
top: 45px;
bottom: 0;
left: 0;
right: 0;
overflow: auto;
}
@media only screen and (min-height: 1500px) {
#photoprism .cluster-control-container {
min-height: 45vh;
}
}
@media only screen and (max-width: 599px) {
#photoprism .cluster-control {
left: 0;
right: 0;
}
#photoprism .cluster-control-container {
min-height: 66vh;
}
}

View file

@ -198,128 +198,112 @@ body.chrome #photoprism .search-results .result {
}
#photoprism .list-view {
box-shadow: 0 0 0 0 rgba(0, 0, 0, .2), 0 0 0 0 rgba(0, 0, 0, .14), 0 0 0 0 rgba(0, 0, 0, .12) !important;
box-shadow: 0 0 0 0 rgba(0,0,0,.2),0 0 0 0 rgba(0,0,0,.14),0 0 0 0 rgba(0,0,0,.12) !important;
}
#photoprism .cards-view .result,
#photoprism .mosaic-view .result {
transition: none;
box-shadow: none !important;
}
#photoprism .cards-view .result {
margin: 2px !important;
}
#photoprism .mosaic-view .result {
margin: 0 !important;
#photoprism .mosaic-view .result
{
-webkit-transition-duration: 15ms !important;
-moz-transition-duration: 15ms !important;
-o-transition-duration: 15ms !important;
transition-duration: 15ms !important;
margin: 4px !important;
box-shadow: 0 0 0 0 rgba(0, 0, 0, .2), 0 0 0 0 rgba(0, 0, 0, .14), 0 0 0 0 rgba(0, 0, 0, .12) !important;
border-radius: 6px;
}
#photoprism .search-results .image-container {
aspect-ratio: 1;
contain: layout paint style size;
aspect-ratio: 1;
contain: layout paint style size;
}
#photoprism .cards-view .result .image,
#photoprism .list-view .result .image,
#photoprism .mosaic-view .result.image {
position: relative;
user-select: none;
aspect-ratio: 1;
background-position: center center;
background-size: cover;
background-repeat: no-repeat;
overflow: hidden;
#photoprism .mosaic-view .result.image
{
position: relative;
user-select: none;
aspect-ratio: 1;
background-position: center center;
background-size: cover;
background-repeat: no-repeat;
overflow: hidden;
}
#photoprism .cards-view .result .image button,
#photoprism .list-view .result .image button,
#photoprism .mosaic-view .result.image button {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
width: 28px;
height: 28px;
padding: 0;
transition: background-color .3s cubic-bezier(.25, .8, .5, 1);
position: absolute;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
width: 28px;
height: 28px;
padding: 0;
transition: background-color .3s cubic-bezier(.25,.8,.5,1);
}
#photoprism .cards-view .result .image button {
width: 36px;
height: 36px;
width: 36px;
height: 36px;
}
#photoprism .list-view .result .image button {
width: 44px;
height: 44px;
width: 44px;
height: 44px;
}
#photoprism .cards-view .result .image button:hover,
#photoprism .list-view .result .image button:hover,
#photoprism .mosaic-view .result.image button:hover {
background-color: rgba(255, 255, 255, 0.12);
background-color: rgba(255, 255, 255, 0.12);
}
#photoprism .mosaic-view .result.image i,
#photoprism .list-view .result .image i,
#photoprism .cards-view .result .image i {
color: #e2e2e2;
font-size: 24px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
font-family: Material Icons;
font-style: normal;
font-weight: 400;
text-transform: none;
line-height: 1;
white-space: nowrap;
color: #e2e2e2;
font-size: 24px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
font-family: Material Icons;
font-style: normal;
font-weight: 400;
text-transform: none;
line-height: 1;
white-space: nowrap;
}
#photoprism .cards-view .result .caption {
hyphens: auto;
word-break: break-word;
hyphens: auto;
word-break: break-word;
}
#photoprism .cards-view .result .card-details i {
font-size: 14px;
display: inline-block;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
font-family: Material Icons;
font-style: normal;
font-weight: 400;
text-transform: none;
line-height: 1;
vertical-align: text-bottom;
white-space: nowrap;
font-size: 14px;
display: inline-block;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
font-family: Material Icons;
font-style: normal;
font-weight: 400;
text-transform: none;
line-height: 1;
vertical-align: text-bottom;
white-space: nowrap;
}
#photoprism .cards-view .result.placeholder .card-details i {
width: 14px;
width: 14px;
}
#photoprism .cards-view .result.is-selected {
margin: 7px !important;
}
#photoprism .cards-view .result.is-selected,
#photoprism .mosaic-view .result.is-selected {
margin: 9px !important;
}
#photoprism .list-view .result.is-selected .image {
margin: 2px !important;
}
#photoprism .cards-view .result.is-selected .card-details {
padding-left: 14px !important;
padding-right: 14px !important;
}
#photoprism .cards-view .result.is-selected .card-details .caption {
font-size: 0.85em !important;
text-overflow: ellipsis;
margin: 1px !important;
box-shadow: 0 6px 6px -3px rgba(0, 0, 0, .2), 0 10px 14px 1px rgba(0, 0, 0, .14), 0 4px 18px 3px rgba(0, 0, 0, .12) !important;
}
#photoprism .cards-view .input-select,
@ -531,27 +515,28 @@ body.chrome #photoprism .search-results .result {
/* old browser support */
@supports not (aspect-ratio: 1) {
/* elements with aspect-ratio 1 and without margin */
#photoprism .search-results .image-container,
#photoprism .cards-view .result .image,
#photoprism .list-view .result .image,
#photoprism .list-view .result.image,
#photoprism .mosaic-view .result.is-selected {
height: 0;
padding-bottom: 100%
}
/* elements with aspect-ratio 1 and without margin */
#photoprism .search-results .image-container,
#photoprism .cards-view .result .image,
#photoprism .list-view .result .image,
#photoprism .list-view .result.image,
#photoprism .mosaic-view .result.is-selected {
height: 0;
padding-bottom: 100%
}
/* elements with aspect-ratio 1 and with margin */
#photoprism .mosaic-view .result {
height: 0;
/*
8px is required because this aspect-ratio-fallback doesn't take
margins into consideration
*/
padding-bottom: calc(100% - 8px)
}
/* elements with aspect-ratio 1 and with margin */
#photoprism .mosaic-view .result
{
height: 0;
/*
8px is required because this aspect-ratio-fallback doesn't take
margins into consideration
*/
padding-bottom: calc(100% - 8px)
}
#photoprism .live-player video {
margin-top: 100%;
}
#photoprism .live-player video {
margin-top: 100%;
}
}

View file

@ -1,18 +1,16 @@
@import url("themes/default.css");
@import url("themes/abyss.css");
@import url("themes/carbon.css");
@import url("themes/chrome.css");
@import url("themes/gemstone.css");
@import url("themes/grayscale.css");
@import url("themes/lavender.css");
@import url("themes/mint.css");
@import url("themes/neon.css");
@import url("themes/nordic.css");
@import url("themes/shadow.css");
@import url("themes/vanta.css");
@import url("themes/yellowstone.css");
@import url("themes/abyss.css");
@import url("themes/vanta.css");
@import url("themes/neon.css");
@import url("themes/gemstone.css");
@import url("themes/carbon.css");
@import url("themes/nordic.css");
@import url("themes/lavender.css");
@import url("themes/default.css");
/* Input Style Overrides */
/* General Styles */
:-webkit-autofill,
:-webkit-autofill:hover,
@ -23,15 +21,90 @@ input:-webkit-autofill:focus {
transition: background-color 9999s !important;
}
body.dark-theme #photoprism .v-tabs .v-badge__badge {
color: #000000;
}
body.dark-theme #photoprism .theme--light.v-table thead th {
color: #ffffff;
}
.card {
background-color: #e0e0e0;
}
body.dark-theme .card {
background-color: #1a1a1a;
}
body.dark-theme #photoprism .v-table .theme--light.v-text-field--solo>.v-input__control>.v-input__slot {
background: transparent;
}
body.dark-theme .theme--light.v-list .v-list__tile--highlighted,
body.dark-theme .theme--light.v-list a:hover {
background: rgba(255,255,255,0.3) !important;
}
body.dark-theme .v-input input::placeholder {
color: rgba(255,255,255,0.5) !important;
}
.v-text-field>.v-input__control>.v-input__slot:after,
.v-text-field>.v-input__control>.v-input__slot:before {
display: none !important;
}
/*
body.dark-theme .v-text-field.v-text-field--enclosed .v-text-field__details {
padding-left: 0;
}*/
.theme--light.v-list .v-list__tile--highlighted,
.theme--light.v-list a:hover {
background: rgba(155,155,155,0.3) !important;
}
body.dark-theme #photoprism .map-control .theme--light.v-input:not(.v-input--is-disabled) i,
body.dark-theme #photoprism .map-control .theme--light.v-input:not(.v-input--is-disabled) input {
color: #333333;
}
body:not(.dark-theme) .theme--dark.v-btn.v-btn--disabled:not(.v-btn--icon):not(.v-btn--flat):not(.v-btn--outline) {
color: rgb(51, 51, 51)!important;
background-color: #d9dadc!important;
}
body.dark-theme #photoprism .theme--light.v-chip,
body.dark-theme #photoprism .v-card .theme--light.v-text-field--box>.v-input__control>.v-input__slot {
background: #00000033;
}
body.dark-theme #photoprism,
body.dark-theme #photoprism .p-page a,
body.dark-theme #photoprism .v-datatable a,
body.dark-theme #photoprism .theme--light.v-expansion-panel .v-expansion-panel__container,
body.dark-theme #photoprism .theme--light.v-tabs__bar .v-tabs__div,
body.dark-theme #photoprism .theme--light {
color: #ffffff;
caret-color: #ffffff;
stroke: #ffffff;
}
body.dark-theme #photoprism .theme--light.v-label {
color: #ffffffaa;
}
body.dark-theme #photoprism .theme--light.v-select .v-select__selections {
color: #ffffffee;
}
#photoprism .v-select.v-text-field--box .v-select__selections {
padding-top: 20px;
}
/* Exceptions */
#photoprism .theme--light.v-text-field--solo.background-inherit>.v-input__control>.v-input__slot {
background: inherit;
box-shadow: none;

View file

@ -33,8 +33,8 @@ body.dark-theme.theme-abyss,
background: #333;
}
#photoprism.theme-abyss .theme--light.v-text-field--solo>.v-input__control>.v-input__slot {
background: rgba(250, 250, 255, 0.1);
#photoprism.theme-abyss .theme--light.v-text-field--solo > .v-input__control > .v-input__slot {
background: #262626;
}
#photoprism.theme-abyss .theme--light.v-input--selection-controls.v-input--is-disabled .v-icon {

View file

@ -1,4 +1,4 @@
/* Carbon Dark Theme */
/* Carbon Theme */
body.dark-theme.theme-carbon,
.theme-carbon .v-content__wrap,

View file

@ -1,146 +0,0 @@
/* Chrome Dark Theme */
body.dark-theme.theme-chrome,
.theme-chrome .v-content__wrap,
.theme-chrome .p-page,
.theme-chrome .form,
.theme-chrome .v-content {
background: #1d1d1d !important;
}
#photoprism.theme-chrome .theme--light.v-small-dialog__content,
#photoprism.theme-chrome .theme--light.v-sheet,
#photoprism.theme-chrome .theme--light.v-card,
.theme-chrome .application.theme--light {
background: #202020;
}
#photoprism.container.theme-chrome {
background-image: linear-gradient(160deg, #808080 0%, #262626 100%);
}
#photoprism.theme-chrome .theme--light .v-table {
background: #262626;
}
body.dark-theme #photoprism.theme-chrome,
body.dark-theme #photoprism.theme-chrome .v-datatable a,
body.dark-theme #photoprism.theme-chrome .theme--light.v-expansion-panel .v-expansion-panel__container,
body.dark-theme #photoprism.theme-chrome .theme--light.v-tabs__bar .v-tabs__div {
color: #ffffff;
caret-color: #ffffff;
stroke: #ffffff;
}
body.dark-theme #photoprism.theme-chrome .p-page a,
body.dark-theme #photoprism.theme-chrome .theme--light,
body.dark-theme #photoprism.theme-chrome #p-navigation .navigation-menu.theme--dark.v-list,
body.dark-theme #photoprism.theme-chrome #p-navigation .navigation-menu .theme--dark.v-icon {
color: #d8d8d8;
caret-color: #d8d8d8;
stroke: #d8d8d8;
}
body.dark-theme #photoprism.theme-chrome .v-input--selection-controls__input .theme--light {
color: #8e8e8e;
caret-color: #8e8e8e;
stroke: #8e8e8e;
}
body.dark-theme #photoprism.theme-chrome #p-navigation .navigation-menu .theme--dark.v-icon {
color: inherit;
}
#photoprism.theme-chrome .theme--light.v-table thead th,
#photoprism.theme-chrome .theme--light.v-table tbody td {
color: #fff;
}
#photoprism.theme-chrome .theme--light.v-table tbody tr:hover {
background: #2b2b2b;
}
#photoprism.theme-chrome .theme--light.v-text-field--solo>.v-input__control>.v-input__slot,
#photoprism.theme-chrome .theme--light.v-text-field--solo>.v-input__control>.v-input__slot {
background: rgba(255, 255, 255, 0.1);
}
#photoprism.theme-chrome .secondary--text {
color: rgba(255, 255, 255, 0.2)!important;
}
body.dark-theme #photoprism.theme-chrome #map .secondary-dark.theme--light,
body.dark-theme #photoprism.theme-chrome #map .secondary-dark--text {
color: #000000!important;
caret-color: #000000!important;
stroke: #000000!important;
}
body.dark-theme #photoprism.theme-chrome .map-control .theme--light {
color: #757575;
caret-color: #757575;
stroke: #757575;
}
body.dark-theme #photoprism.theme-chrome .v-card .theme--light.v-text-field--box>.v-input__control>.v-input__slot {
background: rgba(255, 255, 255, 0.04);
}
#photoprism.theme-chrome .theme--light.v-input--selection-controls.v-input--is-disabled .v-icon {
color: #999 !important;
}
#photoprism.theme-chrome .theme--light.v-input--selection-controls.v-input--is-disabled .v-icon,
#photoprism.theme-chrome .theme--light.v-input--is-disabled .v-label,
#photoprism.theme-chrome .theme--light.v-input--is-disabled input,
#photoprism.theme-chrome .theme--light.v-input--is-disabled textarea {
color: #aaaaaa;
}
#photoprism.theme-chrome .theme--light.v-list {
background: #232425;
}
#photoprism.theme-chrome a.text-link {
color: #ffffff !important;
}
#photoprism.theme-chrome .footer .body-link {
color: #ffffff !important;
}
#photoprism.theme-chrome .theme--light.v-list .v-list__tile__sub-title,
#photoprism.theme-chrome .accent--text {
color: #ffffff !important;
}
#photoprism.theme-chrome .theme--light.v-input:not(.v-input--is-disabled) input,
#photoprism.theme-chrome .theme--light.v-input:not(.v-input--is-disabled) textarea {
color: #fff;
}
#photoprism.theme-chrome .theme--light.v-btn.v-btn--disabled,
#photoprism.theme-chrome .theme--light.v-btn.v-btn--disabled .v-btn__loading,
#photoprism.theme-chrome .theme--light.v-btn.v-btn--disabled .v-icon {
color: #999 !important;
}
#photoprism.theme-chrome .theme--light.v-list .v-list__tile__mask {
color: #cccccc;
background: transparent;
}
#photoprism.theme-chrome .cards-view .card.is-selected .card-details {
color: #fff;
background-color: #5C5C5C;
}
#photoprism.theme-chrome .cards-view .card.is-selected,
#photoprism.theme-chrome .cards-view .card.is-selected .card-background {
background-color: #5C5C5C !important;
}
#photoprism.theme-chrome .cards-view .card.is-selected .card-details .v-icon {
color: #fff;
}

View file

@ -1,75 +1,7 @@
/* Component Color Defaults */
/* Default Theme */
.card {
background-color: #e0e0e0;
}
body.dark-theme .card {
background-color: #1a1a1a;
}
body.dark-theme #photoprism .v-table .theme--light.v-text-field--solo>.v-input__control>.v-input__slot {
background: transparent;
}
body.dark-theme .theme--light.v-list .v-list__tile--highlighted,
body.dark-theme .theme--light.v-list a:hover {
background: rgba(255,255,255,0.3) !important;
}
body.dark-theme .v-input input::placeholder {
color: rgba(255,255,255,0.5) !important;
}
body.dark-theme .theme--light .v-table {
background: #303030;
}
body.dark-theme .theme--light.v-table tbody tr:hover {
background: #343434;
}
.theme--light.v-list .v-list__tile--highlighted,
.theme--light.v-list a:hover {
background: rgba(155,155,155,0.3) !important;
}
body:not(.dark-theme) .theme--dark.v-btn.v-btn--disabled:not(.v-btn--icon):not(.v-btn--flat):not(.v-btn--outline) {
color: rgb(51, 51, 51)!important;
background-color: #d9dadc!important;
}
body.dark-theme #photoprism .theme--light.v-chip,
body.dark-theme #photoprism .v-card .theme--light.v-text-field--box>.v-input__control>.v-input__slot {
background: #00000033;
}
body.dark-theme #photoprism,
body.dark-theme #photoprism .p-page a,
body.dark-theme #photoprism .v-datatable a,
body.dark-theme #photoprism .theme--light.v-expansion-panel .v-expansion-panel__container,
body.dark-theme #photoprism .theme--light.v-tabs__bar .v-tabs__div,
body.dark-theme #photoprism .theme--light {
color: #ffffff;
caret-color: #ffffff;
stroke: #ffffff;
}
body.dark-theme #photoprism .theme--light.v-label {
color: #ffffffaa;
}
body.dark-theme #photoprism .theme--light.v-select .v-select__selections {
color: #ffffffee;
}
body.dark-theme .theme--light.v-small-dialog__content,
body.dark-theme .theme--light.v-sheet,
body.dark-theme .theme--light.v-card {
background: #2f3031;
}
body.dark-theme .theme--light.v-text-field--solo>.v-input__control>.v-input__slot {
#photoprism.theme-default .theme--light.v-text-field--solo>.v-input__control>.v-input__slot,
#photoprism.theme-default .theme--light.v-text-field--solo>.v-input__control>.v-input__slot {
background: rgba(250, 250, 255, 0.1);
}
@ -88,10 +20,7 @@ body.dark-theme #photoprism .p-page a,
body.dark-theme #photoprism .v-datatable a,
body.dark-theme #photoprism .theme--light.v-expansion-panel .v-expansion-panel__container,
body.dark-theme #photoprism .theme--light.v-tabs__bar .v-tabs__div,
body.dark-theme #photoprism .theme--light,
body.dark-theme #photoprism .theme--light.v-table thead th,
body.dark-theme .theme--light.v-table thead th,
body.dark-theme .theme--light.v-table tbody td {
body.dark-theme #photoprism .theme--light {
color: #ffffff;
}
@ -107,12 +36,6 @@ body.dark-theme #photoprism .theme--light.v-select .v-select__selections {
padding-top: 20px;
}
body.dark-theme #photoprism .v-tabs .v-badge__badge {
color: #000000;
}
/* Default Theme */
body.dark-theme.theme-default,
.theme-default .v-content__wrap,
.theme-default .p-page,
@ -128,6 +51,10 @@ body.dark-theme.theme-default,
background: #2f3031;
}
#photoprism.theme-default .theme--light .v-table {
background: #37393a;
}
#photoprism.container.theme-default {
background-image: linear-gradient(160deg, #808080 0%, #262626 100%);
}
@ -141,10 +68,6 @@ body.dark-theme.theme-default,
stroke: rgba(196, 241, 229, 0.3);
}
#photoprism.theme-default .theme--light .v-table {
background: #37393a;
}
#photoprism.theme-default .theme--light.v-table thead th,
#photoprism.theme-default .theme--light.v-table tbody td {
color: #fff;
@ -158,11 +81,6 @@ body.dark-theme.theme-default,
color: #999 !important;
}
#photoprism.theme-default .theme--light.v-text-field--solo>.v-input__control>.v-input__slot,
#photoprism.theme-default .theme--light.v-text-field--solo>.v-input__control>.v-input__slot {
background: rgba(250, 250, 255, 0.1);
}
#photoprism.theme-default .theme--light.v-input--selection-controls.v-input--is-disabled .v-icon,
#photoprism.theme-default .theme--light.v-input--is-disabled .v-label,
#photoprism.theme-default .theme--light.v-input--is-disabled input,
@ -205,12 +123,12 @@ body.dark-theme.theme-default,
#photoprism.theme-default .cards-view .card.is-selected .card-details {
color: #fff;
background-color: #636869;
background-color: #4b4e4f;
}
#photoprism.theme-default .cards-view .card.is-selected,
#photoprism.theme-default .cards-view .card.is-selected .card-background {
background-color: #636869 !important;
background-color: #4b4e4f !important;
}
#photoprism.theme-default .cards-view .card.is-selected .card-details .v-icon {

View file

@ -1,4 +1,4 @@
/* Gemstone Dark Theme */
/* Gemstone Theme */
body.dark-theme.theme-gemstone,
.theme-gemstone .v-content__wrap,

View file

@ -1,4 +1,4 @@
/* Grayscale Dark Theme */
/* Grayscale Theme */
body.dark-theme.theme-grayscale {
background: #525252 !important;

View file

@ -1,92 +0,0 @@
/* Mint Dark Theme */
body.dark-theme.theme-mint,
.theme-mint .v-content__wrap,
.theme-mint .p-page,
.theme-mint .form,
.theme-mint .v-content {
background: #121212 !important;
}
#photoprism.theme-mint .theme--light.v-small-dialog__content,
#photoprism.theme-mint .theme--light.v-sheet,
#photoprism.theme-mint .theme--light.v-card,
.theme-mint .application.theme--light {
background: #1e1e1e;
}
#photoprism.container.theme-mint {
background-image: linear-gradient(160deg, #808080 0%, #262626 100%);
}
#photoprism.theme-mint .theme--light .v-table {
background: #1a1a1a;
}
#photoprism.theme-mint .theme--light.v-table thead th,
#photoprism.theme-mint .theme--light.v-table tbody td {
color: #fff;
}
#photoprism.theme-mint .theme--light.v-table tbody tr:hover {
background: #2b2b2b;
}
#photoprism.theme-mint .theme--light.v-input--selection-controls.v-input--is-disabled .v-icon {
color: #999 !important;
}
#photoprism.theme-mint .theme--light.v-input--selection-controls.v-input--is-disabled .v-icon,
#photoprism.theme-mint .theme--light.v-input--is-disabled .v-label,
#photoprism.theme-mint .theme--light.v-input--is-disabled input,
#photoprism.theme-mint .theme--light.v-input--is-disabled textarea {
color: #ccc;
}
#photoprism.theme-mint .theme--light.v-list {
background: #232425;
}
#photoprism.theme-mint a.text-link {
color: #f6f7e8 !important;
}
#photoprism.theme-mint .footer .body-link {
color: #f6f7e8 !important;
}
#photoprism.theme-mint .theme--light.v-list .v-list__tile__sub-title,
#photoprism.theme-mint .accent--text {
color: #2bb14c !important;
}
#photoprism.theme-mint .theme--light.v-input:not(.v-input--is-disabled) input,
#photoprism.theme-mint .theme--light.v-input:not(.v-input--is-disabled) textarea {
color: #fff;
}
#photoprism.theme-mint .theme--light.v-btn.v-btn--disabled,
#photoprism.theme-mint .theme--light.v-btn.v-btn--disabled .v-btn__loading,
#photoprism.theme-mint .theme--light.v-btn.v-btn--disabled .v-icon {
color: #999 !important;
}
#photoprism.theme-mint .theme--light.v-list .v-list__tile__mask {
color: #cccccc;
background: transparent;
}
#photoprism.theme-mint .cards-view .card.is-selected .card-details {
color: #fff;
background-color: #333333;
}
#photoprism.theme-mint .cards-view .card.is-selected,
#photoprism.theme-mint .cards-view .card.is-selected .card-background {
background-color: #333333 !important;
}
#photoprism.theme-mint .cards-view .card.is-selected .card-details .v-icon {
color: #fff;
}

View file

@ -1,4 +1,4 @@
/* Neon Dark Theme */
/* Neon Theme */
.theme-neon .v-snack.v-snack--active.v-snack--bottom,
.theme-neon .v-speed-dial .v-btn--floating.v-btn--small {

View file

@ -1,4 +1,4 @@
/* Shadow Dark Theme */
/* Shadow Theme */
body.dark-theme.theme-shadow {
background: #444 !important;

View file

@ -1,4 +1,4 @@
/* Vanta Dark Theme */
/* Vanta Theme */
body.dark-theme.theme-vanta {
background: #212121 !important;

View file

@ -1,4 +1,4 @@
/* Yellowstone Dark Theme */
/* Yellowstone Theme */
body.dark-theme.theme-yellowstone,
.theme-yellowstone .v-content__wrap,

View file

@ -39,10 +39,6 @@ button.v-btn.compact {
height: 34px;
}
.caption.filename {
font-weight: 500;
}
/* Line Height */
.lh-15 {

View file

@ -1,5 +1,5 @@
<template>
<v-dialog :value="show" lazy persistent max-width="360" class="p-photo-delete-dialog" @keydown.esc="cancel">
<v-dialog :value="show" lazy persistent max-width="350" class="p-photo-delete-dialog" @keydown.esc="cancel">
<v-card raised elevation="24">
<v-container fluid class="pb-2 pr-2 pl-2">
<v-layout row wrap>
@ -7,25 +7,18 @@
<v-icon size="54" color="secondary-dark lighten-1">delete_outline</v-icon>
</v-flex>
<v-flex xs9 text-xs-left align-self-center>
<div v-if="text === ''" class="subheading pr-1">
<div class="subheading pr-1">
<translate>Are you sure you want to permanently delete these pictures?</translate>
</div>
<div v-else class="subheading pr-1">
{{ text }}
</div>
</v-flex>
<v-flex xs12 text-xs-right class="pt-3">
<v-btn depressed color="secondary-light" class="action-cancel" @click.stop="cancel">
<translate key="Cancel">Cancel</translate>
</v-btn>
<v-btn v-if="action === ''" color="primary-button" depressed dark class="action-confirm"
<v-btn color="primary-button" depressed dark class="action-confirm"
@click.stop="confirm">
<translate key="Delete">Delete</translate>
</v-btn>
<v-btn v-else color="primary-button" depressed dark class="action-confirm"
@click.stop="confirm">
{{ action }}
</v-btn>
</v-flex>
</v-layout>
</v-container>
@ -37,14 +30,6 @@ export default {
name: 'PPhotoDeleteDialog',
props: {
show: Boolean,
text: {
type: String,
default: "",
},
action: {
type: String,
default: "",
},
},
data() {
return {};

View file

@ -24,11 +24,12 @@
</v-toolbar>
<v-tabs
v-model="active"
flat grow
class="form"
flat
grow
color="secondary"
slider-color="secondary-dark"
:height="$vuetify.breakpoint.smAndDown ? 48 : 64"
class="form"
>
<v-tab id="tab-details" ripple>
<v-icon v-if="$vuetify.breakpoint.smAndDown" :title="$gettext('Details')">edit</v-icon>
@ -81,7 +82,7 @@
</v-tab>
<v-tabs-items touchless>
<v-tab-item lazy>
<v-tab-item>
<p-tab-photo-details :key="uid" ref="details" :model="model" :uid="uid"
@close="close" @prev="prev" @next="next"></p-tab-photo-details>
</v-tab-item>

View file

@ -1,408 +1,412 @@
<template>
<div class="p-tab p-tab-photo-details">
<v-form ref="form" lazy-validation
dense class="p-form-photo-details-meta" accept-charset="UTF-8"
@submit.prevent="save">
<v-layout class="pa-2" row wrap align-top fill-height>
<v-flex class="pa-2 p-photo" xs12 sm4 md2 fill-height>
<v-card tile
class="pa-0 ma-0 elevation-0"
:title="model.Title">
<v-img v-touch="{left, right}"
:src="model.thumbnailUrl('tile_500')"
aspect-ratio="1"
class="card darken-1 elevation-0 clickable"
@click.exact="openPhoto()"
>
</v-img>
<v-container fluid>
<v-form ref="form" lazy-validation
dense class="p-form-photo-details-meta" accept-charset="UTF-8"
@submit.prevent="save">
<v-layout row wrap align-top fill-height>
<v-flex
class="p-photo pa-2"
xs12 sm4 md2
>
<v-card tile
class="ma-0 elevation-0"
:title="model.Title">
<v-img v-touch="{left, right}"
:src="model.thumbnailUrl('tile_500')"
aspect-ratio="1"
class="card darken-1 elevation-0 clickable"
@click.exact="openPhoto()"
>
</v-img>
</v-card>
</v-flex>
<v-flex xs12 sm8 md10 fill-height>
<v-layout row wrap>
<v-flex xs12 lg6 class="pa-2">
<v-text-field
v-model="model.Title"
:append-icon="model.TitleSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:rules="[textRule]"
hide-details box flat
:label="$pgettext('Photo', 'Title')"
placeholder=""
color="secondary-dark"
browser-autocomplete="off"
class="input-title"
></v-text-field>
</v-flex>
</v-card>
</v-flex>
<v-flex xs12 sm8 md10 fill-height>
<v-layout row wrap>
<v-flex xs12 lg6 class="pa-2">
<v-text-field
v-model="model.Title"
:append-icon="model.TitleSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:rules="[textRule]"
hide-details box flat
:label="$pgettext('Photo', 'Title')"
placeholder=""
color="secondary-dark"
browser-autocomplete="off"
class="input-title"
></v-text-field>
</v-flex>
<v-flex xs4 md1 pa-2>
<v-autocomplete
v-model="model.Day"
:append-icon="model.TakenSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:error="invalidDate"
:label="$gettext('Day')"
browser-autocomplete="off"
hide-details box flat hide-no-data
color="secondary-dark"
:items="options.Days()"
class="input-day"
@change="updateTime">
</v-autocomplete>
</v-flex>
<v-flex xs4 md1 pa-2>
<v-autocomplete
v-model="model.Month"
:append-icon="model.TakenSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:error="invalidDate"
:label="$gettext('Month')"
browser-autocomplete="off"
hide-details box flat hide-no-data
color="secondary-dark"
:items="options.MonthsShort()"
class="input-month"
@change="updateTime">
</v-autocomplete>
</v-flex>
<v-flex xs4 md2 pa-2>
<v-autocomplete
v-model="model.Year"
:append-icon="model.TakenSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:error="invalidDate"
:label="$gettext('Year')"
browser-autocomplete="off"
hide-details box flat hide-no-data
color="secondary-dark"
:items="options.Years()"
class="input-year"
@change="updateTime">
</v-autocomplete>
</v-flex>
<v-flex xs4 md1 pa-2>
<v-autocomplete
v-model="model.Day"
:append-icon="model.TakenSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:error="invalidDate"
:label="$gettext('Day')"
browser-autocomplete="off"
hide-details box flat hide-no-data
color="secondary-dark"
:items="options.Days()"
class="input-day"
@change="updateTime">
</v-autocomplete>
</v-flex>
<v-flex xs4 md1 pa-2>
<v-autocomplete
v-model="model.Month"
:append-icon="model.TakenSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:error="invalidDate"
:label="$gettext('Month')"
browser-autocomplete="off"
hide-details box flat hide-no-data
color="secondary-dark"
:items="options.MonthsShort()"
class="input-month"
@change="updateTime">
</v-autocomplete>
</v-flex>
<v-flex xs4 md2 pa-2>
<v-autocomplete
v-model="model.Year"
:append-icon="model.TakenSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:error="invalidDate"
:label="$gettext('Year')"
browser-autocomplete="off"
hide-details box flat hide-no-data
color="secondary-dark"
:items="options.Years()"
class="input-year"
@change="updateTime">
</v-autocomplete>
</v-flex>
<v-flex xs6 md2 class="pa-2">
<v-text-field
v-model="time"
:append-icon="model.TakenSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:label="model.timeIsUTC() ? $gettext('Time UTC') : $gettext('Local Time')"
browser-autocomplete="off"
autocorrect="off"
autocapitalize="none"
hide-details box flat
return-masked-value mask="##:##:##"
color="secondary-dark"
class="input-local-time"
></v-text-field>
</v-flex>
<v-flex xs6 md2 class="pa-2">
<v-text-field
v-model="time"
:append-icon="model.TakenSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:label="model.timeIsUTC() ? $gettext('Time UTC') : $gettext('Local Time')"
browser-autocomplete="off"
autocorrect="off"
autocapitalize="none"
hide-details box flat
return-masked-value mask="##:##:##"
color="secondary-dark"
class="input-local-time"
></v-text-field>
</v-flex>
<v-flex xs6 sm6 md6 lg3 class="pa-2">
<v-autocomplete
v-model="model.TimeZone"
:disabled="disabled"
:label="$gettext('Time Zone')"
browser-autocomplete="off"
hide-details box flat hide-no-data
color="secondary-dark"
item-value="ID"
item-text="Name"
:items="options.TimeZones()"
class="input-timezone"
@change="updateTime">
</v-autocomplete>
</v-flex>
<v-flex xs6 sm6 md6 lg3 class="pa-2">
<v-autocomplete
v-model="model.TimeZone"
:disabled="disabled"
:label="$gettext('Time Zone')"
browser-autocomplete="off"
hide-details box flat hide-no-data
color="secondary-dark"
item-value="ID"
item-text="Name"
:items="options.TimeZones()"
class="input-timezone"
@change="updateTime">
</v-autocomplete>
</v-flex>
<v-flex xs12 sm8 md4 lg3 class="pa-2">
<v-autocomplete
v-model="model.Country"
:append-icon="model.PlaceSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:readonly="!!(model.Lat || model.Lng)"
:label="$gettext('Country')" hide-details box flat
hide-no-data
browser-autocomplete="off"
color="secondary-dark"
item-value="Code"
item-text="Name"
:items="countries"
class="input-country">
</v-autocomplete>
</v-flex>
<v-flex xs12 sm8 md4 lg3 class="pa-2">
<v-autocomplete
v-model="model.Country"
:append-icon="model.PlaceSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:readonly="!!(model.Lat || model.Lng)"
:label="$gettext('Country')" hide-details box flat
hide-no-data
browser-autocomplete="off"
color="secondary-dark"
item-value="Code"
item-text="Name"
:items="countries"
class="input-country">
</v-autocomplete>
</v-flex>
<v-flex xs4 md2 lg2 class="pa-2">
<v-text-field
v-model="model.Altitude"
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
autocorrect="off"
autocapitalize="none"
:label="$gettext('Altitude (m)')"
placeholder=""
color="secondary-dark"
class="input-altitude"
></v-text-field>
</v-flex>
<v-flex xs4 md2 lg2 class="pa-2">
<v-text-field
v-model="model.Altitude"
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
autocorrect="off"
autocapitalize="none"
:label="$gettext('Altitude (m)')"
placeholder=""
color="secondary-dark"
class="input-altitude"
></v-text-field>
</v-flex>
<v-flex xs4 sm6 md3 lg2 class="pa-2">
<v-text-field
v-model="model.Lat"
:append-icon="model.PlaceSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
autocorrect="off"
autocapitalize="none"
:label="$gettext('Latitude')"
placeholder=""
color="secondary-dark"
class="input-latitude"
@paste="pastePosition"
></v-text-field>
</v-flex>
<v-flex xs4 sm6 md3 lg2 class="pa-2">
<v-text-field
v-model="model.Lat"
:append-icon="model.PlaceSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
autocorrect="off"
autocapitalize="none"
:label="$gettext('Latitude')"
placeholder=""
color="secondary-dark"
class="input-latitude"
></v-text-field>
</v-flex>
<v-flex xs4 sm6 md3 lg2 class="pa-2">
<v-text-field
v-model="model.Lng"
:append-icon="model.PlaceSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
autocorrect="off"
autocapitalize="none"
:label="$gettext('Longitude')"
placeholder=""
color="secondary-dark"
class="input-longitude"
@paste="pastePosition"
></v-text-field>
</v-flex>
<v-flex xs4 sm6 md3 lg2 class="pa-2">
<v-text-field
v-model="model.Lng"
:append-icon="model.PlaceSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
autocorrect="off"
autocapitalize="none"
:label="$gettext('Longitude')"
placeholder=""
color="secondary-dark"
class="input-longitude"
></v-text-field>
</v-flex>
<v-flex xs12 md6 pa-2 class="p-camera-select">
<v-select
v-model="model.CameraID"
:append-icon="model.CameraSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:label="$gettext('Camera')"
:menu-props="{'maxHeight':346}"
browser-autocomplete="off"
hide-details box flat
color="secondary-dark"
item-value="ID"
item-text="Name"
:items="cameraOptions"
class="input-camera">
</v-select>
</v-flex>
<v-flex xs12 md6 pa-2 class="p-camera-select">
<v-select
v-model="model.CameraID"
:append-icon="model.CameraSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:label="$gettext('Camera')"
:menu-props="{'maxHeight':346}"
browser-autocomplete="off"
hide-details box flat
color="secondary-dark"
item-value="ID"
item-text="Name"
:items="cameraOptions"
class="input-camera">
</v-select>
</v-flex>
<v-flex xs6 md3 class="pa-2">
<v-text-field
v-model="model.Iso"
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
autocorrect="off"
autocapitalize="none"
label="ISO"
placeholder=""
color="secondary-dark"
class="input-iso"
></v-text-field>
</v-flex>
<v-flex xs6 md3 class="pa-2">
<v-text-field
v-model="model.Iso"
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
autocorrect="off"
autocapitalize="none"
label="ISO"
placeholder=""
color="secondary-dark"
class="input-iso"
></v-text-field>
</v-flex>
<v-flex xs6 md3 class="pa-2">
<v-text-field
v-model="model.Exposure"
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
autocorrect="off"
autocapitalize="none"
:label="$gettext('Exposure')"
placeholder=""
color="secondary-dark"
class="input-exposure"
></v-text-field>
</v-flex>
<v-flex xs6 md3 class="pa-2">
<v-text-field
v-model="model.Exposure"
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
autocorrect="off"
autocapitalize="none"
:label="$gettext('Exposure')"
placeholder=""
color="secondary-dark"
class="input-exposure"
></v-text-field>
</v-flex>
<v-flex xs12 md6 pa-2 class="p-lens-select">
<v-select
v-model="model.LensID"
:append-icon="model.CameraSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:label="$gettext('Lens')"
:menu-props="{'maxHeight':346}"
browser-autocomplete="off"
hide-details box flat
color="secondary-dark"
item-value="ID"
item-text="Name"
:items="lensOptions"
class="input-lens">
</v-select>
</v-flex>
<v-flex xs12 md6 pa-2 class="p-lens-select">
<v-select
v-model="model.LensID"
:append-icon="model.CameraSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:label="$gettext('Lens')"
:menu-props="{'maxHeight':346}"
browser-autocomplete="off"
hide-details box flat
color="secondary-dark"
item-value="ID"
item-text="Name"
:items="lensOptions"
class="input-lens">
</v-select>
</v-flex>
<v-flex xs6 md3 class="pa-2">
<v-text-field
v-model="model.FNumber" f
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
autocorrect="off"
autocapitalize="none"
:label="$gettext('F Number')"
placeholder=""
color="secondary-dark"
class="input-fnumber"
></v-text-field>
</v-flex>
<v-flex xs6 md3 class="pa-2">
<v-text-field
v-model="model.FNumber" f
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
autocorrect="off"
autocapitalize="none"
:label="$gettext('F Number')"
placeholder=""
color="secondary-dark"
class="input-fnumber"
></v-text-field>
</v-flex>
<v-flex xs6 md3 class="pa-2">
<v-text-field
v-model="model.FocalLength"
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
:label="$gettext('Focal Length')"
placeholder=""
color="secondary-dark"
class="input-focal-length"
></v-text-field>
</v-flex>
<v-flex xs6 md3 class="pa-2">
<v-text-field
v-model="model.FocalLength"
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
:label="$gettext('Focal Length')"
placeholder=""
color="secondary-dark"
class="input-focal-length"
></v-text-field>
</v-flex>
<v-flex xs12 md6 class="pa-2">
<v-text-field
v-model="model.Details.Artist"
:append-icon="model.Details.ArtistSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:rules="[textRule]"
hide-details box flat
browser-autocomplete="off"
:label="$gettext('Artist')"
placeholder=""
color="secondary-dark"
class="input-artist"
></v-text-field>
</v-flex>
<v-flex xs12 md6 class="pa-2">
<v-text-field
v-model="model.Details.Artist"
:append-icon="model.Details.ArtistSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:rules="[textRule]"
hide-details box flat
browser-autocomplete="off"
:label="$gettext('Artist')"
placeholder=""
color="secondary-dark"
class="input-artist"
></v-text-field>
</v-flex>
<v-flex xs6 md3 class="pa-2">
<v-text-field
v-model="model.Details.Copyright"
:append-icon="model.Details.CopyrightSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:rules="[textRule]"
hide-details box flat
browser-autocomplete="off"
:label="$gettext('Copyright')"
placeholder=""
color="secondary-dark"
class="input-copyright"
></v-text-field>
</v-flex>
<v-flex xs6 md3 class="pa-2">
<v-text-field
v-model="model.Details.Copyright"
:append-icon="model.Details.CopyrightSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:rules="[textRule]"
hide-details box flat
browser-autocomplete="off"
:label="$gettext('Copyright')"
placeholder=""
color="secondary-dark"
class="input-copyright"
></v-text-field>
</v-flex>
<v-flex xs6 md3 class="pa-2">
<v-textarea
v-model="model.Details.License"
:append-icon="model.Details.LicenseSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:rules="[textRule]"
hide-details box flat
browser-autocomplete="off"
auto-grow
:label="$gettext('License')"
placeholder=""
:rows="1"
color="secondary-dark"
class="input-license"
></v-textarea>
</v-flex>
<v-flex xs6 md3 class="pa-2">
<v-textarea
v-model="model.Details.License"
:append-icon="model.Details.LicenseSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:rules="[textRule]"
hide-details box flat
browser-autocomplete="off"
auto-grow
:label="$gettext('License')"
placeholder=""
:rows="1"
color="secondary-dark"
class="input-license"
></v-textarea>
</v-flex>
<v-flex xs12 class="pa-2">
<v-textarea
v-model="model.Details.Subject"
:append-icon="model.Details.SubjectSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:rules="[textRule]"
hide-details box flat
browser-autocomplete="off"
auto-grow
:label="$gettext('Subject')"
placeholder=""
:rows="1"
color="secondary-dark"
class="input-subject"
></v-textarea>
</v-flex>
<v-flex xs12 class="pa-2">
<v-textarea
v-model="model.Details.Subject"
:append-icon="model.Details.SubjectSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
:rules="[textRule]"
hide-details box flat
browser-autocomplete="off"
auto-grow
:label="$gettext('Subject')"
placeholder=""
:rows="1"
color="secondary-dark"
class="input-subject"
></v-textarea>
</v-flex>
<v-flex xs12 class="pa-2">
<v-textarea
v-model="model.Description"
:append-icon="model.DescriptionSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
auto-grow
:label="$gettext('Description')"
placeholder=""
:rows="1"
color="secondary-dark"
class="input-description"
></v-textarea>
</v-flex>
<v-flex xs12 class="pa-2">
<v-textarea
v-model="model.Description"
:append-icon="model.DescriptionSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
auto-grow
:label="$gettext('Description')"
placeholder=""
:rows="1"
color="secondary-dark"
class="input-description"
></v-textarea>
</v-flex>
<v-flex xs12 md8 class="pa-2">
<v-textarea
v-model="model.Details.Keywords"
:append-icon="model.Details.KeywordsSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
auto-grow
:label="$gettext('Keywords')"
placeholder=""
:rows="1"
color="secondary-dark"
class="input-keywords"
></v-textarea>
</v-flex>
<v-flex xs12 md8 class="pa-2">
<v-textarea
v-model="model.Details.Keywords"
:append-icon="model.Details.KeywordsSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
auto-grow
:label="$gettext('Keywords')"
placeholder=""
:rows="1"
color="secondary-dark"
class="input-keywords"
></v-textarea>
</v-flex>
<v-flex xs12 md4 class="pa-2">
<v-textarea
v-model="model.Details.Notes"
:append-icon="model.Details.NotesSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
auto-grow
:label="$gettext('Notes')"
placeholder=""
:rows="1"
color="secondary-dark"
class="input-notes"
></v-textarea>
</v-flex>
<v-flex xs12 md4 class="pa-2">
<v-textarea
v-model="model.Details.Notes"
:append-icon="model.Details.NotesSrc === 'manual' ? 'check' : ''"
:disabled="disabled"
hide-details box flat
browser-autocomplete="off"
auto-grow
:label="$gettext('Notes')"
placeholder=""
:rows="1"
color="secondary-dark"
class="input-notes"
></v-textarea>
</v-flex>
<v-flex v-if="!disabled" xs12 :text-xs-right="!rtl" :text-xs-left="rtl" class="pt-3">
<v-btn depressed color="secondary-light" class="compact action-close"
@click.stop="close">
<translate>Close</translate>
</v-btn>
<v-btn color="primary-button" depressed dark class="compact action-apply action-approve"
@click.stop="save(false)">
<span v-if="$config.feature('review') && model.Quality < 3"><translate>Approve</translate></span>
<span v-else><translate>Apply</translate></span>
</v-btn>
<v-btn color="primary-button" depressed dark class="compact action-done hidden-xs-only"
@click.stop="save(true)">
<translate>Done</translate>
</v-btn>
</v-flex>
</v-layout>
</v-flex>
</v-layout>
<div class="mt-1 clear"></div>
</v-form>
<v-flex v-if="!disabled" xs12 :text-xs-right="!rtl" :text-xs-left="rtl" class="pt-3">
<v-btn depressed color="secondary-light" class="compact action-close"
@click.stop="close">
<translate>Close</translate>
</v-btn>
<v-btn color="primary-button" depressed dark class="compact action-apply action-approve"
@click.stop="save(false)">
<span v-if="$config.feature('review') && model.Quality < 3"><translate>Approve</translate></span>
<span v-else><translate>Apply</translate></span>
</v-btn>
<v-btn color="primary-button" depressed dark class="compact action-done hidden-xs-only"
@click.stop="save(true)">
<translate>Done</translate>
</v-btn>
</v-flex>
</v-layout>
</v-flex>
</v-layout>
<div class="mt-1 clear"></div>
</v-form>
</v-container>
</div>
</template>
@ -471,37 +475,6 @@ export default {
this.time = taken.toFormat("HH:mm:ss");
},
pastePosition(event) {
// Auto-fills the lat and lng fields if the text in the clipboard contains two float values.
const clipboard = event.clipboardData ? event.clipboardData : window.clipboardData;
if (!clipboard) {
return;
}
// Get values from browser clipboard.
const text = clipboard.getData("text");
// Trim spaces before splitting by whitespace and/or commas.
const val = text.trim().split(/[ ,]+/);
// Two values found?
if (val.length >= 2) {
// Parse values.
const lat = parseFloat(val[0]);
const lng = parseFloat(val[1]);
// Lat and long must be valid floating point numbers.
if (!isNaN(lat) && lat >= -90 && lat <= 90 &&
!isNaN(lng) && lng >= -180 && lng <= 180) {
// Update model values.
this.model.Lat = lat;
this.model.Lng = lng;
// Prevent default action.
event.preventDefault();
}
}
},
updateModel() {
if (!this.model.hasId()) {
return;

View file

@ -5,7 +5,7 @@
<v-expansion-panel-content v-if="!file.Missing" :key="file.UID" class="pa-0 elevation-0 secondary-light"
style="margin-top: 1px;">
<template #header>
<div class="caption filename">{{ file.baseName(70) }}</div>
<div class="caption">{{ file.baseName(70) }}</div>
</template>
<v-card>
<v-card-text class="white pa-0">
@ -77,25 +77,25 @@
<td title="Unique ID">
UID
</td>
<td><span class="clickable" @click.stop.prevent="copyText(file.UID)">{{ file.UID | uppercase }}</span></td>
<td>{{ file.UID | uppercase }}</td>
</tr>
<tr v-if="file.InstanceID" title="XMP">
<td>
<translate>Instance ID</translate>
</td>
<td><span class="clickable" @click.stop.prevent="copyText(file.InstanceID)">{{ file.InstanceID | uppercase }}</span></td>
<td>{{ file.InstanceID | uppercase }}</td>
</tr>
<tr>
<td title="SHA-1">
<translate>Hash</translate>
</td>
<td><span class="clickable" @click.stop.prevent="copyText(file.Hash)">{{ file.Hash }}</span></td>
<td>{{ file.Hash }}</td>
</tr>
<tr v-if="file.Name">
<td>
<translate>Filename</translate>
</td>
<td><span class="clickable" @click.stop.prevent="copyText(file.Name)">{{ file.Name }}</span></td>
<td>{{ file.Name }}</td>
</tr>
<tr v-if="file.Root">
<td>
@ -107,7 +107,7 @@
<td>
<translate>Original Name</translate>
</td>
<td><span class="clickable" @click.stop.prevent="copyText(file.OriginalName)">{{ file.OriginalName }}</span></td>
<td>{{ file.OriginalName }}</td>
</tr>
<tr>
<td>
@ -294,10 +294,7 @@ export default {
type: Object,
default: () => {},
},
uid: {
type: String,
default: "",
},
uid: String,
},
data() {
return {
@ -332,18 +329,6 @@ export default {
},
computed: {},
methods: {
async copyText(text) {
if (!text) {
return;
}
try {
await Util.copyToMachineClipboard(text);
this.$notify.success(this.$gettext("Copied to clipboard"));
} catch (error) {
this.$notify.error(this.$gettext("Failed copying to clipboard"));
}
},
orientationClass(file) {
if (!file) {
return [];
@ -380,11 +365,8 @@ export default {
}
const name = m.Name;
const path = name.substring(0, name.lastIndexOf('/'));
// "#" chars in path names must be explicitly escaped,
// see https://github.com/photoprism/photoprism/issues/3695
const path = name.substring(0, name.lastIndexOf('/'))
.replaceAll(":", "%3A").replaceAll("#", "%23");
return this.$router.resolve({ path: '/index/files/' + path }).href;
},
downloadFile(file) {

View file

@ -1,24 +1,23 @@
<template>
<div class="p-tab p-tab-photo-advanced">
<v-form ref="form" lazy-validation dense accept-charset="UTF-8" @submit.prevent>
<div class="v-table__overflow">
<table class="v-datatable v-table theme--light">
<tbody>
<tr>
<td>UID</td>
<td><span class="clickable" @click.stop.prevent="copyText(model.UID)">{{ model.UID | uppercase }}</span></td>
</tr>
<tr v-if="model.DocumentID">
<td>Document ID</td>
<td><span class="clickable" @click.stop.prevent="copyText(model.DocumentID)">{{ model.DocumentID | uppercase }}</span></td>
</tr>
<tr>
<td :title="model.TypeSrc">
<translate>Type</translate>
<v-icon v-if="model.TypeSrc === 'manual'" class="src">check</v-icon>
</td>
<td>
<v-select
<div class="v-table__overflow">
<table class="v-datatable v-table theme--light">
<tbody>
<tr>
<td>UID</td>
<td>{{ model.UID | uppercase }}</td>
</tr>
<tr v-if="model.DocumentID">
<td>Document ID</td>
<td>{{ model.DocumentID | uppercase }}</td>
</tr>
<tr>
<td :title="model.TypeSrc">
<translate>Type</translate>
<v-icon v-if="model.TypeSrc === 'manual'" class="src">check</v-icon>
</td>
<td>
<v-select
v-model="model.Type"
flat solo
browser-autocomplete="off"
@ -27,27 +26,27 @@
:items="options.PhotoTypes()"
class="input-type"
@change="save">
</v-select>
</td>
</tr>
<tr v-if="model.Path">
<td>
<translate>Folder</translate>
</td>
<td><span class="clickable" @click.stop.prevent="copyText(model.Path)">{{ model.Path }}</span></td>
</tr>
<tr>
<td>
<translate>Name</translate>
</td>
<td><span class="clickable" @click.stop.prevent="copyText(model.Name)">{{ model.Name }}</span></td>
</tr>
<tr v-if="model.OriginalName">
<td>
<translate>Original Name</translate>
</td>
<td>
<v-text-field
</v-select>
</td>
</tr>
<tr v-if="model.Path">
<td>
<translate>Folder</translate>
</td>
<td>{{ model.Path }}</td>
</tr>
<tr>
<td>
<translate>Name</translate>
</td>
<td>{{ model.Name }}</td>
</tr>
<tr v-if="model.OriginalName">
<td>
<translate>Original Name</translate>
</td>
<td>
<v-text-field
v-model="model.OriginalName"
flat solo dense hide-details
browser-autocomplete="off"
@ -55,71 +54,64 @@
autocapitalize="none"
color="secondary-dark"
@change="save"
></v-text-field>
</td>
</tr>
<tr>
<td :title="sourceName(model.TitleSrc)">
<translate>Title</translate>
<v-icon v-if="model.TitleSrc === 'manual'" class="src">check</v-icon>
</td>
<td :title="sourceName(model.TitleSrc)">
<span class="clickable" @click.stop.prevent="copyText(model.Title)">{{ model.Title }}</span>
<v-icon v-if="model.TitleSrc === 'name'" class="src">insert_drive_file</v-icon>
</td>
</tr>
<tr>
<td :title="sourceName(model.TakenSrc)">
<translate>Taken</translate>
<v-icon v-if="model.TakenSrc === 'manual'" class="src">check</v-icon>
</td>
<td :title="sourceName(model.TakenSrc)">
{{ model.getDateString() }}
<v-icon v-if="model.TakenSrc === 'name' || model.TakenSrc === 'estimate'" class="src">insights</v-icon>
</td>
</tr>
<tr v-if="albums.length > 0">
<td>
<translate>Albums</translate>
</td>
<td>
<a v-for="(a, i) in albums" :key="i" :href="a.url" class="primary--text text-link"
target="_blank"><span v-if="i > 0">, </span>{{ a.title }}</a>
</td>
</tr>
<tr>
<td>
<translate>Quality Score</translate>
</td>
<td>
<v-rating v-model="model.Quality" :length="7" readonly small></v-rating>
</td>
</tr>
<tr>
<td>
<translate>Resolution</translate>
</td>
<td>{{ model.Resolution }} MP</td>
</tr>
<tr v-if="model.Faces > 0">
<td>
<translate>Faces</translate>
</td>
<td>{{ model.Faces }}</td>
</tr>
<tr v-if="model.CameraSerial">
<td>
<translate>Camera Serial</translate>
</td>
<td>{{ model.CameraSerial }}
</td>
</tr>
<tr v-if="model.Stack < 1">
<td>
<translate>Stackable</translate>
</td>
<td>
<v-switch
></v-text-field>
</td>
</tr>
<tr>
<td :title="model.TitleSrc">
<translate>Title</translate>
<v-icon v-if="model.TitleSrc === 'manual'" class="src">check</v-icon>
</td>
<td>{{ model.Title }}</td>
</tr>
<tr>
<td :title="model.TakenSrc">
<translate>Taken</translate>
<v-icon v-if="model.TakenSrc === 'manual'" class="src">check</v-icon>
</td>
<td>{{ model.getDateString() }}</td>
</tr>
<tr v-if="albums.length > 0">
<td>
<translate>Albums</translate>
</td>
<td>
<a v-for="(a, i) in albums" :key="i" :href="a.url" class="primary--text text-link" target="_blank"><span v-if="i > 0">, </span>{{ a.title }}</a>
</td>
</tr>
<tr>
<td>
<translate>Quality Score</translate>
</td>
<td>
<v-rating v-model="model.Quality" :length="7" readonly small></v-rating>
</td>
</tr>
<tr>
<td>
<translate>Resolution</translate>
</td>
<td>{{ model.Resolution }} MP</td>
</tr>
<tr v-if="model.Faces > 0">
<td>
<translate>Faces</translate>
</td>
<td>{{ model.Faces }}</td>
</tr>
<tr v-if="model.CameraSerial">
<td>
<translate>Camera Serial</translate>
</td>
<td>{{ model.CameraSerial }}
</td>
</tr>
<tr v-if="model.Stack < 1">
<td>
<translate>Stackable</translate>
</td>
<td>
<v-switch
v-model="model.Stack"
hide-details
class="input-stackable"
@ -127,105 +119,104 @@
:false-value="-1"
:label="model.Stack > - 1 ? $gettext('Yes') : $gettext('No')"
@change="save"
></v-switch>
</td>
</tr>
<tr>
<td>
<translate>Favorite</translate>
</td>
<td>
<v-switch
></v-switch>
</td>
</tr>
<tr>
<td>
<translate>Favorite</translate>
</td>
<td>
<v-switch
v-model="model.Favorite"
hide-details
class="input-favorite"
:label="model.Favorite ? $gettext('Yes') : $gettext('No')"
@change="save"
></v-switch>
</td>
</tr>
<tr v-if="$config.feature('private')">
<td>
<translate>Private</translate>
</td>
<td>
<v-switch
></v-switch>
</td>
</tr>
<tr v-if="$config.feature('private')">
<td>
<translate>Private</translate>
</td>
<td>
<v-switch
v-model="model.Private"
hide-details
class="input-private"
:label="model.Private ? $gettext('Yes') : $gettext('No')"
@change="save"
></v-switch>
</td>
</tr>
<tr>
<td>
<translate>Scan</translate>
</td>
<td>
<v-switch
></v-switch>
</td>
</tr>
<tr>
<td>
<translate>Scan</translate>
</td>
<td>
<v-switch
v-model="model.Scan"
hide-details
class="input-scan"
:label="model.Scan ? $gettext('Yes') : $gettext('No')"
@change="save"
></v-switch>
</td>
</tr>
<tr>
<td>
<translate>Panorama</translate>
</td>
<td>
<v-switch
></v-switch>
</td>
</tr>
<tr>
<td>
<translate>Panorama</translate>
</td>
<td>
<v-switch
v-model="model.Panorama"
hide-details
class="input-panorama"
:label="model.Panorama ? $gettext('Yes') : $gettext('No')"
@change="save"
></v-switch>
</td>
</tr>
<tr>
<td :title="sourceName(model.PlaceSrc)">
<translate>Place</translate>
<v-icon v-if="model.PlaceSrc === 'manual'" class="src">check</v-icon>
</td>
<td :title="sourceName(model.PlaceSrc)">
{{ model.locationInfo() }}
<v-icon v-if="model.PlaceSrc === 'estimate'" class="src">insights</v-icon>
</td>
</tr>
<tr v-if="model.Lat">
<td>
<translate>Latitude</translate>
</td>
<td>
{{ model.Lat }}
</td>
</tr>
<tr v-if="model.Lng">
<td>
<translate>Longitude</translate>
</td>
<td>
{{ model.Lng }}
</td>
</tr>
<tr v-if="model.Altitude">
<td>
<translate>Altitude</translate>
</td>
<td>
{{ model.Altitude }} m
</td>
</tr>
<tr v-if="model.Lat">
<td>
<translate>Accuracy</translate>
</td>
<td>
<v-text-field
></v-switch>
</td>
</tr>
<tr>
<td :title="model.PlaceSrc">
<translate>Place</translate>
<v-icon v-if="model.PlaceSrc === 'manual'" class="src">check</v-icon>
</td>
<td>
{{ model.locationInfo() }}
</td>
</tr>
<tr v-if="model.Lat">
<td>
<translate>Latitude</translate>
</td>
<td>
{{ model.Lat }}
</td>
</tr>
<tr v-if="model.Lng">
<td>
<translate>Longitude</translate>
</td>
<td>
{{ model.Lng }}
</td>
</tr>
<tr v-if="model.Altitude">
<td>
<translate>Altitude</translate>
</td>
<td>
{{ model.Altitude }} m
</td>
</tr>
<tr v-if="model.Lat">
<td>
<translate>Accuracy</translate>
</td>
<td>
<v-text-field
v-model="model.CellAccuracy"
flat solo dense hide-details
browser-autocomplete="off"
@ -236,53 +227,52 @@
suffix="m"
style="width: 100px;"
@change="save"
></v-text-field>
</td>
</tr>
<tr>
<td>
<translate>Created</translate>
</td>
<td>
{{ formatTime(model.CreatedAt) }}
</td>
</tr>
<tr>
<td>
<translate>Updated</translate>
</td>
<td>
{{ formatTime(model.UpdatedAt) }}
</td>
</tr>
<tr v-if="model.EditedAt">
<td>
<translate>Edited</translate>
</td>
<td>
{{ formatTime(model.EditedAt) }}
</td>
</tr>
<tr v-if="model.CheckedAt">
<td>
<translate>Checked</translate>
</td>
<td>
{{ formatTime(model.CheckedAt) }}
</td>
</tr>
<tr v-if="model.DeletedAt">
<td>
<translate>Archived</translate>
</td>
<td>
{{ formatTime(model.DeletedAt) }}
</td>
</tr>
</tbody>
</table>
</div>
</v-form>
></v-text-field>
</td>
</tr>
<tr>
<td>
<translate>Created</translate>
</td>
<td>
{{ formatTime(model.CreatedAt) }}
</td>
</tr>
<tr>
<td>
<translate>Updated</translate>
</td>
<td>
{{ formatTime(model.UpdatedAt) }}
</td>
</tr>
<tr v-if="model.EditedAt">
<td>
<translate>Edited</translate>
</td>
<td>
{{ formatTime(model.EditedAt) }}
</td>
</tr>
<tr v-if="model.CheckedAt">
<td>
<translate>Checked</translate>
</td>
<td>
{{ formatTime(model.CheckedAt) }}
</td>
</tr>
<tr v-if="model.DeletedAt">
<td>
<translate>Archived</translate>
</td>
<td>
{{ formatTime(model.DeletedAt) }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
@ -290,21 +280,15 @@
import Thumb from "model/thumb";
import {DateTime, Info} from "luxon";
import * as options from "options/options";
import {T} from "common/vm";
import Util from "common/util";
export default {
name: 'PTabPhotoAdvanced',
props: {
model: {
type: Object,
default: () => {
},
},
uid: {
type: String,
default: "",
default: () => {},
},
uid: String,
},
data() {
return {
@ -340,40 +324,6 @@ export default {
},
},
methods: {
async copyText(text) {
if (!text) {
return;
}
try {
await Util.copyToMachineClipboard(text);
this.$notify.success(this.$gettext("Copied to clipboard"));
} catch (error) {
this.$notify.error(this.$gettext("Failed copying to clipboard"));
}
},
sourceName(s) {
switch (s) {
case "":
case "auto":
case "manual":
return "";
case "meta":
return this.$gettext('Metadata');
case "xmp":
return "XMP";
case "estimate":
return this.$gettext("Estimate");
case "name":
return this.$gettext("Name");
case "image":
return this.$gettext("Image");
case "location":
return this.$gettext("Location");
default:
return T(Util.capitalize(s));
}
},
formatTime(s) {
return DateTime.fromISO(s).toLocaleString(DateTime.DATETIME_MED);
},
@ -391,7 +341,7 @@ export default {
return '#';
}
return this.$router.resolve({name: 'album', params: {uid: m.UID, slug: 'view'}}).href;
return this.$router.resolve({ name: 'album', params: { uid: m.UID, slug: 'view' }}).href;
},
},
};

View file

@ -1,117 +1,102 @@
<template>
<div class="p-tab p-tab-photo-labels">
<v-form ref="form" lazy-validation dense accept-charset="UTF-8" @submit.prevent>
<v-layout class="pa-2-md-and-up" row wrap align-top fill-height>
<v-flex class="pa-2 hidden-sm-and-down" xs12 md2 xxl1 fill-height>
<p-photo-preview :model="model"></p-photo-preview>
</v-flex>
<v-flex class="pa-2-md-and-up ra-4-table-md-and-up" xs12 md10 xxl11 fill-width fill-height>
<v-data-table
v-model="selected"
:headers="listColumns"
:items="model.Labels"
hide-actions
class="elevation-0 p-results"
disable-initial-sort
item-key="ID"
:no-data-text="$gettext('No labels found')"
<v-data-table
v-model="selected"
:headers="listColumns"
:items="model.Labels"
hide-actions
class="elevation-0 p-files p-files-list p-results"
disable-initial-sort
item-key="ID"
:no-data-text="$gettext('No labels found')"
>
<template #items="props" class="p-file">
<td>
<v-edit-dialog
:return-value.sync="props.item.Label.Name"
lazy
class="p-inline-edit"
@save="renameLabel(props.item.Label)"
>
<template #items="props" class="p-file">
<td>
<v-edit-dialog
:return-value.sync="props.item.Label.Name"
lazy
class="p-inline-edit"
@save="renameLabel(props.item.Label)"
>
{{ props.item.Label.Name }}
<template #input>
<v-text-field
v-model="props.item.Label.Name"
:rules="[nameRule]"
:label="$gettext('Name')"
color="secondary-dark"
class="input-rename background-inherit elevation-0"
single-line autofocus solo hide-details
></v-text-field>
</template>
</v-edit-dialog>
</td>
<td class="text-xs-left">{{ sourceName(props.item.LabelSrc) }}</td>
<td class="text-xs-center">{{ 100 - props.item.Uncertainty }}%</td>
<td class="text-xs-center">
<v-btn v-if="disabled" icon small flat :ripple="false"
class="action-view" title="Search"
@click.stop.prevent="searchLabel(props.item.Label)">
<v-icon color="secondary-dark">search</v-icon>
</v-btn>
<v-btn v-else-if="props.item.Uncertainty < 100 && props.item.LabelSrc === 'manual'" icon
small flat :ripple="false"
class="action-delete" title="Delete"
@click.stop.prevent="removeLabel(props.item.Label)">
<v-icon color="secondary-dark">delete</v-icon>
</v-btn>
<v-btn v-else-if="props.item.Uncertainty < 100" icon small flat :ripple="false"
class="action-remove" title="Remove"
@click.stop.prevent="removeLabel(props.item.Label)">
<v-icon color="secondary-dark">remove</v-icon>
</v-btn>
<v-btn v-else icon small flat :ripple="false"
class="action-on" title="Activate"
@click.stop.prevent="activateLabel(props.item.Label)">
<v-icon color="secondary-dark">add</v-icon>
</v-btn>
</td>
</template>
<template v-if="!disabled" #footer>
<td>
<v-text-field
v-model="newLabel"
{{ props.item.Label.Name }}
<template #input>
<v-text-field
v-model="props.item.Label.Name"
:rules="[nameRule]"
color="secondary-dark"
browser-autocomplete="off"
:label="$gettext('Name')"
single-line
flat solo hide-details
autofocus
class="input-label"
@keyup.enter.native="addLabel"
></v-text-field>
</td>
<td class="text-xs-left">{{ sourceName('manual') }}</td>
<td class="text-xs-center">100%</td>
<td class="text-xs-center">
<v-btn icon small flat :ripple="false" title="Add"
class="p-photo-label-add"
@click.stop.prevent="addLabel">
<v-icon color="secondary-dark">add</v-icon>
</v-btn>
</td>
color="secondary-dark"
class="input-rename background-inherit elevation-0"
single-line autofocus solo hide-details
></v-text-field>
</template>
</v-data-table>
</v-flex>
</v-layout>
<!-- div class="mt-1 clear"></div -->
</v-form>
</v-edit-dialog>
</td>
<td class="text-xs-left">{{ sourceName(props.item.LabelSrc) }}</td>
<td class="text-xs-center">{{ 100 - props.item.Uncertainty }}%</td>
<td class="text-xs-center">
<v-btn v-if="disabled" icon small flat :ripple="false"
class="action-view" title="Search"
@click.stop.prevent="searchLabel(props.item.Label)">
<v-icon color="secondary-dark">search</v-icon>
</v-btn>
<v-btn v-else-if="props.item.Uncertainty < 100 && props.item.LabelSrc === 'manual'" icon
small flat :ripple="false"
class="action-delete" title="Delete"
@click.stop.prevent="removeLabel(props.item.Label)">
<v-icon color="secondary-dark">delete</v-icon>
</v-btn>
<v-btn v-else-if="props.item.Uncertainty < 100" icon small flat :ripple="false"
class="action-remove" title="Remove"
@click.stop.prevent="removeLabel(props.item.Label)">
<v-icon color="secondary-dark">remove</v-icon>
</v-btn>
<v-btn v-else icon small flat :ripple="false"
class="action-on" title="Activate"
@click.stop.prevent="activateLabel(props.item.Label)">
<v-icon color="secondary-dark">add</v-icon>
</v-btn>
</td>
</template>
<template v-if="!disabled" #footer>
<td>
<v-text-field
v-model="newLabel"
:rules="[nameRule]"
color="secondary-dark"
browser-autocomplete="off"
:label="$gettext('Name')"
single-line
flat solo hide-details
autofocus
class="input-label"
@keyup.enter.native="addLabel"
></v-text-field>
</td>
<td class="text-xs-left">{{ sourceName('manual') }}</td>
<td class="text-xs-center">100%</td>
<td class="text-xs-center">
<v-btn icon small flat :ripple="false" title="Add"
class="p-photo-label-add"
@click.stop.prevent="addLabel">
<v-icon color="secondary-dark">add</v-icon>
</v-btn>
</td>
</template>
</v-data-table>
</div>
</template>
<script>
import Label from "model/label";
import Thumb from "model/thumb";
export default {
name: 'PTabPhotoLabels',
props: {
model: {
type: Object,
default: () => {
},
},
uid: {
type: String,
default: "",
default: () => {},
},
uid: String,
},
data() {
return {
@ -185,9 +170,6 @@ export default {
this.$router.push({name: 'all', query: {q: 'label:' + label.Slug}}).catch(() => {});
this.$emit('close');
},
openPhoto() {
this.$viewer.show(Thumb.fromFiles([this.model]), 0);
},
},
};
</script>

View file

@ -13,7 +13,7 @@
<translate>Recognition starts after indexing has been completed.</translate>
</p>
</v-alert>
<v-layout class="search-results face-results cards-view" row wrap fill-height>
<v-layout row wrap class="search-results face-results cards-view">
<v-flex
v-for="(marker, index) in markers"
:key="index"

View file

@ -62,16 +62,12 @@
<p class="px-2 ma-0 text-xs-right opacity-85"><span v-if="eta">{{ eta }}</span></p>
</v-progress-linear>
<p v-if="isDemo" class="body-2">
<translate :translate-params="{n: fileLimit}">You can upload up to %{n} files for test purposes.</translate>
<translate>Please do not upload any private, unlawful or offensive pictures. </translate>
</p>
<p v-else-if="rejectNSFW" class="body-2">
<p v-if="safe" class="body-1">
<translate>Please don't upload photos containing offensive content.</translate>
<translate>Uploads that may contain such images will be rejected automatically.</translate>
</p>
<p v-if="featReview" class="body-1">
<p v-if="review" class="body-1">
<translate>Non-photographic and low-quality images require a review before they appear in search results.</translate>
</p>
@ -103,13 +99,8 @@ export default {
name: 'PUploadDialog',
props: {
show: Boolean,
data: {
type: Object,
default: () => {},
},
},
data() {
const isDemo = this.$config.get("demo");
return {
albums: [],
selectedAlbums: [],
@ -129,29 +120,16 @@ export default {
remainingTime: -1,
eta: "",
token: "",
isDemo: isDemo,
fileLimit: isDemo ? 3 : 0,
rejectNSFW: !this.$config.get("uploadNSFW"),
featReview: this.$config.feature("review"),
review: this.$config.feature("review"),
safe: !this.$config.get("uploadNSFW"),
rtl: this.$rtl,
};
},
watch: {
show: function () {
this.reset();
this.isDemo = this.$config.get("demo");
this.fileLimit = this.isDemo ? 3 : 0;
this.rejectNSFW = !this.$config.get("uploadNSFW");
this.featReview = this.$config.feature("review");
// Set currently selected albums.
if (this.data && Array.isArray(this.data.albums)) {
this.selectedAlbums = this.data.albums;
} else {
this.selectedAlbums = [];
}
// Fetch albums from backend.
this.review = this.$config.feature("review");
this.safe = !this.$config.get("uploadNSFW");
this.findAlbums("");
}
},
@ -202,8 +180,8 @@ export default {
this.failed = false;
this.current = 0;
this.total = 0;
this.totalSize = 0;
this.totalFailed = 0;
this.totalSize = 0;
this.completedSize = 0;
this.completedTotal = 0;
this.started = 0;
@ -255,19 +233,10 @@ export default {
return;
}
const files = this.$refs.upload.files;
this.selected = this.$refs.upload.files;
this.total = this.selected.length;
// Too many files selected for upload?
if (this.isDemo && files && files.length > this.fileLimit) {
Notify.error(this.$gettext("Too many files selected"));
return;
}
this.selected = files;
this.total = files.length;
// No files selected?
if (!this.selected || this.total < 1) {
if (this.total < 1) {
return;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

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