mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Frontend: Reformat JS code
This commit is contained in:
@@ -1,23 +1,67 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
commonjs: true,
|
commonjs: true,
|
||||||
es6: true,
|
es6: true,
|
||||||
node: true,
|
node: true,
|
||||||
mocha: true,
|
mocha: true,
|
||||||
},
|
},
|
||||||
extends: 'eslint:recommended',
|
extends: [
|
||||||
parserOptions: {
|
"eslint:recommended",
|
||||||
sourceType: 'module',
|
"plugin:vue/recommended",
|
||||||
},
|
"plugin:prettier-vue/recommended",
|
||||||
rules: {
|
// Do not add `'prettier/vue'` if you don't want to use prettier for `<template>` blocks
|
||||||
'comma-dangle': ['error', 'always-multiline'],
|
"prettier/vue",
|
||||||
indent: ['error', 4, { "SwitchCase": 1 }],
|
],
|
||||||
'linebreak-style': ['error', 'unix'],
|
|
||||||
quotes: ['error', 'double'], // Easier for Go developers!
|
settings: {
|
||||||
semi: ['error', 'always'],
|
"prettier-vue": {
|
||||||
'no-unused-vars': ['warn'],
|
// Settings for how to process Vue SFC Blocks
|
||||||
'no-console': 0,
|
SFCBlocks: {
|
||||||
'no-prototype-builtins': 0,
|
template: false,
|
||||||
|
script: true,
|
||||||
|
style: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Use prettierrc for prettier options or not (default: `true`)
|
||||||
|
usePrettierrc: true,
|
||||||
|
|
||||||
|
// Set the options for `prettier.getFileInfo`.
|
||||||
|
// @see https://prettier.io/docs/en/api.html#prettiergetfileinfofilepath-options
|
||||||
|
fileInfoOptions: {
|
||||||
|
// Path to ignore file (default: `'.prettierignore'`)
|
||||||
|
// Notice that the ignore file is only used for this plugin
|
||||||
|
ignorePath: ".testignore",
|
||||||
|
|
||||||
|
// Process the files in `node_modules` or not (default: `false`)
|
||||||
|
withNodeModules: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: "module",
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// 'comma-dangle': ['error', 'always-multiline'],
|
||||||
|
indent: ["error", 2, { SwitchCase: 1 }],
|
||||||
|
"linebreak-style": ["error", "unix"],
|
||||||
|
quotes: ["off", "double"], // Easier for Go developers!
|
||||||
|
semi: ["error", "always"],
|
||||||
|
"no-unused-vars": ["warn"],
|
||||||
|
"no-console": 0,
|
||||||
|
"no-prototype-builtins": 0,
|
||||||
|
"prettier-vue/prettier": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
// Override all options of `prettier` here
|
||||||
|
// @see https://prettier.io/docs/en/options.html
|
||||||
|
printWidth: 100,
|
||||||
|
singleQuote: false,
|
||||||
|
semi: true,
|
||||||
|
trailingComma: "es5",
|
||||||
|
htmlWhitespaceSensitivity: "strict",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,126 +34,130 @@ const findChrome = require("chrome-finder");
|
|||||||
process.env.CHROME_BIN = findChrome();
|
process.env.CHROME_BIN = findChrome();
|
||||||
|
|
||||||
module.exports = (config) => {
|
module.exports = (config) => {
|
||||||
config.set({
|
config.set({
|
||||||
logLevel: config.LOG_ERROR,
|
logLevel: config.LOG_ERROR,
|
||||||
|
|
||||||
webpackMiddleware: {
|
webpackMiddleware: {
|
||||||
stats: "errors-only",
|
stats: "errors-only",
|
||||||
},
|
},
|
||||||
|
|
||||||
frameworks: ["mocha"],
|
frameworks: ["mocha"],
|
||||||
|
|
||||||
browsers: ["LocalChrome"],
|
browsers: ["LocalChrome"],
|
||||||
|
|
||||||
customLaunchers: {
|
customLaunchers: {
|
||||||
LocalChrome: {
|
LocalChrome: {
|
||||||
base: "ChromeHeadless",
|
base: "ChromeHeadless",
|
||||||
flags: ["--disable-translate", "--disable-extensions", "--no-sandbox", "--disable-web-security", "--disable-dev-shm-usage"],
|
flags: [
|
||||||
},
|
"--disable-translate",
|
||||||
},
|
"--disable-extensions",
|
||||||
|
"--no-sandbox",
|
||||||
files: [
|
"--disable-web-security",
|
||||||
"node_modules/@babel/polyfill/dist/polyfill.js",
|
"--disable-dev-shm-usage",
|
||||||
"node_modules/regenerator-runtime/runtime/runtime.js",
|
|
||||||
{pattern: "tests/unit/**/*_test.js", watched: false},
|
|
||||||
],
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// Preprocess through webpack
|
files: [
|
||||||
preprocessors: {
|
"node_modules/@babel/polyfill/dist/polyfill.js",
|
||||||
"tests/unit/**/*_test.js": ["webpack"],
|
"node_modules/regenerator-runtime/runtime/runtime.js",
|
||||||
|
{ pattern: "tests/unit/**/*_test.js", watched: false },
|
||||||
|
],
|
||||||
|
|
||||||
|
// Preprocess through webpack
|
||||||
|
preprocessors: {
|
||||||
|
"tests/unit/**/*_test.js": ["webpack"],
|
||||||
|
},
|
||||||
|
|
||||||
|
reporters: ["progress", "html", "coverage-istanbul"],
|
||||||
|
|
||||||
|
htmlReporter: {
|
||||||
|
outputFile: "tests/unit.html",
|
||||||
|
},
|
||||||
|
|
||||||
|
coverageIstanbulReporter: {
|
||||||
|
// reports can be any that are listed here: https://github.com/istanbuljs/istanbuljs/tree/aae256fb8b9a3d19414dcf069c592e88712c32c6/packages/istanbul-reports/lib
|
||||||
|
reports: ["html", "lcovonly", "text-summary"],
|
||||||
|
|
||||||
|
// base output directory. If you include %browser% in the path it will be replaced with the karma browser name
|
||||||
|
dir: path.join(__dirname, "coverage"),
|
||||||
|
|
||||||
|
// Combines coverage information from multiple browsers into one report rather than outputting a report
|
||||||
|
// for each browser.
|
||||||
|
combineBrowserReports: true,
|
||||||
|
|
||||||
|
// if using webpack and pre-loaders, work around webpack breaking the source path
|
||||||
|
fixWebpackSourcePaths: true,
|
||||||
|
|
||||||
|
// Omit files with no statements, no functions and no branches from the report
|
||||||
|
skipFilesWithNoCoverage: true,
|
||||||
|
|
||||||
|
// Most reporters accept additional config options. You can pass these through the `report-config` option
|
||||||
|
"report-config": {
|
||||||
|
// all options available at: https://github.com/istanbuljs/istanbuljs/blob/aae256fb8b9a3d19414dcf069c592e88712c32c6/packages/istanbul-reports/lib/html/index.js#L135-L137
|
||||||
|
html: {
|
||||||
|
// outputs the report in ./coverage/html
|
||||||
|
subdir: "html",
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
reporters: ["progress", "html", "coverage-istanbul"],
|
// enforce percentage thresholds
|
||||||
|
// anything under these percentages will cause karma to fail with an exit code of 1 if not running in watch mode
|
||||||
htmlReporter: {
|
thresholds: {
|
||||||
outputFile: "tests/unit.html",
|
emitWarning: true, // set to `true` to not fail the test command when thresholds are not met
|
||||||
|
// thresholds for all files
|
||||||
|
global: {
|
||||||
|
//statements: 90,
|
||||||
|
lines: 90,
|
||||||
|
//branches: 90,
|
||||||
|
//functions: 90,
|
||||||
},
|
},
|
||||||
|
// thresholds per file
|
||||||
coverageIstanbulReporter: {
|
each: {
|
||||||
// reports can be any that are listed here: https://github.com/istanbuljs/istanbuljs/tree/aae256fb8b9a3d19414dcf069c592e88712c32c6/packages/istanbul-reports/lib
|
//statements: 90,
|
||||||
reports: ["html", "lcovonly", "text-summary"],
|
lines: 90,
|
||||||
|
//branches: 90,
|
||||||
// base output directory. If you include %browser% in the path it will be replaced with the karma browser name
|
//functions: 90,
|
||||||
dir: path.join(__dirname, "coverage"),
|
overrides: {
|
||||||
|
"src/common/viewer.js": {
|
||||||
// Combines coverage information from multiple browsers into one report rather than outputting a report
|
lines: 0,
|
||||||
// for each browser.
|
functions: 0,
|
||||||
combineBrowserReports: true,
|
|
||||||
|
|
||||||
// if using webpack and pre-loaders, work around webpack breaking the source path
|
|
||||||
fixWebpackSourcePaths: true,
|
|
||||||
|
|
||||||
// Omit files with no statements, no functions and no branches from the report
|
|
||||||
skipFilesWithNoCoverage: true,
|
|
||||||
|
|
||||||
// Most reporters accept additional config options. You can pass these through the `report-config` option
|
|
||||||
"report-config": {
|
|
||||||
// all options available at: https://github.com/istanbuljs/istanbuljs/blob/aae256fb8b9a3d19414dcf069c592e88712c32c6/packages/istanbul-reports/lib/html/index.js#L135-L137
|
|
||||||
html: {
|
|
||||||
// outputs the report in ./coverage/html
|
|
||||||
subdir: "html",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
// enforce percentage thresholds
|
|
||||||
// anything under these percentages will cause karma to fail with an exit code of 1 if not running in watch mode
|
|
||||||
thresholds: {
|
|
||||||
emitWarning: true, // set to `true` to not fail the test command when thresholds are not met
|
|
||||||
// thresholds for all files
|
|
||||||
global: {
|
|
||||||
//statements: 90,
|
|
||||||
lines: 90,
|
|
||||||
//branches: 90,
|
|
||||||
//functions: 90,
|
|
||||||
},
|
|
||||||
// thresholds per file
|
|
||||||
each: {
|
|
||||||
//statements: 90,
|
|
||||||
lines: 90,
|
|
||||||
//branches: 90,
|
|
||||||
//functions: 90,
|
|
||||||
overrides: {
|
|
||||||
"src/common/viewer.js": {
|
|
||||||
lines: 0,
|
|
||||||
functions: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
verbose: false, // output config used by istanbul for debugging
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
webpack: {
|
verbose: false, // output config used by istanbul for debugging
|
||||||
mode: "development",
|
},
|
||||||
|
|
||||||
resolve: {
|
webpack: {
|
||||||
modules: [
|
mode: "development",
|
||||||
path.join(__dirname, "src"),
|
|
||||||
path.join(__dirname, "node_modules"),
|
resolve: {
|
||||||
path.join(__dirname, "tests/unit"),
|
modules: [
|
||||||
],
|
path.join(__dirname, "src"),
|
||||||
alias: {
|
path.join(__dirname, "node_modules"),
|
||||||
vue: "vue/dist/vue.min.js",
|
path.join(__dirname, "tests/unit"),
|
||||||
},
|
],
|
||||||
},
|
alias: {
|
||||||
module: {
|
vue: "vue/dist/vue.min.js",
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
loader: "babel-loader",
|
|
||||||
exclude: file => (
|
|
||||||
/node_modules/.test(file)
|
|
||||||
),
|
|
||||||
query: {
|
|
||||||
presets: ["@babel/preset-env"],
|
|
||||||
compact: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
loader: "babel-loader",
|
||||||
|
exclude: (file) => /node_modules/.test(file),
|
||||||
|
query: {
|
||||||
|
presets: ["@babel/preset-env"],
|
||||||
|
compact: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
singleRun: true,
|
singleRun: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
152
frontend/package-lock.json
generated
152
frontend/package-lock.json
generated
@@ -1926,6 +1926,62 @@
|
|||||||
"@vue/shared": "3.0.2"
|
"@vue/shared": "3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@vue/component-compiler-utils": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-lejBLa7xAMsfiZfNp7Kv51zOzifnb29FwdnMLa96z26kXErPFioSf9BMcePVIQ6/Gc6/mC0UrPpxAWIHyae0vw==",
|
||||||
|
"requires": {
|
||||||
|
"consolidate": "^0.15.1",
|
||||||
|
"hash-sum": "^1.0.2",
|
||||||
|
"lru-cache": "^4.1.2",
|
||||||
|
"merge-source-map": "^1.1.0",
|
||||||
|
"postcss": "^7.0.14",
|
||||||
|
"postcss-selector-parser": "^6.0.2",
|
||||||
|
"prettier": "^1.18.2",
|
||||||
|
"source-map": "~0.6.1",
|
||||||
|
"vue-template-es2015-compiler": "^1.9.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"consolidate": {
|
||||||
|
"version": "0.15.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz",
|
||||||
|
"integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==",
|
||||||
|
"requires": {
|
||||||
|
"bluebird": "^3.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hash-sum": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ="
|
||||||
|
},
|
||||||
|
"lru-cache": {
|
||||||
|
"version": "4.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
|
||||||
|
"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
|
||||||
|
"requires": {
|
||||||
|
"pseudomap": "^1.0.2",
|
||||||
|
"yallist": "^2.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"version": "1.19.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz",
|
||||||
|
"integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"source-map": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||||
|
},
|
||||||
|
"yallist": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
||||||
|
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@vue/shared": {
|
"@vue/shared": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.0.2.tgz",
|
||||||
@@ -5040,6 +5096,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"eslint-config-prettier": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-8Y8lGLVPPZdaNA7JXqnvETVC7IiVRgAP6afQu9gOQRn90YY3otMNh+x7Vr2vMePQntF+5erdSUBqSzCmU/AxaQ=="
|
||||||
|
},
|
||||||
"eslint-config-standard": {
|
"eslint-config-standard": {
|
||||||
"version": "14.1.1",
|
"version": "14.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz",
|
||||||
@@ -5381,6 +5442,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"eslint-plugin-prettier-vue": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier-vue/-/eslint-plugin-prettier-vue-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-B9nYJCwf6508tc36fBU6a7QRwmp688Z8q6BPDvHLftR5KccqVNRkCUwPYjHXouw1m6NzdcRZraJ0A0jXwtNCTQ==",
|
||||||
|
"requires": {
|
||||||
|
"@vue/component-compiler-utils": "^3.1.2",
|
||||||
|
"chalk": "^4.0.0",
|
||||||
|
"prettier": "^1.18.2 || ^2.0.0",
|
||||||
|
"prettier-linter-helpers": "^1.0.0",
|
||||||
|
"vue-template-compiler": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"eslint-plugin-promise": {
|
"eslint-plugin-promise": {
|
||||||
"version": "4.2.1",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz",
|
||||||
@@ -5391,6 +5464,27 @@
|
|||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz",
|
||||||
"integrity": "sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ=="
|
"integrity": "sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ=="
|
||||||
},
|
},
|
||||||
|
"eslint-plugin-vue": {
|
||||||
|
"version": "7.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.3.0.tgz",
|
||||||
|
"integrity": "sha512-4rc9xrZgwT4aLz3XE6lrHu+FZtDLWennYvtzVvvS81kW9c65U4DUzQQWAFjDCgCFvN6HYWxi7ueEtxZVSB+f0g==",
|
||||||
|
"requires": {
|
||||||
|
"eslint-utils": "^2.1.0",
|
||||||
|
"natural-compare": "^1.4.0",
|
||||||
|
"semver": "^7.3.2",
|
||||||
|
"vue-eslint-parser": "^7.3.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"semver": {
|
||||||
|
"version": "7.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
|
||||||
|
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
|
||||||
|
"requires": {
|
||||||
|
"lru-cache": "^6.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"eslint-rule-docs": {
|
"eslint-rule-docs": {
|
||||||
"version": "1.1.213",
|
"version": "1.1.213",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-rule-docs/-/eslint-rule-docs-1.1.213.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-rule-docs/-/eslint-rule-docs-1.1.213.tgz",
|
||||||
@@ -5708,6 +5802,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||||
},
|
},
|
||||||
|
"fast-diff": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w=="
|
||||||
|
},
|
||||||
"fast-glob": {
|
"fast-glob": {
|
||||||
"version": "3.2.4",
|
"version": "3.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz",
|
||||||
@@ -8026,7 +8125,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz",
|
||||||
"integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==",
|
"integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==",
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"source-map": "^0.6.1"
|
"source-map": "^0.6.1"
|
||||||
},
|
},
|
||||||
@@ -8034,8 +8132,7 @@
|
|||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||||
"optional": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -10597,9 +10694,17 @@
|
|||||||
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
|
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"version": "1.19.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz",
|
||||||
"integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew=="
|
"integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q=="
|
||||||
|
},
|
||||||
|
"prettier-linter-helpers": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
|
||||||
|
"requires": {
|
||||||
|
"fast-diff": "^1.1.2"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"pretty-error": {
|
"pretty-error": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
@@ -13473,6 +13578,36 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz",
|
||||||
"integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg=="
|
"integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg=="
|
||||||
},
|
},
|
||||||
|
"vue-eslint-parser": {
|
||||||
|
"version": "7.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.3.0.tgz",
|
||||||
|
"integrity": "sha512-n5PJKZbyspD0+8LnaZgpEvNCrjQx1DyDHw8JdWwoxhhC+yRip4TAvSDpXGf9SWX6b0umeB5aR61gwUo6NVvFxw==",
|
||||||
|
"requires": {
|
||||||
|
"debug": "^4.1.1",
|
||||||
|
"eslint-scope": "^5.0.0",
|
||||||
|
"eslint-visitor-keys": "^1.1.0",
|
||||||
|
"espree": "^6.2.1",
|
||||||
|
"esquery": "^1.0.1",
|
||||||
|
"lodash": "^4.17.15"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"acorn": {
|
||||||
|
"version": "7.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
|
||||||
|
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="
|
||||||
|
},
|
||||||
|
"espree": {
|
||||||
|
"version": "6.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz",
|
||||||
|
"integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==",
|
||||||
|
"requires": {
|
||||||
|
"acorn": "^7.1.1",
|
||||||
|
"acorn-jsx": "^5.2.0",
|
||||||
|
"eslint-visitor-keys": "^1.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"vue-fullscreen": {
|
"vue-fullscreen": {
|
||||||
"version": "2.1.6",
|
"version": "2.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/vue-fullscreen/-/vue-fullscreen-2.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/vue-fullscreen/-/vue-fullscreen-2.1.6.tgz",
|
||||||
@@ -13637,6 +13772,11 @@
|
|||||||
"uniq": "^1.0.1"
|
"uniq": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"prettier": {
|
||||||
|
"version": "1.19.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz",
|
||||||
|
"integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew=="
|
||||||
|
},
|
||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"watch": "webpack --watch",
|
"watch": "webpack --watch",
|
||||||
"build": "webpack --optimize-minimize",
|
"build": "webpack --optimize-minimize",
|
||||||
"lint": "eslint --cache src/ *.js",
|
"lint": "eslint --cache src/ *.js",
|
||||||
"fmt": "eslint --cache --fix src/ *.js",
|
"fmt": "eslint --cache --fix src/ *.js .eslintrc.js",
|
||||||
"test": "karma start",
|
"test": "karma start",
|
||||||
"upgrade": "npm --depth 10 update && npm audit fix",
|
"upgrade": "npm --depth 10 update && npm audit fix",
|
||||||
"acceptance": "testcafe \"chromium:headless --disable-dev-shm-usage\" --skip-js-errors --selector-timeout 5000 -S -s tests/screenshots tests/acceptance",
|
"acceptance": "testcafe \"chromium:headless --disable-dev-shm-usage\" --skip-js-errors --selector-timeout 5000 -S -s tests/screenshots tests/acceptance",
|
||||||
@@ -50,6 +50,7 @@
|
|||||||
"cssnano": "^4.1.10",
|
"cssnano": "^4.1.10",
|
||||||
"easygettext": "^2.16.1",
|
"easygettext": "^2.16.1",
|
||||||
"eslint": "^7.15.0",
|
"eslint": "^7.15.0",
|
||||||
|
"eslint-config-prettier": "^7.0.0",
|
||||||
"eslint-config-standard": "^14.1.1",
|
"eslint-config-standard": "^14.1.1",
|
||||||
"eslint-formatter-pretty": "^4.0.0",
|
"eslint-formatter-pretty": "^4.0.0",
|
||||||
"eslint-friendly-formatter": "^4.0.1",
|
"eslint-friendly-formatter": "^4.0.1",
|
||||||
@@ -57,8 +58,10 @@
|
|||||||
"eslint-plugin-html": "^6.1.1",
|
"eslint-plugin-html": "^6.1.1",
|
||||||
"eslint-plugin-import": "^2.22.1",
|
"eslint-plugin-import": "^2.22.1",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
|
"eslint-plugin-prettier-vue": "^2.1.1",
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^4.2.1",
|
||||||
"eslint-plugin-standard": "^4.1.0",
|
"eslint-plugin-standard": "^4.1.0",
|
||||||
|
"eslint-plugin-vue": "^7.3.0",
|
||||||
"eventsource-polyfill": "^0.9.6",
|
"eventsource-polyfill": "^0.9.6",
|
||||||
"file-loader": "^3.0.1",
|
"file-loader": "^3.0.1",
|
||||||
"friendly-errors-webpack-plugin": "^1.7.0",
|
"friendly-errors-webpack-plugin": "^1.7.0",
|
||||||
@@ -94,6 +97,7 @@
|
|||||||
"postcss-preset-env": "^6.7.0",
|
"postcss-preset-env": "^6.7.0",
|
||||||
"postcss-reporter": "^6.0.1",
|
"postcss-reporter": "^6.0.1",
|
||||||
"postcss-url": "^8.0.0",
|
"postcss-url": "^8.0.0",
|
||||||
|
"prettier": "^2.2.1",
|
||||||
"pubsub-js": "^1.9.2",
|
"pubsub-js": "^1.9.2",
|
||||||
"puppeteer-core": "^5.5.0",
|
"puppeteer-core": "^5.5.0",
|
||||||
"regenerator-runtime": "^0.13.7",
|
"regenerator-runtime": "^0.13.7",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: {
|
plugins: {
|
||||||
"postcss-import": {},
|
"postcss-import": {},
|
||||||
"postcss-preset-env": {},
|
"postcss-preset-env": {},
|
||||||
"cssnano": {},
|
cssnano: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ import Log from "common/log";
|
|||||||
import PhotoPrism from "app.vue";
|
import PhotoPrism from "app.vue";
|
||||||
import Router from "vue-router";
|
import Router from "vue-router";
|
||||||
import Routes from "routes";
|
import Routes from "routes";
|
||||||
import {config, session} from "session";
|
import { config, session } from "session";
|
||||||
import {Settings} from "luxon";
|
import { Settings } from "luxon";
|
||||||
import Socket from "common/websocket";
|
import Socket from "common/websocket";
|
||||||
import Viewer from "common/viewer";
|
import Viewer from "common/viewer";
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
@@ -53,13 +53,15 @@ import VueFullscreen from "vue-fullscreen";
|
|||||||
import VueInfiniteScroll from "vue-infinite-scroll";
|
import VueInfiniteScroll from "vue-infinite-scroll";
|
||||||
import VueModal from "vue-js-modal";
|
import VueModal from "vue-js-modal";
|
||||||
import Hls from "hls.js";
|
import Hls from "hls.js";
|
||||||
import {$gettext, Mount} from "common/vm";
|
import { $gettext, Mount } from "common/vm";
|
||||||
|
|
||||||
// Initialize helpers
|
// Initialize helpers
|
||||||
const viewer = new Viewer();
|
const viewer = new Viewer();
|
||||||
const clipboard = new Clipboard(window.localStorage, "photo_clipboard");
|
const clipboard = new Clipboard(window.localStorage, "photo_clipboard");
|
||||||
const isPublic = config.get("public");
|
const isPublic = config.get("public");
|
||||||
const isMobile = (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent));
|
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
||||||
|
navigator.userAgent
|
||||||
|
);
|
||||||
|
|
||||||
// HTTP Live Streaming (video support)
|
// HTTP Live Streaming (video support)
|
||||||
window.Hls = Hls;
|
window.Hls = Hls;
|
||||||
@@ -77,23 +79,23 @@ Vue.prototype.$clipboard = clipboard;
|
|||||||
Vue.prototype.$isMobile = isMobile;
|
Vue.prototype.$isMobile = isMobile;
|
||||||
|
|
||||||
// Register Vuetify
|
// Register Vuetify
|
||||||
Vue.use(Vuetify, {"theme": config.theme});
|
Vue.use(Vuetify, { theme: config.theme });
|
||||||
|
|
||||||
Vue.config.language = config.values.settings.ui.language;
|
Vue.config.language = config.values.settings.ui.language;
|
||||||
Settings.defaultLocale = Vue.config.language.substring(0, 2);
|
Settings.defaultLocale = Vue.config.language.substring(0, 2);
|
||||||
|
|
||||||
// Register other VueJS plugins
|
// Register other VueJS plugins
|
||||||
Vue.use(GetTextPlugin, {
|
Vue.use(GetTextPlugin, {
|
||||||
translations: config.translations,
|
translations: config.translations,
|
||||||
silent: true, // !config.values.debug,
|
silent: true, // !config.values.debug,
|
||||||
defaultLanguage: Vue.config.language,
|
defaultLanguage: Vue.config.language,
|
||||||
autoAddKeyAttributes: true,
|
autoAddKeyAttributes: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
Vue.use(VueLuxon);
|
Vue.use(VueLuxon);
|
||||||
Vue.use(VueInfiniteScroll);
|
Vue.use(VueInfiniteScroll);
|
||||||
Vue.use(VueFullscreen);
|
Vue.use(VueFullscreen);
|
||||||
Vue.use(VueModal, {dynamic: true, dynamicDefaults: {clickToClose: true}});
|
Vue.use(VueModal, { dynamic: true, dynamicDefaults: { clickToClose: true } });
|
||||||
Vue.use(VueFilters);
|
Vue.use(VueFilters);
|
||||||
Vue.use(Components);
|
Vue.use(Components);
|
||||||
Vue.use(Dialogs);
|
Vue.use(Dialogs);
|
||||||
@@ -101,52 +103,52 @@ Vue.use(Router);
|
|||||||
|
|
||||||
// Configure client-side routing
|
// Configure client-side routing
|
||||||
const router = new Router({
|
const router = new Router({
|
||||||
routes: Routes,
|
routes: Routes,
|
||||||
mode: "history",
|
mode: "history",
|
||||||
saveScrollPosition: true,
|
saveScrollPosition: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
if (to.matched.some(record => record.meta.settings) && config.values.disable.settings) {
|
if (to.matched.some((record) => record.meta.settings) && config.values.disable.settings) {
|
||||||
next({name: "home"});
|
next({ name: "home" });
|
||||||
} else if (to.matched.some(record => record.meta.admin)) {
|
} else if (to.matched.some((record) => record.meta.admin)) {
|
||||||
if (isPublic || session.isAdmin()) {
|
if (isPublic || session.isAdmin()) {
|
||||||
next();
|
next();
|
||||||
} else {
|
|
||||||
next({
|
|
||||||
name: "login",
|
|
||||||
params: {nextUrl: to.fullPath},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (to.matched.some(record => record.meta.auth)) {
|
|
||||||
if (isPublic || session.isUser()) {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
next({
|
|
||||||
name: "login",
|
|
||||||
params: {nextUrl: to.fullPath},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
next();
|
next({
|
||||||
|
name: "login",
|
||||||
|
params: { nextUrl: to.fullPath },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
} else if (to.matched.some((record) => record.meta.auth)) {
|
||||||
|
if (isPublic || session.isUser()) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
next({
|
||||||
|
name: "login",
|
||||||
|
params: { nextUrl: to.fullPath },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.afterEach((to) => {
|
router.afterEach((to) => {
|
||||||
if (to.meta.title && config.values.siteTitle !== to.meta.title) {
|
if (to.meta.title && config.values.siteTitle !== to.meta.title) {
|
||||||
config.page.title = $gettext(to.meta.title);
|
config.page.title = $gettext(to.meta.title);
|
||||||
window.document.title = config.values.siteTitle + ": " + config.page.title;
|
window.document.title = config.values.siteTitle + ": " + config.page.title;
|
||||||
} else {
|
} else {
|
||||||
config.page.title = config.values.siteTitle;
|
config.page.title = config.values.siteTitle;
|
||||||
window.document.title = config.values.siteTitle + ": " + config.values.siteCaption;
|
window.document.title = config.values.siteTitle + ": " + config.values.siteCaption;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pull client config every 10 minutes in case push fails (except on mobile to save battery).
|
// Pull client config every 10 minutes in case push fails (except on mobile to save battery).
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
document.body.classList.add("mobile");
|
document.body.classList.add("mobile");
|
||||||
} else {
|
} else {
|
||||||
setInterval(() => config.update(), 600000);
|
setInterval(() => config.update(), 600000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start application.
|
// Start application.
|
||||||
|
|||||||
@@ -30,65 +30,73 @@ https://docs.photoprism.org/developer-guide/
|
|||||||
|
|
||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import Notify from "common/notify";
|
import Notify from "common/notify";
|
||||||
import {$gettext} from "./vm";
|
import { $gettext } from "./vm";
|
||||||
|
|
||||||
const testConfig = {"jsHash":"48019917", "cssHash":"2b327230", "version": "test"};
|
const testConfig = { jsHash: "48019917", cssHash: "2b327230", version: "test" };
|
||||||
const config = window.__CONFIG__ ? window.__CONFIG__ : testConfig;
|
const config = window.__CONFIG__ ? window.__CONFIG__ : testConfig;
|
||||||
|
|
||||||
const Api = Axios.create({
|
const Api = Axios.create({
|
||||||
baseURL: "/api/v1",
|
baseURL: "/api/v1",
|
||||||
headers: {common: {
|
headers: {
|
||||||
"X-Session-ID": window.localStorage.getItem("session_id"),
|
common: {
|
||||||
"X-Client-Hash": config.jsHash,
|
"X-Session-ID": window.localStorage.getItem("session_id"),
|
||||||
"X-Client-Version": config.version,
|
"X-Client-Hash": config.jsHash,
|
||||||
}},
|
"X-Client-Version": config.version,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Api.interceptors.request.use(function (config) {
|
Api.interceptors.request.use(
|
||||||
|
function (config) {
|
||||||
// Do something before request is sent
|
// Do something before request is sent
|
||||||
Notify.ajaxStart();
|
Notify.ajaxStart();
|
||||||
return config;
|
return config;
|
||||||
}, function (error) {
|
},
|
||||||
|
function (error) {
|
||||||
// Do something with request error
|
// Do something with request error
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
Api.interceptors.response.use(function (response) {
|
Api.interceptors.response.use(
|
||||||
|
function (response) {
|
||||||
Notify.ajaxEnd();
|
Notify.ajaxEnd();
|
||||||
|
|
||||||
if(typeof response.data == "string") {
|
if (typeof response.data == "string") {
|
||||||
Notify.error($gettext("Request failed - invalid response"));
|
Notify.error($gettext("Request failed - invalid response"));
|
||||||
console.warn("WARNING: Server returned HTML instead of JSON - API not implemented?");
|
console.warn("WARNING: Server returned HTML instead of JSON - API not implemented?");
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}, function (error) {
|
},
|
||||||
|
function (error) {
|
||||||
Notify.ajaxEnd();
|
Notify.ajaxEnd();
|
||||||
|
|
||||||
if (Axios.isCancel(error)) {
|
if (Axios.isCancel(error)) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(console && console.log) {
|
if (console && console.log) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
let errorMessage = $gettext("An error occurred - are you offline?");
|
let errorMessage = $gettext("An error occurred - are you offline?");
|
||||||
let code = error.code;
|
let code = error.code;
|
||||||
|
|
||||||
if(error.response && error.response.data) {
|
if (error.response && error.response.data) {
|
||||||
let data = error.response.data;
|
let data = error.response.data;
|
||||||
code = data.code;
|
code = data.code;
|
||||||
errorMessage = data.message ? data.message : data.error;
|
errorMessage = data.message ? data.message : data.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code === 401) {
|
if (code === 401) {
|
||||||
Notify.logout(errorMessage);
|
Notify.logout(errorMessage);
|
||||||
} else {
|
} else {
|
||||||
Notify.error(errorMessage);
|
Notify.error(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default Api;
|
export default Api;
|
||||||
|
|||||||
@@ -30,188 +30,188 @@ https://docs.photoprism.org/developer-guide/
|
|||||||
|
|
||||||
import RestModel from "model/rest";
|
import RestModel from "model/rest";
|
||||||
import Notify from "common/notify";
|
import Notify from "common/notify";
|
||||||
import {$gettext} from "./vm";
|
import { $gettext } from "./vm";
|
||||||
|
|
||||||
export const MaxItems = 999;
|
export const MaxItems = 999;
|
||||||
|
|
||||||
export default class Clipboard {
|
export default class Clipboard {
|
||||||
/**
|
/**
|
||||||
* @param {Storage} storage
|
* @param {Storage} storage
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
*/
|
*/
|
||||||
constructor(storage, key) {
|
constructor(storage, key) {
|
||||||
this.storageKey = key ? key : "clipboard";
|
this.storageKey = key ? key : "clipboard";
|
||||||
|
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.selectionMap = {};
|
this.selectionMap = {};
|
||||||
this.selection = [];
|
this.selection = [];
|
||||||
this.lastId = "";
|
this.lastId = "";
|
||||||
this.maxItems = MaxItems;
|
this.maxItems = MaxItems;
|
||||||
|
|
||||||
this.loadFromStorage();
|
this.loadFromStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
isModel(model) {
|
||||||
|
if (!model) {
|
||||||
|
console.warn("Clipboard::isModel() - empty model", model);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
isModel(model) {
|
if (typeof model.getId !== "function") {
|
||||||
if (!model) {
|
console.warn("Clipboard::isModel() - model.getId() is not a function", model);
|
||||||
console.warn("Clipboard::isModel() - empty model", model);
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof model.getId !== "function") {
|
|
||||||
console.warn("Clipboard::isModel() - model.getId() is not a function", model);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadFromStorage() {
|
return true;
|
||||||
const photosJson = this.storage.getItem(this.storageKey);
|
}
|
||||||
|
|
||||||
if (photosJson !== null && typeof photosJson !== "undefined") {
|
loadFromStorage() {
|
||||||
this.setIds(JSON.parse(photosJson));
|
const photosJson = this.storage.getItem(this.storageKey);
|
||||||
}
|
|
||||||
|
if (photosJson !== null && typeof photosJson !== "undefined") {
|
||||||
|
this.setIds(JSON.parse(photosJson));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveToStorage() {
|
||||||
|
this.storage.setItem(this.storageKey, JSON.stringify(this.selection));
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle(model) {
|
||||||
|
if (!this.isModel(model)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveToStorage() {
|
const id = model.getId();
|
||||||
this.storage.setItem(this.storageKey, JSON.stringify(this.selection));
|
this.toggleId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleId(id) {
|
||||||
|
const index = this.selection.indexOf(id);
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
if (this.selection.length >= this.maxItems) {
|
||||||
|
Notify.warn($gettext("Can't select more items"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selection.push(id);
|
||||||
|
this.selectionMap["id:" + id] = true;
|
||||||
|
this.lastId = id;
|
||||||
|
} else {
|
||||||
|
this.selection.splice(index, 1);
|
||||||
|
delete this.selectionMap["id:" + id];
|
||||||
|
this.lastId = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle(model) {
|
this.saveToStorage();
|
||||||
if (!this.isModel(model)) {
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = model.getId();
|
add(model) {
|
||||||
this.toggleId(id);
|
if (!this.isModel(model)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleId(id) {
|
const id = model.getId();
|
||||||
const index = this.selection.indexOf(id);
|
|
||||||
|
|
||||||
if (index === -1) {
|
this.addId(id);
|
||||||
if (this.selection.length >= this.maxItems) {
|
}
|
||||||
Notify.warn($gettext("Can't select more items"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.selection.push(id);
|
addId(id) {
|
||||||
this.selectionMap["id:" + id] = true;
|
if (this.hasId(id)) {
|
||||||
this.lastId = id;
|
return;
|
||||||
} else {
|
|
||||||
this.selection.splice(index, 1);
|
|
||||||
delete this.selectionMap["id:" + id];
|
|
||||||
this.lastId = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
this.saveToStorage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
add(model) {
|
if (this.selection.length >= this.maxItems) {
|
||||||
if (!this.isModel(model)) {
|
Notify.warn($gettext("Can't select more items"));
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
const id = model.getId();
|
|
||||||
|
|
||||||
this.addId(id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addId(id) {
|
this.selection.push(id);
|
||||||
if (this.hasId(id)) {
|
this.selectionMap["id:" + id] = true;
|
||||||
return;
|
this.lastId = id;
|
||||||
}
|
|
||||||
|
|
||||||
if (this.selection.length >= this.maxItems) {
|
this.saveToStorage();
|
||||||
Notify.warn($gettext("Can't select more items"));
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.selection.push(id);
|
addRange(rangeEnd, models) {
|
||||||
this.selectionMap["id:" + id] = true;
|
if (!models || !models[rangeEnd] || !(models[rangeEnd] instanceof RestModel)) {
|
||||||
this.lastId = id;
|
console.warn("Clipboard::addRange() - invalid arguments:", rangeEnd, models);
|
||||||
|
return;
|
||||||
this.saveToStorage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addRange(rangeEnd, models) {
|
let rangeStart = models.findIndex((photo) => photo.UID === this.lastId);
|
||||||
if (!models || !models[rangeEnd] || !(models[rangeEnd] instanceof RestModel)) {
|
|
||||||
console.warn("Clipboard::addRange() - invalid arguments:", rangeEnd, models);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let rangeStart = models.findIndex((photo) => photo.UID === this.lastId);
|
if (rangeStart === -1) {
|
||||||
|
this.toggle(models[rangeEnd]);
|
||||||
if (rangeStart === -1) {
|
return 1;
|
||||||
this.toggle(models[rangeEnd]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rangeStart > rangeEnd) {
|
|
||||||
const newEnd = rangeStart;
|
|
||||||
rangeStart = rangeEnd;
|
|
||||||
rangeEnd = newEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = rangeStart; i <= rangeEnd; i++) {
|
|
||||||
this.add(models[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (rangeEnd - rangeStart) + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
has(model) {
|
if (rangeStart > rangeEnd) {
|
||||||
if (!this.isModel(model)) {
|
const newEnd = rangeStart;
|
||||||
return;
|
rangeStart = rangeEnd;
|
||||||
}
|
rangeEnd = newEnd;
|
||||||
|
|
||||||
return this.hasId(model.getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasId(id) {
|
for (let i = rangeStart; i <= rangeEnd; i++) {
|
||||||
return typeof this.selectionMap["id:" + id] !== "undefined";
|
this.add(models[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(model) {
|
return rangeEnd - rangeStart + 1;
|
||||||
if (!this.isModel(model)) {
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.removeId(model.getId());
|
has(model) {
|
||||||
|
if (!this.isModel(model)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeId(id) {
|
return this.hasId(model.getId());
|
||||||
if (!this.hasId(id)) return;
|
}
|
||||||
|
|
||||||
const index = this.selection.indexOf(id);
|
hasId(id) {
|
||||||
|
return typeof this.selectionMap["id:" + id] !== "undefined";
|
||||||
|
}
|
||||||
|
|
||||||
this.selection.splice(index, 1);
|
remove(model) {
|
||||||
this.lastId = "";
|
if (!this.isModel(model)) {
|
||||||
delete this.selectionMap["id:" + id];
|
return;
|
||||||
|
|
||||||
this.saveToStorage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getIds() {
|
this.removeId(model.getId());
|
||||||
return this.selection;
|
}
|
||||||
|
|
||||||
|
removeId(id) {
|
||||||
|
if (!this.hasId(id)) return;
|
||||||
|
|
||||||
|
const index = this.selection.indexOf(id);
|
||||||
|
|
||||||
|
this.selection.splice(index, 1);
|
||||||
|
this.lastId = "";
|
||||||
|
delete this.selectionMap["id:" + id];
|
||||||
|
|
||||||
|
this.saveToStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
getIds() {
|
||||||
|
return this.selection;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIds(ids) {
|
||||||
|
if (!Array.isArray(ids)) return;
|
||||||
|
|
||||||
|
this.selection = ids;
|
||||||
|
this.selectionMap = {};
|
||||||
|
this.lastId = "";
|
||||||
|
|
||||||
|
for (let i = 0; i < this.selection.length; i++) {
|
||||||
|
this.selectionMap["id:" + this.selection[i]] = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setIds(ids) {
|
clear() {
|
||||||
if (!Array.isArray(ids)) return;
|
this.lastId = "";
|
||||||
|
this.selectionMap = {};
|
||||||
this.selection = ids;
|
this.selection.splice(0, this.selection.length);
|
||||||
this.selectionMap = {};
|
this.storage.removeItem(this.storageKey);
|
||||||
this.lastId = "";
|
}
|
||||||
|
|
||||||
for (let i = 0; i < this.selection.length; i++) {
|
|
||||||
this.selectionMap["id:" + this.selection[i]] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
this.lastId = "";
|
|
||||||
this.selectionMap = {};
|
|
||||||
this.selection.splice(0, this.selection.length);
|
|
||||||
this.storage.removeItem(this.storageKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,198 +34,198 @@ import translations from "locales/translations.json";
|
|||||||
import Api from "./api";
|
import Api from "./api";
|
||||||
|
|
||||||
export default class Config {
|
export default class Config {
|
||||||
/**
|
/**
|
||||||
* @param {Storage} storage
|
* @param {Storage} storage
|
||||||
* @param {object} values
|
* @param {object} values
|
||||||
*/
|
*/
|
||||||
constructor(storage, values) {
|
constructor(storage, values) {
|
||||||
this.disconnected = false;
|
this.disconnected = false;
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.storage_key = "config";
|
this.storage_key = "config";
|
||||||
|
|
||||||
this.$vuetify = null;
|
this.$vuetify = null;
|
||||||
this.translations = translations;
|
this.translations = translations;
|
||||||
|
|
||||||
if (!values || !values.siteTitle) {
|
if (!values || !values.siteTitle) {
|
||||||
console.warn("config: values are empty");
|
console.warn("config: values are empty");
|
||||||
this.debug = true;
|
this.debug = true;
|
||||||
this.demo = false;
|
this.demo = false;
|
||||||
this.values = {};
|
this.values = {};
|
||||||
this.page = {
|
this.page = {
|
||||||
title: "PhotoPrism",
|
title: "PhotoPrism",
|
||||||
caption: "Browse Your Life",
|
caption: "Browse Your Life",
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
this.page = {
|
|
||||||
title: values.siteTitle,
|
|
||||||
caption: values.siteCaption,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.values = values;
|
|
||||||
this.debug = !!values.debug;
|
|
||||||
this.demo = !!values.demo;
|
|
||||||
|
|
||||||
Event.subscribe("config.updated", (ev, data) => this.setValues(data.config));
|
|
||||||
Event.subscribe("count", (ev, data) => this.onCount(ev, data));
|
|
||||||
|
|
||||||
if (this.has("settings")) {
|
|
||||||
this.setTheme(this.get("settings").ui.theme);
|
|
||||||
} else {
|
|
||||||
this.setTheme("default");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
this.page = {
|
||||||
Api.get("config").then(
|
title: values.siteTitle,
|
||||||
(response) => this.setValues(response.data),
|
caption: values.siteCaption,
|
||||||
() => console.warn("failed pulling updated client config")
|
};
|
||||||
);
|
|
||||||
|
this.values = values;
|
||||||
|
this.debug = !!values.debug;
|
||||||
|
this.demo = !!values.demo;
|
||||||
|
|
||||||
|
Event.subscribe("config.updated", (ev, data) => this.setValues(data.config));
|
||||||
|
Event.subscribe("count", (ev, data) => this.onCount(ev, data));
|
||||||
|
|
||||||
|
if (this.has("settings")) {
|
||||||
|
this.setTheme(this.get("settings").ui.theme);
|
||||||
|
} else {
|
||||||
|
this.setTheme("default");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
Api.get("config").then(
|
||||||
|
(response) => this.setValues(response.data),
|
||||||
|
() => console.warn("failed pulling updated client config")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setValues(values) {
|
||||||
|
if (!values) return;
|
||||||
|
|
||||||
|
if (this.debug) {
|
||||||
|
console.log("config: new values", values);
|
||||||
}
|
}
|
||||||
|
|
||||||
setValues(values) {
|
if (values.jsHash && this.values.jsHash !== values.jsHash) {
|
||||||
if (!values) return;
|
Event.publish("dialog.reload", { values });
|
||||||
|
|
||||||
if (this.debug) {
|
|
||||||
console.log("config: new values", values);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (values.jsHash && this.values.jsHash !== values.jsHash) {
|
|
||||||
Event.publish("dialog.reload", {values});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let key in values) {
|
|
||||||
if (values.hasOwnProperty(key)) {
|
|
||||||
this.set(key, values[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (values.settings) {
|
|
||||||
this.setTheme(values.settings.ui.theme);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onCount(ev, data) {
|
for (let key in values) {
|
||||||
const type = ev.split(".")[1];
|
if (values.hasOwnProperty(key)) {
|
||||||
|
this.set(key, values[key]);
|
||||||
switch (type) {
|
}
|
||||||
case "cameras":
|
|
||||||
this.values.count.cameras += data.count;
|
|
||||||
this.update();
|
|
||||||
break;
|
|
||||||
case "lenses":
|
|
||||||
this.values.count.lenses += data.count;
|
|
||||||
break;
|
|
||||||
case "countries":
|
|
||||||
this.values.count.countries += data.count;
|
|
||||||
this.update();
|
|
||||||
break;
|
|
||||||
case "states":
|
|
||||||
this.values.count.states += data.count;
|
|
||||||
break;
|
|
||||||
case "places":
|
|
||||||
this.values.count.places += data.count;
|
|
||||||
break;
|
|
||||||
case "labels":
|
|
||||||
this.values.count.labels += data.count;
|
|
||||||
break;
|
|
||||||
case "videos":
|
|
||||||
this.values.count.videos += data.count;
|
|
||||||
break;
|
|
||||||
case "albums":
|
|
||||||
this.values.count.albums += data.count;
|
|
||||||
break;
|
|
||||||
case "moments":
|
|
||||||
this.values.count.moments += data.count;
|
|
||||||
break;
|
|
||||||
case "months":
|
|
||||||
this.values.count.months += data.count;
|
|
||||||
break;
|
|
||||||
case "folders":
|
|
||||||
this.values.count.folders += data.count;
|
|
||||||
break;
|
|
||||||
case "files":
|
|
||||||
this.values.count.files += data.count;
|
|
||||||
break;
|
|
||||||
case "favorites":
|
|
||||||
this.values.count.favorites += data.count;
|
|
||||||
break;
|
|
||||||
case "review":
|
|
||||||
this.values.count.review += data.count;
|
|
||||||
break;
|
|
||||||
case "private":
|
|
||||||
this.values.count.private += data.count;
|
|
||||||
break;
|
|
||||||
case "photos":
|
|
||||||
this.values.count.photos += data.count;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.warn("unknown count type", ev, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.values.count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setVuetify(instance) {
|
if (values.settings) {
|
||||||
this.$vuetify = instance;
|
this.setTheme(values.settings.ui.theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTheme(name) {
|
return this;
|
||||||
this.theme = themes[name] ? themes[name] : themes["default"];
|
}
|
||||||
|
|
||||||
if (this.$vuetify) {
|
onCount(ev, data) {
|
||||||
this.$vuetify.theme = this.theme;
|
const type = ev.split(".")[1];
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
switch (type) {
|
||||||
|
case "cameras":
|
||||||
|
this.values.count.cameras += data.count;
|
||||||
|
this.update();
|
||||||
|
break;
|
||||||
|
case "lenses":
|
||||||
|
this.values.count.lenses += data.count;
|
||||||
|
break;
|
||||||
|
case "countries":
|
||||||
|
this.values.count.countries += data.count;
|
||||||
|
this.update();
|
||||||
|
break;
|
||||||
|
case "states":
|
||||||
|
this.values.count.states += data.count;
|
||||||
|
break;
|
||||||
|
case "places":
|
||||||
|
this.values.count.places += data.count;
|
||||||
|
break;
|
||||||
|
case "labels":
|
||||||
|
this.values.count.labels += data.count;
|
||||||
|
break;
|
||||||
|
case "videos":
|
||||||
|
this.values.count.videos += data.count;
|
||||||
|
break;
|
||||||
|
case "albums":
|
||||||
|
this.values.count.albums += data.count;
|
||||||
|
break;
|
||||||
|
case "moments":
|
||||||
|
this.values.count.moments += data.count;
|
||||||
|
break;
|
||||||
|
case "months":
|
||||||
|
this.values.count.months += data.count;
|
||||||
|
break;
|
||||||
|
case "folders":
|
||||||
|
this.values.count.folders += data.count;
|
||||||
|
break;
|
||||||
|
case "files":
|
||||||
|
this.values.count.files += data.count;
|
||||||
|
break;
|
||||||
|
case "favorites":
|
||||||
|
this.values.count.favorites += data.count;
|
||||||
|
break;
|
||||||
|
case "review":
|
||||||
|
this.values.count.review += data.count;
|
||||||
|
break;
|
||||||
|
case "private":
|
||||||
|
this.values.count.private += data.count;
|
||||||
|
break;
|
||||||
|
case "photos":
|
||||||
|
this.values.count.photos += data.count;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn("unknown count type", ev, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
getValues() {
|
this.values.count;
|
||||||
return this.values;
|
}
|
||||||
|
|
||||||
|
setVuetify(instance) {
|
||||||
|
this.$vuetify = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTheme(name) {
|
||||||
|
this.theme = themes[name] ? themes[name] : themes["default"];
|
||||||
|
|
||||||
|
if (this.$vuetify) {
|
||||||
|
this.$vuetify.theme = this.theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
storeValues() {
|
return this;
|
||||||
this.storage.setItem(this.storage_key, JSON.stringify(this.getValues()));
|
}
|
||||||
return this;
|
|
||||||
|
getValues() {
|
||||||
|
return this.values;
|
||||||
|
}
|
||||||
|
|
||||||
|
storeValues() {
|
||||||
|
this.storage.setItem(this.storage_key, JSON.stringify(this.getValues()));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key, value) {
|
||||||
|
this.values[key] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
has(key) {
|
||||||
|
return !!this.values[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
return this.values[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
feature(name) {
|
||||||
|
return this.values.settings.features[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
settings() {
|
||||||
|
return this.values.settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadToken() {
|
||||||
|
return this.values["downloadToken"];
|
||||||
|
}
|
||||||
|
|
||||||
|
previewToken() {
|
||||||
|
return this.values["previewToken"];
|
||||||
|
}
|
||||||
|
|
||||||
|
albumCategories() {
|
||||||
|
if (this.values["albumCategories"]) {
|
||||||
|
return this.values["albumCategories"];
|
||||||
}
|
}
|
||||||
|
|
||||||
set(key, value) {
|
return [];
|
||||||
this.values[key] = value;
|
}
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
has(key) {
|
|
||||||
return !!this.values[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
get(key) {
|
|
||||||
return this.values[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
feature(name) {
|
|
||||||
return this.values.settings.features[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
settings() {
|
|
||||||
return this.values.settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadToken() {
|
|
||||||
return this.values["downloadToken"];
|
|
||||||
}
|
|
||||||
|
|
||||||
previewToken() {
|
|
||||||
return this.values["previewToken"];
|
|
||||||
}
|
|
||||||
|
|
||||||
albumCategories() {
|
|
||||||
if (this.values["albumCategories"]) {
|
|
||||||
return this.values["albumCategories"];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,80 +29,80 @@ https://docs.photoprism.org/developer-guide/
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export const FormPropertyType = Object.freeze({
|
export const FormPropertyType = Object.freeze({
|
||||||
String: "string",
|
String: "string",
|
||||||
Number: "number",
|
Number: "number",
|
||||||
Object: "object",
|
Object: "object",
|
||||||
});
|
});
|
||||||
|
|
||||||
export default class Form {
|
export default class Form {
|
||||||
constructor(definition) {
|
constructor(definition) {
|
||||||
this.definition = definition;
|
this.definition = definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
setValues(values) {
|
||||||
|
const def = this.getDefinition();
|
||||||
|
|
||||||
|
for (let prop in def) {
|
||||||
|
if (values.hasOwnProperty(prop)) {
|
||||||
|
this.setValue(prop, values[prop]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setValues(values) {
|
return this;
|
||||||
const def = this.getDefinition();
|
}
|
||||||
|
|
||||||
for (let prop in def) {
|
getValues() {
|
||||||
if (values.hasOwnProperty(prop)) {
|
const result = {};
|
||||||
this.setValue(prop, values[prop]);
|
const def = this.getDefinition();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
for (let prop in def) {
|
||||||
|
result[prop] = this.getValue(prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
getValues() {
|
return result;
|
||||||
const result = {};
|
}
|
||||||
const def = this.getDefinition();
|
|
||||||
|
|
||||||
for (let prop in def) {
|
setValue(name, value) {
|
||||||
result[prop] = this.getValue(prop);
|
const def = this.getDefinition();
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
if (!def.hasOwnProperty(name)) {
|
||||||
|
throw `Property ${name} not found`;
|
||||||
|
} else if (typeof value != def[name].type) {
|
||||||
|
throw `Property ${name} must be ${def[name].type}`;
|
||||||
|
} else {
|
||||||
|
def[name].value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue(name, value) {
|
return this;
|
||||||
const def = this.getDefinition();
|
}
|
||||||
|
|
||||||
if (!def.hasOwnProperty(name)) {
|
getValue(name) {
|
||||||
throw `Property ${name} not found`;
|
const def = this.getDefinition();
|
||||||
} else if (typeof value != def[name].type) {
|
|
||||||
throw `Property ${name} must be ${def[name].type}`;
|
|
||||||
} else {
|
|
||||||
def[name].value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
if (def.hasOwnProperty(name)) {
|
||||||
|
return def[name].value;
|
||||||
|
} else {
|
||||||
|
throw `Property ${name} not found`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setDefinition(definition) {
|
||||||
|
this.definition = definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefinition() {
|
||||||
|
return this.definition ? this.definition : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
getOptions(fieldName) {
|
||||||
|
if (
|
||||||
|
this.definition &&
|
||||||
|
this.definition.hasOwnProperty(fieldName) &&
|
||||||
|
this.definition[fieldName].hasOwnProperty("options")
|
||||||
|
) {
|
||||||
|
return this.definition[fieldName].options;
|
||||||
}
|
}
|
||||||
|
|
||||||
getValue(name) {
|
return [{ option: "", label: "" }];
|
||||||
const def = this.getDefinition();
|
}
|
||||||
|
|
||||||
if (def.hasOwnProperty(name)) {
|
|
||||||
return def[name].value;
|
|
||||||
} else {
|
|
||||||
throw `Property ${name} not found`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setDefinition(definition) {
|
|
||||||
this.definition = definition;
|
|
||||||
}
|
|
||||||
|
|
||||||
getDefinition() {
|
|
||||||
return this.definition ? this.definition : {};
|
|
||||||
}
|
|
||||||
|
|
||||||
getOptions(fieldName) {
|
|
||||||
if (
|
|
||||||
this.definition &&
|
|
||||||
this.definition.hasOwnProperty(fieldName) &&
|
|
||||||
this.definition[fieldName].hasOwnProperty("options")
|
|
||||||
) {
|
|
||||||
return this.definition[fieldName].options;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [{ option: "", label: "" }];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,35 +31,35 @@ https://docs.photoprism.org/developer-guide/
|
|||||||
import Event from "pubsub-js";
|
import Event from "pubsub-js";
|
||||||
|
|
||||||
class Log {
|
class Log {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.cap = 150;
|
this.cap = 150;
|
||||||
this.created = new Date;
|
this.created = new Date();
|
||||||
this.logs = [
|
this.logs = [
|
||||||
/* EXAMPLE LOG MESSAGE
|
/* EXAMPLE LOG MESSAGE
|
||||||
{
|
{
|
||||||
"message": "waiting for events",
|
"message": "waiting for events",
|
||||||
"level": "debug",
|
"level": "debug",
|
||||||
"time": this.created.toISOString(),
|
"time": this.created.toISOString(),
|
||||||
},
|
},
|
||||||
*/
|
*/
|
||||||
];
|
];
|
||||||
|
|
||||||
this.logId = 0;
|
this.logId = 0;
|
||||||
|
|
||||||
Event.subscribe("log", this.onLog.bind(this));
|
Event.subscribe("log", this.onLog.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
onLog(ev, data) {
|
onLog(ev, data) {
|
||||||
data.id = this.logId++;
|
data.id = this.logId++;
|
||||||
|
|
||||||
this.logs.unshift(data);
|
this.logs.unshift(data);
|
||||||
|
|
||||||
if(this.logs.length > this.cap) {
|
if (this.logs.length > this.cap) {
|
||||||
this.logs.splice(this.cap);
|
this.logs.splice(this.cap);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const log = new Log;
|
const log = new Log();
|
||||||
|
|
||||||
export default log;
|
export default log;
|
||||||
|
|||||||
@@ -29,48 +29,48 @@ https://docs.photoprism.org/developer-guide/
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Event from "pubsub-js";
|
import Event from "pubsub-js";
|
||||||
import {$gettext} from "./vm";
|
import { $gettext } from "./vm";
|
||||||
|
|
||||||
const Notify = {
|
const Notify = {
|
||||||
info: function (message) {
|
info: function (message) {
|
||||||
Event.publish("notify.info", {message});
|
Event.publish("notify.info", { message });
|
||||||
},
|
},
|
||||||
warn: function (message) {
|
warn: function (message) {
|
||||||
Event.publish("notify.warning", {message});
|
Event.publish("notify.warning", { message });
|
||||||
},
|
},
|
||||||
error: function (message) {
|
error: function (message) {
|
||||||
Event.publish("notify.error", {message});
|
Event.publish("notify.error", { message });
|
||||||
},
|
},
|
||||||
success: function (message) {
|
success: function (message) {
|
||||||
Event.publish("notify.success", {message});
|
Event.publish("notify.success", { message });
|
||||||
},
|
},
|
||||||
logout: function (message) {
|
logout: function (message) {
|
||||||
Event.publish("notify.error", {message});
|
Event.publish("notify.error", { message });
|
||||||
Event.publish("session.logout", {message});
|
Event.publish("session.logout", { message });
|
||||||
},
|
},
|
||||||
ajaxStart: function() {
|
ajaxStart: function () {
|
||||||
Event.publish("ajax.start");
|
Event.publish("ajax.start");
|
||||||
},
|
},
|
||||||
ajaxEnd: function() {
|
ajaxEnd: function () {
|
||||||
Event.publish("ajax.end");
|
Event.publish("ajax.end");
|
||||||
},
|
},
|
||||||
blockUI: function() {
|
blockUI: function () {
|
||||||
const el = document.getElementById("busy-overlay");
|
const el = document.getElementById("busy-overlay");
|
||||||
|
|
||||||
if(el) {
|
if (el) {
|
||||||
el.style.display = "block";
|
el.style.display = "block";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
unblockUI: function() {
|
unblockUI: function () {
|
||||||
const el = document.getElementById("busy-overlay");
|
const el = document.getElementById("busy-overlay");
|
||||||
|
|
||||||
if(el) {
|
if (el) {
|
||||||
el.style.display = "none";
|
el.style.display = "none";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
wait: function () {
|
wait: function () {
|
||||||
this.warn($gettext("Busy, please wait…"));
|
this.warn($gettext("Busy, please wait…"));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Notify;
|
export default Notify;
|
||||||
|
|||||||
@@ -34,217 +34,213 @@ import User from "model/user";
|
|||||||
import Socket from "./websocket";
|
import Socket from "./websocket";
|
||||||
|
|
||||||
export default class Session {
|
export default class Session {
|
||||||
/**
|
/**
|
||||||
* @param {Storage} storage
|
* @param {Storage} storage
|
||||||
* @param {Config} config
|
* @param {Config} config
|
||||||
*/
|
*/
|
||||||
constructor(storage, config) {
|
constructor(storage, config) {
|
||||||
this.auth = false;
|
this.auth = false;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
|
||||||
if (storage.getItem("session_storage") === "true") {
|
if (storage.getItem("session_storage") === "true") {
|
||||||
this.storage = window.sessionStorage;
|
this.storage = window.sessionStorage;
|
||||||
} else {
|
} else {
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.applyId(this.storage.getItem("session_id"))) {
|
if (this.applyId(this.storage.getItem("session_id"))) {
|
||||||
const dataJson = this.storage.getItem("data");
|
const dataJson = this.storage.getItem("data");
|
||||||
this.data = dataJson !== "undefined" ? JSON.parse(dataJson) : null;
|
this.data = dataJson !== "undefined" ? JSON.parse(dataJson) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.data && this.data.user) {
|
if (this.data && this.data.user) {
|
||||||
this.user = new User(this.data.user);
|
this.user = new User(this.data.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isUser()) {
|
if (this.isUser()) {
|
||||||
this.auth = true;
|
this.auth = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Event.subscribe("session.logout", () => {
|
Event.subscribe("session.logout", () => {
|
||||||
return this.onLogout();
|
return this.onLogout();
|
||||||
|
});
|
||||||
|
|
||||||
|
Event.subscribe("websocket.connected", () => {
|
||||||
|
this.sendClientInfo();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.sendClientInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
useSessionStorage() {
|
||||||
|
this.deleteId();
|
||||||
|
this.storage.setItem("session_storage", "true");
|
||||||
|
this.storage = window.sessionStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
useLocalStorage() {
|
||||||
|
this.storage.setItem("session_storage", "false");
|
||||||
|
this.storage = window.localStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyId(id) {
|
||||||
|
if (!id) {
|
||||||
|
this.deleteId();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.session_id = id;
|
||||||
|
Api.defaults.headers.common["X-Session-ID"] = id;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
setId(id) {
|
||||||
|
this.storage.setItem("session_id", id);
|
||||||
|
return this.applyId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfig(values) {
|
||||||
|
this.config.setValues(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
getId() {
|
||||||
|
return this.session_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasId() {
|
||||||
|
return !!this.session_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteId() {
|
||||||
|
this.session_id = null;
|
||||||
|
this.storage.removeItem("session_id");
|
||||||
|
delete Api.defaults.headers.common["X-Session-ID"];
|
||||||
|
this.deleteData();
|
||||||
|
}
|
||||||
|
|
||||||
|
setData(data) {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data = data;
|
||||||
|
this.user = new User(this.data.user);
|
||||||
|
this.storage.setItem("data", JSON.stringify(data));
|
||||||
|
this.auth = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUser() {
|
||||||
|
return this.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEmail() {
|
||||||
|
if (this.isUser()) {
|
||||||
|
return this.user.PrimaryEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
getNickName() {
|
||||||
|
if (this.isUser()) {
|
||||||
|
return this.user.NickName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
getFullName() {
|
||||||
|
if (this.isUser()) {
|
||||||
|
return this.user.FullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
isUser() {
|
||||||
|
return this.user && this.user.hasId();
|
||||||
|
}
|
||||||
|
|
||||||
|
isAdmin() {
|
||||||
|
return this.user && this.user.hasId() && this.user.RoleAdmin;
|
||||||
|
}
|
||||||
|
|
||||||
|
isAnonymous() {
|
||||||
|
return !this.user || !this.user.hasId();
|
||||||
|
}
|
||||||
|
|
||||||
|
hasToken(token) {
|
||||||
|
if (!this.data || !this.data.tokens) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.data.tokens.indexOf(token) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteData() {
|
||||||
|
this.auth = false;
|
||||||
|
this.user = new User();
|
||||||
|
this.data = null;
|
||||||
|
this.storage.removeItem("data");
|
||||||
|
}
|
||||||
|
|
||||||
|
sendClientInfo() {
|
||||||
|
const clientInfo = {
|
||||||
|
session: this.getId(),
|
||||||
|
js: window.__CONFIG__.jsHash,
|
||||||
|
css: window.__CONFIG__.cssHash,
|
||||||
|
version: window.__CONFIG__.version,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
Socket.send(JSON.stringify(clientInfo));
|
||||||
|
} catch (e) {
|
||||||
|
if (this.config.debug) {
|
||||||
|
console.log("session: can't use websocket, not connected (yet)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
login(username, password, token) {
|
||||||
|
this.deleteId();
|
||||||
|
|
||||||
|
return Api.post("session", { username, password, token }).then((resp) => {
|
||||||
|
this.setConfig(resp.data.config);
|
||||||
|
this.setId(resp.data.id);
|
||||||
|
this.setData(resp.data.data);
|
||||||
|
this.sendClientInfo();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
redeemToken(token) {
|
||||||
|
return Api.post("session", { token }).then((resp) => {
|
||||||
|
this.setConfig(resp.data.config);
|
||||||
|
this.setId(resp.data.id);
|
||||||
|
this.setData(resp.data.data);
|
||||||
|
this.sendClientInfo();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onLogout(noRedirect) {
|
||||||
|
this.deleteId();
|
||||||
|
if (noRedirect !== true) {
|
||||||
|
window.location = "/";
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
logout(noRedirect) {
|
||||||
|
if (this.hasId()) {
|
||||||
|
return Api.delete("session/" + this.getId())
|
||||||
|
.then(() => {
|
||||||
|
return this.onLogout(noRedirect);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return this.onLogout(noRedirect);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
Event.subscribe("websocket.connected", () => {
|
return this.onLogout(noRedirect);
|
||||||
this.sendClientInfo();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.sendClientInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
useSessionStorage() {
|
|
||||||
this.deleteId();
|
|
||||||
this.storage.setItem("session_storage", "true");
|
|
||||||
this.storage = window.sessionStorage;
|
|
||||||
}
|
|
||||||
|
|
||||||
useLocalStorage() {
|
|
||||||
this.storage.setItem("session_storage", "false");
|
|
||||||
this.storage = window.localStorage;
|
|
||||||
}
|
|
||||||
|
|
||||||
applyId(id) {
|
|
||||||
if (!id) {
|
|
||||||
this.deleteId();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.session_id = id;
|
|
||||||
Api.defaults.headers.common["X-Session-ID"] = id;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
setId(id) {
|
|
||||||
this.storage.setItem("session_id", id);
|
|
||||||
return this.applyId(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
setConfig(values) {
|
|
||||||
this.config.setValues(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
getId() {
|
|
||||||
return this.session_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasId() {
|
|
||||||
return !!this.session_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteId() {
|
|
||||||
this.session_id = null;
|
|
||||||
this.storage.removeItem("session_id");
|
|
||||||
delete Api.defaults.headers.common["X-Session-ID"];
|
|
||||||
this.deleteData();
|
|
||||||
}
|
|
||||||
|
|
||||||
setData(data) {
|
|
||||||
if (!data) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.data = data;
|
|
||||||
this.user = new User(this.data.user);
|
|
||||||
this.storage.setItem("data", JSON.stringify(data));
|
|
||||||
this.auth = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
getUser() {
|
|
||||||
return this.user;
|
|
||||||
}
|
|
||||||
|
|
||||||
getEmail() {
|
|
||||||
if (this.isUser()) {
|
|
||||||
return this.user.PrimaryEmail;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
getNickName() {
|
|
||||||
if (this.isUser()) {
|
|
||||||
return this.user.NickName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
getFullName() {
|
|
||||||
if (this.isUser()) {
|
|
||||||
return this.user.FullName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
isUser() {
|
|
||||||
return this.user && this.user.hasId();
|
|
||||||
}
|
|
||||||
|
|
||||||
isAdmin() {
|
|
||||||
return this.user && this.user.hasId() && this.user.RoleAdmin;
|
|
||||||
}
|
|
||||||
|
|
||||||
isAnonymous() {
|
|
||||||
return !this.user || !this.user.hasId();
|
|
||||||
}
|
|
||||||
|
|
||||||
hasToken(token) {
|
|
||||||
if (!this.data || !this.data.tokens) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.data.tokens.indexOf(token) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteData() {
|
|
||||||
this.auth = false;
|
|
||||||
this.user = new User;
|
|
||||||
this.data = null;
|
|
||||||
this.storage.removeItem("data");
|
|
||||||
}
|
|
||||||
|
|
||||||
sendClientInfo() {
|
|
||||||
const clientInfo = {
|
|
||||||
"session": this.getId(),
|
|
||||||
"js": window.__CONFIG__.jsHash,
|
|
||||||
"css": window.__CONFIG__.cssHash,
|
|
||||||
"version": window.__CONFIG__.version,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
Socket.send(JSON.stringify(clientInfo));
|
|
||||||
} catch (e) {
|
|
||||||
if (this.config.debug) {
|
|
||||||
console.log("session: can't use websocket, not connected (yet)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
login(username, password, token) {
|
|
||||||
this.deleteId();
|
|
||||||
|
|
||||||
return Api.post("session", {username, password, token}).then(
|
|
||||||
(resp) => {
|
|
||||||
this.setConfig(resp.data.config);
|
|
||||||
this.setId(resp.data.id);
|
|
||||||
this.setData(resp.data.data);
|
|
||||||
this.sendClientInfo();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
redeemToken(token) {
|
|
||||||
return Api.post("session", {token}).then(
|
|
||||||
(resp) => {
|
|
||||||
this.setConfig(resp.data.config);
|
|
||||||
this.setId(resp.data.id);
|
|
||||||
this.setData(resp.data.data);
|
|
||||||
this.sendClientInfo();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onLogout(noRedirect) {
|
|
||||||
this.deleteId();
|
|
||||||
if (noRedirect !== true) {
|
|
||||||
window.location = "/";
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
logout(noRedirect) {
|
|
||||||
if (this.hasId()) {
|
|
||||||
return Api.delete("session/" + this.getId())
|
|
||||||
.then(() => {
|
|
||||||
return this.onLogout(noRedirect);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
return this.onLogout(noRedirect);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return this.onLogout(noRedirect);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,94 +36,93 @@ const Minute = 60 * Second;
|
|||||||
const Hour = 60 * Minute;
|
const Hour = 60 * Minute;
|
||||||
|
|
||||||
export default class Util {
|
export default class Util {
|
||||||
static duration(d) {
|
static duration(d) {
|
||||||
let u = d;
|
let u = d;
|
||||||
|
|
||||||
let neg = d < 0;
|
let neg = d < 0;
|
||||||
|
|
||||||
if (neg) {
|
if (neg) {
|
||||||
u = -u;
|
u = -u;
|
||||||
}
|
|
||||||
|
|
||||||
if (u < Second) {
|
|
||||||
// Special case: if duration is smaller than a second,
|
|
||||||
// use smaller units, like 1.2ms
|
|
||||||
if (!u) {
|
|
||||||
return "0s";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (u < Microsecond) {
|
|
||||||
return u + "ns";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (u < Millisecond) {
|
|
||||||
return Math.round(u / Microsecond) + "µs";
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.round(u / Millisecond) + "ms";
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = [];
|
|
||||||
|
|
||||||
let h = Math.floor(u / Hour);
|
|
||||||
let min = Math.floor(u / Minute)%60;
|
|
||||||
let sec = Math.ceil(u / Second)%60;
|
|
||||||
|
|
||||||
result.push(h.toString().padStart(2, "0"));
|
|
||||||
result.push(min.toString().padStart(2, "0"));
|
|
||||||
result.push(sec.toString().padStart(2, "0"));
|
|
||||||
|
|
||||||
// return `${h}h${min}m${sec}s`
|
|
||||||
|
|
||||||
return result.join(":");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static arabicToRoman(number) {
|
if (u < Second) {
|
||||||
let roman = "";
|
// Special case: if duration is smaller than a second,
|
||||||
const romanNumList = {
|
// use smaller units, like 1.2ms
|
||||||
M: 1000,
|
if (!u) {
|
||||||
CM: 900,
|
return "0s";
|
||||||
D: 500,
|
}
|
||||||
CD: 400,
|
|
||||||
C: 100,
|
|
||||||
XC: 90,
|
|
||||||
L: 50,
|
|
||||||
XV: 40,
|
|
||||||
X: 10,
|
|
||||||
IX: 9,
|
|
||||||
V: 5,
|
|
||||||
IV: 4,
|
|
||||||
I: 1,
|
|
||||||
};
|
|
||||||
let a;
|
|
||||||
if (number < 1 || number > 3999)
|
|
||||||
return "";
|
|
||||||
else {
|
|
||||||
for (let key in romanNumList) {
|
|
||||||
a = Math.floor(number / romanNumList[key]);
|
|
||||||
if (a >= 0) {
|
|
||||||
for (let i = 0; i < a; i++) {
|
|
||||||
roman += key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
number = number % romanNumList[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return roman;
|
if (u < Microsecond) {
|
||||||
|
return u + "ns";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (u < Millisecond) {
|
||||||
|
return Math.round(u / Microsecond) + "µs";
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.round(u / Millisecond) + "ms";
|
||||||
}
|
}
|
||||||
|
|
||||||
static truncate(str, length, ending) {
|
let result = [];
|
||||||
if (length == null) {
|
|
||||||
length = 100;
|
let h = Math.floor(u / Hour);
|
||||||
}
|
let min = Math.floor(u / Minute) % 60;
|
||||||
if (ending == null) {
|
let sec = Math.ceil(u / Second) % 60;
|
||||||
ending = "…";
|
|
||||||
}
|
result.push(h.toString().padStart(2, "0"));
|
||||||
if (str.length > length) {
|
result.push(min.toString().padStart(2, "0"));
|
||||||
return str.substring(0, length - ending.length) + ending;
|
result.push(sec.toString().padStart(2, "0"));
|
||||||
} else {
|
|
||||||
return str;
|
// return `${h}h${min}m${sec}s`
|
||||||
|
|
||||||
|
return result.join(":");
|
||||||
|
}
|
||||||
|
|
||||||
|
static arabicToRoman(number) {
|
||||||
|
let roman = "";
|
||||||
|
const romanNumList = {
|
||||||
|
M: 1000,
|
||||||
|
CM: 900,
|
||||||
|
D: 500,
|
||||||
|
CD: 400,
|
||||||
|
C: 100,
|
||||||
|
XC: 90,
|
||||||
|
L: 50,
|
||||||
|
XV: 40,
|
||||||
|
X: 10,
|
||||||
|
IX: 9,
|
||||||
|
V: 5,
|
||||||
|
IV: 4,
|
||||||
|
I: 1,
|
||||||
|
};
|
||||||
|
let a;
|
||||||
|
if (number < 1 || number > 3999) return "";
|
||||||
|
else {
|
||||||
|
for (let key in romanNumList) {
|
||||||
|
a = Math.floor(number / romanNumList[key]);
|
||||||
|
if (a >= 0) {
|
||||||
|
for (let i = 0; i < a; i++) {
|
||||||
|
roman += key;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
number = number % romanNumList[key];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return roman;
|
||||||
|
}
|
||||||
|
|
||||||
|
static truncate(str, length, ending) {
|
||||||
|
if (length == null) {
|
||||||
|
length = 100;
|
||||||
|
}
|
||||||
|
if (ending == null) {
|
||||||
|
ending = "…";
|
||||||
|
}
|
||||||
|
if (str.length > length) {
|
||||||
|
return str.substring(0, length - ending.length) + ending;
|
||||||
|
} else {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,151 +36,186 @@ import stripHtml from "string-strip-html";
|
|||||||
const thumbs = window.__CONFIG__.thumbs;
|
const thumbs = window.__CONFIG__.thumbs;
|
||||||
|
|
||||||
class Viewer {
|
class Viewer {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.el = null;
|
this.el = null;
|
||||||
this.gallery = null;
|
this.gallery = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEl() {
|
||||||
|
if (!this.el) {
|
||||||
|
this.el = document.getElementById("p-photo-viewer");
|
||||||
|
|
||||||
|
if (this.el === null) {
|
||||||
|
let err = "no photo viewer element found";
|
||||||
|
console.warn(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getEl() {
|
return this.el;
|
||||||
if (!this.el) {
|
}
|
||||||
this.el = document.getElementById("p-photo-viewer");
|
|
||||||
|
|
||||||
if (this.el === null) {
|
show(items, index = 0) {
|
||||||
let err = "no photo viewer element found";
|
if (!Array.isArray(items) || items.length === 0 || index >= items.length) {
|
||||||
console.warn(err);
|
console.log("photo list passed to gallery was empty:", items);
|
||||||
throw err;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shareButtons = [
|
||||||
|
{
|
||||||
|
id: "fit_720",
|
||||||
|
template: "Tiny (size)",
|
||||||
|
label: "Tiny",
|
||||||
|
url: "{{raw_image_url}}",
|
||||||
|
download: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "fit_1280",
|
||||||
|
template: "Small (size)",
|
||||||
|
label: "Small",
|
||||||
|
url: "{{raw_image_url}}",
|
||||||
|
download: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "fit_2048",
|
||||||
|
template: "Medium (size)",
|
||||||
|
label: "Medium",
|
||||||
|
url: "{{raw_image_url}}",
|
||||||
|
download: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "fit_2560",
|
||||||
|
template: "Large (size)",
|
||||||
|
label: "Large",
|
||||||
|
url: "{{raw_image_url}}",
|
||||||
|
download: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "original",
|
||||||
|
template: "Original (size)",
|
||||||
|
label: "Original",
|
||||||
|
url: "{{raw_image_url}}",
|
||||||
|
download: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
index: index,
|
||||||
|
history: true,
|
||||||
|
preload: [1, 1],
|
||||||
|
focus: true,
|
||||||
|
modal: true,
|
||||||
|
closeEl: true,
|
||||||
|
captionEl: true,
|
||||||
|
fullscreenEl: true,
|
||||||
|
zoomEl: true,
|
||||||
|
shareEl: true,
|
||||||
|
shareButtons: shareButtons,
|
||||||
|
counterEl: false,
|
||||||
|
arrowEl: true,
|
||||||
|
preloaderEl: true,
|
||||||
|
addCaptionHTMLFn: function (item, captionEl /*, isFake */) {
|
||||||
|
// item - slide object
|
||||||
|
// captionEl - caption DOM element
|
||||||
|
// isFake - true when content is added to fake caption container
|
||||||
|
// (used to get size of next or previous caption)
|
||||||
|
|
||||||
|
if (!item.title) {
|
||||||
|
captionEl.children[0].innerHTML = "";
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.el;
|
captionEl.children[0].innerHTML = stripHtml(item.title);
|
||||||
}
|
|
||||||
|
|
||||||
show(items, index = 0) {
|
if (item.playable) {
|
||||||
if (!Array.isArray(items) || items.length === 0 || index >= items.length) {
|
captionEl.children[0].innerHTML +=
|
||||||
console.log("photo list passed to gallery was empty:", items);
|
' <i aria-hidden="true" class="v-icon material-icons theme--dark">movie_creation</i>';
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const shareButtons = [
|
if (item.description) {
|
||||||
{id: "fit_720", template: "Tiny (size)", label: "Tiny", url: "{{raw_image_url}}", download: true},
|
captionEl.children[0].innerHTML +=
|
||||||
{id: "fit_1280", template: "Small (size)", label: "Small", url: "{{raw_image_url}}", download: true},
|
'<br><span class="description">' + stripHtml(item.description) + "</span>";
|
||||||
{id: "fit_2048", template: "Medium (size)", label: "Medium", url: "{{raw_image_url}}", download: true},
|
|
||||||
{id: "fit_2560", template: "Large (size)", label: "Large", url: "{{raw_image_url}}", download: true},
|
|
||||||
{id: "original", template: "Original (size)", label: "Original", url: "{{raw_image_url}}", download: true},
|
|
||||||
];
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
index: index,
|
|
||||||
history: true,
|
|
||||||
preload: [1, 1],
|
|
||||||
focus: true,
|
|
||||||
modal: true,
|
|
||||||
closeEl: true,
|
|
||||||
captionEl: true,
|
|
||||||
fullscreenEl: true,
|
|
||||||
zoomEl: true,
|
|
||||||
shareEl: true,
|
|
||||||
shareButtons: shareButtons,
|
|
||||||
counterEl: false,
|
|
||||||
arrowEl: true,
|
|
||||||
preloaderEl: true,
|
|
||||||
addCaptionHTMLFn: function(item, captionEl /*, isFake */) {
|
|
||||||
// item - slide object
|
|
||||||
// captionEl - caption DOM element
|
|
||||||
// isFake - true when content is added to fake caption container
|
|
||||||
// (used to get size of next or previous caption)
|
|
||||||
|
|
||||||
if(!item.title) {
|
|
||||||
captionEl.children[0].innerHTML = "";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
captionEl.children[0].innerHTML = stripHtml(item.title);
|
|
||||||
|
|
||||||
if(item.playable) {
|
|
||||||
captionEl.children[0].innerHTML += " <i aria-hidden=\"true\" class=\"v-icon material-icons theme--dark\">movie_creation</i>";
|
|
||||||
}
|
|
||||||
|
|
||||||
if(item.description) {
|
|
||||||
captionEl.children[0].innerHTML += "<br><span class=\"description\">" + stripHtml(item.description) + "</span>";
|
|
||||||
}
|
|
||||||
|
|
||||||
if(item.playable) {
|
|
||||||
captionEl.children[0].innerHTML = "<button>" + captionEl.children[0].innerHTML + "</button>";
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let gallery = new PhotoSwipe(this.getEl(), PhotoSwipeUI_Default, items, options);
|
|
||||||
let realViewportWidth;
|
|
||||||
let realViewportHeight;
|
|
||||||
let previousSize;
|
|
||||||
let nextSize;
|
|
||||||
let firstResize = true;
|
|
||||||
let photoSrcWillChange;
|
|
||||||
|
|
||||||
this.gallery = gallery;
|
|
||||||
|
|
||||||
Event.publish("viewer.show");
|
|
||||||
|
|
||||||
gallery.listen("close", () => {
|
|
||||||
Event.publish("viewer.pause");
|
|
||||||
Event.publish("viewer.hide");
|
|
||||||
});
|
|
||||||
gallery.listen("shareLinkClick", () => Event.publish("viewer.pause"));
|
|
||||||
gallery.listen("initialZoomIn", () => Event.publish("viewer.pause"));
|
|
||||||
gallery.listen("initialZoomOut", () => Event.publish("viewer.pause"));
|
|
||||||
|
|
||||||
gallery.listen("beforeChange", () => Event.publish("viewer.change", {gallery: gallery, item: gallery.currItem}));
|
|
||||||
|
|
||||||
gallery.listen("beforeResize", () => {
|
|
||||||
realViewportWidth = gallery.viewportSize.x * window.devicePixelRatio;
|
|
||||||
realViewportHeight = gallery.viewportSize.y * window.devicePixelRatio;
|
|
||||||
|
|
||||||
if (!previousSize) {
|
|
||||||
previousSize = "tile_720";
|
|
||||||
}
|
|
||||||
|
|
||||||
nextSize = this.constructor.mapViewportToImageSize(realViewportWidth, realViewportHeight);
|
|
||||||
|
|
||||||
if (nextSize !== previousSize) {
|
|
||||||
photoSrcWillChange = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (photoSrcWillChange && !firstResize) {
|
|
||||||
gallery.invalidateCurrItems();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstResize) {
|
|
||||||
firstResize = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
photoSrcWillChange = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
gallery.listen("gettingData", function (index, item) {
|
|
||||||
item.src = item[nextSize].src;
|
|
||||||
item.w = item[nextSize].w;
|
|
||||||
item.h = item[nextSize].h;
|
|
||||||
previousSize = nextSize;
|
|
||||||
});
|
|
||||||
|
|
||||||
gallery.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
static mapViewportToImageSize(viewportWidth, viewportHeight) {
|
|
||||||
for (let i = 0; i < thumbs.length; i++) {
|
|
||||||
let t = thumbs[i];
|
|
||||||
|
|
||||||
if (t.w >= viewportWidth || t.h >= viewportHeight) {
|
|
||||||
return t.size;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "fit_7680";
|
if (item.playable) {
|
||||||
|
captionEl.children[0].innerHTML =
|
||||||
|
"<button>" + captionEl.children[0].innerHTML + "</button>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let gallery = new PhotoSwipe(this.getEl(), PhotoSwipeUI_Default, items, options);
|
||||||
|
let realViewportWidth;
|
||||||
|
let realViewportHeight;
|
||||||
|
let previousSize;
|
||||||
|
let nextSize;
|
||||||
|
let firstResize = true;
|
||||||
|
let photoSrcWillChange;
|
||||||
|
|
||||||
|
this.gallery = gallery;
|
||||||
|
|
||||||
|
Event.publish("viewer.show");
|
||||||
|
|
||||||
|
gallery.listen("close", () => {
|
||||||
|
Event.publish("viewer.pause");
|
||||||
|
Event.publish("viewer.hide");
|
||||||
|
});
|
||||||
|
gallery.listen("shareLinkClick", () => Event.publish("viewer.pause"));
|
||||||
|
gallery.listen("initialZoomIn", () => Event.publish("viewer.pause"));
|
||||||
|
gallery.listen("initialZoomOut", () => Event.publish("viewer.pause"));
|
||||||
|
|
||||||
|
gallery.listen("beforeChange", () =>
|
||||||
|
Event.publish("viewer.change", { gallery: gallery, item: gallery.currItem })
|
||||||
|
);
|
||||||
|
|
||||||
|
gallery.listen("beforeResize", () => {
|
||||||
|
realViewportWidth = gallery.viewportSize.x * window.devicePixelRatio;
|
||||||
|
realViewportHeight = gallery.viewportSize.y * window.devicePixelRatio;
|
||||||
|
|
||||||
|
if (!previousSize) {
|
||||||
|
previousSize = "tile_720";
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSize = this.constructor.mapViewportToImageSize(realViewportWidth, realViewportHeight);
|
||||||
|
|
||||||
|
if (nextSize !== previousSize) {
|
||||||
|
photoSrcWillChange = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (photoSrcWillChange && !firstResize) {
|
||||||
|
gallery.invalidateCurrItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstResize) {
|
||||||
|
firstResize = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
photoSrcWillChange = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
gallery.listen("gettingData", function (index, item) {
|
||||||
|
item.src = item[nextSize].src;
|
||||||
|
item.w = item[nextSize].w;
|
||||||
|
item.h = item[nextSize].h;
|
||||||
|
previousSize = nextSize;
|
||||||
|
});
|
||||||
|
|
||||||
|
gallery.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
static mapViewportToImageSize(viewportWidth, viewportHeight) {
|
||||||
|
for (let i = 0; i < thumbs.length; i++) {
|
||||||
|
let t = thumbs[i];
|
||||||
|
|
||||||
|
if (t.w >= viewportWidth || t.h >= viewportHeight) {
|
||||||
|
return t.size;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return "fit_7680";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Viewer;
|
export default Viewer;
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
export let vm = {
|
export let vm = {
|
||||||
$gettext: (msgid) => msgid,
|
$gettext: (msgid) => msgid,
|
||||||
$ngettext: (msgid, plural, n) => { return n > 1 ? plural : msgid; },
|
$ngettext: (msgid, plural, n) => {
|
||||||
$pgettext:(context, msgid) => msgid,
|
return n > 1 ? plural : msgid;
|
||||||
$npgettext: (context, msgid) => msgid,
|
},
|
||||||
|
$pgettext: (context, msgid) => msgid,
|
||||||
|
$npgettext: (context, msgid) => msgid,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function $gettext (msgid) {
|
export function $gettext(msgid) {
|
||||||
return vm.$gettext(msgid);
|
return vm.$gettext(msgid);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function $ngettext (msgid, plural, n) {
|
export function $ngettext(msgid, plural, n) {
|
||||||
return vm.$ngettext(msgid, plural, n);
|
return vm.$ngettext(msgid, plural, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Mount (Vue, app, router) {
|
export function Mount(Vue, app, router) {
|
||||||
vm = new Vue({
|
vm = new Vue({
|
||||||
router,
|
router,
|
||||||
render: h => h(app),
|
render: (h) => h(app),
|
||||||
}).$mount("#photoprism");
|
}).$mount("#photoprism");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,31 +30,31 @@ https://docs.photoprism.org/developer-guide/
|
|||||||
|
|
||||||
import Sockette from "sockette";
|
import Sockette from "sockette";
|
||||||
import Event from "pubsub-js";
|
import Event from "pubsub-js";
|
||||||
import {config} from "session";
|
import { config } from "session";
|
||||||
|
|
||||||
const host = window.location.host;
|
const host = window.location.host;
|
||||||
const prot = ("https:" === document.location.protocol ? "wss://" : "ws://");
|
const prot = "https:" === document.location.protocol ? "wss://" : "ws://";
|
||||||
const url = prot + host + "/api/v1/ws";
|
const url = prot + host + "/api/v1/ws";
|
||||||
|
|
||||||
const Socket = new Sockette(url, {
|
const Socket = new Sockette(url, {
|
||||||
timeout: 5e3,
|
timeout: 5e3,
|
||||||
onopen: e => {
|
onopen: (e) => {
|
||||||
console.log("websocket: connected");
|
console.log("websocket: connected");
|
||||||
config.disconnected = false;
|
config.disconnected = false;
|
||||||
document.body.classList.remove("disconnected");
|
document.body.classList.remove("disconnected");
|
||||||
Event.publish("websocket.connected", e);
|
Event.publish("websocket.connected", e);
|
||||||
},
|
},
|
||||||
onmessage: e => {
|
onmessage: (e) => {
|
||||||
const m = JSON.parse(e.data);
|
const m = JSON.parse(e.data);
|
||||||
Event.publish(m.event, m.data);
|
Event.publish(m.event, m.data);
|
||||||
},
|
},
|
||||||
onreconnect: () => console.log("websocket: reconnecting"),
|
onreconnect: () => console.log("websocket: reconnecting"),
|
||||||
onmaximum: () => console.warn("websocket: hit max reconnect limit"),
|
onmaximum: () => console.warn("websocket: hit max reconnect limit"),
|
||||||
onclose: () => {
|
onclose: () => {
|
||||||
console.warn("websocket: disconnected");
|
console.warn("websocket: disconnected");
|
||||||
config.disconnected = true;
|
config.disconnected = true;
|
||||||
document.body.classList.add("disconnected");
|
document.body.classList.add("disconnected");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default Socket;
|
export default Socket;
|
||||||
|
|||||||
@@ -48,22 +48,22 @@ import PAboutFooter from "./footer.vue";
|
|||||||
const components = {};
|
const components = {};
|
||||||
|
|
||||||
components.install = (Vue) => {
|
components.install = (Vue) => {
|
||||||
Vue.component("p-notify", PNotify);
|
Vue.component("PNotify", PNotify);
|
||||||
Vue.component("p-navigation", PNavigation);
|
Vue.component("PNavigation", PNavigation);
|
||||||
Vue.component("p-scroll-top", PScrollTop);
|
Vue.component("PScrollTop", PScrollTop);
|
||||||
Vue.component("p-loading-bar", PLoadingBar);
|
Vue.component("PLoadingBar", PLoadingBar);
|
||||||
Vue.component("p-video-player", PVideoPlayer);
|
Vue.component("PVideoPlayer", PVideoPlayer);
|
||||||
Vue.component("p-photo-viewer", PPhotoViewer);
|
Vue.component("PPhotoViewer", PPhotoViewer);
|
||||||
Vue.component("p-photo-toolbar", PPhotoToolbar);
|
Vue.component("PPhotoToolbar", PPhotoToolbar);
|
||||||
Vue.component("p-photo-cards", PPhotoCards);
|
Vue.component("PPhotoCards", PPhotoCards);
|
||||||
Vue.component("p-photo-mosaic", PPhotoMosaic);
|
Vue.component("PPhotoMosaic", PPhotoMosaic);
|
||||||
Vue.component("p-photo-list", PPhotoList);
|
Vue.component("PPhotoList", PPhotoList);
|
||||||
Vue.component("p-photo-clipboard", PPhotoClipboard);
|
Vue.component("PPhotoClipboard", PPhotoClipboard);
|
||||||
Vue.component("p-album-clipboard", PAlbumClipboard);
|
Vue.component("PAlbumClipboard", PAlbumClipboard);
|
||||||
Vue.component("p-album-toolbar", PAlbumToolbar);
|
Vue.component("PAlbumToolbar", PAlbumToolbar);
|
||||||
Vue.component("p-label-clipboard", PLabelClipboard);
|
Vue.component("PLabelClipboard", PLabelClipboard);
|
||||||
Vue.component("p-file-clipboard", PFileClipboard);
|
Vue.component("PFileClipboard", PFileClipboard);
|
||||||
Vue.component("p-about-footer", PAboutFooter);
|
Vue.component("PAboutFooter", PAboutFooter);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default components;
|
export default components;
|
||||||
|
|||||||
@@ -48,22 +48,22 @@ import PReloadDialog from "./reload.vue";
|
|||||||
const dialogs = {};
|
const dialogs = {};
|
||||||
|
|
||||||
dialogs.install = (Vue) => {
|
dialogs.install = (Vue) => {
|
||||||
Vue.component("p-account-add-dialog", PAccountAddDialog);
|
Vue.component("PAccountAddDialog", PAccountAddDialog);
|
||||||
Vue.component("p-account-remove-dialog", PAccountRemoveDialog);
|
Vue.component("PAccountRemoveDialog", PAccountRemoveDialog);
|
||||||
Vue.component("p-account-edit-dialog", PAccountEditDialog);
|
Vue.component("PAccountEditDialog", PAccountEditDialog);
|
||||||
Vue.component("p-photo-archive-dialog", PPhotoArchiveDialog);
|
Vue.component("PPhotoArchiveDialog", PPhotoArchiveDialog);
|
||||||
Vue.component("p-photo-album-dialog", PPhotoAlbumDialog);
|
Vue.component("PPhotoAlbumDialog", PPhotoAlbumDialog);
|
||||||
Vue.component("p-photo-edit-dialog", PPhotoEditDialog);
|
Vue.component("PPhotoEditDialog", PPhotoEditDialog);
|
||||||
Vue.component("p-file-delete-dialog", PFileDeleteDialog);
|
Vue.component("PFileDeleteDialog", PFileDeleteDialog);
|
||||||
Vue.component("p-album-edit-dialog", PAlbumEditDialog);
|
Vue.component("PAlbumEditDialog", PAlbumEditDialog);
|
||||||
Vue.component("p-album-delete-dialog", PAlbumDeleteDialog);
|
Vue.component("PAlbumDeleteDialog", PAlbumDeleteDialog);
|
||||||
Vue.component("p-label-delete-dialog", PLabelDeleteDialog);
|
Vue.component("PLabelDeleteDialog", PLabelDeleteDialog);
|
||||||
Vue.component("p-upload-dialog", PUploadDialog);
|
Vue.component("PUploadDialog", PUploadDialog);
|
||||||
Vue.component("p-video-dialog", PVideoDialog);
|
Vue.component("PVideoDialog", PVideoDialog);
|
||||||
Vue.component("p-share-dialog", PShareDialog);
|
Vue.component("PShareDialog", PShareDialog);
|
||||||
Vue.component("p-share-upload-dialog", PShareUploadDialog);
|
Vue.component("PShareUploadDialog", PShareUploadDialog);
|
||||||
Vue.component("p-webdav-dialog", PWebdavDialog);
|
Vue.component("PWebdavDialog", PWebdavDialog);
|
||||||
Vue.component("p-reload-dialog", PReloadDialog);
|
Vue.component("PReloadDialog", PReloadDialog);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default dialogs;
|
export default dialogs;
|
||||||
|
|||||||
@@ -30,67 +30,71 @@ https://docs.photoprism.org/developer-guide/
|
|||||||
|
|
||||||
import RestModel from "model/rest";
|
import RestModel from "model/rest";
|
||||||
import Api from "common/api";
|
import Api from "common/api";
|
||||||
import {$gettext} from "common/vm";
|
import { $gettext } from "common/vm";
|
||||||
import {config} from "../session";
|
import { config } from "../session";
|
||||||
|
|
||||||
export class Account extends RestModel {
|
export class Account extends RestModel {
|
||||||
getDefaults() {
|
getDefaults() {
|
||||||
return {
|
return {
|
||||||
ID: 0,
|
ID: 0,
|
||||||
AccName: "",
|
AccName: "",
|
||||||
AccOwner: "",
|
AccOwner: "",
|
||||||
AccURL: "",
|
AccURL: "",
|
||||||
AccType: "",
|
AccType: "",
|
||||||
AccKey: "",
|
AccKey: "",
|
||||||
AccUser: "",
|
AccUser: "",
|
||||||
AccPass: "",
|
AccPass: "",
|
||||||
AccError: "",
|
AccError: "",
|
||||||
AccErrors: 0,
|
AccErrors: 0,
|
||||||
AccShare: true,
|
AccShare: true,
|
||||||
AccSync: false,
|
AccSync: false,
|
||||||
RetryLimit: 3,
|
RetryLimit: 3,
|
||||||
SharePath: "/",
|
SharePath: "/",
|
||||||
ShareSize: "",
|
ShareSize: "",
|
||||||
ShareExpires: 0,
|
ShareExpires: 0,
|
||||||
SyncPath: "/",
|
SyncPath: "/",
|
||||||
SyncStatus: "",
|
SyncStatus: "",
|
||||||
SyncInterval: 86400,
|
SyncInterval: 86400,
|
||||||
SyncDate: null,
|
SyncDate: null,
|
||||||
SyncFilenames: true,
|
SyncFilenames: true,
|
||||||
SyncUpload: false,
|
SyncUpload: false,
|
||||||
SyncDownload: !config.get("readonly"),
|
SyncDownload: !config.get("readonly"),
|
||||||
SyncRaw: true,
|
SyncRaw: true,
|
||||||
CreatedAt: "",
|
CreatedAt: "",
|
||||||
UpdatedAt: "",
|
UpdatedAt: "",
|
||||||
DeletedAt: null,
|
DeletedAt: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getEntityName() {
|
getEntityName() {
|
||||||
return this.AccName;
|
return this.AccName;
|
||||||
}
|
}
|
||||||
|
|
||||||
getId() {
|
getId() {
|
||||||
return this.ID;
|
return this.ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
Folders() {
|
Folders() {
|
||||||
return Api.get(this.getEntityResource() + "/folders").then((response) => Promise.resolve(response.data));
|
return Api.get(this.getEntityResource() + "/folders").then((response) =>
|
||||||
}
|
Promise.resolve(response.data)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Share(photos, dest) {
|
Share(photos, dest) {
|
||||||
const values = {Photos: photos, Destination: dest};
|
const values = { Photos: photos, Destination: dest };
|
||||||
|
|
||||||
return Api.post(this.getEntityResource() + "/share", values).then((response) => Promise.resolve(response.data));
|
return Api.post(this.getEntityResource() + "/share", values).then((response) =>
|
||||||
}
|
Promise.resolve(response.data)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static getCollectionResource() {
|
static getCollectionResource() {
|
||||||
return "accounts";
|
return "accounts";
|
||||||
}
|
}
|
||||||
|
|
||||||
static getModelName() {
|
static getModelName() {
|
||||||
return $gettext("Account");
|
return $gettext("Account");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Account;
|
export default Account;
|
||||||
|
|||||||
@@ -30,136 +30,136 @@ https://docs.photoprism.org/developer-guide/
|
|||||||
|
|
||||||
import RestModel from "model/rest";
|
import RestModel from "model/rest";
|
||||||
import Api from "common/api";
|
import Api from "common/api";
|
||||||
import {DateTime} from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import {config} from "../session";
|
import { config } from "../session";
|
||||||
import {$gettext} from "common/vm";
|
import { $gettext } from "common/vm";
|
||||||
|
|
||||||
export class Album extends RestModel {
|
export class Album extends RestModel {
|
||||||
getDefaults() {
|
getDefaults() {
|
||||||
return {
|
return {
|
||||||
UID: "",
|
UID: "",
|
||||||
Cover: "",
|
Cover: "",
|
||||||
Parent: "",
|
Parent: "",
|
||||||
Folder: "",
|
Folder: "",
|
||||||
Slug: "",
|
Slug: "",
|
||||||
Type: "",
|
Type: "",
|
||||||
Title: "",
|
Title: "",
|
||||||
Location: "",
|
Location: "",
|
||||||
Caption: "",
|
Caption: "",
|
||||||
Category: "",
|
Category: "",
|
||||||
Description: "",
|
Description: "",
|
||||||
Notes: "",
|
Notes: "",
|
||||||
Filter: "",
|
Filter: "",
|
||||||
Order: "",
|
Order: "",
|
||||||
Template: "",
|
Template: "",
|
||||||
Country: "",
|
Country: "",
|
||||||
Day: -1,
|
Day: -1,
|
||||||
Year: -1,
|
Year: -1,
|
||||||
Month: -1,
|
Month: -1,
|
||||||
Favorite: true,
|
Favorite: true,
|
||||||
Private: false,
|
Private: false,
|
||||||
PhotoCount: 0,
|
PhotoCount: 0,
|
||||||
LinkCount: 0,
|
LinkCount: 0,
|
||||||
CreatedAt: "",
|
CreatedAt: "",
|
||||||
UpdatedAt: "",
|
UpdatedAt: "",
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getEntityName() {
|
||||||
|
return this.Slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTitle() {
|
||||||
|
return this.Title;
|
||||||
|
}
|
||||||
|
|
||||||
|
thumbnailUrl(size) {
|
||||||
|
return `/api/v1/albums/${this.getId()}/t/${config.previewToken()}/${size}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
dayString() {
|
||||||
|
if (!this.Day || this.Day <= 0) {
|
||||||
|
return "01";
|
||||||
}
|
}
|
||||||
|
|
||||||
getEntityName() {
|
return this.Day.toString().padStart(2, "0");
|
||||||
return this.Slug;
|
}
|
||||||
|
|
||||||
|
monthString() {
|
||||||
|
if (!this.Month || this.Month <= 0) {
|
||||||
|
return "01";
|
||||||
}
|
}
|
||||||
|
|
||||||
getTitle() {
|
return this.Month.toString().padStart(2, "0");
|
||||||
return this.Title;
|
}
|
||||||
|
|
||||||
|
yearString() {
|
||||||
|
if (!this.Year || this.Year <= 1000) {
|
||||||
|
return new Date().getFullYear().toString().padStart(4, "0");
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbnailUrl(size) {
|
return this.Year.toString();
|
||||||
return `/api/v1/albums/${this.getId()}/t/${config.previewToken()}/${size}`;
|
}
|
||||||
|
|
||||||
|
getDate() {
|
||||||
|
let date = this.yearString() + "-" + this.monthString() + "-" + this.dayString();
|
||||||
|
|
||||||
|
return DateTime.fromISO(`${date}T12:00:00Z`).toUTC();
|
||||||
|
}
|
||||||
|
|
||||||
|
localDate(time) {
|
||||||
|
if (!this.TakenAtLocal) {
|
||||||
|
return this.utcDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
dayString() {
|
let zone = this.getTimeZone();
|
||||||
if (!this.Day || this.Day <= 0) {
|
|
||||||
return "01";
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.Day.toString().padStart(2, "0");
|
return DateTime.fromISO(this.localDateString(time), { zone });
|
||||||
|
}
|
||||||
|
|
||||||
|
getDateString() {
|
||||||
|
if (!this.Year || this.Year <= 1000) {
|
||||||
|
return $gettext("Unknown");
|
||||||
|
} else if (!this.Month || this.Month <= 0) {
|
||||||
|
return this.localYearString();
|
||||||
|
} else if (!this.Day || this.Day <= 0) {
|
||||||
|
return this.getDate().toLocaleString({ month: "long", year: "numeric" });
|
||||||
}
|
}
|
||||||
|
|
||||||
monthString() {
|
return this.localDate().toLocaleString(DateTime.DATE_HUGE);
|
||||||
if (!this.Month || this.Month <= 0) {
|
}
|
||||||
return "01";
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.Month.toString().padStart(2, "0");
|
getCreatedString() {
|
||||||
|
return DateTime.fromISO(this.CreatedAt).toLocaleString(DateTime.DATETIME_MED);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleLike() {
|
||||||
|
this.Favorite = !this.Favorite;
|
||||||
|
|
||||||
|
if (this.Favorite) {
|
||||||
|
return Api.post(this.getEntityResource() + "/like");
|
||||||
|
} else {
|
||||||
|
return Api.delete(this.getEntityResource() + "/like");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
yearString() {
|
like() {
|
||||||
if (!this.Year || this.Year <= 1000) {
|
this.Favorite = true;
|
||||||
return new Date().getFullYear().toString().padStart(4, "0");
|
return Api.post(this.getEntityResource() + "/like");
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.Year.toString();
|
unlike() {
|
||||||
}
|
this.Favorite = false;
|
||||||
|
return Api.delete(this.getEntityResource() + "/like");
|
||||||
|
}
|
||||||
|
|
||||||
getDate() {
|
static getCollectionResource() {
|
||||||
let date = this.yearString() + "-" + this.monthString() + "-" + this.dayString();
|
return "albums";
|
||||||
|
}
|
||||||
|
|
||||||
return DateTime.fromISO(`${date}T12:00:00Z`).toUTC();
|
static getModelName() {
|
||||||
}
|
return $gettext("Album");
|
||||||
|
}
|
||||||
localDate(time) {
|
|
||||||
if(!this.TakenAtLocal) {
|
|
||||||
return this.utcDate();
|
|
||||||
}
|
|
||||||
|
|
||||||
let zone = this.getTimeZone();
|
|
||||||
|
|
||||||
return DateTime.fromISO(this.localDateString(time), {zone});
|
|
||||||
}
|
|
||||||
|
|
||||||
getDateString() {
|
|
||||||
if (!this.Year || this.Year <= 1000) {
|
|
||||||
return $gettext("Unknown");
|
|
||||||
} else if (!this.Month || this.Month <= 0) {
|
|
||||||
return this.localYearString();
|
|
||||||
} else if (!this.Day || this.Day <= 0) {
|
|
||||||
return this.getDate().toLocaleString({month: "long", year: "numeric"});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.localDate().toLocaleString(DateTime.DATE_HUGE);
|
|
||||||
}
|
|
||||||
|
|
||||||
getCreatedString() {
|
|
||||||
return DateTime.fromISO(this.CreatedAt).toLocaleString(DateTime.DATETIME_MED);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleLike() {
|
|
||||||
this.Favorite = !this.Favorite;
|
|
||||||
|
|
||||||
if (this.Favorite) {
|
|
||||||
return Api.post(this.getEntityResource() + "/like");
|
|
||||||
} else {
|
|
||||||
return Api.delete(this.getEntityResource() + "/like");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
like() {
|
|
||||||
this.Favorite = true;
|
|
||||||
return Api.post(this.getEntityResource() + "/like");
|
|
||||||
}
|
|
||||||
|
|
||||||
unlike() {
|
|
||||||
this.Favorite = false;
|
|
||||||
return Api.delete(this.getEntityResource() + "/like");
|
|
||||||
}
|
|
||||||
|
|
||||||
static getCollectionResource() {
|
|
||||||
return "albums";
|
|
||||||
}
|
|
||||||
|
|
||||||
static getModelName() {
|
|
||||||
return $gettext("Album");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Album;
|
export default Album;
|
||||||
|
|||||||
@@ -30,208 +30,208 @@ https://docs.photoprism.org/developer-guide/
|
|||||||
|
|
||||||
import RestModel from "model/rest";
|
import RestModel from "model/rest";
|
||||||
import Api from "common/api";
|
import Api from "common/api";
|
||||||
import {DateTime} from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import Util from "common/util";
|
import Util from "common/util";
|
||||||
import {config} from "../session";
|
import { config } from "../session";
|
||||||
import {$gettext} from "common/vm";
|
import { $gettext } from "common/vm";
|
||||||
|
|
||||||
export class File extends RestModel {
|
export class File extends RestModel {
|
||||||
getDefaults() {
|
getDefaults() {
|
||||||
return {
|
return {
|
||||||
UID: "",
|
UID: "",
|
||||||
PhotoUID: "",
|
PhotoUID: "",
|
||||||
InstanceID: "",
|
InstanceID: "",
|
||||||
Root: "/",
|
Root: "/",
|
||||||
Name: "",
|
Name: "",
|
||||||
OriginalName: "",
|
OriginalName: "",
|
||||||
Hash: "",
|
Hash: "",
|
||||||
Size: 0,
|
Size: 0,
|
||||||
ModTime: 0,
|
ModTime: 0,
|
||||||
Codec: "",
|
Codec: "",
|
||||||
Type: "",
|
Type: "",
|
||||||
Mime: "",
|
Mime: "",
|
||||||
Primary: false,
|
Primary: false,
|
||||||
Sidecar: false,
|
Sidecar: false,
|
||||||
Missing: false,
|
Missing: false,
|
||||||
Portrait: false,
|
Portrait: false,
|
||||||
Video: false,
|
Video: false,
|
||||||
Duration: 0,
|
Duration: 0,
|
||||||
Width: 0,
|
Width: 0,
|
||||||
Height: 0,
|
Height: 0,
|
||||||
Orientation: 0,
|
Orientation: 0,
|
||||||
Projection: "",
|
Projection: "",
|
||||||
AspectRatio: 1.0,
|
AspectRatio: 1.0,
|
||||||
MainColor: "",
|
MainColor: "",
|
||||||
Colors: "",
|
Colors: "",
|
||||||
Luminance: "",
|
Luminance: "",
|
||||||
Diff: 0,
|
Diff: 0,
|
||||||
Chroma: 0,
|
Chroma: 0,
|
||||||
Notes: "",
|
Notes: "",
|
||||||
Error: "",
|
Error: "",
|
||||||
CreatedAt: "",
|
CreatedAt: "",
|
||||||
CreatedIn: 0,
|
CreatedIn: 0,
|
||||||
UpdatedAt: "",
|
UpdatedAt: "",
|
||||||
UpdatedIn: 0,
|
UpdatedIn: 0,
|
||||||
DeletedAt: "",
|
DeletedAt: "",
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
baseName(truncate) {
|
||||||
|
let result = this.Name;
|
||||||
|
const slash = result.lastIndexOf("/");
|
||||||
|
|
||||||
|
if (slash >= 0) {
|
||||||
|
result = this.Name.substring(slash + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
baseName(truncate) {
|
if (truncate) {
|
||||||
let result = this.Name;
|
result = Util.truncate(result, truncate, "…");
|
||||||
const slash = result.lastIndexOf("/");
|
|
||||||
|
|
||||||
if (slash >= 0) {
|
|
||||||
result = this.Name.substring(slash + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (truncate) {
|
|
||||||
result = Util.truncate(result, truncate, "…");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isFile() {
|
return result;
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
isFile() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEntityName() {
|
||||||
|
return this.Root + "/" + this.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
thumbnailUrl(size) {
|
||||||
|
if (this.Error) {
|
||||||
|
return "/api/v1/svg/broken";
|
||||||
|
} else if (this.Type === "raw") {
|
||||||
|
return "/api/v1/svg/raw";
|
||||||
}
|
}
|
||||||
|
|
||||||
getEntityName() {
|
return `/api/v1/t/${this.Hash}/${config.previewToken()}/${size}`;
|
||||||
return this.Root + "/" + this.Name;
|
}
|
||||||
|
|
||||||
|
getDownloadUrl() {
|
||||||
|
return "/api/v1/dl/" + this.Hash + "?t=" + config.downloadToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
download() {
|
||||||
|
if (!this.Hash) {
|
||||||
|
console.warn("no file hash found for download", this);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbnailUrl(size) {
|
let link = document.createElement("a");
|
||||||
if (this.Error) {
|
link.href = this.getDownloadUrl();
|
||||||
return "/api/v1/svg/broken";
|
link.download = this.baseName(this.Name);
|
||||||
} else if (this.Type === "raw") {
|
link.click();
|
||||||
return "/api/v1/svg/raw";
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return `/api/v1/t/${this.Hash}/${config.previewToken()}/${size}`;
|
calculateSize(width, height) {
|
||||||
|
if (width >= this.Width && height >= this.Height) {
|
||||||
|
// Smaller
|
||||||
|
return { width: this.Width, height: this.Height };
|
||||||
}
|
}
|
||||||
|
|
||||||
getDownloadUrl() {
|
const srcAspectRatio = this.Width / this.Height;
|
||||||
return "/api/v1/dl/" + this.Hash + "?t=" + config.downloadToken();
|
const maxAspectRatio = width / height;
|
||||||
|
|
||||||
|
let newW, newH;
|
||||||
|
|
||||||
|
if (srcAspectRatio > maxAspectRatio) {
|
||||||
|
newW = width;
|
||||||
|
newH = Math.round(newW / srcAspectRatio);
|
||||||
|
} else {
|
||||||
|
newH = height;
|
||||||
|
newW = Math.round(newH * srcAspectRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
download() {
|
return { width: newW, height: newH };
|
||||||
if (!this.Hash) {
|
}
|
||||||
console.warn("no file hash found for download", this);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let link = document.createElement("a");
|
getDateString() {
|
||||||
link.href = this.getDownloadUrl();
|
return DateTime.fromISO(this.CreatedAt).toLocaleString(DateTime.DATETIME_MED);
|
||||||
link.download = this.baseName(this.Name);
|
}
|
||||||
link.click();
|
|
||||||
|
getInfo() {
|
||||||
|
let info = [];
|
||||||
|
|
||||||
|
if (this.Type) {
|
||||||
|
info.push(this.Type.toUpperCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateSize(width, height) {
|
if (this.Duration > 0) {
|
||||||
if (width >= this.Width && height >= this.Height) { // Smaller
|
info.push(Util.duration(this.Duration));
|
||||||
return {width: this.Width, height: this.Height};
|
|
||||||
}
|
|
||||||
|
|
||||||
const srcAspectRatio = this.Width / this.Height;
|
|
||||||
const maxAspectRatio = width / height;
|
|
||||||
|
|
||||||
let newW, newH;
|
|
||||||
|
|
||||||
if (srcAspectRatio > maxAspectRatio) {
|
|
||||||
newW = width;
|
|
||||||
newH = Math.round(newW / srcAspectRatio);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
newH = height;
|
|
||||||
newW = Math.round(newH * srcAspectRatio);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {width: newW, height: newH};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getDateString() {
|
this.addSizeInfo(info);
|
||||||
return DateTime.fromISO(this.CreatedAt).toLocaleString(DateTime.DATETIME_MED);
|
|
||||||
|
return info.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
typeInfo() {
|
||||||
|
if (this.Video) {
|
||||||
|
return $gettext("Video");
|
||||||
|
} else if (this.Sidecar) {
|
||||||
|
return $gettext("Sidecar");
|
||||||
}
|
}
|
||||||
|
|
||||||
getInfo() {
|
return this.Type.toUpperCase();
|
||||||
let info = [];
|
}
|
||||||
|
|
||||||
if (this.Type) {
|
sizeInfo() {
|
||||||
info.push(this.Type.toUpperCase());
|
let info = [];
|
||||||
}
|
|
||||||
|
|
||||||
if (this.Duration > 0) {
|
this.addSizeInfo(info);
|
||||||
info.push(Util.duration(this.Duration));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addSizeInfo(info);
|
return info.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
return info.join(", ");
|
addSizeInfo(info) {
|
||||||
|
if (this.Width && this.Height) {
|
||||||
|
info.push(this.Width + " × " + this.Height);
|
||||||
}
|
}
|
||||||
|
|
||||||
typeInfo() {
|
if (this.Size > 102400) {
|
||||||
if (this.Video) {
|
const size = Number.parseFloat(this.Size) / 1048576;
|
||||||
return $gettext("Video");
|
|
||||||
} else if (this.Sidecar) {
|
|
||||||
return $gettext("Sidecar");
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.Type.toUpperCase();
|
info.push(size.toFixed(1) + " MB");
|
||||||
|
} else if (this.Size) {
|
||||||
|
const size = Number.parseFloat(this.Size) / 1024;
|
||||||
|
|
||||||
|
info.push(size.toFixed(1) + " KB");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sizeInfo() {
|
toggleLike() {
|
||||||
let info = [];
|
this.Favorite = !this.Favorite;
|
||||||
|
|
||||||
this.addSizeInfo(info);
|
if (this.Favorite) {
|
||||||
|
return Api.post(this.getPhotoResource() + "/like");
|
||||||
return info.join(", ");
|
} else {
|
||||||
|
return Api.delete(this.getPhotoResource() + "/like");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addSizeInfo(info) {
|
getPhotoResource() {
|
||||||
if (this.Width && this.Height) {
|
return "photos/" + this.PhotoUID;
|
||||||
info.push(this.Width + " × " + this.Height);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (this.Size > 102400) {
|
like() {
|
||||||
const size = Number.parseFloat(this.Size) / 1048576;
|
this.Favorite = true;
|
||||||
|
return Api.post(this.getPhotoResource() + "/like");
|
||||||
|
}
|
||||||
|
|
||||||
info.push(size.toFixed(1) + " MB");
|
unlike() {
|
||||||
} else if (this.Size) {
|
this.Favorite = false;
|
||||||
const size = Number.parseFloat(this.Size) / 1024;
|
return Api.delete(this.getPhotoResource() + "/like");
|
||||||
|
}
|
||||||
|
|
||||||
info.push(size.toFixed(1) + " KB");
|
static getCollectionResource() {
|
||||||
}
|
return "files";
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleLike() {
|
static getModelName() {
|
||||||
this.Favorite = !this.Favorite;
|
return $gettext("File");
|
||||||
|
}
|
||||||
if (this.Favorite) {
|
|
||||||
return Api.post(this.getPhotoResource() + "/like");
|
|
||||||
} else {
|
|
||||||
return Api.delete(this.getPhotoResource() + "/like");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPhotoResource() {
|
|
||||||
return "photos/" + this.PhotoUID;
|
|
||||||
}
|
|
||||||
|
|
||||||
like() {
|
|
||||||
this.Favorite = true;
|
|
||||||
return Api.post(this.getPhotoResource() + "/like");
|
|
||||||
}
|
|
||||||
|
|
||||||
unlike() {
|
|
||||||
this.Favorite = false;
|
|
||||||
return Api.delete(this.getPhotoResource() + "/like");
|
|
||||||
}
|
|
||||||
|
|
||||||
static getCollectionResource() {
|
|
||||||
return "files";
|
|
||||||
}
|
|
||||||
|
|
||||||
static getModelName() {
|
|
||||||
return $gettext("File");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default File;
|
export default File;
|
||||||
|
|||||||
@@ -30,162 +30,162 @@ https://docs.photoprism.org/developer-guide/
|
|||||||
|
|
||||||
import RestModel from "model/rest";
|
import RestModel from "model/rest";
|
||||||
import Api from "common/api";
|
import Api from "common/api";
|
||||||
import {DateTime} from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import File from "model/file";
|
import File from "model/file";
|
||||||
import Util from "common/util";
|
import Util from "common/util";
|
||||||
import {$gettext} from "common/vm";
|
import { $gettext } from "common/vm";
|
||||||
|
|
||||||
export const RootImport = "import";
|
export const RootImport = "import";
|
||||||
export const RootOriginals = "originals";
|
export const RootOriginals = "originals";
|
||||||
|
|
||||||
export class Folder extends RestModel {
|
export class Folder extends RestModel {
|
||||||
getDefaults() {
|
getDefaults() {
|
||||||
return {
|
return {
|
||||||
Folder: true,
|
Folder: true,
|
||||||
Path: "",
|
Path: "",
|
||||||
Root: "",
|
Root: "",
|
||||||
UID: "",
|
UID: "",
|
||||||
Type: "",
|
Type: "",
|
||||||
Title: "",
|
Title: "",
|
||||||
Category: "",
|
Category: "",
|
||||||
Description: "",
|
Description: "",
|
||||||
Order: "",
|
Order: "",
|
||||||
Country: "",
|
Country: "",
|
||||||
Year: "",
|
Year: "",
|
||||||
Month: "",
|
Month: "",
|
||||||
Favorite: false,
|
Favorite: false,
|
||||||
Private: false,
|
Private: false,
|
||||||
Ignore: false,
|
Ignore: false,
|
||||||
Watch: false,
|
Watch: false,
|
||||||
FileCount: 0,
|
FileCount: 0,
|
||||||
CreatedAt: "",
|
CreatedAt: "",
|
||||||
UpdatedAt: "",
|
UpdatedAt: "",
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
baseName(truncate) {
|
||||||
|
let result = this.Path;
|
||||||
|
const slash = result.lastIndexOf("/");
|
||||||
|
|
||||||
|
if (slash >= 0) {
|
||||||
|
result = this.Path.substring(slash + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
baseName(truncate) {
|
if (truncate) {
|
||||||
let result = this.Path;
|
result = Util.truncate(result, truncate, "…");
|
||||||
const slash = result.lastIndexOf("/");
|
}
|
||||||
|
|
||||||
if (slash >= 0) {
|
return result;
|
||||||
result = this.Path.substring(slash + 1);
|
}
|
||||||
|
|
||||||
|
isFile() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEntityName() {
|
||||||
|
return this.Root + "/" + this.Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
thumbnailUrl() {
|
||||||
|
return "/api/v1/svg/folder";
|
||||||
|
}
|
||||||
|
|
||||||
|
getDateString() {
|
||||||
|
return DateTime.fromISO(this.CreatedAt).toLocaleString(DateTime.DATETIME_MED);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleLike() {
|
||||||
|
this.Favorite = !this.Favorite;
|
||||||
|
|
||||||
|
if (this.Favorite) {
|
||||||
|
return Api.post(this.getEntityResource() + "/like");
|
||||||
|
} else {
|
||||||
|
return Api.delete(this.getEntityResource() + "/like");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
like() {
|
||||||
|
this.Favorite = true;
|
||||||
|
return Api.post(this.getEntityResource() + "/like");
|
||||||
|
}
|
||||||
|
|
||||||
|
unlike() {
|
||||||
|
this.Favorite = false;
|
||||||
|
return Api.delete(this.getEntityResource() + "/like");
|
||||||
|
}
|
||||||
|
|
||||||
|
static findAll(path) {
|
||||||
|
return this.search(path, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
static findAllUncached(path) {
|
||||||
|
return this.search(path, { recursive: true, uncached: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
static originals(path, params) {
|
||||||
|
if (!path || path[0] !== "/") {
|
||||||
|
path = "/" + path;
|
||||||
|
}
|
||||||
|
return this.search(RootOriginals + path, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static search(path, params) {
|
||||||
|
const options = {
|
||||||
|
params: params,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!path || path[0] !== "/") {
|
||||||
|
path = "/" + path;
|
||||||
|
}
|
||||||
|
return Api.get(this.getCollectionResource() + path, options).then((response) => {
|
||||||
|
let folders = response.data.folders;
|
||||||
|
let files = response.data.files ? response.data.files : [];
|
||||||
|
|
||||||
|
let count = folders.length + files.length;
|
||||||
|
|
||||||
|
let limit = 0;
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
if (response.headers) {
|
||||||
|
if (response.headers["x-count"]) {
|
||||||
|
count = parseInt(response.headers["x-count"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(truncate) {
|
if (response.headers["x-limit"]) {
|
||||||
result = Util.truncate(result, truncate, "…");
|
limit = parseInt(response.headers["x-limit"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
if (response.headers["x-offset"]) {
|
||||||
}
|
offset = parseInt(response.headers["x-offset"]);
|
||||||
|
|
||||||
isFile() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
getEntityName() {
|
|
||||||
return this.Root + "/" + this.Path;
|
|
||||||
}
|
|
||||||
|
|
||||||
thumbnailUrl() {
|
|
||||||
return "/api/v1/svg/folder";
|
|
||||||
}
|
|
||||||
|
|
||||||
getDateString() {
|
|
||||||
return DateTime.fromISO(this.CreatedAt).toLocaleString(DateTime.DATETIME_MED);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleLike() {
|
|
||||||
this.Favorite = !this.Favorite;
|
|
||||||
|
|
||||||
if (this.Favorite) {
|
|
||||||
return Api.post(this.getEntityResource() + "/like");
|
|
||||||
} else {
|
|
||||||
return Api.delete(this.getEntityResource() + "/like");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
like() {
|
response.models = [];
|
||||||
this.Favorite = true;
|
response.files = files.length;
|
||||||
return Api.post(this.getEntityResource() + "/like");
|
response.folders = folders.length;
|
||||||
}
|
response.count = count;
|
||||||
|
response.limit = limit;
|
||||||
|
response.offset = offset;
|
||||||
|
|
||||||
unlike() {
|
for (let i = 0; i < folders.length; i++) {
|
||||||
this.Favorite = false;
|
response.models.push(new this(folders[i]));
|
||||||
return Api.delete(this.getEntityResource() + "/like");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static findAll(path) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
return this.search(path, {recursive: true});
|
response.models.push(new File(files[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
static findAllUncached(path) {
|
return Promise.resolve(response);
|
||||||
return this.search(path, {recursive: true, uncached: true});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static originals(path, params) {
|
static getCollectionResource() {
|
||||||
if(!path || path[0] !== "/") {
|
return "folders";
|
||||||
path = "/" + path;
|
}
|
||||||
}
|
|
||||||
return this.search(RootOriginals + path, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
static search(path, params) {
|
static getModelName() {
|
||||||
const options = {
|
return $gettext("Folder");
|
||||||
params: params,
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if (!path || path[0] !== "/") {
|
|
||||||
path = "/" + path;
|
|
||||||
}
|
|
||||||
return Api.get(this.getCollectionResource() + path, options).then((response) => {
|
|
||||||
let folders = response.data.folders;
|
|
||||||
let files = response.data.files ? response.data.files : [];
|
|
||||||
|
|
||||||
let count = folders.length + files.length;
|
|
||||||
|
|
||||||
let limit = 0;
|
|
||||||
let offset = 0;
|
|
||||||
|
|
||||||
if (response.headers) {
|
|
||||||
if (response.headers["x-count"]) {
|
|
||||||
count = parseInt(response.headers["x-count"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.headers["x-limit"]) {
|
|
||||||
limit = parseInt(response.headers["x-limit"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.headers["x-offset"]) {
|
|
||||||
offset = parseInt(response.headers["x-offset"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response.models = [];
|
|
||||||
response.files = files.length;
|
|
||||||
response.folders = folders.length;
|
|
||||||
response.count = count;
|
|
||||||
response.limit = limit;
|
|
||||||
response.offset = offset;
|
|
||||||
|
|
||||||
for (let i = 0; i < folders.length; i++) {
|
|
||||||
response.models.push(new this(folders[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
response.models.push(new File(files[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve(response);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static getCollectionResource() {
|
|
||||||
return "folders";
|
|
||||||
}
|
|
||||||
|
|
||||||
static getModelName() {
|
|
||||||
return $gettext("Folder");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Folder;
|
export default Folder;
|
||||||
|
|||||||
@@ -30,72 +30,72 @@ https://docs.photoprism.org/developer-guide/
|
|||||||
|
|
||||||
import RestModel from "model/rest";
|
import RestModel from "model/rest";
|
||||||
import Api from "common/api";
|
import Api from "common/api";
|
||||||
import {DateTime} from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import {config} from "../session";
|
import { config } from "../session";
|
||||||
import {$gettext} from "common/vm";
|
import { $gettext } from "common/vm";
|
||||||
|
|
||||||
export class Label extends RestModel {
|
export class Label extends RestModel {
|
||||||
getDefaults() {
|
getDefaults() {
|
||||||
return {
|
return {
|
||||||
ID: 0,
|
ID: 0,
|
||||||
UID: "",
|
UID: "",
|
||||||
Slug: "",
|
Slug: "",
|
||||||
CustomSlug: "",
|
CustomSlug: "",
|
||||||
Name: "",
|
Name: "",
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Favorite: false,
|
Favorite: false,
|
||||||
Description: "",
|
Description: "",
|
||||||
Notes: "",
|
Notes: "",
|
||||||
PhotoCount: 0,
|
PhotoCount: 0,
|
||||||
CreatedAt: "",
|
CreatedAt: "",
|
||||||
UpdatedAt: "",
|
UpdatedAt: "",
|
||||||
DeletedAt: "",
|
DeletedAt: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getEntityName() {
|
getEntityName() {
|
||||||
return this.Slug;
|
return this.Slug;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTitle() {
|
getTitle() {
|
||||||
return this.Name;
|
return this.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbnailUrl(size) {
|
thumbnailUrl(size) {
|
||||||
return `/api/v1/labels/${this.getId()}/t/${config.previewToken()}/${size}`;
|
return `/api/v1/labels/${this.getId()}/t/${config.previewToken()}/${size}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDateString() {
|
getDateString() {
|
||||||
return DateTime.fromISO(this.CreatedAt).toLocaleString(DateTime.DATETIME_MED);
|
return DateTime.fromISO(this.CreatedAt).toLocaleString(DateTime.DATETIME_MED);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleLike() {
|
toggleLike() {
|
||||||
this.Favorite = !this.Favorite;
|
this.Favorite = !this.Favorite;
|
||||||
|
|
||||||
if (this.Favorite) {
|
if (this.Favorite) {
|
||||||
return Api.post(this.getEntityResource() + "/like");
|
return Api.post(this.getEntityResource() + "/like");
|
||||||
} else {
|
} else {
|
||||||
return Api.delete(this.getEntityResource() + "/like");
|
return Api.delete(this.getEntityResource() + "/like");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
like() {
|
like() {
|
||||||
this.Favorite = true;
|
this.Favorite = true;
|
||||||
return Api.post(this.getEntityResource() + "/like");
|
return Api.post(this.getEntityResource() + "/like");
|
||||||
}
|
}
|
||||||
|
|
||||||
unlike() {
|
unlike() {
|
||||||
this.Favorite = false;
|
this.Favorite = false;
|
||||||
return Api.delete(this.getEntityResource() + "/like");
|
return Api.delete(this.getEntityResource() + "/like");
|
||||||
}
|
}
|
||||||
|
|
||||||
static getCollectionResource() {
|
static getCollectionResource() {
|
||||||
return "labels";
|
return "labels";
|
||||||
}
|
}
|
||||||
|
|
||||||
static getModelName() {
|
static getModelName() {
|
||||||
return $gettext("Label");
|
return $gettext("Label");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Label;
|
export default Label;
|
||||||
|
|||||||
@@ -29,79 +29,81 @@ https://docs.photoprism.org/developer-guide/
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Model from "./model";
|
import Model from "./model";
|
||||||
import {DateTime} from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import {$gettext} from "common/vm";
|
import { $gettext } from "common/vm";
|
||||||
|
|
||||||
export default class Link extends Model {
|
export default class Link extends Model {
|
||||||
getDefaults() {
|
getDefaults() {
|
||||||
return {
|
return {
|
||||||
UID: "",
|
UID: "",
|
||||||
Share: "",
|
Share: "",
|
||||||
Slug: "",
|
Slug: "",
|
||||||
Token: "",
|
Token: "",
|
||||||
Expires: 0,
|
Expires: 0,
|
||||||
Views: 0,
|
Views: 0,
|
||||||
MaxViews: 0,
|
MaxViews: 0,
|
||||||
Password: "",
|
Password: "",
|
||||||
HasPassword: false,
|
HasPassword: false,
|
||||||
CanComment: false,
|
CanComment: false,
|
||||||
CanEdit: false,
|
CanEdit: false,
|
||||||
CreatedAt: "",
|
CreatedAt: "",
|
||||||
ModifiedAt: "",
|
ModifiedAt: "",
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getToken() {
|
||||||
|
return this.Token.toLowerCase().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
url() {
|
||||||
|
let token = this.getToken();
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
token = "…";
|
||||||
}
|
}
|
||||||
|
|
||||||
getToken() {
|
if (this.hasSlug()) {
|
||||||
return this.Token.toLowerCase().trim();
|
return `${window.location.origin}/s/${token}/${this.Slug}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
url() {
|
return `${window.location.origin}/s/${token}/${this.Share}`;
|
||||||
let token = this.getToken();
|
}
|
||||||
|
|
||||||
if(!token) {
|
caption() {
|
||||||
token = "…";
|
return `/s/${this.getToken()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.hasSlug()) {
|
getId() {
|
||||||
return `${window.location.origin}/s/${token}/${this.Slug}`;
|
return this.UID;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${window.location.origin}/s/${token}/${this.Share}`;
|
hasId() {
|
||||||
}
|
return !!this.getId();
|
||||||
|
}
|
||||||
|
|
||||||
caption() {
|
getSlug() {
|
||||||
return `/s/${this.getToken()}`;
|
return this.Slug ? this.Slug : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
getId() {
|
hasSlug() {
|
||||||
return this.UID;
|
return !!this.getSlug();
|
||||||
}
|
}
|
||||||
|
|
||||||
hasId() {
|
clone() {
|
||||||
return !!this.getId();
|
return new this.constructor(this.getValues());
|
||||||
}
|
}
|
||||||
|
|
||||||
getSlug() {
|
expires() {
|
||||||
return this.Slug ? this.Slug : "";
|
return DateTime.fromISO(this.UpdatedAt)
|
||||||
}
|
.plus({ seconds: this.Expires })
|
||||||
|
.toLocaleString(DateTime.DATE_SHORT);
|
||||||
|
}
|
||||||
|
|
||||||
hasSlug() {
|
static getCollectionResource() {
|
||||||
return !!this.getSlug();
|
return "links";
|
||||||
}
|
}
|
||||||
|
|
||||||
clone() {
|
static getModelName() {
|
||||||
return new this.constructor(this.getValues());
|
return $gettext("Link");
|
||||||
}
|
}
|
||||||
|
|
||||||
expires() {
|
|
||||||
return DateTime.fromISO(this.UpdatedAt).plus({ seconds: this.Expires }).toLocaleString(DateTime.DATE_SHORT);
|
|
||||||
}
|
|
||||||
|
|
||||||
static getCollectionResource() {
|
|
||||||
return "links";
|
|
||||||
}
|
|
||||||
|
|
||||||
static getModelName() {
|
|
||||||
return $gettext("Link");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,87 +29,86 @@ https://docs.photoprism.org/developer-guide/
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export class Model {
|
export class Model {
|
||||||
constructor(values) {
|
constructor(values) {
|
||||||
this.__originalValues = {};
|
this.__originalValues = {};
|
||||||
|
|
||||||
if (values) {
|
if (values) {
|
||||||
this.setValues(values);
|
this.setValues(values);
|
||||||
|
} else {
|
||||||
|
this.setValues(this.getDefaults());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setValues(values, scalarOnly) {
|
||||||
|
if (!values) return;
|
||||||
|
|
||||||
|
for (let key in values) {
|
||||||
|
if (values.hasOwnProperty(key) && key !== "__originalValues") {
|
||||||
|
this[key] = values[key];
|
||||||
|
|
||||||
|
if (typeof values[key] !== "object") {
|
||||||
|
this.__originalValues[key] = values[key];
|
||||||
|
} else if (!scalarOnly) {
|
||||||
|
this.__originalValues[key] = JSON.parse(JSON.stringify(values[key]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
getValues(changed) {
|
||||||
|
const result = {};
|
||||||
|
const defaults = this.getDefaults();
|
||||||
|
|
||||||
|
for (let key in this.__originalValues) {
|
||||||
|
if (this.__originalValues.hasOwnProperty(key) && key !== "__originalValues") {
|
||||||
|
let val;
|
||||||
|
if (defaults.hasOwnProperty(key)) {
|
||||||
|
switch (typeof defaults[key]) {
|
||||||
|
case "string":
|
||||||
|
if (this[key] === null || this[key] === undefined) {
|
||||||
|
val = "";
|
||||||
|
} else {
|
||||||
|
val = this[key];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "bigint":
|
||||||
|
case "number":
|
||||||
|
val = parseFloat(this[key]);
|
||||||
|
break;
|
||||||
|
case "boolean":
|
||||||
|
val = !!this[key];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
val = this[key];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.setValues(this.getDefaults());
|
val = this[key];
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setValues(values, scalarOnly) {
|
|
||||||
if (!values) return;
|
|
||||||
|
|
||||||
for (let key in values) {
|
|
||||||
if (values.hasOwnProperty(key) && key !== "__originalValues") {
|
|
||||||
this[key] = values[key];
|
|
||||||
|
|
||||||
if (typeof values[key] !== "object") {
|
|
||||||
this.__originalValues[key] = values[key];
|
|
||||||
} else if (!scalarOnly) {
|
|
||||||
this.__originalValues[key] = JSON.parse(JSON.stringify(values[key]));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
if (!changed || JSON.stringify(val) !== JSON.stringify(this.__originalValues[key])) {
|
||||||
}
|
result[key] = val;
|
||||||
|
|
||||||
getValues(changed) {
|
|
||||||
const result = {};
|
|
||||||
const defaults = this.getDefaults();
|
|
||||||
|
|
||||||
for (let key in this.__originalValues) {
|
|
||||||
if (this.__originalValues.hasOwnProperty(key) && key !== "__originalValues") {
|
|
||||||
let val;
|
|
||||||
if (defaults.hasOwnProperty(key)) {
|
|
||||||
switch (typeof defaults[key]) {
|
|
||||||
case "string":
|
|
||||||
if(this[key] === null || this[key] === undefined) {
|
|
||||||
val = "";
|
|
||||||
} else {
|
|
||||||
val = this[key];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "bigint":
|
|
||||||
case "number":
|
|
||||||
val = parseFloat(this[key]);
|
|
||||||
break;
|
|
||||||
case "boolean":
|
|
||||||
val = !!this[key];
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
val = this[key];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val = this[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!changed || JSON.stringify(val) !== JSON.stringify(this.__originalValues[key])) {
|
|
||||||
result[key] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wasChanged() {
|
return result;
|
||||||
const changed = this.getValues(true);
|
}
|
||||||
|
|
||||||
if(!changed) {
|
wasChanged() {
|
||||||
return false;
|
const changed = this.getValues(true);
|
||||||
}
|
|
||||||
|
|
||||||
return !(changed.constructor === Object && Object.keys(changed).length === 0);
|
if (!changed) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaults() {
|
return !(changed.constructor === Object && Object.keys(changed).length === 0);
|
||||||
return {};
|
}
|
||||||
}
|
|
||||||
|
getDefaults() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Model;
|
export default Model;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -32,173 +32,183 @@ import Api from "common/api";
|
|||||||
import Form from "common/form";
|
import Form from "common/form";
|
||||||
import Model from "./model";
|
import Model from "./model";
|
||||||
import Link from "./link";
|
import Link from "./link";
|
||||||
import {$gettext} from "common/vm";
|
import { $gettext } from "common/vm";
|
||||||
|
|
||||||
export class Rest extends Model {
|
export class Rest extends Model {
|
||||||
getId() {
|
getId() {
|
||||||
return this.UID ? this.UID : this.ID;
|
return this.UID ? this.UID : this.ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasId() {
|
||||||
|
return !!this.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
getSlug() {
|
||||||
|
return this.Slug ? this.Slug : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
return new this.constructor(this.getValues());
|
||||||
|
}
|
||||||
|
|
||||||
|
find(id, params) {
|
||||||
|
return Api.get(this.getEntityResource(id), params).then((resp) =>
|
||||||
|
Promise.resolve(new this.constructor(resp.data))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
if (this.hasId()) {
|
||||||
|
return this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
hasId() {
|
return Api.post(this.constructor.getCollectionResource(), this.getValues()).then((resp) =>
|
||||||
return !!this.getId();
|
Promise.resolve(this.setValues(resp.data))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
return Api.put(this.getEntityResource(), this.getValues(true)).then((resp) =>
|
||||||
|
Promise.resolve(this.setValues(resp.data))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove() {
|
||||||
|
return Api.delete(this.getEntityResource()).then(() => Promise.resolve(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
getEditForm() {
|
||||||
|
return Api.options(this.getEntityResource()).then((resp) =>
|
||||||
|
Promise.resolve(new Form(resp.data))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getEntityResource(id) {
|
||||||
|
if (!id) {
|
||||||
|
id = this.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
getSlug() {
|
return this.constructor.getCollectionResource() + "/" + id;
|
||||||
return this.Slug ? this.Slug : "";
|
}
|
||||||
|
|
||||||
|
getEntityName() {
|
||||||
|
return this.constructor.getModelName() + " " + this.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
createLink(password, expires) {
|
||||||
|
return Api.post(this.getEntityResource() + "/links", {
|
||||||
|
Password: password ? password : "",
|
||||||
|
Expires: expires ? expires : 0,
|
||||||
|
Slug: this.getSlug(),
|
||||||
|
CanEdit: false,
|
||||||
|
CanComment: false,
|
||||||
|
}).then((resp) => Promise.resolve(new Link(resp.data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLink(link) {
|
||||||
|
let values = link.getValues(false);
|
||||||
|
|
||||||
|
if (link.Token) {
|
||||||
|
values["Token"] = link.getToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
clone() {
|
if (link.Password) {
|
||||||
return new this.constructor(this.getValues());
|
values["Password"] = link.Password;
|
||||||
}
|
}
|
||||||
|
|
||||||
find(id, params) {
|
return Api.put(this.getEntityResource() + "/links/" + link.getId(), values).then((resp) =>
|
||||||
return Api.get(this.getEntityResource(id), params).then((resp) => Promise.resolve(new this.constructor(resp.data)));
|
Promise.resolve(link.setValues(resp.data))
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
save() {
|
removeLink(link) {
|
||||||
if (this.hasId()) {
|
return Api.delete(this.getEntityResource() + "/links/" + link.getId()).then((resp) =>
|
||||||
return this.update();
|
Promise.resolve(link.setValues(resp.data))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
links() {
|
||||||
|
return Api.get(this.getEntityResource() + "/links").then((resp) => {
|
||||||
|
resp.models = [];
|
||||||
|
resp.count = resp.data.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < resp.data.length; i++) {
|
||||||
|
resp.models.push(new Link(resp.data[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(resp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
modelName() {
|
||||||
|
return this.constructor.getModelName();
|
||||||
|
}
|
||||||
|
|
||||||
|
static getCollectionResource() {
|
||||||
|
// Needs to be implemented!
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static getCreateResource() {
|
||||||
|
return this.getCollectionResource();
|
||||||
|
}
|
||||||
|
|
||||||
|
static getCreateForm() {
|
||||||
|
return Api.options(this.getCreateResource()).then((resp) =>
|
||||||
|
Promise.resolve(new Form(resp.data))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getModelName() {
|
||||||
|
return $gettext("Item");
|
||||||
|
}
|
||||||
|
|
||||||
|
static getSearchForm() {
|
||||||
|
return Api.options(this.getCollectionResource()).then((resp) =>
|
||||||
|
Promise.resolve(new Form(resp.data))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static limit() {
|
||||||
|
return 3333;
|
||||||
|
}
|
||||||
|
|
||||||
|
static search(params) {
|
||||||
|
const options = {
|
||||||
|
params: params,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Api.get(this.getCollectionResource(), options).then((resp) => {
|
||||||
|
let count = resp.data.length;
|
||||||
|
let limit = 0;
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
if (resp.headers) {
|
||||||
|
if (resp.headers["x-count"]) {
|
||||||
|
count = parseInt(resp.headers["x-count"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Api.post(this.constructor.getCollectionResource(), this.getValues()).then((resp) => Promise.resolve(this.setValues(resp.data)));
|
if (resp.headers["x-limit"]) {
|
||||||
}
|
limit = parseInt(resp.headers["x-limit"]);
|
||||||
|
|
||||||
update() {
|
|
||||||
return Api.put(this.getEntityResource(), this.getValues(true)).then((resp) => Promise.resolve(this.setValues(resp.data)));
|
|
||||||
}
|
|
||||||
|
|
||||||
remove() {
|
|
||||||
return Api.delete(this.getEntityResource()).then(() => Promise.resolve(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
getEditForm() {
|
|
||||||
return Api.options(this.getEntityResource()).then(resp => Promise.resolve(new Form(resp.data)));
|
|
||||||
}
|
|
||||||
|
|
||||||
getEntityResource(id) {
|
|
||||||
if (!id) {
|
|
||||||
id = this.getId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.constructor.getCollectionResource() + "/" + id;
|
if (resp.headers["x-offset"]) {
|
||||||
}
|
offset = parseInt(resp.headers["x-offset"]);
|
||||||
|
|
||||||
getEntityName() {
|
|
||||||
return this.constructor.getModelName() + " " + this.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
createLink(password, expires) {
|
|
||||||
return Api
|
|
||||||
.post(this.getEntityResource() + "/links", {
|
|
||||||
"Password": password ? password : "",
|
|
||||||
"Expires": expires ? expires : 0,
|
|
||||||
"Slug": this.getSlug(),
|
|
||||||
"CanEdit": false,
|
|
||||||
"CanComment": false,
|
|
||||||
})
|
|
||||||
.then((resp) => Promise.resolve(new Link(resp.data)));
|
|
||||||
}
|
|
||||||
|
|
||||||
updateLink(link) {
|
|
||||||
let values = link.getValues(false);
|
|
||||||
|
|
||||||
if(link.Token) {
|
|
||||||
values["Token"] = link.getToken();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(link.Password) {
|
resp.models = [];
|
||||||
values["Password"] = link.Password;
|
resp.count = count;
|
||||||
}
|
resp.limit = limit;
|
||||||
|
resp.offset = offset;
|
||||||
|
|
||||||
return Api
|
for (let i = 0; i < resp.data.length; i++) {
|
||||||
.put(this.getEntityResource() + "/links/" + link.getId(), values)
|
resp.models.push(new this(resp.data[i]));
|
||||||
.then((resp) => Promise.resolve(link.setValues(resp.data)));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
removeLink(link) {
|
return Promise.resolve(resp);
|
||||||
return Api
|
});
|
||||||
.delete(this.getEntityResource() + "/links/" + link.getId())
|
}
|
||||||
.then((resp) => Promise.resolve(link.setValues(resp.data)));
|
|
||||||
}
|
|
||||||
|
|
||||||
links() {
|
|
||||||
return Api.get(this.getEntityResource() + "/links").then((resp) => {
|
|
||||||
resp.models = [];
|
|
||||||
resp.count = resp.data.length;
|
|
||||||
|
|
||||||
for (let i = 0; i < resp.data.length; i++) {
|
|
||||||
resp.models.push(new Link(resp.data[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve(resp);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
modelName() {
|
|
||||||
return this.constructor.getModelName();
|
|
||||||
}
|
|
||||||
|
|
||||||
static getCollectionResource() {
|
|
||||||
// Needs to be implemented!
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
static getCreateResource() {
|
|
||||||
return this.getCollectionResource();
|
|
||||||
}
|
|
||||||
|
|
||||||
static getCreateForm() {
|
|
||||||
return Api.options(this.getCreateResource()).then(resp => Promise.resolve(new Form(resp.data)));
|
|
||||||
}
|
|
||||||
|
|
||||||
static getModelName() {
|
|
||||||
return $gettext("Item");
|
|
||||||
}
|
|
||||||
|
|
||||||
static getSearchForm() {
|
|
||||||
return Api.options(this.getCollectionResource()).then(resp => Promise.resolve(new Form(resp.data)));
|
|
||||||
}
|
|
||||||
|
|
||||||
static limit() {
|
|
||||||
return 3333;
|
|
||||||
}
|
|
||||||
|
|
||||||
static search(params) {
|
|
||||||
const options = {
|
|
||||||
params: params,
|
|
||||||
};
|
|
||||||
|
|
||||||
return Api.get(this.getCollectionResource(), options).then((resp) => {
|
|
||||||
let count = resp.data.length;
|
|
||||||
let limit = 0;
|
|
||||||
let offset = 0;
|
|
||||||
|
|
||||||
if (resp.headers) {
|
|
||||||
if (resp.headers["x-count"]) {
|
|
||||||
count = parseInt(resp.headers["x-count"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resp.headers["x-limit"]) {
|
|
||||||
limit = parseInt(resp.headers["x-limit"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resp.headers["x-offset"]) {
|
|
||||||
offset = parseInt(resp.headers["x-offset"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.models = [];
|
|
||||||
resp.count = count;
|
|
||||||
resp.limit = limit;
|
|
||||||
resp.offset = offset;
|
|
||||||
|
|
||||||
for (let i = 0; i < resp.data.length; i++) {
|
|
||||||
resp.models.push(new this(resp.data[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve(resp);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Rest;
|
export default Rest;
|
||||||
|
|||||||
@@ -32,23 +32,25 @@ import Api from "common/api";
|
|||||||
import Model from "./model";
|
import Model from "./model";
|
||||||
|
|
||||||
export class Settings extends Model {
|
export class Settings extends Model {
|
||||||
changed(area, key) {
|
changed(area, key) {
|
||||||
if (typeof this.__originalValues[area] === "undefined") {
|
if (typeof this.__originalValues[area] === "undefined") {
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
return (this[area][key] !== this.__originalValues[area][key]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
load() {
|
return this[area][key] !== this.__originalValues[area][key];
|
||||||
return Api.get("settings").then((response) => {
|
}
|
||||||
return Promise.resolve(this.setValues(response.data));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
save() {
|
load() {
|
||||||
return Api.post("settings", this.getValues(true)).then((response) => Promise.resolve(this.setValues(response.data)));
|
return Api.get("settings").then((response) => {
|
||||||
}
|
return Promise.resolve(this.setValues(response.data));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
return Api.post("settings", this.getValues(true)).then((response) =>
|
||||||
|
Promise.resolve(this.setValues(response.data))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Settings;
|
export default Settings;
|
||||||
|
|||||||
@@ -30,219 +30,218 @@ https://docs.photoprism.org/developer-guide/
|
|||||||
|
|
||||||
import Model from "./model";
|
import Model from "./model";
|
||||||
import Api from "common/api";
|
import Api from "common/api";
|
||||||
import {config} from "../session";
|
import { config } from "../session";
|
||||||
import {$gettext} from "common/vm";
|
import { $gettext } from "common/vm";
|
||||||
|
|
||||||
const thumbs = window.__CONFIG__.thumbs;
|
const thumbs = window.__CONFIG__.thumbs;
|
||||||
|
|
||||||
export class Thumb extends Model {
|
export class Thumb extends Model {
|
||||||
getDefaults() {
|
getDefaults() {
|
||||||
return {
|
return {
|
||||||
uid: "",
|
uid: "",
|
||||||
title: "",
|
title: "",
|
||||||
taken: "",
|
taken: "",
|
||||||
description: "",
|
description: "",
|
||||||
favorite: false,
|
favorite: false,
|
||||||
playable: false,
|
playable: false,
|
||||||
original_w: 0,
|
original_w: 0,
|
||||||
original_h: 0,
|
original_h: 0,
|
||||||
download_url: "",
|
download_url: "",
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getId() {
|
||||||
|
return this.uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasId() {
|
||||||
|
return !!this.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleLike() {
|
||||||
|
this.favorite = !this.favorite;
|
||||||
|
|
||||||
|
if (this.favorite) {
|
||||||
|
return Api.post("photos/" + this.uid + "/like");
|
||||||
|
} else {
|
||||||
|
return Api.delete("photos/" + this.uid + "/like");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static thumbNotFound() {
|
||||||
|
const result = {
|
||||||
|
uid: "",
|
||||||
|
title: $gettext("Not Found"),
|
||||||
|
taken: "",
|
||||||
|
description: "",
|
||||||
|
favorite: false,
|
||||||
|
playable: false,
|
||||||
|
original_w: 0,
|
||||||
|
original_h: 0,
|
||||||
|
download_url: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < thumbs.length; i++) {
|
||||||
|
let t = thumbs[i];
|
||||||
|
|
||||||
|
result[t.size] = {
|
||||||
|
src: "/api/v1/svg/photo",
|
||||||
|
w: t.w,
|
||||||
|
h: t.h,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getId() {
|
return result;
|
||||||
return this.uid;
|
}
|
||||||
|
|
||||||
|
static fromPhotos(photos) {
|
||||||
|
let result = [];
|
||||||
|
const n = photos.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
result.push(this.fromPhoto(photos[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
hasId() {
|
return result;
|
||||||
return !!this.getId();
|
}
|
||||||
|
|
||||||
|
static fromPhoto(photo) {
|
||||||
|
if (photo.Files) {
|
||||||
|
return this.fromFile(photo, photo.mainFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleLike() {
|
if (!photo || !photo.Hash) {
|
||||||
this.favorite = !this.favorite;
|
return this.thumbNotFound();
|
||||||
|
|
||||||
if (this.favorite) {
|
|
||||||
return Api.post("photos/" + this.uid + "/like");
|
|
||||||
} else {
|
|
||||||
return Api.delete("photos/" + this.uid + "/like");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static thumbNotFound() {
|
const result = {
|
||||||
const result = {
|
uid: photo.UID,
|
||||||
uid: "",
|
title: photo.Title,
|
||||||
title: $gettext("Not Found"),
|
taken: photo.getDateString(),
|
||||||
taken: "",
|
description: photo.Description,
|
||||||
description: "",
|
favorite: photo.Favorite,
|
||||||
favorite: false,
|
playable: photo.isPlayable(),
|
||||||
playable: false,
|
download_url: this.downloadUrl(photo),
|
||||||
original_w: 0,
|
original_w: photo.Width,
|
||||||
original_h: 0,
|
original_h: photo.Height,
|
||||||
download_url: "",
|
};
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < thumbs.length; i++) {
|
for (let i = 0; i < thumbs.length; i++) {
|
||||||
let t = thumbs[i];
|
let t = thumbs[i];
|
||||||
|
let size = photo.calculateSize(t.w, t.h);
|
||||||
|
|
||||||
result[t.size] = {
|
result[t.size] = {
|
||||||
src: "/api/v1/svg/photo",
|
src: photo.thumbnailUrl(t.size),
|
||||||
w: t.w,
|
w: size.width,
|
||||||
h: t.h,
|
h: size.height,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromPhotos(photos) {
|
return new this(result);
|
||||||
let result = [];
|
}
|
||||||
const n = photos.length;
|
|
||||||
|
|
||||||
for (let i = 0; i < n; i++) {
|
static fromFile(photo, file) {
|
||||||
result.push(this.fromPhoto(photos[i]));
|
if (!photo || !file || !file.Hash) {
|
||||||
}
|
return this.thumbNotFound();
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromPhoto(photo) {
|
const result = {
|
||||||
if (photo.Files) {
|
uid: photo.UID,
|
||||||
return this.fromFile(photo, photo.mainFile());
|
title: photo.Title,
|
||||||
}
|
taken: photo.getDateString(),
|
||||||
|
description: photo.Description,
|
||||||
|
favorite: photo.Favorite,
|
||||||
|
playable: photo.isPlayable(),
|
||||||
|
download_url: this.downloadUrl(file),
|
||||||
|
original_w: file.Width,
|
||||||
|
original_h: file.Height,
|
||||||
|
};
|
||||||
|
|
||||||
if (!photo || !photo.Hash) {
|
for (let i = 0; i < thumbs.length; i++) {
|
||||||
return this.thumbNotFound();
|
let t = thumbs[i];
|
||||||
}
|
let size = this.calculateSize(file, t.w, t.h);
|
||||||
|
|
||||||
const result = {
|
result[t.size] = {
|
||||||
uid: photo.UID,
|
src: this.thumbnailUrl(file, t.size),
|
||||||
title: photo.Title,
|
w: size.width,
|
||||||
taken: photo.getDateString(),
|
h: size.height,
|
||||||
description: photo.Description,
|
};
|
||||||
favorite: photo.Favorite,
|
|
||||||
playable: photo.isPlayable(),
|
|
||||||
download_url: this.downloadUrl(photo),
|
|
||||||
original_w: photo.Width,
|
|
||||||
original_h: photo.Height,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < thumbs.length; i++) {
|
|
||||||
let t = thumbs[i];
|
|
||||||
let size = photo.calculateSize(t.w, t.h);
|
|
||||||
|
|
||||||
result[t.size] = {
|
|
||||||
src: photo.thumbnailUrl(t.size),
|
|
||||||
w: size.width,
|
|
||||||
h: size.height,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return new this(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromFile(photo, file) {
|
return new this(result);
|
||||||
if (!photo || !file || !file.Hash) {
|
}
|
||||||
return this.thumbNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = {
|
static fromFiles(photos) {
|
||||||
uid: photo.UID,
|
let result = [];
|
||||||
title: photo.Title,
|
|
||||||
taken: photo.getDateString(),
|
|
||||||
description: photo.Description,
|
|
||||||
favorite: photo.Favorite,
|
|
||||||
playable: photo.isPlayable(),
|
|
||||||
download_url: this.downloadUrl(file),
|
|
||||||
original_w: file.Width,
|
|
||||||
original_h: file.Height,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < thumbs.length; i++) {
|
if (!photos || !photos.length) {
|
||||||
let t = thumbs[i];
|
return result;
|
||||||
let size = this.calculateSize(file, t.w, t.h);
|
|
||||||
|
|
||||||
result[t.size] = {
|
|
||||||
src: this.thumbnailUrl(file, t.size),
|
|
||||||
w: size.width,
|
|
||||||
h: size.height,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return new this(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromFiles(photos) {
|
const n = photos.length;
|
||||||
let result = [];
|
|
||||||
|
|
||||||
if (!photos || !photos.length) {
|
for (let i = 0; i < n; i++) {
|
||||||
return result;
|
let p = photos[i];
|
||||||
|
|
||||||
|
if (!p.Files || !p.Files.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = 0; j < p.Files.length; j++) {
|
||||||
|
let f = p.Files[j];
|
||||||
|
|
||||||
|
if (!f || f.Type !== "jpg") {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const n = photos.length;
|
let thumb = this.fromFile(p, f);
|
||||||
|
|
||||||
for (let i = 0; i < n; i++) {
|
if (thumb) {
|
||||||
let p = photos[i];
|
result.push(thumb);
|
||||||
|
|
||||||
if (!p.Files || !p.Files.length) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let j = 0; j < p.Files.length; j++) {
|
|
||||||
let f = p.Files[j];
|
|
||||||
|
|
||||||
if (!f || f.Type !== "jpg") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let thumb = this.fromFile(p, f);
|
|
||||||
|
|
||||||
if (thumb) {
|
|
||||||
result.push(thumb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static calculateSize(file, width, height) {
|
return result;
|
||||||
if (width >= file.Width && height >= file.Height) { // Smaller
|
}
|
||||||
return {width: file.Width, height: file.Height};
|
|
||||||
}
|
|
||||||
|
|
||||||
const srcAspectRatio = file.Width / file.Height;
|
static calculateSize(file, width, height) {
|
||||||
const maxAspectRatio = width / height;
|
if (width >= file.Width && height >= file.Height) {
|
||||||
|
// Smaller
|
||||||
let newW, newH;
|
return { width: file.Width, height: file.Height };
|
||||||
|
|
||||||
if (srcAspectRatio > maxAspectRatio) {
|
|
||||||
newW = width;
|
|
||||||
newH = Math.round(newW / srcAspectRatio);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
newH = height;
|
|
||||||
newW = Math.round(newH * srcAspectRatio);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {width: newW, height: newH};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static thumbnailUrl(file, size) {
|
const srcAspectRatio = file.Width / file.Height;
|
||||||
if (!file.Hash) {
|
const maxAspectRatio = width / height;
|
||||||
return "/api/v1/svg/photo";
|
|
||||||
|
|
||||||
}
|
let newW, newH;
|
||||||
|
|
||||||
return `/api/v1/t/${file.Hash}/${config.previewToken()}/${size}`;
|
if (srcAspectRatio > maxAspectRatio) {
|
||||||
|
newW = width;
|
||||||
|
newH = Math.round(newW / srcAspectRatio);
|
||||||
|
} else {
|
||||||
|
newH = height;
|
||||||
|
newW = Math.round(newH * srcAspectRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
static downloadUrl(file) {
|
return { width: newW, height: newH };
|
||||||
if (!file || !file.Hash) {
|
}
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return `/api/v1/dl/${file.Hash}?t=${config.downloadToken()}`;
|
static thumbnailUrl(file, size) {
|
||||||
|
if (!file.Hash) {
|
||||||
|
return "/api/v1/svg/photo";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return `/api/v1/t/${file.Hash}/${config.previewToken()}/${size}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static downloadUrl(file) {
|
||||||
|
if (!file || !file.Hash) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return `/api/v1/dl/${file.Hash}?t=${config.downloadToken()}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Thumb;
|
export default Thumb;
|
||||||
|
|||||||
@@ -31,90 +31,96 @@ https://docs.photoprism.org/developer-guide/
|
|||||||
import RestModel from "model/rest";
|
import RestModel from "model/rest";
|
||||||
import Form from "common/form";
|
import Form from "common/form";
|
||||||
import Api from "common/api";
|
import Api from "common/api";
|
||||||
import {$gettext} from "common/vm";
|
import { $gettext } from "common/vm";
|
||||||
|
|
||||||
export class User extends RestModel {
|
export class User extends RestModel {
|
||||||
getDefaults() {
|
getDefaults() {
|
||||||
return {
|
return {
|
||||||
UID: "",
|
UID: "",
|
||||||
Address: {},
|
Address: {},
|
||||||
MotherUID: "",
|
MotherUID: "",
|
||||||
FatherUID: "",
|
FatherUID: "",
|
||||||
GlobalUID: "",
|
GlobalUID: "",
|
||||||
FullName: "",
|
FullName: "",
|
||||||
NickName: "",
|
NickName: "",
|
||||||
MaidenName: "",
|
MaidenName: "",
|
||||||
ArtistName: "",
|
ArtistName: "",
|
||||||
UserName: "",
|
UserName: "",
|
||||||
UserStatus: "",
|
UserStatus: "",
|
||||||
UserDisabled: false,
|
UserDisabled: false,
|
||||||
UserSettings: "",
|
UserSettings: "",
|
||||||
PrimaryEmail: "",
|
PrimaryEmail: "",
|
||||||
EmailConfirmed: false,
|
EmailConfirmed: false,
|
||||||
BackupEmail: "",
|
BackupEmail: "",
|
||||||
PersonURL: "",
|
PersonURL: "",
|
||||||
PersonPhone: "",
|
PersonPhone: "",
|
||||||
PersonStatus: "",
|
PersonStatus: "",
|
||||||
PersonAvatar: "",
|
PersonAvatar: "",
|
||||||
PersonLocation: "",
|
PersonLocation: "",
|
||||||
PersonBio: "",
|
PersonBio: "",
|
||||||
BusinessURL: "",
|
BusinessURL: "",
|
||||||
BusinessPhone: "",
|
BusinessPhone: "",
|
||||||
BusinessEmail: "",
|
BusinessEmail: "",
|
||||||
CompanyName: "",
|
CompanyName: "",
|
||||||
DepartmentName: "",
|
DepartmentName: "",
|
||||||
JobTitle: "",
|
JobTitle: "",
|
||||||
BirthYear: -1,
|
BirthYear: -1,
|
||||||
BirthMonth: -1,
|
BirthMonth: -1,
|
||||||
BirthDay: -1,
|
BirthDay: -1,
|
||||||
TermsAccepted: false,
|
TermsAccepted: false,
|
||||||
IsArtist: false,
|
IsArtist: false,
|
||||||
IsSubject: false,
|
IsSubject: false,
|
||||||
RoleAdmin: false,
|
RoleAdmin: false,
|
||||||
RoleGuest: false,
|
RoleGuest: false,
|
||||||
RoleChild: false,
|
RoleChild: false,
|
||||||
RoleFamily: false,
|
RoleFamily: false,
|
||||||
RoleFriend: false,
|
RoleFriend: false,
|
||||||
WebDAV: false,
|
WebDAV: false,
|
||||||
StoragePath: "",
|
StoragePath: "",
|
||||||
CanInvite: false,
|
CanInvite: false,
|
||||||
InviteToken: "",
|
InviteToken: "",
|
||||||
InvitedBy: "",
|
InvitedBy: "",
|
||||||
CreatedAt: "",
|
CreatedAt: "",
|
||||||
UpdatedAt: "",
|
UpdatedAt: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getEntityName() {
|
getEntityName() {
|
||||||
return this.FullName ? this.FullName : this.UserName;
|
return this.FullName ? this.FullName : this.UserName;
|
||||||
}
|
}
|
||||||
|
|
||||||
getRegisterForm() {
|
getRegisterForm() {
|
||||||
return Api.options(this.getEntityResource() + "/register").then(response => Promise.resolve(new Form(response.data)));
|
return Api.options(this.getEntityResource() + "/register").then((response) =>
|
||||||
}
|
Promise.resolve(new Form(response.data))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getProfileForm() {
|
getProfileForm() {
|
||||||
return Api.options(this.getEntityResource() + "/profile").then(response => Promise.resolve(new Form(response.data)));
|
return Api.options(this.getEntityResource() + "/profile").then((response) =>
|
||||||
}
|
Promise.resolve(new Form(response.data))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
changePassword(oldPassword, newPassword) {
|
changePassword(oldPassword, newPassword) {
|
||||||
return Api.put(this.getEntityResource() + "/password", {
|
return Api.put(this.getEntityResource() + "/password", {
|
||||||
old: oldPassword,
|
old: oldPassword,
|
||||||
new: newPassword,
|
new: newPassword,
|
||||||
}).then((response) => Promise.resolve(response.data));
|
}).then((response) => Promise.resolve(response.data));
|
||||||
}
|
}
|
||||||
|
|
||||||
saveProfile() {
|
saveProfile() {
|
||||||
return Api.post(this.getEntityResource() + "/profile", this.getValues()).then((response) => Promise.resolve(this.setValues(response.data)));
|
return Api.post(this.getEntityResource() + "/profile", this.getValues()).then((response) =>
|
||||||
}
|
Promise.resolve(this.setValues(response.data))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static getCollectionResource() {
|
static getCollectionResource() {
|
||||||
return "users";
|
return "users";
|
||||||
}
|
}
|
||||||
|
|
||||||
static getModelName() {
|
static getModelName() {
|
||||||
return $gettext("User");
|
return $gettext("User");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default User;
|
export default User;
|
||||||
|
|||||||
@@ -1,276 +1,279 @@
|
|||||||
import {$gettext} from "common/vm";
|
import { $gettext } from "common/vm";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
import {Info} from "luxon";
|
import { Info } from "luxon";
|
||||||
import {config} from "../session";
|
import { config } from "../session";
|
||||||
import {TypeVideo,TypeImage,TypeLive,TypeRaw} from "../model/photo";
|
import { TypeVideo, TypeImage, TypeLive, TypeRaw } from "../model/photo";
|
||||||
|
|
||||||
export const TimeZones = () => moment.tz.names();
|
export const TimeZones = () => moment.tz.names();
|
||||||
|
|
||||||
export const Days = () => {
|
export const Days = () => {
|
||||||
let result = [];
|
let result = [];
|
||||||
|
|
||||||
for (let i = 1; i <= 31; i++) {
|
for (let i = 1; i <= 31; i++) {
|
||||||
result.push({"value": i, "text": i.toString().padStart(2, "0")});
|
result.push({ value: i, text: i.toString().padStart(2, "0") });
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push({"value": -1, "text": $gettext("Unknown")});
|
result.push({ value: -1, text: $gettext("Unknown") });
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Years = () => {
|
export const Years = () => {
|
||||||
let result = [];
|
let result = [];
|
||||||
|
|
||||||
const currentYear = new Date().getUTCFullYear();
|
const currentYear = new Date().getUTCFullYear();
|
||||||
|
|
||||||
for (let i = currentYear; i >= 1750; i--) {
|
for (let i = currentYear; i >= 1750; i--) {
|
||||||
result.push({"value": i, "text": i.toString().padStart(4, "0")});
|
result.push({ value: i, text: i.toString().padStart(4, "0") });
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push({"value": -1, "text": $gettext("Unknown")});
|
result.push({ value: -1, text: $gettext("Unknown") });
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IndexedYears = () => {
|
export const IndexedYears = () => {
|
||||||
let result = [];
|
let result = [];
|
||||||
|
|
||||||
if (config.values.years) {
|
if (config.values.years) {
|
||||||
for (let i = 0; i < config.values.years.length; i++) {
|
for (let i = 0; i < config.values.years.length; i++) {
|
||||||
result.push({"value": parseInt(config.values.years[i]), "text": config.values.years[i].toString()});
|
result.push({
|
||||||
}
|
value: parseInt(config.values.years[i]),
|
||||||
|
text: config.values.years[i].toString(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result.push({"value": -1, "text": $gettext("Unknown")});
|
result.push({ value: -1, text: $gettext("Unknown") });
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Months = () => {
|
export const Months = () => {
|
||||||
let result = [];
|
let result = [];
|
||||||
|
|
||||||
const months = Info.months("long");
|
const months = Info.months("long");
|
||||||
|
|
||||||
for (let i = 0; i < months.length; i++) {
|
for (let i = 0; i < months.length; i++) {
|
||||||
result.push({"value": i + 1, "text": months[i]});
|
result.push({ value: i + 1, text: months[i] });
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push({"value": -1, "text": $gettext("Unknown")});
|
result.push({ value: -1, text: $gettext("Unknown") });
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MonthsShort = () => {
|
export const MonthsShort = () => {
|
||||||
let result = [];
|
let result = [];
|
||||||
|
|
||||||
for (let i = 1; i <= 12; i++) {
|
for (let i = 1; i <= 12; i++) {
|
||||||
result.push({"value": i, "text": i.toString().padStart(2, "0")});
|
result.push({ value: i, text: i.toString().padStart(2, "0") });
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push({"value": -1, "text": $gettext("Unknown")});
|
result.push({ value: -1, text: $gettext("Unknown") });
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Languages = () => [
|
export const Languages = () => [
|
||||||
{
|
{
|
||||||
"text": "English",
|
text: "English",
|
||||||
"translated": $gettext("English"),
|
translated: $gettext("English"),
|
||||||
"value": "en",
|
value: "en",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Deutsch",
|
text: "Deutsch",
|
||||||
"translated": $gettext("German"),
|
translated: $gettext("German"),
|
||||||
"value": "de",
|
value: "de",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Español",
|
text: "Español",
|
||||||
"translated": $gettext("Spanish"),
|
translated: $gettext("Spanish"),
|
||||||
"value": "es",
|
value: "es",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Français",
|
text: "Français",
|
||||||
"translated": $gettext("French"),
|
translated: $gettext("French"),
|
||||||
"value": "fr",
|
value: "fr",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "हिन्दी",
|
text: "हिन्दी",
|
||||||
"translated": $gettext("Hindi"),
|
translated: $gettext("Hindi"),
|
||||||
"value": "hi",
|
value: "hi",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Nederlands",
|
text: "Nederlands",
|
||||||
"translated": $gettext("Dutch"),
|
translated: $gettext("Dutch"),
|
||||||
"value": "nl",
|
value: "nl",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Polski",
|
text: "Polski",
|
||||||
"translated": $gettext("Polish"),
|
translated: $gettext("Polish"),
|
||||||
"value": "pl",
|
value: "pl",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Português do Brasil",
|
text: "Português do Brasil",
|
||||||
"translated": $gettext("Brazilian Portuguese"),
|
translated: $gettext("Brazilian Portuguese"),
|
||||||
"value": "pt_BR",
|
value: "pt_BR",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Русский",
|
text: "Русский",
|
||||||
"translated": $gettext("Russian"),
|
translated: $gettext("Russian"),
|
||||||
"value": "ru",
|
value: "ru",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Slovenčina",
|
text: "Slovenčina",
|
||||||
"translated": $gettext("Slovak"),
|
translated: $gettext("Slovak"),
|
||||||
"value": "sk",
|
value: "sk",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "简体中文",
|
text: "简体中文",
|
||||||
"translated": $gettext("Chinese Simplified"),
|
translated: $gettext("Chinese Simplified"),
|
||||||
"value": "zh",
|
value: "zh",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "繁体中文",
|
text: "繁体中文",
|
||||||
"translated": $gettext("Chinese Traditional"),
|
translated: $gettext("Chinese Traditional"),
|
||||||
"value": "zh_TW",
|
value: "zh_TW",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const Themes = () => [
|
export const Themes = () => [
|
||||||
{
|
{
|
||||||
"text": $gettext("Default"),
|
text: $gettext("Default"),
|
||||||
"value": "default",
|
value: "default",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": $gettext("Cyano"),
|
text: $gettext("Cyano"),
|
||||||
"value": "cyano",
|
value: "cyano",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": $gettext("Lavender"),
|
text: $gettext("Lavender"),
|
||||||
"value": "lavender",
|
value: "lavender",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": $gettext("Moonlight"),
|
text: $gettext("Moonlight"),
|
||||||
"value": "moonlight",
|
value: "moonlight",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": $gettext("Onyx"),
|
text: $gettext("Onyx"),
|
||||||
"value": "onyx",
|
value: "onyx",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": $gettext("Raspberry"),
|
text: $gettext("Raspberry"),
|
||||||
"value": "raspberry",
|
value: "raspberry",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": $gettext("Seaweed"),
|
text: $gettext("Seaweed"),
|
||||||
"value": "seaweed",
|
value: "seaweed",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
export const MapsAnimate = () => [
|
export const MapsAnimate = () => [
|
||||||
{
|
{
|
||||||
"text": $gettext("None"),
|
text: $gettext("None"),
|
||||||
"value": 0,
|
value: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": $gettext("Fast"),
|
text: $gettext("Fast"),
|
||||||
"value": 2500,
|
value: 2500,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": $gettext("Medium"),
|
text: $gettext("Medium"),
|
||||||
"value": 6250,
|
value: 6250,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": $gettext("Slow"),
|
text: $gettext("Slow"),
|
||||||
"value": 10000,
|
value: 10000,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const MapsStyle = () => [
|
export const MapsStyle = () => [
|
||||||
{
|
{
|
||||||
"text": $gettext("Offline"),
|
text: $gettext("Offline"),
|
||||||
"value": "offline",
|
value: "offline",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": $gettext("Streets"),
|
text: $gettext("Streets"),
|
||||||
"value": "streets",
|
value: "streets",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": $gettext("Hybrid"),
|
text: $gettext("Hybrid"),
|
||||||
"value": "hybrid",
|
value: "hybrid",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": $gettext("Topographic"),
|
text: $gettext("Topographic"),
|
||||||
"value": "topographique",
|
value: "topographique",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": $gettext("Outdoor"),
|
text: $gettext("Outdoor"),
|
||||||
"value": "outdoor",
|
value: "outdoor",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PhotoTypes = () => [
|
export const PhotoTypes = () => [
|
||||||
{
|
{
|
||||||
"text": $gettext("Image"),
|
text: $gettext("Image"),
|
||||||
"value": TypeImage,
|
value: TypeImage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": $gettext("Raw"),
|
text: $gettext("Raw"),
|
||||||
"value": TypeRaw,
|
value: TypeRaw,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": $gettext("Live"),
|
text: $gettext("Live"),
|
||||||
"value": TypeLive,
|
value: TypeLive,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": $gettext("Video"),
|
text: $gettext("Video"),
|
||||||
"value": TypeVideo,
|
value: TypeVideo,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const Intervals = () => [
|
export const Intervals = () => [
|
||||||
{"value": 0, "text": $gettext("Never")},
|
{ value: 0, text: $gettext("Never") },
|
||||||
{"value": 3600, "text": $gettext("1 hour")},
|
{ value: 3600, text: $gettext("1 hour") },
|
||||||
{"value": 3600 * 4, "text": $gettext("4 hours")},
|
{ value: 3600 * 4, text: $gettext("4 hours") },
|
||||||
{"value": 3600 * 12, "text": $gettext("12 hours")},
|
{ value: 3600 * 12, text: $gettext("12 hours") },
|
||||||
{"value": 86400, "text": $gettext("Daily")},
|
{ value: 86400, text: $gettext("Daily") },
|
||||||
{"value": 86400 * 2, "text": $gettext("Every two days")},
|
{ value: 86400 * 2, text: $gettext("Every two days") },
|
||||||
{"value": 86400 * 7, "text": $gettext("Once a week")},
|
{ value: 86400 * 7, text: $gettext("Once a week") },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const Expires = () => [
|
export const Expires = () => [
|
||||||
{"value": 0, "text": $gettext("Never")},
|
{ value: 0, text: $gettext("Never") },
|
||||||
{"value": 86400, "text": $gettext("After 1 day")},
|
{ value: 86400, text: $gettext("After 1 day") },
|
||||||
{"value": 86400 * 3, "text": $gettext("After 3 days")},
|
{ value: 86400 * 3, text: $gettext("After 3 days") },
|
||||||
{"value": 86400 * 7, "text": $gettext("After 7 days")},
|
{ value: 86400 * 7, text: $gettext("After 7 days") },
|
||||||
{"value": 86400 * 14, "text": $gettext("After two weeks")},
|
{ value: 86400 * 14, text: $gettext("After two weeks") },
|
||||||
{"value": 86400 * 31, "text": $gettext("After one month")},
|
{ value: 86400 * 31, text: $gettext("After one month") },
|
||||||
{"value": 86400 * 60, "text": $gettext("After two months")},
|
{ value: 86400 * 60, text: $gettext("After two months") },
|
||||||
{"value": 86400 * 365, "text": $gettext("After one year")},
|
{ value: 86400 * 365, text: $gettext("After one year") },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const Colors = () => [
|
export const Colors = () => [
|
||||||
{"Example": "#AB47BC", "Name": $gettext("Purple"), "Slug": "purple"},
|
{ Example: "#AB47BC", Name: $gettext("Purple"), Slug: "purple" },
|
||||||
{"Example": "#FF00FF", "Name": $gettext("Magenta"), "Slug": "magenta"},
|
{ Example: "#FF00FF", Name: $gettext("Magenta"), Slug: "magenta" },
|
||||||
{"Example": "#EC407A", "Name": $gettext("Pink"), "Slug": "pink"},
|
{ Example: "#EC407A", Name: $gettext("Pink"), Slug: "pink" },
|
||||||
{"Example": "#EF5350", "Name": $gettext("Red"), "Slug": "red"},
|
{ Example: "#EF5350", Name: $gettext("Red"), Slug: "red" },
|
||||||
{"Example": "#FFA726", "Name": $gettext("Orange"), "Slug": "orange"},
|
{ Example: "#FFA726", Name: $gettext("Orange"), Slug: "orange" },
|
||||||
{"Example": "#D4AF37", "Name": $gettext("Gold"), "Slug": "gold"},
|
{ Example: "#D4AF37", Name: $gettext("Gold"), Slug: "gold" },
|
||||||
{"Example": "#FDD835", "Name": $gettext("Yellow"), "Slug": "yellow"},
|
{ Example: "#FDD835", Name: $gettext("Yellow"), Slug: "yellow" },
|
||||||
{"Example": "#CDDC39", "Name": $gettext("Lime"), "Slug": "lime"},
|
{ Example: "#CDDC39", Name: $gettext("Lime"), Slug: "lime" },
|
||||||
{"Example": "#66BB6A", "Name": $gettext("Green"), "Slug": "green"},
|
{ Example: "#66BB6A", Name: $gettext("Green"), Slug: "green" },
|
||||||
{"Example": "#009688", "Name": $gettext("Teal"), "Slug": "teal"},
|
{ Example: "#009688", Name: $gettext("Teal"), Slug: "teal" },
|
||||||
{"Example": "#00BCD4", "Name": $gettext("Cyan"), "Slug": "cyan"},
|
{ Example: "#00BCD4", Name: $gettext("Cyan"), Slug: "cyan" },
|
||||||
{"Example": "#2196F3", "Name": $gettext("Blue"), "Slug": "blue"},
|
{ Example: "#2196F3", Name: $gettext("Blue"), Slug: "blue" },
|
||||||
{"Example": "#A1887F", "Name": $gettext("Brown"), "Slug": "brown"},
|
{ Example: "#A1887F", Name: $gettext("Brown"), Slug: "brown" },
|
||||||
{"Example": "#F5F5F5", "Name": $gettext("White"), "Slug": "white"},
|
{ Example: "#F5F5F5", Name: $gettext("White"), Slug: "white" },
|
||||||
{"Example": "#9E9E9E", "Name": $gettext("Grey"), "Slug": "grey"},
|
{ Example: "#9E9E9E", Name: $gettext("Grey"), Slug: "grey" },
|
||||||
{"Example": "#212121", "Name": $gettext("Black"), "Slug": "black"},
|
{ Example: "#212121", Name: $gettext("Black"), Slug: "black" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const FeedbackCategories = () => [
|
export const FeedbackCategories = () => [
|
||||||
{"value": "help", "text": $gettext("Customer Support")},
|
{ value: "help", text: $gettext("Customer Support") },
|
||||||
{"value": "feedback", "text": $gettext("Product Feedback")},
|
{ value: "feedback", text: $gettext("Product Feedback") },
|
||||||
{"value": "feature", "text": $gettext("Feature Request")},
|
{ value: "feature", text: $gettext("Feature Request") },
|
||||||
{"value": "bug", "text": $gettext("Bug Report")},
|
{ value: "bug", text: $gettext("Bug Report") },
|
||||||
{"value": "donations", "text": $gettext("Donations")},
|
{ value: "donations", text: $gettext("Donations") },
|
||||||
{"value": "other", "text": $gettext("Other")},
|
{ value: "other", text: $gettext("Other") },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -44,288 +44,309 @@ import About from "pages/about/about.vue";
|
|||||||
import Feedback from "pages/about/feedback.vue";
|
import Feedback from "pages/about/feedback.vue";
|
||||||
import License from "pages/about/license.vue";
|
import License from "pages/about/license.vue";
|
||||||
import Help from "pages/help.vue";
|
import Help from "pages/help.vue";
|
||||||
import {$gettext} from "common/vm";
|
import { $gettext } from "common/vm";
|
||||||
|
|
||||||
const c = window.__CONFIG__;
|
const c = window.__CONFIG__;
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
name: "home",
|
name: "home",
|
||||||
path: "/",
|
path: "/",
|
||||||
redirect: "/photos",
|
redirect: "/photos",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "about",
|
||||||
|
path: "/about",
|
||||||
|
component: About,
|
||||||
|
meta: { title: c.name, auth: false },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "feedback",
|
||||||
|
path: "/feedback",
|
||||||
|
component: Feedback,
|
||||||
|
meta: { title: c.name, auth: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "license",
|
||||||
|
path: "/about/license",
|
||||||
|
component: License,
|
||||||
|
meta: { title: c.name, auth: false },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "help",
|
||||||
|
path: "/help*",
|
||||||
|
component: Help,
|
||||||
|
meta: { title: c.name, auth: false },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "login",
|
||||||
|
path: "/login",
|
||||||
|
component: Login,
|
||||||
|
meta: { auth: false },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "photos",
|
||||||
|
path: "/photos",
|
||||||
|
component: Photos,
|
||||||
|
meta: { title: c.name, auth: true },
|
||||||
|
props: { staticFilter: { photo: "true" } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "moments",
|
||||||
|
path: "/moments",
|
||||||
|
component: Albums,
|
||||||
|
meta: { title: $gettext("Moments"), auth: true },
|
||||||
|
props: { view: "moment", staticFilter: { type: "moment" } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "moment",
|
||||||
|
path: "/moments/:uid/:slug",
|
||||||
|
component: AlbumPhotos,
|
||||||
|
meta: { title: $gettext("Moments"), auth: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "albums",
|
||||||
|
path: "/albums",
|
||||||
|
component: Albums,
|
||||||
|
meta: { title: $gettext("Albums"), auth: true },
|
||||||
|
props: { view: "album", staticFilter: { type: "album" } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "album",
|
||||||
|
path: "/albums/:uid/:slug",
|
||||||
|
component: AlbumPhotos,
|
||||||
|
meta: { auth: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "calendar",
|
||||||
|
path: "/calendar",
|
||||||
|
component: Albums,
|
||||||
|
meta: { title: $gettext("Calendar"), auth: true },
|
||||||
|
props: { view: "month", staticFilter: { type: "month" } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "month",
|
||||||
|
path: "/calendar/:uid/:slug",
|
||||||
|
component: AlbumPhotos,
|
||||||
|
meta: { title: $gettext("Calendar"), auth: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "folders",
|
||||||
|
path: "/folders",
|
||||||
|
component: Albums,
|
||||||
|
meta: { title: $gettext("Folders"), auth: true },
|
||||||
|
props: { view: "folder", staticFilter: { type: "folder", order: "default" } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "folder",
|
||||||
|
path: "/folders/:uid/:slug",
|
||||||
|
component: AlbumPhotos,
|
||||||
|
meta: { title: $gettext("Folders"), auth: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsorted",
|
||||||
|
path: "/unsorted",
|
||||||
|
component: Photos,
|
||||||
|
meta: { title: $gettext("Unsorted"), auth: true },
|
||||||
|
props: { staticFilter: { unsorted: true } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "favorites",
|
||||||
|
path: "/favorites",
|
||||||
|
component: Photos,
|
||||||
|
meta: { title: $gettext("Favorites"), auth: true },
|
||||||
|
props: { staticFilter: { favorite: true } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "videos",
|
||||||
|
path: "/videos",
|
||||||
|
component: Photos,
|
||||||
|
meta: { title: $gettext("Videos"), auth: true },
|
||||||
|
props: { staticFilter: { video: "true" } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "review",
|
||||||
|
path: "/review",
|
||||||
|
component: Photos,
|
||||||
|
meta: { title: $gettext("Review"), auth: true },
|
||||||
|
props: { staticFilter: { review: true } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "private",
|
||||||
|
path: "/private",
|
||||||
|
component: Photos,
|
||||||
|
meta: { title: $gettext("Private"), auth: true },
|
||||||
|
props: { staticFilter: { private: true } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "archive",
|
||||||
|
path: "/archive",
|
||||||
|
component: Photos,
|
||||||
|
meta: { title: $gettext("Archive"), auth: true },
|
||||||
|
props: { staticFilter: { archived: true } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "places",
|
||||||
|
path: "/places",
|
||||||
|
component: Places,
|
||||||
|
meta: { title: $gettext("Places"), auth: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "place",
|
||||||
|
path: "/places/:q",
|
||||||
|
component: Places,
|
||||||
|
meta: { title: $gettext("Places"), auth: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "states",
|
||||||
|
path: "/states",
|
||||||
|
component: Albums,
|
||||||
|
meta: { title: $gettext("Places"), auth: true },
|
||||||
|
props: { view: "state", staticFilter: { type: "state" } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "state",
|
||||||
|
path: "/states/:uid/:slug",
|
||||||
|
component: AlbumPhotos,
|
||||||
|
meta: { title: $gettext("Places"), auth: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "files",
|
||||||
|
path: "/library/files*",
|
||||||
|
component: Files,
|
||||||
|
meta: { title: $gettext("File Browser"), auth: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hidden",
|
||||||
|
path: "/library/hidden",
|
||||||
|
component: Photos,
|
||||||
|
meta: { title: $gettext("Hidden Files"), auth: true },
|
||||||
|
props: { staticFilter: { hidden: true } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "errors",
|
||||||
|
path: "/library/errors",
|
||||||
|
component: Errors,
|
||||||
|
meta: { title: c.name, auth: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "labels",
|
||||||
|
path: "/labels",
|
||||||
|
component: Labels,
|
||||||
|
meta: { title: $gettext("Labels"), auth: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "browse",
|
||||||
|
path: "/browse",
|
||||||
|
component: Photos,
|
||||||
|
meta: { title: $gettext("Search"), auth: true },
|
||||||
|
props: { staticFilter: { quality: 0 } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "people",
|
||||||
|
path: "/people",
|
||||||
|
component: People,
|
||||||
|
meta: { title: $gettext("People"), auth: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "library",
|
||||||
|
path: "/library",
|
||||||
|
component: Library,
|
||||||
|
meta: { title: $gettext("Library"), auth: true, background: "application-light" },
|
||||||
|
props: { tab: "library-index" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "library_import",
|
||||||
|
path: "/library/import",
|
||||||
|
component: Library,
|
||||||
|
meta: { title: $gettext("Library"), auth: true, background: "application-light" },
|
||||||
|
props: { tab: "library-import" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "library_logs",
|
||||||
|
path: "/library/logs",
|
||||||
|
component: Library,
|
||||||
|
meta: { title: $gettext("Library"), auth: true, background: "application-light" },
|
||||||
|
props: { tab: "library-logs" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "settings",
|
||||||
|
path: "/settings",
|
||||||
|
component: Settings,
|
||||||
|
meta: {
|
||||||
|
title: $gettext("Settings"),
|
||||||
|
auth: true,
|
||||||
|
settings: true,
|
||||||
|
background: "application-light",
|
||||||
},
|
},
|
||||||
{
|
props: { tab: "settings-general" },
|
||||||
name: "about",
|
},
|
||||||
path: "/about",
|
{
|
||||||
component: About,
|
name: "settings_library",
|
||||||
meta: {title: c.name, auth: false},
|
path: "/settings/library",
|
||||||
|
component: Settings,
|
||||||
|
meta: {
|
||||||
|
title: $gettext("Settings"),
|
||||||
|
auth: true,
|
||||||
|
settings: true,
|
||||||
|
background: "application-light",
|
||||||
},
|
},
|
||||||
{
|
props: { tab: "settings-library" },
|
||||||
name: "feedback",
|
},
|
||||||
path: "/feedback",
|
{
|
||||||
component: Feedback,
|
name: "settings_sync",
|
||||||
meta: {title: c.name, auth: true},
|
path: "/settings/sync",
|
||||||
|
component: Settings,
|
||||||
|
meta: {
|
||||||
|
title: $gettext("Settings"),
|
||||||
|
auth: true,
|
||||||
|
settings: true,
|
||||||
|
background: "application-light",
|
||||||
},
|
},
|
||||||
{
|
props: { tab: "settings-sync" },
|
||||||
name: "license",
|
},
|
||||||
path: "/about/license",
|
{
|
||||||
component: License,
|
name: "settings_account",
|
||||||
meta: {title: c.name, auth: false},
|
path: "/settings/account",
|
||||||
},
|
component: Settings,
|
||||||
{
|
meta: {
|
||||||
name: "help",
|
title: $gettext("Settings"),
|
||||||
path: "/help*",
|
auth: true,
|
||||||
component: Help,
|
settings: true,
|
||||||
meta: {title: c.name, auth: false},
|
background: "application-light",
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "login",
|
|
||||||
path: "/login",
|
|
||||||
component: Login,
|
|
||||||
meta: {auth: false},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "photos",
|
|
||||||
path: "/photos",
|
|
||||||
component: Photos,
|
|
||||||
meta: {title: c.name, auth: true},
|
|
||||||
props: {staticFilter: {photo: "true"}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "moments",
|
|
||||||
path: "/moments",
|
|
||||||
component: Albums,
|
|
||||||
meta: {title: $gettext("Moments"), auth: true},
|
|
||||||
props: {view: "moment", staticFilter: {type: "moment"}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "moment",
|
|
||||||
path: "/moments/:uid/:slug",
|
|
||||||
component: AlbumPhotos,
|
|
||||||
meta: {title: $gettext("Moments"), auth: true},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "albums",
|
|
||||||
path: "/albums",
|
|
||||||
component: Albums,
|
|
||||||
meta: {title: $gettext("Albums"), auth: true},
|
|
||||||
props: {view: "album", staticFilter: {type: "album"}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "album",
|
|
||||||
path: "/albums/:uid/:slug",
|
|
||||||
component: AlbumPhotos,
|
|
||||||
meta: {auth: true},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "calendar",
|
|
||||||
path: "/calendar",
|
|
||||||
component: Albums,
|
|
||||||
meta: {title: $gettext("Calendar"), auth: true},
|
|
||||||
props: {view: "month", staticFilter: {type: "month"}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "month",
|
|
||||||
path: "/calendar/:uid/:slug",
|
|
||||||
component: AlbumPhotos,
|
|
||||||
meta: {title: $gettext("Calendar"), auth: true},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "folders",
|
|
||||||
path: "/folders",
|
|
||||||
component: Albums,
|
|
||||||
meta: {title: $gettext("Folders"), auth: true},
|
|
||||||
props: {view: "folder", staticFilter: {type: "folder", order: "default"}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "folder",
|
|
||||||
path: "/folders/:uid/:slug",
|
|
||||||
component: AlbumPhotos,
|
|
||||||
meta: {title: $gettext("Folders"), auth: true},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "unsorted",
|
|
||||||
path: "/unsorted",
|
|
||||||
component: Photos,
|
|
||||||
meta: {title: $gettext("Unsorted"), auth: true},
|
|
||||||
props: {staticFilter: {unsorted: true}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "favorites",
|
|
||||||
path: "/favorites",
|
|
||||||
component: Photos,
|
|
||||||
meta: {title: $gettext("Favorites"), auth: true},
|
|
||||||
props: {staticFilter: {favorite: true}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "videos",
|
|
||||||
path: "/videos",
|
|
||||||
component: Photos,
|
|
||||||
meta: {title: $gettext("Videos"), auth: true},
|
|
||||||
props: {staticFilter: {video: "true"}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "review",
|
|
||||||
path: "/review",
|
|
||||||
component: Photos,
|
|
||||||
meta: {title: $gettext("Review"), auth: true},
|
|
||||||
props: {staticFilter: {review: true}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "private",
|
|
||||||
path: "/private",
|
|
||||||
component: Photos,
|
|
||||||
meta: {title: $gettext("Private"), auth: true},
|
|
||||||
props: {staticFilter: {private: true}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "archive",
|
|
||||||
path: "/archive",
|
|
||||||
component: Photos,
|
|
||||||
meta: {title: $gettext("Archive"), auth: true},
|
|
||||||
props: {staticFilter: {archived: true}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "places",
|
|
||||||
path: "/places",
|
|
||||||
component: Places,
|
|
||||||
meta: {title: $gettext("Places"), auth: true},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "place",
|
|
||||||
path: "/places/:q",
|
|
||||||
component: Places,
|
|
||||||
meta: {title: $gettext("Places"), auth: true},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "states",
|
|
||||||
path: "/states",
|
|
||||||
component: Albums,
|
|
||||||
meta: {title: $gettext("Places"), auth: true},
|
|
||||||
props: {view: "state", staticFilter: {type: "state"}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "state",
|
|
||||||
path: "/states/:uid/:slug",
|
|
||||||
component: AlbumPhotos,
|
|
||||||
meta: {title: $gettext("Places"), auth: true},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "files",
|
|
||||||
path: "/library/files*",
|
|
||||||
component: Files,
|
|
||||||
meta: {title: $gettext("File Browser"), auth: true},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "hidden",
|
|
||||||
path: "/library/hidden",
|
|
||||||
component: Photos,
|
|
||||||
meta: {title: $gettext("Hidden Files"), auth: true},
|
|
||||||
props: {staticFilter: {hidden: true}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "errors",
|
|
||||||
path: "/library/errors",
|
|
||||||
component: Errors,
|
|
||||||
meta: {title: c.name, auth: true},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "labels",
|
|
||||||
path: "/labels",
|
|
||||||
component: Labels,
|
|
||||||
meta: {title: $gettext("Labels"), auth: true},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "browse",
|
|
||||||
path: "/browse",
|
|
||||||
component: Photos,
|
|
||||||
meta: {title: $gettext("Search"), auth: true},
|
|
||||||
props: {staticFilter: {quality: 0}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "people",
|
|
||||||
path: "/people",
|
|
||||||
component: People,
|
|
||||||
meta: {title: $gettext("People"), auth: true},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "library",
|
|
||||||
path: "/library",
|
|
||||||
component: Library,
|
|
||||||
meta: {title: $gettext("Library"), auth: true, background: "application-light"},
|
|
||||||
props: {tab: "library-index"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "library_import",
|
|
||||||
path: "/library/import",
|
|
||||||
component: Library,
|
|
||||||
meta: {title: $gettext("Library"), auth: true, background: "application-light"},
|
|
||||||
props: {tab: "library-import"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "library_logs",
|
|
||||||
path: "/library/logs",
|
|
||||||
component: Library,
|
|
||||||
meta: {title: $gettext("Library"), auth: true, background: "application-light"},
|
|
||||||
props: {tab: "library-logs"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "settings",
|
|
||||||
path: "/settings",
|
|
||||||
component: Settings,
|
|
||||||
meta: {title: $gettext("Settings"), auth: true, settings: true, background: "application-light"},
|
|
||||||
props: {tab: "settings-general"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "settings_library",
|
|
||||||
path: "/settings/library",
|
|
||||||
component: Settings,
|
|
||||||
meta: {title: $gettext("Settings"), auth: true, settings: true, background: "application-light"},
|
|
||||||
props: {tab: "settings-library"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "settings_sync",
|
|
||||||
path: "/settings/sync",
|
|
||||||
component: Settings,
|
|
||||||
meta: {title: $gettext("Settings"), auth: true, settings: true, background: "application-light"},
|
|
||||||
props: {tab: "settings-sync"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "settings_account",
|
|
||||||
path: "/settings/account",
|
|
||||||
component: Settings,
|
|
||||||
meta: {title: $gettext("Settings"), auth: true, settings: true, background: "application-light"},
|
|
||||||
props: {tab: "settings-account"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "discover",
|
|
||||||
path: "/discover",
|
|
||||||
component: Discover,
|
|
||||||
meta: {title: $gettext("Discover"), auth: true, background: "application-light"},
|
|
||||||
props: {tab: 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "discover_similar",
|
|
||||||
path: "/discover/similar",
|
|
||||||
component: Discover,
|
|
||||||
meta: {title: $gettext("Discover"), auth: true, background: "application-light"},
|
|
||||||
props: {tab: 1},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "discover_season",
|
|
||||||
path: "/discover/season",
|
|
||||||
component: Discover,
|
|
||||||
meta: {title: $gettext("Discover"), auth: true, background: "application-light"},
|
|
||||||
props: {tab: 2},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "discover_random",
|
|
||||||
path: "/discover/random",
|
|
||||||
component: Discover,
|
|
||||||
meta: {title: $gettext("Discover"), auth: true, background: "application-light"},
|
|
||||||
props: {tab: 3},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "*", redirect: "/photos",
|
|
||||||
},
|
},
|
||||||
|
props: { tab: "settings-account" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "discover",
|
||||||
|
path: "/discover",
|
||||||
|
component: Discover,
|
||||||
|
meta: { title: $gettext("Discover"), auth: true, background: "application-light" },
|
||||||
|
props: { tab: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "discover_similar",
|
||||||
|
path: "/discover/similar",
|
||||||
|
component: Discover,
|
||||||
|
meta: { title: $gettext("Discover"), auth: true, background: "application-light" },
|
||||||
|
props: { tab: 1 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "discover_season",
|
||||||
|
path: "/discover/season",
|
||||||
|
component: Discover,
|
||||||
|
meta: { title: $gettext("Discover"), auth: true, background: "application-light" },
|
||||||
|
props: { tab: 2 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "discover_random",
|
||||||
|
path: "/discover/random",
|
||||||
|
component: Discover,
|
||||||
|
meta: { title: $gettext("Discover"), auth: true, background: "application-light" },
|
||||||
|
props: { tab: 3 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "*",
|
||||||
|
redirect: "/photos",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ import Log from "common/log";
|
|||||||
import PhotoPrism from "share.vue";
|
import PhotoPrism from "share.vue";
|
||||||
import Router from "vue-router";
|
import Router from "vue-router";
|
||||||
import Routes from "share/routes";
|
import Routes from "share/routes";
|
||||||
import {config, session} from "session";
|
import { config, session } from "session";
|
||||||
import {Settings} from "luxon";
|
import { Settings } from "luxon";
|
||||||
import Socket from "common/websocket";
|
import Socket from "common/websocket";
|
||||||
import Viewer from "common/viewer";
|
import Viewer from "common/viewer";
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
@@ -53,13 +53,15 @@ import VueFullscreen from "vue-fullscreen";
|
|||||||
import VueInfiniteScroll from "vue-infinite-scroll";
|
import VueInfiniteScroll from "vue-infinite-scroll";
|
||||||
import VueModal from "vue-js-modal";
|
import VueModal from "vue-js-modal";
|
||||||
import Hls from "hls.js";
|
import Hls from "hls.js";
|
||||||
import {$gettext, Mount} from "common/vm";
|
import { $gettext, Mount } from "common/vm";
|
||||||
|
|
||||||
// Initialize helpers
|
// Initialize helpers
|
||||||
const viewer = new Viewer();
|
const viewer = new Viewer();
|
||||||
const clipboard = new Clipboard(window.localStorage, "photo_clipboard");
|
const clipboard = new Clipboard(window.localStorage, "photo_clipboard");
|
||||||
const isPublic = config.get("public");
|
const isPublic = config.get("public");
|
||||||
const isMobile = (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent));
|
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
||||||
|
navigator.userAgent
|
||||||
|
);
|
||||||
|
|
||||||
// HTTP Live Streaming (video support)
|
// HTTP Live Streaming (video support)
|
||||||
window.Hls = Hls;
|
window.Hls = Hls;
|
||||||
@@ -77,23 +79,23 @@ Vue.prototype.$clipboard = clipboard;
|
|||||||
Vue.prototype.$isMobile = isMobile;
|
Vue.prototype.$isMobile = isMobile;
|
||||||
|
|
||||||
// Register Vuetify
|
// Register Vuetify
|
||||||
Vue.use(Vuetify, {"theme": config.theme});
|
Vue.use(Vuetify, { theme: config.theme });
|
||||||
|
|
||||||
Vue.config.language = config.values.settings.ui.language;
|
Vue.config.language = config.values.settings.ui.language;
|
||||||
Settings.defaultLocale = Vue.config.language.substring(0, 2);
|
Settings.defaultLocale = Vue.config.language.substring(0, 2);
|
||||||
|
|
||||||
// Register other VueJS plugins
|
// Register other VueJS plugins
|
||||||
Vue.use(GetTextPlugin, {
|
Vue.use(GetTextPlugin, {
|
||||||
translations: config.translations,
|
translations: config.translations,
|
||||||
silent: true, // !config.values.debug,
|
silent: true, // !config.values.debug,
|
||||||
defaultLanguage: Vue.config.language,
|
defaultLanguage: Vue.config.language,
|
||||||
autoAddKeyAttributes: true,
|
autoAddKeyAttributes: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
Vue.use(VueLuxon);
|
Vue.use(VueLuxon);
|
||||||
Vue.use(VueInfiniteScroll);
|
Vue.use(VueInfiniteScroll);
|
||||||
Vue.use(VueFullscreen);
|
Vue.use(VueFullscreen);
|
||||||
Vue.use(VueModal, {dynamic: true, dynamicDefaults: {clickToClose: true}});
|
Vue.use(VueModal, { dynamic: true, dynamicDefaults: { clickToClose: true } });
|
||||||
Vue.use(VueFilters);
|
Vue.use(VueFilters);
|
||||||
Vue.use(Components);
|
Vue.use(Components);
|
||||||
Vue.use(Dialogs);
|
Vue.use(Dialogs);
|
||||||
@@ -101,52 +103,52 @@ Vue.use(Router);
|
|||||||
|
|
||||||
// Configure client-side routing
|
// Configure client-side routing
|
||||||
const router = new Router({
|
const router = new Router({
|
||||||
routes: Routes,
|
routes: Routes,
|
||||||
mode: "history",
|
mode: "history",
|
||||||
saveScrollPosition: true,
|
saveScrollPosition: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
if (to.matched.some(record => record.meta.settings) && config.values.disable.settings) {
|
if (to.matched.some((record) => record.meta.settings) && config.values.disable.settings) {
|
||||||
next({name: "home"});
|
next({ name: "home" });
|
||||||
} else if (to.matched.some(record => record.meta.admin)) {
|
} else if (to.matched.some((record) => record.meta.admin)) {
|
||||||
if (isPublic || session.isAdmin()) {
|
if (isPublic || session.isAdmin()) {
|
||||||
next();
|
next();
|
||||||
} else {
|
|
||||||
next({
|
|
||||||
name: "login",
|
|
||||||
params: {nextUrl: to.fullPath},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (to.matched.some(record => record.meta.auth)) {
|
|
||||||
if (isPublic || session.isUser()) {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
next({
|
|
||||||
name: "login",
|
|
||||||
params: {nextUrl: to.fullPath},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
next();
|
next({
|
||||||
|
name: "login",
|
||||||
|
params: { nextUrl: to.fullPath },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
} else if (to.matched.some((record) => record.meta.auth)) {
|
||||||
|
if (isPublic || session.isUser()) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
next({
|
||||||
|
name: "login",
|
||||||
|
params: { nextUrl: to.fullPath },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.afterEach((to) => {
|
router.afterEach((to) => {
|
||||||
if (to.meta.title && config.values.siteTitle !== to.meta.title) {
|
if (to.meta.title && config.values.siteTitle !== to.meta.title) {
|
||||||
config.page.title = $gettext(to.meta.title);
|
config.page.title = $gettext(to.meta.title);
|
||||||
window.document.title = config.page.title;
|
window.document.title = config.page.title;
|
||||||
} else {
|
} else {
|
||||||
config.page.title = config.values.siteTitle;
|
config.page.title = config.values.siteTitle;
|
||||||
window.document.title = config.values.siteTitle;
|
window.document.title = config.values.siteTitle;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pull client config every 10 minutes in case push fails (except on mobile to save battery).
|
// Pull client config every 10 minutes in case push fails (except on mobile to save battery).
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
document.body.classList.add("mobile");
|
document.body.classList.add("mobile");
|
||||||
} else {
|
} else {
|
||||||
setInterval(() => config.update(), 600000);
|
setInterval(() => config.update(), 600000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start application.
|
// Start application.
|
||||||
|
|||||||
@@ -43,17 +43,17 @@ import PAlbumClipboard from "./album/clipboard.vue";
|
|||||||
const components = {};
|
const components = {};
|
||||||
|
|
||||||
components.install = (Vue) => {
|
components.install = (Vue) => {
|
||||||
Vue.component("p-notify", PNotify);
|
Vue.component("PNotify", PNotify);
|
||||||
Vue.component("p-navigation", PNavigation);
|
Vue.component("PNavigation", PNavigation);
|
||||||
Vue.component("p-scroll-top", PScrollTop);
|
Vue.component("PScrollTop", PScrollTop);
|
||||||
Vue.component("p-loading-bar", PLoadingBar);
|
Vue.component("PLoadingBar", PLoadingBar);
|
||||||
Vue.component("p-video-player", PVideoPlayer);
|
Vue.component("PVideoPlayer", PVideoPlayer);
|
||||||
Vue.component("p-photo-viewer", PPhotoViewer);
|
Vue.component("PPhotoViewer", PPhotoViewer);
|
||||||
Vue.component("p-photo-cards", PPhotoCards);
|
Vue.component("PPhotoCards", PPhotoCards);
|
||||||
Vue.component("p-photo-mosaic", PPhotoMosaic);
|
Vue.component("PPhotoMosaic", PPhotoMosaic);
|
||||||
Vue.component("p-photo-list", PPhotoList);
|
Vue.component("PPhotoList", PPhotoList);
|
||||||
Vue.component("p-photo-clipboard", PPhotoClipboard);
|
Vue.component("PPhotoClipboard", PPhotoClipboard);
|
||||||
Vue.component("p-album-clipboard", PAlbumClipboard);
|
Vue.component("PAlbumClipboard", PAlbumClipboard);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default components;
|
export default components;
|
||||||
|
|||||||
@@ -2,28 +2,30 @@ import Albums from "share/albums.vue";
|
|||||||
import AlbumPhotos from "share/photos.vue";
|
import AlbumPhotos from "share/photos.vue";
|
||||||
|
|
||||||
const c = window.__CONFIG__;
|
const c = window.__CONFIG__;
|
||||||
const shareTitle = c.settings.share.title ? c.settings.share.title : c.siteAuthor ? c.siteAuthor : c.name;
|
const siteTitle = c.siteAuthor ? c.siteAuthor : c.name;
|
||||||
|
const shareTitle = c.settings.share.title ? c.settings.share.title : siteTitle;
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
name: "home",
|
name: "home",
|
||||||
path: "/",
|
path: "/",
|
||||||
redirect: {name: "albums"},
|
redirect: { name: "albums" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "albums",
|
name: "albums",
|
||||||
path: "/s/:token",
|
path: "/s/:token",
|
||||||
component: Albums,
|
component: Albums,
|
||||||
meta: {title: shareTitle, auth: true},
|
meta: { title: shareTitle, auth: true },
|
||||||
props: {view: "album", staticFilter: {type: "album"}},
|
props: { view: "album", staticFilter: { type: "album" } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "album",
|
name: "album",
|
||||||
path: "/s/:token/:uid",
|
path: "/s/:token/:uid",
|
||||||
component: AlbumPhotos,
|
component: AlbumPhotos,
|
||||||
meta: {title: shareTitle, auth: true},
|
meta: { title: shareTitle, auth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "*", redirect: {name: "albums"},
|
path: "*",
|
||||||
},
|
redirect: { name: "albums" },
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -36,205 +36,199 @@ const webpack = require("webpack");
|
|||||||
|
|
||||||
const isDev = process.env.NODE_ENV !== "production";
|
const isDev = process.env.NODE_ENV !== "production";
|
||||||
|
|
||||||
if(isDev) {
|
if (isDev) {
|
||||||
console.log("Building frontend in DEVELOPMENT mode. Please wait.");
|
console.log("Building frontend in DEVELOPMENT mode. Please wait.");
|
||||||
} else {
|
} else {
|
||||||
console.log("Building frontend in PRODUCTION mode. Please wait.");
|
console.log("Building frontend in PRODUCTION mode. Please wait.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const PATHS = {
|
const PATHS = {
|
||||||
app: path.join(__dirname, "src/app.js"),
|
app: path.join(__dirname, "src/app.js"),
|
||||||
share: path.join(__dirname, "src/share.js"),
|
share: path.join(__dirname, "src/share.js"),
|
||||||
js: path.join(__dirname, "src"),
|
js: path.join(__dirname, "src"),
|
||||||
css: path.join(__dirname, "src/css"),
|
css: path.join(__dirname, "src/css"),
|
||||||
build: path.join(__dirname, "../assets/static/build"),
|
build: path.join(__dirname, "../assets/static/build"),
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
mode: isDev ? "development" : "production",
|
mode: isDev ? "development" : "production",
|
||||||
devtool: isDev ? "inline-source-map" : false,
|
devtool: isDev ? "inline-source-map" : false,
|
||||||
entry: {
|
entry: {
|
||||||
app: PATHS.app,
|
app: PATHS.app,
|
||||||
share: PATHS.share,
|
share: PATHS.share,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: PATHS.build,
|
||||||
|
filename: "[name].js",
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
modules: [path.join(__dirname, "src"), path.join(__dirname, "node_modules")],
|
||||||
|
alias: {
|
||||||
|
vue: isDev ? "vue/dist/vue.js" : "vue/dist/vue.min.js",
|
||||||
},
|
},
|
||||||
output: {
|
},
|
||||||
path: PATHS.build,
|
plugins: [
|
||||||
filename: "[name].js",
|
new MiniCssExtractPlugin({
|
||||||
},
|
filename: "[name].css",
|
||||||
resolve: {
|
}),
|
||||||
modules: [
|
],
|
||||||
path.join(__dirname, "src"),
|
node: {
|
||||||
path.join(__dirname, "node_modules"),
|
fs: "empty",
|
||||||
],
|
},
|
||||||
alias: {
|
performance: {
|
||||||
vue: isDev ? "vue/dist/vue.js" : "vue/dist/vue.min.js",
|
hints: isDev ? false : "error",
|
||||||
|
maxEntrypointSize: 4000000,
|
||||||
|
maxAssetSize: 4000000,
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
include: PATHS.app,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
enforce: "pre",
|
||||||
|
loader: "eslint-loader",
|
||||||
|
options: {
|
||||||
|
formatter: require("eslint-formatter-pretty"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
{
|
||||||
new MiniCssExtractPlugin({
|
test: /\.vue$/,
|
||||||
filename: "[name].css",
|
loader: "vue-loader",
|
||||||
}),
|
include: PATHS.js,
|
||||||
],
|
options: {
|
||||||
node: {
|
loaders: {
|
||||||
fs: "empty",
|
js: "babel-loader",
|
||||||
},
|
css: "css-loader",
|
||||||
performance: {
|
},
|
||||||
hints: isDev ? false : "error",
|
},
|
||||||
maxEntrypointSize: 4000000,
|
},
|
||||||
maxAssetSize: 4000000,
|
{
|
||||||
},
|
test: /\.js$/,
|
||||||
module: {
|
loader: "babel-loader",
|
||||||
rules: [
|
include: PATHS.js,
|
||||||
{
|
exclude: (file) => /node_modules/.test(file),
|
||||||
test: /\.js$/,
|
query: {
|
||||||
include: PATHS.app,
|
presets: ["@babel/preset-env"],
|
||||||
exclude: /node_modules/,
|
compact: false,
|
||||||
enforce: "pre",
|
},
|
||||||
loader: "eslint-loader",
|
},
|
||||||
options: {
|
{
|
||||||
formatter: require("eslint-formatter-pretty"),
|
test: /\.css$/,
|
||||||
|
include: PATHS.css,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: MiniCssExtractPlugin.loader,
|
||||||
|
options: {
|
||||||
|
hmr: false,
|
||||||
|
fallback: "vue-style-loader",
|
||||||
|
use: [
|
||||||
|
"style-loader",
|
||||||
|
{
|
||||||
|
loader: "css-loader",
|
||||||
|
options: {
|
||||||
|
importLoaders: 1,
|
||||||
|
sourceMap: isDev,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
loader: "postcss-loader",
|
||||||
test: /\.vue$/,
|
options: {
|
||||||
loader: "vue-loader",
|
sourceMap: isDev,
|
||||||
include: PATHS.js,
|
config: {
|
||||||
options: {
|
path: path.resolve(__dirname, "./postcss.config.js"),
|
||||||
loaders: {
|
|
||||||
js: "babel-loader",
|
|
||||||
css: "css-loader",
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
"resolve-url-loader",
|
||||||
|
],
|
||||||
|
publicPath: PATHS.build,
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
test: /\.js$/,
|
"css-loader",
|
||||||
loader: "babel-loader",
|
|
||||||
include: PATHS.js,
|
|
||||||
exclude: file => (
|
|
||||||
/node_modules/.test(file)
|
|
||||||
),
|
|
||||||
query: {
|
|
||||||
presets: ["@babel/preset-env"],
|
|
||||||
compact: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.css$/,
|
|
||||||
include: PATHS.css,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: MiniCssExtractPlugin.loader,
|
|
||||||
options: {
|
|
||||||
hmr: false,
|
|
||||||
fallback: "vue-style-loader",
|
|
||||||
use: [
|
|
||||||
"style-loader",
|
|
||||||
{
|
|
||||||
loader: "css-loader",
|
|
||||||
options: {
|
|
||||||
importLoaders: 1,
|
|
||||||
sourceMap: isDev,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: "postcss-loader",
|
|
||||||
options: {
|
|
||||||
sourceMap: isDev,
|
|
||||||
config: {
|
|
||||||
path: path.resolve(__dirname, "./postcss.config.js"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"resolve-url-loader",
|
|
||||||
],
|
|
||||||
publicPath: PATHS.build,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"css-loader",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.css$/,
|
|
||||||
include: /node_modules/,
|
|
||||||
loaders: [
|
|
||||||
"vue-style-loader",
|
|
||||||
"style-loader",
|
|
||||||
{
|
|
||||||
loader: "css-loader",
|
|
||||||
options: { importLoaders: 1, sourceMap: isDev },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: "postcss-loader",
|
|
||||||
options: {
|
|
||||||
sourceMap: isDev,
|
|
||||||
config: {
|
|
||||||
path: path.resolve(__dirname, "./postcss.config.js"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"resolve-url-loader",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.s[c|a]ss$/,
|
|
||||||
use: [
|
|
||||||
"vue-style-loader",
|
|
||||||
"style-loader",
|
|
||||||
{
|
|
||||||
loader: "css-loader",
|
|
||||||
options: { importLoaders: 2, sourceMap: isDev },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: "postcss-loader",
|
|
||||||
options: {
|
|
||||||
sourceMap: isDev,
|
|
||||||
config: {
|
|
||||||
path: path.resolve(__dirname, "./postcss.config.js"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"resolve-url-loader",
|
|
||||||
"sass-loader",
|
|
||||||
],
|
|
||||||
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(png|jpg|jpeg|gif)$/,
|
|
||||||
loader: "file-loader",
|
|
||||||
options: {
|
|
||||||
name: "[hash].[ext]",
|
|
||||||
publicPath: "/static/build/img",
|
|
||||||
outputPath: "img",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
|
|
||||||
loader: "file-loader",
|
|
||||||
options: {
|
|
||||||
name: "[hash].[ext]",
|
|
||||||
publicPath: "/static/build/fonts",
|
|
||||||
outputPath: "fonts",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.svg/,
|
|
||||||
use: {
|
|
||||||
loader: "svg-url-loader",
|
|
||||||
options: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
include: /node_modules/,
|
||||||
|
loaders: [
|
||||||
|
"vue-style-loader",
|
||||||
|
"style-loader",
|
||||||
|
{
|
||||||
|
loader: "css-loader",
|
||||||
|
options: { importLoaders: 1, sourceMap: isDev },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: "postcss-loader",
|
||||||
|
options: {
|
||||||
|
sourceMap: isDev,
|
||||||
|
config: {
|
||||||
|
path: path.resolve(__dirname, "./postcss.config.js"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"resolve-url-loader",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.s[c|a]ss$/,
|
||||||
|
use: [
|
||||||
|
"vue-style-loader",
|
||||||
|
"style-loader",
|
||||||
|
{
|
||||||
|
loader: "css-loader",
|
||||||
|
options: { importLoaders: 2, sourceMap: isDev },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: "postcss-loader",
|
||||||
|
options: {
|
||||||
|
sourceMap: isDev,
|
||||||
|
config: {
|
||||||
|
path: path.resolve(__dirname, "./postcss.config.js"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"resolve-url-loader",
|
||||||
|
"sass-loader",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(png|jpg|jpeg|gif)$/,
|
||||||
|
loader: "file-loader",
|
||||||
|
options: {
|
||||||
|
name: "[hash].[ext]",
|
||||||
|
publicPath: "/static/build/img",
|
||||||
|
outputPath: "img",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
|
||||||
|
loader: "file-loader",
|
||||||
|
options: {
|
||||||
|
name: "[hash].[ext]",
|
||||||
|
publicPath: "/static/build/fonts",
|
||||||
|
outputPath: "fonts",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.svg/,
|
||||||
|
use: {
|
||||||
|
loader: "svg-url-loader",
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// No sourcemap for production
|
// No sourcemap for production
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
const devToolPlugin = new webpack.SourceMapDevToolPlugin({
|
const devToolPlugin = new webpack.SourceMapDevToolPlugin({
|
||||||
filename: "[file].map",
|
filename: "[file].map",
|
||||||
});
|
});
|
||||||
|
|
||||||
config.plugins.push(devToolPlugin);
|
config.plugins.push(devToolPlugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
|||||||
Reference in New Issue
Block a user