Compare commits
No commits in common. "develop" and "230719-73fa7bbe8" have entirely different histories.
develop
...
230719-73f
527 changed files with 22417 additions and 42132 deletions
|
@ -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:
|
||||
|
|
|
@ -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
113
Makefile
|
@ -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)
|
||||
|
|
27
README.md
27
README.md
|
@ -2,19 +2,20 @@ PhotoPrism: Browse Your Life in Pictures
|
|||
========================================
|
||||
|
||||
[](https://docs.photoprism.app/license/agpl/)
|
||||
[](https://www.photoprism.app/about/team)
|
||||
[](https://docs.photoprism.app/)
|
||||
[](https://link.photoprism.app/chat)
|
||||
[](https://link.photoprism.app/discussions)
|
||||
[](https://photoprism.bsky.social/)
|
||||
[](https://floss.social/@photoprism)
|
||||
[](https://floss.social/@photoprism)
|
||||
[](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.
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
|
||||
----
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
@ -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 |
|
@ -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
|
||||
}]
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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 && \
|
||||
|
|
|
@ -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"]
|
|
@ -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 && \
|
||||
|
|
|
@ -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"]
|
|
@ -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"]
|
|
@ -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 \
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"]
|
3555
frontend/package-lock.json
generated
3555
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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')"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
];
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* Event Logs */
|
||||
|
||||
#photoprism .p-logs {
|
||||
color: white;
|
||||
|
|
26
frontend/src/css/maps.css
Normal file
26
frontend/src/css/maps.css
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Carbon Dark Theme */
|
||||
/* Carbon Theme */
|
||||
|
||||
body.dark-theme.theme-carbon,
|
||||
.theme-carbon .v-content__wrap,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Gemstone Dark Theme */
|
||||
/* Gemstone Theme */
|
||||
|
||||
body.dark-theme.theme-gemstone,
|
||||
.theme-gemstone .v-content__wrap,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Grayscale Dark Theme */
|
||||
/* Grayscale Theme */
|
||||
|
||||
body.dark-theme.theme-grayscale {
|
||||
background: #525252 !important;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Shadow Dark Theme */
|
||||
/* Shadow Theme */
|
||||
|
||||
body.dark-theme.theme-shadow {
|
||||
background: #444 !important;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Vanta Dark Theme */
|
||||
/* Vanta Theme */
|
||||
|
||||
body.dark-theme.theme-vanta {
|
||||
background: #212121 !important;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Yellowstone Dark Theme */
|
||||
/* Yellowstone Theme */
|
||||
|
||||
body.dark-theme.theme-yellowstone,
|
||||
.theme-yellowstone .v-content__wrap,
|
||||
|
|
|
@ -39,10 +39,6 @@ button.v-btn.compact {
|
|||
height: 34px;
|
||||
}
|
||||
|
||||
.caption.filename {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Line Height */
|
||||
|
||||
.lh-15 {
|
||||
|
|
|
@ -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 {};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
Loading…
Add table
Reference in a new issue