mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Upgrade Webpack and JS dependencies
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
FROM photoprism/development:20190506
|
FROM photoprism/development:20190507
|
||||||
|
|
||||||
# Set up project directory
|
# Set up project directory
|
||||||
WORKDIR "/go/src/github.com/photoprism/photoprism"
|
WORKDIR "/go/src/github.com/photoprism/photoprism"
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -24,6 +24,8 @@ build:
|
|||||||
js:
|
js:
|
||||||
(cd frontend && npm install --production)
|
(cd frontend && npm install --production)
|
||||||
(cd frontend && env NODE_ENV=production npm run build)
|
(cd frontend && env NODE_ENV=production npm run build)
|
||||||
|
test-js:
|
||||||
|
(cd frontend && env NODE_ENV=development npm run test)
|
||||||
start:
|
start:
|
||||||
go run cmd/photoprism/photoprism.go start
|
go run cmd/photoprism/photoprism.go start
|
||||||
migrate:
|
migrate:
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ services:
|
|||||||
PHOTOPRISM_SQL_PORT: 4000
|
PHOTOPRISM_SQL_PORT: 4000
|
||||||
PHOTOPRISM_SQL_PASSWORD: "photoprism"
|
PHOTOPRISM_SQL_PASSWORD: "photoprism"
|
||||||
PHOTOPRISM_DARKTABLE_CLI: "/usr/bin/darktable-cli"
|
PHOTOPRISM_DARKTABLE_CLI: "/usr/bin/darktable-cli"
|
||||||
|
TF_CPP_MIN_LOG_LEVEL: 1
|
||||||
|
|
||||||
database:
|
database:
|
||||||
image: mysql:8.0.16
|
image: mysql:8.0.16
|
||||||
|
|||||||
@@ -5,5 +5,8 @@ RUN mkdir -p /srv/photoprism/photos/import && \
|
|||||||
|
|
||||||
RUN photoprism import
|
RUN photoprism import
|
||||||
|
|
||||||
|
# Hide TensorFlow warnings
|
||||||
|
ENV TF_CPP_MIN_LOG_LEVEL 2
|
||||||
|
|
||||||
# Start PhotoPrism server
|
# Start PhotoPrism server
|
||||||
CMD photoprism start
|
CMD photoprism start
|
||||||
@@ -16,7 +16,13 @@ RUN apt-get update && apt-get upgrade && \
|
|||||||
apt-get install \
|
apt-get install \
|
||||||
build-essential \
|
build-essential \
|
||||||
curl \
|
curl \
|
||||||
|
chrpath \
|
||||||
|
libssl-dev \
|
||||||
|
libxft-dev \
|
||||||
|
libfreetype6 \
|
||||||
libfreetype6-dev \
|
libfreetype6-dev \
|
||||||
|
libfontconfig1 \
|
||||||
|
libfontconfig1-dev \
|
||||||
libhdf5-serial-dev \
|
libhdf5-serial-dev \
|
||||||
libpng-dev \
|
libpng-dev \
|
||||||
libzmq3-dev \
|
libzmq3-dev \
|
||||||
@@ -35,7 +41,11 @@ RUN apt-get update && apt-get upgrade && \
|
|||||||
git \
|
git \
|
||||||
mysql-client \
|
mysql-client \
|
||||||
libgtk-3-bin \
|
libgtk-3-bin \
|
||||||
tzdata
|
tzdata \
|
||||||
|
gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libgcc1 \
|
||||||
|
libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 \
|
||||||
|
libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 \
|
||||||
|
libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils
|
||||||
|
|
||||||
# Install darktable (RAW to JPEG converter)
|
# Install darktable (RAW to JPEG converter)
|
||||||
RUN add-apt-repository ppa:pmjdebruijn/darktable-release && \
|
RUN add-apt-repository ppa:pmjdebruijn/darktable-release && \
|
||||||
@@ -50,15 +60,18 @@ RUN curl -L \
|
|||||||
tar -C "/usr/local" -xz
|
tar -C "/usr/local" -xz
|
||||||
RUN ldconfig
|
RUN ldconfig
|
||||||
|
|
||||||
# Hide some warnings
|
# Show TensorFlow debug log
|
||||||
ENV TF_CPP_MIN_LOG_LEVEL 2
|
ENV TF_CPP_MIN_LOG_LEVEL 0
|
||||||
|
|
||||||
# Install NPM (NodeJS)
|
# Install NodeJS
|
||||||
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -
|
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install nodejs && \
|
apt-get install nodejs && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install and configure NodeJS Package Manager (npm)
|
||||||
|
ENV NODE_ENV production
|
||||||
RUN npm install -g npm
|
RUN npm install -g npm
|
||||||
RUN npm config set cache ~/.cache/npm
|
RUN npm config set cache ~/.cache/npm
|
||||||
|
|
||||||
@@ -91,10 +104,6 @@ RUN wget "https://dl.photoprism.org/fixtures/testdata.zip?$BUILD_DATE" -O /tmp/p
|
|||||||
# Install goimports
|
# Install goimports
|
||||||
RUN env GO111MODULE=off /usr/local/go/bin/go get golang.org/x/tools/cmd/goimports
|
RUN env GO111MODULE=off /usr/local/go/bin/go get golang.org/x/tools/cmd/goimports
|
||||||
|
|
||||||
# Configure JS environment for building
|
|
||||||
ENV NODE_ENV production
|
|
||||||
ENV YARN_CACHE_FOLDER /root/.cache/yarn
|
|
||||||
|
|
||||||
# Configure broadwayd (HTML5 display server)
|
# Configure broadwayd (HTML5 display server)
|
||||||
# Command: broadwayd -p 8080 -a 0.0.0.0 :5
|
# Command: broadwayd -p 8080 -a 0.0.0.0 :5
|
||||||
ENV GDK_BACKEND broadway
|
ENV GDK_BACKEND broadway
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM photoprism/development:20190506 as build
|
FROM photoprism/development:20190507 as build
|
||||||
|
|
||||||
# Set up project directory
|
# Set up project directory
|
||||||
WORKDIR "/go/src/github.com/photoprism/photoprism"
|
WORKDIR "/go/src/github.com/photoprism/photoprism"
|
||||||
@@ -45,6 +45,9 @@ RUN apt-get update && \
|
|||||||
# Show photoprism version
|
# Show photoprism version
|
||||||
RUN photoprism -v
|
RUN photoprism -v
|
||||||
|
|
||||||
|
# Hide TensorFlow warnings
|
||||||
|
ENV TF_CPP_MIN_LOG_LEVEL 2
|
||||||
|
|
||||||
# Expose HTTP & TiDB port
|
# Expose HTTP & TiDB port
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
EXPOSE 4000
|
EXPOSE 4000
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM photoprism/development:20190506
|
FROM photoprism/development:20190507
|
||||||
|
|
||||||
# Install Python and TensorFlow
|
# Install Python and TensorFlow
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ main {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#app main a {
|
||||||
|
color: #00B8D4;
|
||||||
|
}
|
||||||
|
|
||||||
.v-badge__badge {
|
.v-badge__badge {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
height: 19px;
|
height: 19px;
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
// See https://github.com/webpack/loader-utils/issues/56
|
process.env.CHROME_BIN = require('puppeteer').executablePath()
|
||||||
process.noDeprecation = true;
|
|
||||||
|
|
||||||
module.exports = (config) => {
|
module.exports = (config) => {
|
||||||
const tests = 'tests/*/*.test.js';
|
|
||||||
|
|
||||||
config.set({
|
config.set({
|
||||||
frameworks: ['mocha'],
|
frameworks: ['mocha'],
|
||||||
|
|
||||||
phantomjsLauncher: {
|
browsers: ['PhotoPrism'],
|
||||||
// Have phantomjs exit if a ResourceError is encountered (useful if karma exits without killing phantom)
|
|
||||||
exitOnResourceError: true
|
|
||||||
},
|
|
||||||
|
|
||||||
browsers: ['PhantomJS'],
|
customLaunchers: {
|
||||||
|
PhotoPrism: {
|
||||||
|
base: 'ChromeHeadless',
|
||||||
|
flags: ['--disable-translate', '--disable-extensions', '--no-sandbox', '--disable-web-security'],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
files: [
|
files: [
|
||||||
{pattern: 'tests/**/*_test.js', watched: false}
|
{pattern: 'tests/**/*_test.js', watched: false}
|
||||||
@@ -32,6 +31,8 @@ module.exports = (config) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
webpack: {
|
webpack: {
|
||||||
|
mode: 'development',
|
||||||
|
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: [
|
modules: [
|
||||||
path.join(__dirname, 'src'),
|
path.join(__dirname, 'src'),
|
||||||
@@ -47,8 +48,12 @@ module.exports = (config) => {
|
|||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
|
exclude: file => (
|
||||||
|
/node_modules/.test(file)
|
||||||
|
),
|
||||||
query: {
|
query: {
|
||||||
presets: ['es2015']
|
presets: ['@babel/preset-env'],
|
||||||
|
compact: false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
10130
frontend/package-lock.json
generated
10130
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -10,83 +10,100 @@
|
|||||||
"lint": "eslint app/ webpack.*.js --cache",
|
"lint": "eslint app/ webpack.*.js --cache",
|
||||||
"test": "karma start"
|
"test": "karma start"
|
||||||
},
|
},
|
||||||
|
"babel": {
|
||||||
|
"presets": [
|
||||||
|
"@babel/preset-env"
|
||||||
|
]
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.2.0",
|
"@babel/cli": "^7.4.4",
|
||||||
|
"@babel/core": "^7.4.4",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.4.4",
|
||||||
|
"@babel/polyfill": "^7.4.4",
|
||||||
|
"@babel/preset-env": "^7.4.4",
|
||||||
|
"@babel/register": "^7.4.4",
|
||||||
|
"@fortawesome/fontawesome-free": "^5.8.1",
|
||||||
"@types/leaflet": "^1.4.4",
|
"@types/leaflet": "^1.4.4",
|
||||||
"autoprefixer": "^6.7.2",
|
"acorn": "^6.1.1",
|
||||||
"axios": "^0.16.1",
|
"ajv": "^6.10.0",
|
||||||
"axios-mock-adapter": "^1.8.1",
|
"autoprefixer": "^9.5.1",
|
||||||
"babel-core": "^6.22.1",
|
"axios": "^0.18.0",
|
||||||
"babel-eslint": "^7.1.1",
|
"axios-mock-adapter": "^1.16.0",
|
||||||
"babel-loader": "^7.1.5",
|
"babel-eslint": "^10.0.1",
|
||||||
"babel-plugin-istanbul": "^3.1.2",
|
"babel-loader": "^8.0.5",
|
||||||
"babel-plugin-transform-runtime": "^6.22.0",
|
"browserslist": "^4.5.6",
|
||||||
"babel-polyfill": "^6.23.0",
|
"chai": "^4.2.0",
|
||||||
"babel-preset-env": "^1.2.1",
|
|
||||||
"babel-preset-es2015": "^6.24.1",
|
|
||||||
"babel-preset-stage-2": "^6.22.0",
|
|
||||||
"babel-register": "^6.22.0",
|
|
||||||
"chai": "^3.5.0",
|
|
||||||
"chalk": "^1.1.3",
|
"chalk": "^1.1.3",
|
||||||
"chart.js": "^2.5.0",
|
"chart.js": "^2.5.0",
|
||||||
|
"clean-webpack-plugin": "^2.0.2",
|
||||||
"connect-history-api-fallback": "^1.3.0",
|
"connect-history-api-fallback": "^1.3.0",
|
||||||
"copy-webpack-plugin": "^4.0.1",
|
"copy-webpack-plugin": "^4.6.0",
|
||||||
"cross-env": "^3.1.4",
|
"cross-env": "^5.2.0",
|
||||||
"css-loader": "^0.28.11",
|
"css-loader": "^2.1.1",
|
||||||
"eslint": "^3.14.1",
|
"cssnano": "^4.1.10",
|
||||||
"eslint-config-standard": "^6.2.1",
|
"eslint": "^5.16.0",
|
||||||
"eslint-friendly-formatter": "^2.0.7",
|
"eslint-config-standard": "^12.0.0",
|
||||||
"eslint-loader": "^1.6.1",
|
"eslint-friendly-formatter": "^4.0.1",
|
||||||
"eslint-plugin-html": "^2.0.0",
|
"eslint-loader": "^2.1.2",
|
||||||
"eslint-plugin-promise": "^3.4.0",
|
"eslint-plugin-html": "^2.0.3",
|
||||||
"eslint-plugin-standard": "^2.0.1",
|
"eslint-plugin-import": "^2.17.2",
|
||||||
|
"eslint-plugin-node": "^9.0.1",
|
||||||
|
"eslint-plugin-promise": "^4.1.1",
|
||||||
|
"eslint-plugin-standard": "^4.0.0",
|
||||||
"eventsource-polyfill": "^0.9.6",
|
"eventsource-polyfill": "^0.9.6",
|
||||||
"extract-text-webpack-plugin": "^3.0.2",
|
"file-loader": "^3.0.1",
|
||||||
"file-loader": "^0.10.0",
|
"friendly-errors-webpack-plugin": "^1.7.0",
|
||||||
"friendly-errors-webpack-plugin": "^1.1.3",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"html-webpack-plugin": "^2.28.0",
|
"http-proxy-middleware": "^0.19.1",
|
||||||
"http-proxy-middleware": "^0.17.3",
|
"inject-loader": "^4.0.1",
|
||||||
"inject-loader": "^2.0.1",
|
"karma": "^4.1.0",
|
||||||
"justified-gallery": "^0.1.0",
|
"karma-chrome-launcher": "^2.2.0",
|
||||||
"karma": "^1.7.0",
|
"karma-firefox-launcher": "^1.1.0",
|
||||||
"karma-chrome-launcher": "^2.1.1",
|
"karma-htmlfile-reporter": "^0.3.8",
|
||||||
"karma-firefox-launcher": "^1.0.1",
|
|
||||||
"karma-htmlfile-reporter": "^0.3.5",
|
|
||||||
"karma-mocha": "^1.3.0",
|
"karma-mocha": "^1.3.0",
|
||||||
"karma-phantomjs-launcher": "^1.0.4",
|
|
||||||
"karma-webdriver-launcher": "^1.0.5",
|
"karma-webdriver-launcher": "^1.0.5",
|
||||||
"karma-webpack": "^2.0.3",
|
"karma-webpack": "^4.0.0-rc.6",
|
||||||
"leaflet": "^1.4.0",
|
"leaflet": "^1.4.0",
|
||||||
"material-design-icons-iconfont": "^3.0.3",
|
"material-design-icons-iconfont": "^3.0.3",
|
||||||
"mocha": "^3.3.0",
|
"mini-css-extract-plugin": "^0.6.0",
|
||||||
"node-sass": "^4.9.2",
|
"mocha": "^6.1.4",
|
||||||
"optimize-css-assets-webpack-plugin": "^1.3.0",
|
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||||
"ora": "^1.1.0",
|
"ora": "^1.4.0",
|
||||||
"phantomjs-prebuilt": "^2.1.14",
|
|
||||||
"photoswipe": "^4.1.3",
|
"photoswipe": "^4.1.3",
|
||||||
"pluralize": "^4.0.0",
|
"pluralize": "^7.0.0",
|
||||||
"pubsub-js": "^1.5.6",
|
"postcss": "^7.0.16",
|
||||||
"sass-loader": "^7.0.3",
|
"postcss-browser-reporter": "^0.6.0",
|
||||||
"style-loader": "^0.16.1",
|
"postcss-import": "^12.0.1",
|
||||||
"svg-url-loader": "^2.0.2",
|
"postcss-loader": "^3.0.0",
|
||||||
"url-loader": "^0.5.8",
|
"postcss-preset-env": "^6.6.0",
|
||||||
"vue": "^2.4.4",
|
"postcss-reporter": "^6.0.1",
|
||||||
"vue-fullscreen": "^2.1.3",
|
"postcss-url": "^8.0.0",
|
||||||
|
"pubsub-js": "^1.7.0",
|
||||||
|
"puppeteer": "^1.15.0",
|
||||||
|
"resolve-url-loader": "^3.1.0",
|
||||||
|
"sass-loader": "^7.1.0",
|
||||||
|
"style-loader": "^0.23.1",
|
||||||
|
"sugarss": "^2.0.0",
|
||||||
|
"svg-url-loader": "^2.3.2",
|
||||||
|
"tar": "^4.4.8",
|
||||||
|
"url-loader": "^1.1.2",
|
||||||
|
"vue": "^2.6.10",
|
||||||
|
"vue-fullscreen": "^2.1.5",
|
||||||
"vue-infinite-scroll": "^2.0.2",
|
"vue-infinite-scroll": "^2.0.2",
|
||||||
"vue-justified-layout": "^0.1.0",
|
"vue-loader": "^14.2.4",
|
||||||
"vue-loader": "^11.1.4",
|
|
||||||
"vue-moment": "^4.0.0",
|
"vue-moment": "^4.0.0",
|
||||||
"vue-router": "^2.7.0",
|
"vue-router": "^3.0.6",
|
||||||
"vue-style-loader": "^2.0.0",
|
"vue-style-loader": "^4.1.2",
|
||||||
"vue-template-compiler": "^2.4.4",
|
"vue-template-compiler": "^2.6.10",
|
||||||
"vue-truncate-filter": "^1.1.7",
|
"vue-truncate-filter": "^1.1.7",
|
||||||
"vue2-leaflet": "^2.1.1",
|
"vue2-leaflet": "^2.1.1",
|
||||||
"vuelidate": "^0.4.3",
|
"vuelidate": "^0.7.4",
|
||||||
"vuetify": "^1.2.3",
|
"vuetify": "^1.5.14",
|
||||||
"webpack": "^3.12.0",
|
"webpack": "^4.30.0",
|
||||||
"webpack-bundle-analyzer": "^2.13.1",
|
"webpack-bundle-analyzer": "^3.3.2",
|
||||||
"webpack-dev-middleware": "^2.0.0",
|
"webpack-cli": "^3.3.2",
|
||||||
"webpack-hot-middleware": "^2.22.3",
|
"webpack-hot-middleware": "^2.24.4",
|
||||||
|
"webpack-md5-hash": "0.0.6",
|
||||||
"webpack-merge": "^4.1.3"
|
"webpack-merge": "^4.1.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -95,7 +112,7 @@
|
|||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 1%",
|
"> 1%",
|
||||||
"last 2 versions",
|
"not ie <= 9",
|
||||||
"not ie <= 8"
|
"last 3 versions"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
7
frontend/postcss.config.js
Normal file
7
frontend/postcss.config.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = ({ file, options, env }) => ({
|
||||||
|
plugins: {
|
||||||
|
"postcss-import": {},
|
||||||
|
"postcss-preset-env": true,
|
||||||
|
"cssnano": env === "production",
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -568,19 +568,3 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
h1, h2 {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -547,19 +547,3 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
h1, h2 {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -88,23 +88,3 @@
|
|||||||
methods: {}
|
methods: {}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
a {
|
|
||||||
color: #00B8D4;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2 {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -27,23 +27,3 @@
|
|||||||
methods: {}
|
methods: {}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
a {
|
|
||||||
color: #00B8D4;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2 {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
Additionally duplicates are removed and images get tagged and metadata (like location, camera model etc.) will be extracted. In case you have not supported file types
|
Additionally duplicates are removed and images get tagged and metadata (like location, camera model etc.) will be extracted. In case you have not supported file types
|
||||||
(e.g. videos) within the folder you import --> those are ignored. </p>
|
(e.g. videos) within the folder you import --> those are ignored. </p>
|
||||||
<v-btn color="success" @click="$refs.inputUpload.click()" type="file" class="importbtn">Import & Index</v-btn>
|
<v-btn color="success" @click="$refs.inputUpload.click()" type="file" class="importbtn">Import & Index</v-btn>
|
||||||
<input v-show="false" ref="inputUpload" type="file" @change="inputFileExcel" >
|
<input v-show="false" ref="inputUpload" type="file" >
|
||||||
</v-form>
|
</v-form>
|
||||||
</v-container>
|
</v-container>
|
||||||
<v-container fluid>
|
<v-container fluid>
|
||||||
@@ -28,233 +28,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Photo from 'model/photo';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'import',
|
name: 'import',
|
||||||
props: {},
|
props: {},
|
||||||
data() {
|
data() {
|
||||||
const query = this.$route.query;
|
return {}
|
||||||
const order = query['order'] ? query['order'] : 'newest';
|
|
||||||
const camera = query['camera'] ? parseInt(query['camera']) : 0;
|
|
||||||
const q = query['q'] ? query['q'] : '';
|
|
||||||
const country = query['country'] ? query['country'] : '';
|
|
||||||
const view = query['view'] ? query['view'] : 'details';
|
|
||||||
const cameras = [{ID: 0, CameraModel: 'All Cameras'}].concat(this.$config.getValue('cameras'));
|
|
||||||
const countries = [{
|
|
||||||
LocCountryCode: '',
|
|
||||||
LocCountry: 'All Countries'
|
|
||||||
}].concat(this.$config.getValue('countries'));
|
|
||||||
|
|
||||||
return {
|
|
||||||
'snackbarVisible': false,
|
|
||||||
'snackbarText': '',
|
|
||||||
'advandedSearch': false,
|
|
||||||
'window': {
|
|
||||||
width: 0,
|
|
||||||
height: 0
|
|
||||||
},
|
|
||||||
'results': [],
|
|
||||||
'query': {
|
|
||||||
view: view,
|
|
||||||
country: country,
|
|
||||||
camera: camera,
|
|
||||||
order: order,
|
|
||||||
q: q,
|
|
||||||
},
|
|
||||||
'options': {
|
|
||||||
'categories': [
|
|
||||||
{value: '', text: 'All Categories'},
|
|
||||||
{value: 'airport', text: 'Airport'},
|
|
||||||
{value: 'amenity', text: 'Amenity'},
|
|
||||||
{value: 'building', text: 'Building'},
|
|
||||||
{value: 'historic', text: 'Historic'},
|
|
||||||
{value: 'shop', text: 'Shop'},
|
|
||||||
{value: 'tourism', text: 'Tourism'},
|
|
||||||
],
|
|
||||||
'views': [
|
|
||||||
{value: 'details', text: 'Details'},
|
|
||||||
{value: 'list', text: 'List'},
|
|
||||||
{value: 'tiles', text: 'Tiles'},
|
|
||||||
],
|
|
||||||
'countries': countries,
|
|
||||||
'cameras': cameras,
|
|
||||||
'sorting': [
|
|
||||||
{value: 'newest', text: 'Newest first'},
|
|
||||||
{value: 'oldest', text: 'Oldest first'},
|
|
||||||
{value: 'imported', text: 'Recently imported'},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'listColumns': [
|
|
||||||
{text: 'Title', value: 'PhotoTitle'},
|
|
||||||
{text: 'Description', value: 'PhotoFavorite'},
|
|
||||||
{text: 'Taken At', value: 'TakenAt'},
|
|
||||||
{text: 'City', value: 'LocCity'},
|
|
||||||
{text: 'Country', value: 'LocCountry'},
|
|
||||||
{text: 'Camera', value: 'CameraModel'},
|
|
||||||
],
|
|
||||||
'view': view,
|
|
||||||
'loadMoreDisabled': true,
|
|
||||||
'pageSize': 60,
|
|
||||||
'offset': 0,
|
|
||||||
'lastQuery': {},
|
|
||||||
'submitTimeout': false,
|
|
||||||
'selected': [],
|
|
||||||
'dialog': false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
window.removeEventListener('resize', this.handleResize)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleResize() {
|
|
||||||
this.window.width = window.innerWidth;
|
|
||||||
this.window.height = window.innerHeight;
|
|
||||||
},
|
|
||||||
clearSelection() {
|
|
||||||
for (let i = 0; i < this.selected.length; i++) {
|
|
||||||
this.selected[i].selected = false;
|
|
||||||
}
|
|
||||||
this.selected = [];
|
|
||||||
this.updateSnackbar();
|
|
||||||
},
|
|
||||||
updateSnackbar(text) {
|
|
||||||
if (!text) text = "";
|
|
||||||
|
|
||||||
this.snackbarText = text;
|
|
||||||
|
|
||||||
this.snackbarVisible = this.snackbarText !== "";
|
|
||||||
},
|
|
||||||
showSnackbar() {
|
|
||||||
this.snackbarVisible = this.snackbarText !== "";
|
|
||||||
},
|
|
||||||
hideSnackbar() {
|
|
||||||
this.snackbarVisible = false;
|
|
||||||
},
|
|
||||||
selectPhoto(photo, ev) {
|
|
||||||
if (photo.selected) {
|
|
||||||
for (let i = 0; i < this.selected.length; i++) {
|
|
||||||
if (this.selected[i].id === photo.id) {
|
|
||||||
this.selected.splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
photo.selected = false;
|
|
||||||
} else {
|
|
||||||
this.selected.push(photo);
|
|
||||||
photo.selected = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.selected.length > 0) {
|
|
||||||
if (this.selected.length === 1) {
|
|
||||||
this.snackbarText = 'One photo selected';
|
|
||||||
} else {
|
|
||||||
this.snackbarText = this.selected.length + ' photos selected';
|
|
||||||
}
|
|
||||||
this.snackbarVisible = true;
|
|
||||||
} else {
|
|
||||||
this.snackbarText = '';
|
|
||||||
this.snackbarVisible = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
likePhoto(photo) {
|
|
||||||
photo.PhotoFavorite = !photo.PhotoFavorite;
|
|
||||||
photo.like(photo.PhotoFavorite);
|
|
||||||
},
|
|
||||||
deletePhoto(photo) {
|
|
||||||
this.$alert.success('Photo deleted');
|
|
||||||
},
|
|
||||||
formChange(event) {
|
|
||||||
this.refreshList();
|
|
||||||
},
|
|
||||||
clearQuery() {
|
|
||||||
this.query.q = '';
|
|
||||||
this.refreshList();
|
|
||||||
},
|
|
||||||
openPhoto(index) {
|
|
||||||
this.$refs.gallery.openPhoto(index)
|
|
||||||
},
|
|
||||||
loadMore() {
|
|
||||||
if (this.loadMoreDisabled) return;
|
|
||||||
|
|
||||||
this.loadMoreDisabled = true;
|
|
||||||
|
|
||||||
this.offset += this.pageSize;
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
count: this.pageSize,
|
|
||||||
offset: this.offset,
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(params, this.lastQuery);
|
|
||||||
|
|
||||||
Photo.search(params).then(response => {
|
|
||||||
console.log(response);
|
|
||||||
this.results = this.results.concat(response.models);
|
|
||||||
|
|
||||||
this.loadMoreDisabled = (response.models.length < this.pageSize);
|
|
||||||
|
|
||||||
if (this.loadMoreDisabled) {
|
|
||||||
this.$alert.info('All ' + this.results.length + ' photos loaded');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
refreshList() {
|
|
||||||
this.loadMoreDisabled = true;
|
|
||||||
|
|
||||||
// Don't query the same data more than once:197
|
|
||||||
if (JSON.stringify(this.lastQuery) === JSON.stringify(this.query)) return;
|
|
||||||
|
|
||||||
Object.assign(this.lastQuery, this.query);
|
|
||||||
|
|
||||||
this.offset = 0;
|
|
||||||
|
|
||||||
this.$router.replace({query: this.query});
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
count: this.pageSize,
|
|
||||||
offset: this.offset,
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(params, this.query);
|
|
||||||
|
|
||||||
Photo.search(params).then(response => {
|
|
||||||
this.results = response.models;
|
|
||||||
|
|
||||||
this.loadMoreDisabled = (response.models.length < this.pageSize);
|
|
||||||
|
|
||||||
if (this.loadMoreDisabled) {
|
|
||||||
this.$alert.info(this.results.length + ' photos found');
|
|
||||||
} else {
|
|
||||||
this.$alert.info('More than 50 photos found');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeRouteLeave(to, from, next) {
|
|
||||||
next()
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
window.addEventListener('resize', this.handleResize);
|
|
||||||
this.handleResize();
|
|
||||||
this.refreshList();
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
h1, h2 {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
Additionally duplicates are removed and images get tagged and metadata (like location, camera model etc.) will be extracted. In case you have not supported file types
|
Additionally duplicates are removed and images get tagged and metadata (like location, camera model etc.) will be extracted. In case you have not supported file types
|
||||||
(e.g. videos) within the folder you import --> those are ignored. </p>
|
(e.g. videos) within the folder you import --> those are ignored. </p>
|
||||||
<v-btn color="success" @click="$refs.inputUpload.click()" type="file" class="importbtn" disabled>Import & Index</v-btn>
|
<v-btn color="success" @click="$refs.inputUpload.click()" type="file" class="importbtn" disabled>Import & Index</v-btn>
|
||||||
<input v-show="false" ref="inputUpload" type="file" @change="inputFileExcel" >
|
<input v-show="false" ref="inputUpload" type="file">
|
||||||
<v-flex xs12 sm6 offset-sm3>
|
<v-flex xs12 sm6 offset-sm3>
|
||||||
<v-card class="card">
|
<v-card class="card">
|
||||||
<v-card-title primary-title>
|
<v-card-title primary-title>
|
||||||
@@ -42,233 +42,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Photo from 'model/photo';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'import',
|
name: 'import',
|
||||||
props: {},
|
props: {},
|
||||||
data() {
|
data() {
|
||||||
const query = this.$route.query;
|
return {}
|
||||||
const order = query['order'] ? query['order'] : 'newest';
|
|
||||||
const camera = query['camera'] ? parseInt(query['camera']) : 0;
|
|
||||||
const q = query['q'] ? query['q'] : '';
|
|
||||||
const country = query['country'] ? query['country'] : '';
|
|
||||||
const view = query['view'] ? query['view'] : 'details';
|
|
||||||
const cameras = [{ID: 0, CameraModel: 'All Cameras'}].concat(this.$config.getValue('cameras'));
|
|
||||||
const countries = [{
|
|
||||||
LocCountryCode: '',
|
|
||||||
LocCountry: 'All Countries'
|
|
||||||
}].concat(this.$config.getValue('countries'));
|
|
||||||
|
|
||||||
return {
|
|
||||||
'snackbarVisible': false,
|
|
||||||
'snackbarText': '',
|
|
||||||
'advandedSearch': false,
|
|
||||||
'window': {
|
|
||||||
width: 0,
|
|
||||||
height: 0
|
|
||||||
},
|
|
||||||
'results': [],
|
|
||||||
'query': {
|
|
||||||
view: view,
|
|
||||||
country: country,
|
|
||||||
camera: camera,
|
|
||||||
order: order,
|
|
||||||
q: q,
|
|
||||||
},
|
|
||||||
'options': {
|
|
||||||
'categories': [
|
|
||||||
{value: '', text: 'All Categories'},
|
|
||||||
{value: 'airport', text: 'Airport'},
|
|
||||||
{value: 'amenity', text: 'Amenity'},
|
|
||||||
{value: 'building', text: 'Building'},
|
|
||||||
{value: 'historic', text: 'Historic'},
|
|
||||||
{value: 'shop', text: 'Shop'},
|
|
||||||
{value: 'tourism', text: 'Tourism'},
|
|
||||||
],
|
|
||||||
'views': [
|
|
||||||
{value: 'details', text: 'Details'},
|
|
||||||
{value: 'list', text: 'List'},
|
|
||||||
{value: 'tiles', text: 'Tiles'},
|
|
||||||
],
|
|
||||||
'countries': countries,
|
|
||||||
'cameras': cameras,
|
|
||||||
'sorting': [
|
|
||||||
{value: 'newest', text: 'Newest first'},
|
|
||||||
{value: 'oldest', text: 'Oldest first'},
|
|
||||||
{value: 'imported', text: 'Recently imported'},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'listColumns': [
|
|
||||||
{text: 'Title', value: 'PhotoTitle'},
|
|
||||||
{text: 'Description', value: 'PhotoFavorite'},
|
|
||||||
{text: 'Taken At', value: 'TakenAt'},
|
|
||||||
{text: 'City', value: 'LocCity'},
|
|
||||||
{text: 'Country', value: 'LocCountry'},
|
|
||||||
{text: 'Camera', value: 'CameraModel'},
|
|
||||||
],
|
|
||||||
'view': view,
|
|
||||||
'loadMoreDisabled': true,
|
|
||||||
'pageSize': 60,
|
|
||||||
'offset': 0,
|
|
||||||
'lastQuery': {},
|
|
||||||
'submitTimeout': false,
|
|
||||||
'selected': [],
|
|
||||||
'dialog': false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
window.removeEventListener('resize', this.handleResize)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleResize() {
|
|
||||||
this.window.width = window.innerWidth;
|
|
||||||
this.window.height = window.innerHeight;
|
|
||||||
},
|
|
||||||
clearSelection() {
|
|
||||||
for (let i = 0; i < this.selected.length; i++) {
|
|
||||||
this.selected[i].selected = false;
|
|
||||||
}
|
|
||||||
this.selected = [];
|
|
||||||
this.updateSnackbar();
|
|
||||||
},
|
|
||||||
updateSnackbar(text) {
|
|
||||||
if (!text) text = "";
|
|
||||||
|
|
||||||
this.snackbarText = text;
|
|
||||||
|
|
||||||
this.snackbarVisible = this.snackbarText !== "";
|
|
||||||
},
|
|
||||||
showSnackbar() {
|
|
||||||
this.snackbarVisible = this.snackbarText !== "";
|
|
||||||
},
|
|
||||||
hideSnackbar() {
|
|
||||||
this.snackbarVisible = false;
|
|
||||||
},
|
|
||||||
selectPhoto(photo, ev) {
|
|
||||||
if (photo.selected) {
|
|
||||||
for (let i = 0; i < this.selected.length; i++) {
|
|
||||||
if (this.selected[i].id === photo.id) {
|
|
||||||
this.selected.splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
photo.selected = false;
|
|
||||||
} else {
|
|
||||||
this.selected.push(photo);
|
|
||||||
photo.selected = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.selected.length > 0) {
|
|
||||||
if (this.selected.length === 1) {
|
|
||||||
this.snackbarText = 'One photo selected';
|
|
||||||
} else {
|
|
||||||
this.snackbarText = this.selected.length + ' photos selected';
|
|
||||||
}
|
|
||||||
this.snackbarVisible = true;
|
|
||||||
} else {
|
|
||||||
this.snackbarText = '';
|
|
||||||
this.snackbarVisible = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
likePhoto(photo) {
|
|
||||||
photo.PhotoFavorite = !photo.PhotoFavorite;
|
|
||||||
photo.like(photo.PhotoFavorite);
|
|
||||||
},
|
|
||||||
deletePhoto(photo) {
|
|
||||||
this.$alert.success('Photo deleted');
|
|
||||||
},
|
|
||||||
formChange(event) {
|
|
||||||
this.refreshList();
|
|
||||||
},
|
|
||||||
clearQuery() {
|
|
||||||
this.query.q = '';
|
|
||||||
this.refreshList();
|
|
||||||
},
|
|
||||||
openPhoto(index) {
|
|
||||||
this.$refs.gallery.openPhoto(index)
|
|
||||||
},
|
|
||||||
loadMore() {
|
|
||||||
if (this.loadMoreDisabled) return;
|
|
||||||
|
|
||||||
this.loadMoreDisabled = true;
|
|
||||||
|
|
||||||
this.offset += this.pageSize;
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
count: this.pageSize,
|
|
||||||
offset: this.offset,
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(params, this.lastQuery);
|
|
||||||
|
|
||||||
Photo.search(params).then(response => {
|
|
||||||
console.log(response);
|
|
||||||
this.results = this.results.concat(response.models);
|
|
||||||
|
|
||||||
this.loadMoreDisabled = (response.models.length < this.pageSize);
|
|
||||||
|
|
||||||
if (this.loadMoreDisabled) {
|
|
||||||
this.$alert.info('All ' + this.results.length + ' photos loaded');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
refreshList() {
|
|
||||||
this.loadMoreDisabled = true;
|
|
||||||
|
|
||||||
// Don't query the same data more than once:197
|
|
||||||
if (JSON.stringify(this.lastQuery) === JSON.stringify(this.query)) return;
|
|
||||||
|
|
||||||
Object.assign(this.lastQuery, this.query);
|
|
||||||
|
|
||||||
this.offset = 0;
|
|
||||||
|
|
||||||
this.$router.replace({query: this.query});
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
count: this.pageSize,
|
|
||||||
offset: this.offset,
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(params, this.query);
|
|
||||||
|
|
||||||
Photo.search(params).then(response => {
|
|
||||||
this.results = response.models;
|
|
||||||
|
|
||||||
this.loadMoreDisabled = (response.models.length < this.pageSize);
|
|
||||||
|
|
||||||
if (this.loadMoreDisabled) {
|
|
||||||
this.$alert.info(this.results.length + ' photos found');
|
|
||||||
} else {
|
|
||||||
this.$alert.info('More than 50 photos found');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeRouteLeave(to, from, next) {
|
|
||||||
next()
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
window.addEventListener('resize', this.handleResize);
|
|
||||||
this.handleResize();
|
|
||||||
this.refreshList();
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
h1, h2 {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
Additionally duplicates are removed and images get tagged and metadata (like location, camera model etc.) will be extracted. In case you have not supported file types
|
Additionally duplicates are removed and images get tagged and metadata (like location, camera model etc.) will be extracted. In case you have not supported file types
|
||||||
(e.g. videos) within the folder you import --> those are ignored. </p>
|
(e.g. videos) within the folder you import --> those are ignored. </p>
|
||||||
<v-btn color="success" @click="$refs.inputUpload.click()" type="file" class="importbtn" disabled>Import & Index</v-btn>
|
<v-btn color="success" @click="$refs.inputUpload.click()" type="file" class="importbtn" disabled>Import & Index</v-btn>
|
||||||
<input v-show="false" ref="inputUpload" type="file" @change="inputFileExcel" >
|
<input v-show="false" ref="inputUpload" type="file">
|
||||||
<v-flex xs12 sm6 offset-sm3>
|
<v-flex xs12 sm6 offset-sm3>
|
||||||
<v-card class="card">
|
<v-card class="card">
|
||||||
<v-card-title primary-title>
|
<v-card-title primary-title>
|
||||||
@@ -47,233 +47,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Photo from 'model/photo';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'import',
|
name: 'import',
|
||||||
props: {},
|
props: {},
|
||||||
data() {
|
data() {
|
||||||
const query = this.$route.query;
|
return {}
|
||||||
const order = query['order'] ? query['order'] : 'newest';
|
|
||||||
const camera = query['camera'] ? parseInt(query['camera']) : 0;
|
|
||||||
const q = query['q'] ? query['q'] : '';
|
|
||||||
const country = query['country'] ? query['country'] : '';
|
|
||||||
const view = query['view'] ? query['view'] : 'details';
|
|
||||||
const cameras = [{ID: 0, CameraModel: 'All Cameras'}].concat(this.$config.getValue('cameras'));
|
|
||||||
const countries = [{
|
|
||||||
LocCountryCode: '',
|
|
||||||
LocCountry: 'All Countries'
|
|
||||||
}].concat(this.$config.getValue('countries'));
|
|
||||||
|
|
||||||
return {
|
|
||||||
'snackbarVisible': false,
|
|
||||||
'snackbarText': '',
|
|
||||||
'advandedSearch': false,
|
|
||||||
'window': {
|
|
||||||
width: 0,
|
|
||||||
height: 0
|
|
||||||
},
|
|
||||||
'results': [],
|
|
||||||
'query': {
|
|
||||||
view: view,
|
|
||||||
country: country,
|
|
||||||
camera: camera,
|
|
||||||
order: order,
|
|
||||||
q: q,
|
|
||||||
},
|
|
||||||
'options': {
|
|
||||||
'categories': [
|
|
||||||
{value: '', text: 'All Categories'},
|
|
||||||
{value: 'airport', text: 'Airport'},
|
|
||||||
{value: 'amenity', text: 'Amenity'},
|
|
||||||
{value: 'building', text: 'Building'},
|
|
||||||
{value: 'historic', text: 'Historic'},
|
|
||||||
{value: 'shop', text: 'Shop'},
|
|
||||||
{value: 'tourism', text: 'Tourism'},
|
|
||||||
],
|
|
||||||
'views': [
|
|
||||||
{value: 'details', text: 'Details'},
|
|
||||||
{value: 'list', text: 'List'},
|
|
||||||
{value: 'tiles', text: 'Tiles'},
|
|
||||||
],
|
|
||||||
'countries': countries,
|
|
||||||
'cameras': cameras,
|
|
||||||
'sorting': [
|
|
||||||
{value: 'newest', text: 'Newest first'},
|
|
||||||
{value: 'oldest', text: 'Oldest first'},
|
|
||||||
{value: 'imported', text: 'Recently imported'},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'listColumns': [
|
|
||||||
{text: 'Title', value: 'PhotoTitle'},
|
|
||||||
{text: 'Description', value: 'PhotoFavorite'},
|
|
||||||
{text: 'Taken At', value: 'TakenAt'},
|
|
||||||
{text: 'City', value: 'LocCity'},
|
|
||||||
{text: 'Country', value: 'LocCountry'},
|
|
||||||
{text: 'Camera', value: 'CameraModel'},
|
|
||||||
],
|
|
||||||
'view': view,
|
|
||||||
'loadMoreDisabled': true,
|
|
||||||
'pageSize': 60,
|
|
||||||
'offset': 0,
|
|
||||||
'lastQuery': {},
|
|
||||||
'submitTimeout': false,
|
|
||||||
'selected': [],
|
|
||||||
'dialog': false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
window.removeEventListener('resize', this.handleResize)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleResize() {
|
|
||||||
this.window.width = window.innerWidth;
|
|
||||||
this.window.height = window.innerHeight;
|
|
||||||
},
|
|
||||||
clearSelection() {
|
|
||||||
for (let i = 0; i < this.selected.length; i++) {
|
|
||||||
this.selected[i].selected = false;
|
|
||||||
}
|
|
||||||
this.selected = [];
|
|
||||||
this.updateSnackbar();
|
|
||||||
},
|
|
||||||
updateSnackbar(text) {
|
|
||||||
if (!text) text = "";
|
|
||||||
|
|
||||||
this.snackbarText = text;
|
|
||||||
|
|
||||||
this.snackbarVisible = this.snackbarText !== "";
|
|
||||||
},
|
|
||||||
showSnackbar() {
|
|
||||||
this.snackbarVisible = this.snackbarText !== "";
|
|
||||||
},
|
|
||||||
hideSnackbar() {
|
|
||||||
this.snackbarVisible = false;
|
|
||||||
},
|
|
||||||
selectPhoto(photo, ev) {
|
|
||||||
if (photo.selected) {
|
|
||||||
for (let i = 0; i < this.selected.length; i++) {
|
|
||||||
if (this.selected[i].id === photo.id) {
|
|
||||||
this.selected.splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
photo.selected = false;
|
|
||||||
} else {
|
|
||||||
this.selected.push(photo);
|
|
||||||
photo.selected = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.selected.length > 0) {
|
|
||||||
if (this.selected.length === 1) {
|
|
||||||
this.snackbarText = 'One photo selected';
|
|
||||||
} else {
|
|
||||||
this.snackbarText = this.selected.length + ' photos selected';
|
|
||||||
}
|
|
||||||
this.snackbarVisible = true;
|
|
||||||
} else {
|
|
||||||
this.snackbarText = '';
|
|
||||||
this.snackbarVisible = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
likePhoto(photo) {
|
|
||||||
photo.PhotoFavorite = !photo.PhotoFavorite;
|
|
||||||
photo.like(photo.PhotoFavorite);
|
|
||||||
},
|
|
||||||
deletePhoto(photo) {
|
|
||||||
this.$alert.success('Photo deleted');
|
|
||||||
},
|
|
||||||
formChange(event) {
|
|
||||||
this.refreshList();
|
|
||||||
},
|
|
||||||
clearQuery() {
|
|
||||||
this.query.q = '';
|
|
||||||
this.refreshList();
|
|
||||||
},
|
|
||||||
openPhoto(index) {
|
|
||||||
this.$refs.gallery.openPhoto(index)
|
|
||||||
},
|
|
||||||
loadMore() {
|
|
||||||
if (this.loadMoreDisabled) return;
|
|
||||||
|
|
||||||
this.loadMoreDisabled = true;
|
|
||||||
|
|
||||||
this.offset += this.pageSize;
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
count: this.pageSize,
|
|
||||||
offset: this.offset,
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(params, this.lastQuery);
|
|
||||||
|
|
||||||
Photo.search(params).then(response => {
|
|
||||||
console.log(response);
|
|
||||||
this.results = this.results.concat(response.models);
|
|
||||||
|
|
||||||
this.loadMoreDisabled = (response.models.length < this.pageSize);
|
|
||||||
|
|
||||||
if (this.loadMoreDisabled) {
|
|
||||||
this.$alert.info('All ' + this.results.length + ' photos loaded');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
refreshList() {
|
|
||||||
this.loadMoreDisabled = true;
|
|
||||||
|
|
||||||
// Don't query the same data more than once:197
|
|
||||||
if (JSON.stringify(this.lastQuery) === JSON.stringify(this.query)) return;
|
|
||||||
|
|
||||||
Object.assign(this.lastQuery, this.query);
|
|
||||||
|
|
||||||
this.offset = 0;
|
|
||||||
|
|
||||||
this.$router.replace({query: this.query});
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
count: this.pageSize,
|
|
||||||
offset: this.offset,
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(params, this.query);
|
|
||||||
|
|
||||||
Photo.search(params).then(response => {
|
|
||||||
this.results = response.models;
|
|
||||||
|
|
||||||
this.loadMoreDisabled = (response.models.length < this.pageSize);
|
|
||||||
|
|
||||||
if (this.loadMoreDisabled) {
|
|
||||||
this.$alert.info(this.results.length + ' photos found');
|
|
||||||
} else {
|
|
||||||
this.$alert.info('More than 50 photos found');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeRouteLeave(to, from, next) {
|
|
||||||
next()
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
window.addEventListener('resize', this.handleResize);
|
|
||||||
this.handleResize();
|
|
||||||
this.refreshList();
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
h1, h2 {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
v-model="query.q"
|
v-model="query.q"
|
||||||
@keyup.enter.native="formChange"
|
@keyup.enter.native="formChange"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
<!-- v-btn @click="formChange" color="secondary">Create Filter</v-btn -->
|
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
|
|
||||||
<v-btn icon @click="advandedSearch = !advandedSearch">
|
<v-btn icon @click="advandedSearch = !advandedSearch">
|
||||||
@@ -324,7 +324,6 @@
|
|||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-combobox
|
<v-combobox
|
||||||
v-model="model"
|
v-model="model"
|
||||||
:filter="filter"
|
|
||||||
:hide-no-data="!search"
|
:hide-no-data="!search"
|
||||||
:items="items"
|
:items="items"
|
||||||
:search-input.sync="search"
|
:search-input.sync="search"
|
||||||
@@ -390,7 +389,6 @@
|
|||||||
<h4>Remove tags</h4>
|
<h4>Remove tags</h4>
|
||||||
<v-combobox
|
<v-combobox
|
||||||
v-model="model"
|
v-model="model"
|
||||||
:filter="filter"
|
|
||||||
:hide-no-data="!search"
|
:hide-no-data="!search"
|
||||||
:items="items"
|
:items="items"
|
||||||
:search-input.sync="search"
|
:search-input.sync="search"
|
||||||
@@ -722,7 +720,7 @@
|
|||||||
this.$alert.info('More than 50 photos found');
|
this.$alert.info('More than 50 photos found');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
beforeRouteLeave(to, from, next) {
|
beforeRouteLeave(to, from, next) {
|
||||||
next()
|
next()
|
||||||
@@ -734,6 +732,3 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -398,6 +398,3 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -22,24 +22,3 @@
|
|||||||
methods: {},
|
methods: {},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.map {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2 {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -39,23 +39,3 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
a {
|
|
||||||
color: #00B8D4;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2 {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -461,6 +461,9 @@
|
|||||||
this.$alert.info('More than 50 photos found');
|
this.$alert.info('More than 50 photos found');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
translation() {
|
||||||
|
return ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeRouteLeave(to, from, next) {
|
beforeRouteLeave(to, from, next) {
|
||||||
@@ -473,6 +476,3 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -27,23 +27,3 @@
|
|||||||
methods: {}
|
methods: {}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
a {
|
|
||||||
color: #00B8D4;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2 {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Event from 'pubsub-js';
|
import Event from 'pubsub-js';
|
||||||
import 'babel-polyfill';
|
import '@babel/polyfill';
|
||||||
|
|
||||||
const Api = axios.create({
|
const Api = axios.create({
|
||||||
baseURL: '/api/v1',
|
baseURL: '/api/v1',
|
||||||
|
|||||||
@@ -117,6 +117,3 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -293,6 +293,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
||||||
@@ -223,6 +223,3 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -113,6 +113,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
openGallery: function () {
|
||||||
|
},
|
||||||
|
|
||||||
openPhoto: function (index = 0) {
|
openPhoto: function (index = 0) {
|
||||||
if (this.$props.images.length === 0) {
|
if (this.$props.images.length === 0) {
|
||||||
return
|
return
|
||||||
@@ -170,7 +173,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
|
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
|
|
||||||
@@ -10,15 +10,11 @@ const PATHS = {
|
|||||||
build: path.join(__dirname, '../assets/server/public/build'),
|
build: path.join(__dirname, '../assets/server/public/build'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const cssPlugin = new ExtractTextPlugin({
|
const isDev = process.env.NODE_ENV !== 'production';
|
||||||
filename: '[name].css',
|
|
||||||
});
|
|
||||||
|
|
||||||
// See https://github.com/webpack/loader-utils/issues/56
|
|
||||||
process.noDeprecation = true;
|
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
devtool: false,
|
mode: 'production',
|
||||||
|
devtool: isDev ? 'inline-source-map' : false,
|
||||||
entry: {
|
entry: {
|
||||||
app: PATHS.app,
|
app: PATHS.app,
|
||||||
},
|
},
|
||||||
@@ -36,11 +32,18 @@ const config = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
cssPlugin
|
new MiniCssExtractPlugin({
|
||||||
|
filename: '[name].css',
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
node: {
|
node: {
|
||||||
fs: 'empty',
|
fs: 'empty',
|
||||||
},
|
},
|
||||||
|
performance: {
|
||||||
|
hints: 'warning',
|
||||||
|
maxEntrypointSize: 1512000,
|
||||||
|
maxAssetSize: 1512000
|
||||||
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
@@ -50,46 +53,120 @@ const config = {
|
|||||||
loader: 'eslint-loader',
|
loader: 'eslint-loader',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.vue$/,
|
||||||
loader: 'babel-loader',
|
loader: "vue-loader",
|
||||||
query: {
|
options: {
|
||||||
presets: ['es2015'],
|
loaders: {
|
||||||
|
js: 'babel-loader',
|
||||||
|
css: 'css-loader',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.vue$/,
|
test: /\.js$/,
|
||||||
loader: 'vue-loader',
|
loader: 'babel-loader',
|
||||||
options: {
|
exclude: file => (
|
||||||
loaders: {
|
/node_modules/.test(file)
|
||||||
js: 'babel-loader?presets[]=es2015',
|
),
|
||||||
},
|
query: {
|
||||||
|
presets: ['@babel/preset-env'],
|
||||||
|
compact: false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
include: PATHS.css,
|
include: PATHS.css,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
use: cssPlugin.extract({
|
use: [
|
||||||
use: 'css-loader',
|
{
|
||||||
fallback: 'style-loader',
|
loader: MiniCssExtractPlugin.loader,
|
||||||
}),
|
options: {
|
||||||
|
hmr: false,
|
||||||
|
fallback: 'vue-style-loader',
|
||||||
|
use: [
|
||||||
|
// "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',
|
||||||
|
],
|
||||||
|
publicPath: PATHS.build,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"css-loader",
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
include: /node_modules/,
|
include: /node_modules/,
|
||||||
loaders: ['style-loader', 'css-loader']
|
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: /\.scss$/,
|
test: /\.s[c|a]ss$/,
|
||||||
loaders: ['style-loader', 'css-loader', 'sass-loader']
|
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|svg|woff|woff2)$/,
|
test: /\.(png|jpg|jpeg|gif)$/,
|
||||||
loader: 'url-loader',
|
loader: 'url-loader',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(wav|mp3|eot|ttf)$/,
|
test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
|
||||||
loader: 'file-loader',
|
loader: 'file-loader',
|
||||||
|
options: {
|
||||||
|
name: '[hash].[ext]',
|
||||||
|
publicPath: '/assets/build/fonts',
|
||||||
|
outputPath: 'fonts',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.svg/,
|
test: /\.svg/,
|
||||||
@@ -103,7 +180,7 @@ const config = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// No sourcemap for production
|
// No sourcemap for production
|
||||||
if (process.env.NODE_ENV !== "production") {
|
if (isDev) {
|
||||||
const devToolPlugin = new webpack.SourceMapDevToolPlugin({
|
const devToolPlugin = new webpack.SourceMapDevToolPlugin({
|
||||||
filename: '[name].map',
|
filename: '[name].map',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ if [[ -z $TRAVIS_BRANCH ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $TRAVIS_BRANCH == "develop" ]]; then
|
if [[ $TRAVIS_BRANCH == "develop" ]]; then
|
||||||
docker-compose -f docker-compose.travis.yml exec photoprism make all install migrate test-codecov;
|
docker-compose -f docker-compose.travis.yml exec photoprism make all test-js install migrate test-codecov;
|
||||||
else
|
else
|
||||||
docker-compose -f docker-compose.travis.yml exec photoprism make all install migrate test;
|
docker-compose -f docker-compose.travis.yml exec photoprism make all test-js install migrate test;
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user