People: Improve performance & add counter to new faces page #1576 #1594

This commit is contained in:
Michael Mayer 2021-10-06 11:19:07 +02:00
parent b195b7e4f8
commit 444c94bf9e
24 changed files with 320 additions and 238 deletions

View file

@ -1887,9 +1887,9 @@
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4="
},
"node_modules/@types/node": {
"version": "16.10.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.2.tgz",
"integrity": "sha512-zCclL4/rx+W5SQTzFs9wyvvyCwoK9QtBpratqz2IYJ3O8Umrn0m3nsTv0wQBk9sRGpvUe9CwPDrQFB10f1FIjQ=="
"version": "16.10.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.3.tgz",
"integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ=="
},
"node_modules/@types/parse-json": {
"version": "4.0.0",
@ -3019,9 +3019,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001264",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001264.tgz",
"integrity": "sha512-Ftfqqfcs/ePiUmyaySsQ4PUsdcYyXG2rfoBVsk3iY1ahHaJEw65vfb7Suzqm+cEkwwPIv/XWkg27iCpRavH4zA==",
"version": "1.0.30001265",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz",
"integrity": "sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
@ -3428,9 +3428,9 @@
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"node_modules/core-js": {
"version": "3.18.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.18.1.tgz",
"integrity": "sha512-vJlUi/7YdlCZeL6fXvWNaLUPh/id12WXj3MbkMw5uOyF0PfWPBNOCNbs53YqgrvtujLNlt9JQpruyIKkUZ+PKA==",
"version": "3.18.2",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.18.2.tgz",
"integrity": "sha512-zNhPOUoSgoizoSQFdX1MeZO16ORRb9FFQLts8gSYbZU5FcgXhp24iMWMxnOQo5uIaIG7/6FA/IqJPwev1o9ZXQ==",
"hasInstallScript": true,
"funding": {
"type": "opencollective",
@ -3438,11 +3438,11 @@
}
},
"node_modules/core-js-compat": {
"version": "3.18.1",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.18.1.tgz",
"integrity": "sha512-XJMYx58zo4W0kLPmIingVZA10+7TuKrMLPt83+EzDmxFJQUMcTVVmQ+n5JP4r6Z14qSzhQBRi3NSWoeVyKKXUg==",
"version": "3.18.2",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.18.2.tgz",
"integrity": "sha512-25VJYCJtGjZwLguj7d66oiHfmnVw3TMOZ0zV8DyMJp/aeQ3OjR519iOOeck08HMyVVRAqXxafc2Hl+5QstJrsQ==",
"dependencies": {
"browserslist": "^4.17.1",
"browserslist": "^4.17.3",
"semver": "7.0.0"
},
"funding": {
@ -4281,9 +4281,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.3.859",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.859.tgz",
"integrity": "sha512-gXRXKNWedfdiKIzwr0Mg/VGCvxXzy+4SuK9hp1BDvfbCwx0O5Ot+2f4CoqQkqEJ3Zj/eAV/GoAFgBVFgkBLXuQ=="
"version": "1.3.860",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.860.tgz",
"integrity": "sha512-gWwGZ+Wv4Mou2SJRH6JQzhTPjL5f95SX7n6VkLTQ/Q/INsZLZNQ1vH2GlZjozKyvT0kkFuCmWTwIoCj+/hUDPw=="
},
"node_modules/emoji-regex": {
"version": "8.0.0",
@ -6088,10 +6088,9 @@
}
},
"node_modules/gl-matrix": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.0.tgz",
"integrity": "sha512-n7fF4meQ6jbBSw91jGmP83I/wkQud5kIRSW/dr5z+9YJdQz61GWpgmRK9k7c4O/1QsXaIXu9o2vf/q2Gu3Ybow==",
"deprecated": "Broke various systems. Will investigate and likely republish as a new major version"
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA=="
},
"node_modules/glob": {
"version": "7.2.0",
@ -7895,9 +7894,9 @@
}
},
"node_modules/mini-css-extract-plugin": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.3.0.tgz",
"integrity": "sha512-uzWaOwC+gJrnKbr23J1ZRWx/Wd9W9Ce1mKPlsBGBV/r8zG7/G7oKMxGmxbI65pVGbae2cR7CUx9Ulk0HQt8BfQ==",
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.4.1.tgz",
"integrity": "sha512-97R1JD3GCG7wN5DNM6JrJMxnWNGf2oDlwRgoDc4HOQ5GprahF98mvcPvDMTb5eI1n3vmmMzCpbXH8tpcnr/Nmw==",
"dependencies": {
"schema-utils": "^3.1.0"
},
@ -10591,9 +10590,9 @@
}
},
"node_modules/postcss/node_modules/nanoid": {
"version": "3.1.28",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.28.tgz",
"integrity": "sha512-gSu9VZ2HtmoKYe/lmyPFES5nknFrHa+/DT9muUFWFMi6Jh9E1I7bkvlQ8xxf1Kos9pi9o8lBnIOkatMhKX/YUw==",
"version": "3.1.29",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.29.tgz",
"integrity": "sha512-dW2pUSGZ8ZnCFIlBIA31SV8huOGCHb6OwzVCc7A69rb/a+SgPBwfmLvK5TKQ3INPbRkcI8a/Owo0XbiTNH19wg==",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@ -13163,9 +13162,9 @@
}
},
"node_modules/webpack": {
"version": "5.57.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.57.0.tgz",
"integrity": "sha512-kbKX1HOJpEhX9GZDFgHauU/7HfgGeGzUzjSUV+wZjGxP3PFeau7BgYFtm5+UTtJJSqmXYKFuBpWRDrSdQ3d8zA==",
"version": "5.57.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.57.1.tgz",
"integrity": "sha512-kHszukYjTPVfCOEyrUthA3jqJwduY/P3eO8I0gMNOZGIQWKAwZftxmp5hq6paophvwo9NoUrcZOecs9ulOyyTg==",
"dependencies": {
"@types/eslint-scope": "^3.7.0",
"@types/estree": "^0.0.50",
@ -14952,9 +14951,9 @@
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4="
},
"@types/node": {
"version": "16.10.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.2.tgz",
"integrity": "sha512-zCclL4/rx+W5SQTzFs9wyvvyCwoK9QtBpratqz2IYJ3O8Umrn0m3nsTv0wQBk9sRGpvUe9CwPDrQFB10f1FIjQ=="
"version": "16.10.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.3.tgz",
"integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ=="
},
"@types/parse-json": {
"version": "4.0.0",
@ -15837,9 +15836,9 @@
}
},
"caniuse-lite": {
"version": "1.0.30001264",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001264.tgz",
"integrity": "sha512-Ftfqqfcs/ePiUmyaySsQ4PUsdcYyXG2rfoBVsk3iY1ahHaJEw65vfb7Suzqm+cEkwwPIv/XWkg27iCpRavH4zA=="
"version": "1.0.30001265",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz",
"integrity": "sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw=="
},
"chai": {
"version": "4.3.4",
@ -16169,16 +16168,16 @@
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"core-js": {
"version": "3.18.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.18.1.tgz",
"integrity": "sha512-vJlUi/7YdlCZeL6fXvWNaLUPh/id12WXj3MbkMw5uOyF0PfWPBNOCNbs53YqgrvtujLNlt9JQpruyIKkUZ+PKA=="
"version": "3.18.2",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.18.2.tgz",
"integrity": "sha512-zNhPOUoSgoizoSQFdX1MeZO16ORRb9FFQLts8gSYbZU5FcgXhp24iMWMxnOQo5uIaIG7/6FA/IqJPwev1o9ZXQ=="
},
"core-js-compat": {
"version": "3.18.1",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.18.1.tgz",
"integrity": "sha512-XJMYx58zo4W0kLPmIingVZA10+7TuKrMLPt83+EzDmxFJQUMcTVVmQ+n5JP4r6Z14qSzhQBRi3NSWoeVyKKXUg==",
"version": "3.18.2",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.18.2.tgz",
"integrity": "sha512-25VJYCJtGjZwLguj7d66oiHfmnVw3TMOZ0zV8DyMJp/aeQ3OjR519iOOeck08HMyVVRAqXxafc2Hl+5QstJrsQ==",
"requires": {
"browserslist": "^4.17.1",
"browserslist": "^4.17.3",
"semver": "7.0.0"
},
"dependencies": {
@ -16771,9 +16770,9 @@
"integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA=="
},
"electron-to-chromium": {
"version": "1.3.859",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.859.tgz",
"integrity": "sha512-gXRXKNWedfdiKIzwr0Mg/VGCvxXzy+4SuK9hp1BDvfbCwx0O5Ot+2f4CoqQkqEJ3Zj/eAV/GoAFgBVFgkBLXuQ=="
"version": "1.3.860",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.860.tgz",
"integrity": "sha512-gWwGZ+Wv4Mou2SJRH6JQzhTPjL5f95SX7n6VkLTQ/Q/INsZLZNQ1vH2GlZjozKyvT0kkFuCmWTwIoCj+/hUDPw=="
},
"emoji-regex": {
"version": "8.0.0",
@ -18122,9 +18121,9 @@
}
},
"gl-matrix": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.0.tgz",
"integrity": "sha512-n7fF4meQ6jbBSw91jGmP83I/wkQud5kIRSW/dr5z+9YJdQz61GWpgmRK9k7c4O/1QsXaIXu9o2vf/q2Gu3Ybow=="
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA=="
},
"glob": {
"version": "7.2.0",
@ -19471,9 +19470,9 @@
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
},
"mini-css-extract-plugin": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.3.0.tgz",
"integrity": "sha512-uzWaOwC+gJrnKbr23J1ZRWx/Wd9W9Ce1mKPlsBGBV/r8zG7/G7oKMxGmxbI65pVGbae2cR7CUx9Ulk0HQt8BfQ==",
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.4.1.tgz",
"integrity": "sha512-97R1JD3GCG7wN5DNM6JrJMxnWNGf2oDlwRgoDc4HOQ5GprahF98mvcPvDMTb5eI1n3vmmMzCpbXH8tpcnr/Nmw==",
"requires": {
"schema-utils": "^3.1.0"
},
@ -20117,9 +20116,9 @@
},
"dependencies": {
"nanoid": {
"version": "3.1.28",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.28.tgz",
"integrity": "sha512-gSu9VZ2HtmoKYe/lmyPFES5nknFrHa+/DT9muUFWFMi6Jh9E1I7bkvlQ8xxf1Kos9pi9o8lBnIOkatMhKX/YUw=="
"version": "3.1.29",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.29.tgz",
"integrity": "sha512-dW2pUSGZ8ZnCFIlBIA31SV8huOGCHb6OwzVCc7A69rb/a+SgPBwfmLvK5TKQ3INPbRkcI8a/Owo0XbiTNH19wg=="
}
}
},
@ -23323,9 +23322,9 @@
}
},
"webpack": {
"version": "5.57.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.57.0.tgz",
"integrity": "sha512-kbKX1HOJpEhX9GZDFgHauU/7HfgGeGzUzjSUV+wZjGxP3PFeau7BgYFtm5+UTtJJSqmXYKFuBpWRDrSdQ3d8zA==",
"version": "5.57.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.57.1.tgz",
"integrity": "sha512-kHszukYjTPVfCOEyrUthA3jqJwduY/P3eO8I0gMNOZGIQWKAwZftxmp5hq6paophvwo9NoUrcZOecs9ulOyyTg==",
"requires": {
"@types/eslint-scope": "^3.7.0",
"@types/estree": "^0.0.50",

View file

@ -21,7 +21,7 @@
<v-layout row wrap class="search-results photo-results cards-view" :class="{'select-results': selectMode}">
<v-flex
v-for="(photo, index) in photos"
:key="index"
:key="photo.ID"
xs12 sm6 md4 lg3 xlg2 xxxl1 d-flex
>
<v-card tile

View file

@ -21,7 +21,7 @@
<v-layout row wrap class="search-results photo-results mosaic-view" :class="{'select-results': selectMode}">
<v-flex
v-for="(photo, index) in photos"
:key="index"
:key="photo.ID"
xs4 sm3 md2 lg1 d-flex
>
<v-card tile

View file

@ -202,6 +202,8 @@ main {
#photoprism .v-tabs .v-badge__badge {
right: -22px;
font-size: 9px;
width: 1.1rem;
width: auto;
padding: 0 0.2rem;
min-width: 1.1rem;
height: 1.1rem;
}

View file

@ -1,8 +1,8 @@
<template>
<div class="p-tab p-tab-photo-files">
<v-expansion-panel expand class="pa-0 elevation-0 secondary" :value="state">
<template v-for="(file, index) in model.fileModels()">
<v-expansion-panel-content v-if="!file.Missing" :key="index" class="pa-0 elevation-0 secondary-light"
<template v-for="file in model.fileModels()">
<v-expansion-panel-content v-if="!file.Missing" :key="file.UID" class="pa-0 elevation-0 secondary-light"
style="margin-top: 1px;">
<template #header>
<div class="caption">{{ file.baseName(70) }}</div>

View file

@ -18,7 +18,7 @@
</v-card-title>
<v-card-text>
<v-expansion-panel class="pa-0 elevation-0">
<v-expansion-panel-content v-for="(link, index) in links" :key="index"
<v-expansion-panel-content v-for="(link, index) in links" :key="link.UID"
class="pa-0 elevation-0 secondary mb-1">
<template #header>
<button :class="`text-xs-${!rtl ? 'left' : 'right'} action-url ml-0 mt-0 mb-0 pa-0 mr-2`" style="user-select: none;"

File diff suppressed because one or more lines are too long

View file

@ -9,8 +9,8 @@ msgstr ""
msgid ""
msgstr ""
#: src/dialog/photo/files.vue:131
#: src/dialog/photo/files.vue:128
#: src/dialog/photo/files.vue:137
#: src/dialog/photo/files.vue:134
msgid "{{ file.Orientation }}"
msgstr ""
@ -36,7 +36,8 @@ msgstr ""
msgid "%{n} labels found"
msgstr ""
#: src/pages/people/faces.vue:366
#: src/pages/people/faces.vue:267
#: src/pages/people/faces.vue:353
#: src/pages/people/subjects.vue:392
msgid "%{n} people found"
msgstr ""
@ -161,7 +162,7 @@ msgstr ""
msgid "After two weeks"
msgstr ""
#: src/model/album.js:189
#: src/model/album.js:179
msgid "Album"
msgstr ""
@ -198,7 +199,6 @@ msgstr ""
msgid "All %{n} labels loaded"
msgstr ""
#: src/pages/people/faces.vue:269
#: src/pages/people/subjects.vue:295
msgid "All %{n} people loaded"
msgstr ""
@ -329,8 +329,8 @@ msgstr ""
msgid "Artist"
msgstr ""
#: src/dialog/photo/files.vue:122
#: src/dialog/photo/files.vue:119
#: src/dialog/photo/files.vue:128
#: src/dialog/photo/files.vue:125
msgid "Aspect Ratio"
msgstr ""
@ -420,8 +420,8 @@ msgstr ""
#: src/pages/labels.vue:198
#: src/pages/library/files.vue:193
#: src/pages/library/files.vue:209
#: src/pages/people/faces.vue:203
#: src/pages/people/faces.vue:219
#: src/pages/people/faces.vue:196
#: src/pages/people/faces.vue:212
#: src/pages/people/subjects.vue:229
#: src/pages/people/subjects.vue:245
#: src/share/albums.vue:345
@ -482,8 +482,8 @@ msgstr ""
msgid "Chinese Traditional"
msgstr ""
#: src/dialog/photo/files.vue:142
#: src/dialog/photo/files.vue:139
#: src/dialog/photo/files.vue:148
#: src/dialog/photo/files.vue:145
msgid "Chroma"
msgstr ""
@ -494,8 +494,8 @@ msgstr ""
msgid "Close"
msgstr ""
#: src/dialog/photo/files.vue:94
#: src/dialog/photo/files.vue:91
#: src/dialog/photo/files.vue:100
#: src/dialog/photo/files.vue:97
msgid "Codec"
msgstr ""
@ -894,8 +894,8 @@ msgstr ""
msgid "English"
msgstr ""
#: src/dialog/photo/files.vue:148
#: src/dialog/photo/files.vue:145
#: src/dialog/photo/files.vue:52
#: src/dialog/photo/files.vue:49
msgid "Error"
msgstr ""
@ -944,7 +944,7 @@ msgstr ""
msgid "F Number"
msgstr ""
#: src/model/face.js:115
#: src/model/face.js:153
msgid "Face"
msgstr ""
@ -992,7 +992,7 @@ msgstr ""
msgid "Feedback"
msgstr ""
#: src/model/file.js:245
#: src/model/file.js:244
msgid "File"
msgstr ""
@ -1076,8 +1076,8 @@ msgstr ""
msgid "Group by similarity"
msgstr ""
#: src/dialog/photo/files.vue:58
#: src/dialog/photo/files.vue:55
#: src/dialog/photo/files.vue:64
#: src/dialog/photo/files.vue:61
msgid "Hash"
msgstr ""
@ -1097,7 +1097,7 @@ msgstr ""
msgid "Hidden Files"
msgstr ""
#: src/pages/people/faces.vue:192
#: src/pages/people/faces.vue:186
msgid "Hide"
msgstr ""
@ -1184,8 +1184,8 @@ msgstr ""
msgid "Indexing media and sidecar files…"
msgstr ""
#: src/dialog/photo/files.vue:52
#: src/dialog/photo/files.vue:49
#: src/dialog/photo/files.vue:58
#: src/dialog/photo/files.vue:55
msgid "Instance ID"
msgstr ""
@ -1381,8 +1381,8 @@ msgstr ""
msgid "Magenta"
msgstr ""
#: src/dialog/photo/files.vue:136
#: src/dialog/photo/files.vue:133
#: src/dialog/photo/files.vue:142
#: src/dialog/photo/files.vue:139
msgid "Main Color"
msgstr ""
@ -1445,10 +1445,6 @@ msgstr ""
msgid "More than 20 albums found"
msgstr ""
#: src/pages/people/faces.vue:369
msgid "More than 20 faces found"
msgstr ""
#: src/pages/labels.vue:342
msgid "More than 20 labels found"
msgstr ""
@ -1486,8 +1482,8 @@ msgstr ""
#: src/component/photo/list.vue:237
#: src/dialog/account/edit.vue:397
#: src/dialog/album/edit.vue:106
#: src/dialog/photo/files.vue:70
#: src/dialog/photo/files.vue:67
#: src/dialog/photo/files.vue:76
#: src/dialog/photo/files.vue:73
#: src/dialog/photo/files.vue:30
#: src/dialog/photo/info.vue:31
#: src/dialog/photo/labels.vue:48
@ -1496,8 +1492,8 @@ msgstr ""
#: src/pages/about/feedback.vue:144
#: src/pages/labels.vue:337
#: src/pages/login.vue:73
#: src/pages/people/faces.vue:47
#: src/pages/people/faces.vue:310
#: src/pages/people/faces.vue:48
#: src/pages/people/faces.vue:304
#: src/pages/people/subjects.vue:315
#: src/share/photo/cards.vue:30
#: src/share/photo/list.vue:34
@ -1511,7 +1507,7 @@ msgstr ""
#: src/dialog/photo/people.vue:21
#: src/pages/labels.vue:38
#: src/pages/library/files.vue:40
#: src/pages/people/faces.vue:41
#: src/pages/people/faces.vue:42
#: src/pages/people/subjects.vue:40
msgid "Name too long"
msgstr ""
@ -1566,7 +1562,8 @@ msgstr ""
#: src/dialog/photo/people.vue:5
#: src/pages/people/faces.vue:23
#: src/pages/people/faces.vue:362
#: src/pages/people/faces.vue:263
#: src/pages/people/faces.vue:349
#: src/pages/people/subjects.vue:26
#: src/pages/people/subjects.vue:388
msgid "No people found"
@ -1680,7 +1677,8 @@ msgstr ""
msgid "One label found"
msgstr ""
#: src/pages/people/faces.vue:364
#: src/pages/people/faces.vue:265
#: src/pages/people/faces.vue:351
#: src/pages/people/subjects.vue:390
msgid "One person found"
msgstr ""
@ -1701,8 +1699,8 @@ msgstr ""
msgid "Orange"
msgstr ""
#: src/dialog/photo/files.vue:128
#: src/dialog/photo/files.vue:125
#: src/dialog/photo/files.vue:134
#: src/dialog/photo/files.vue:131
msgid "Orientation"
msgstr ""
@ -1714,8 +1712,8 @@ msgstr ""
msgid "Original file names will be stored and indexed."
msgstr ""
#: src/dialog/photo/files.vue:76
#: src/dialog/photo/files.vue:73
#: src/dialog/photo/files.vue:82
#: src/dialog/photo/files.vue:79
#: src/dialog/photo/info.vue:37
msgid "Original Name"
msgstr ""
@ -1848,8 +1846,8 @@ msgstr ""
msgid "Polish"
msgstr ""
#: src/dialog/photo/files.vue:108
#: src/dialog/photo/files.vue:105
#: src/dialog/photo/files.vue:114
#: src/dialog/photo/files.vue:111
msgid "Portrait"
msgstr ""
@ -1880,9 +1878,9 @@ msgid "Preview"
msgstr ""
#: src/dialog/photo/files.vue:34
#: src/dialog/photo/files.vue:100
#: src/dialog/photo/files.vue:106
#: src/dialog/photo/files.vue:31
#: src/dialog/photo/files.vue:97
#: src/dialog/photo/files.vue:103
#: src/dialog/photo/files.vue:24
msgid "Primary"
msgstr ""
@ -1899,8 +1897,8 @@ msgstr ""
msgid "Product Feedback"
msgstr ""
#: src/dialog/photo/files.vue:116
#: src/dialog/photo/files.vue:113
#: src/dialog/photo/files.vue:122
#: src/dialog/photo/files.vue:119
msgid "Projection"
msgstr ""
@ -1984,7 +1982,7 @@ msgstr ""
#: src/pages/labels.vue:91
#: src/pages/library/errors.vue:69
#: src/pages/library/files.vue:100
#: src/pages/people/faces.vue:67
#: src/pages/people/faces.vue:63
#: src/pages/people/subjects.vue:98
msgid "Reload"
msgstr ""
@ -2203,7 +2201,7 @@ msgstr ""
msgid "Shows more detailed log messages. Requires a restart."
msgstr ""
#: src/model/file.js:186
#: src/model/file.js:185
msgid "Sidecar"
msgstr ""
@ -2220,8 +2218,8 @@ msgid "Similar"
msgstr ""
#: src/dialog/account/edit.vue:220
#: src/dialog/photo/files.vue:82
#: src/dialog/photo/files.vue:79
#: src/dialog/photo/files.vue:88
#: src/dialog/photo/files.vue:85
#: src/dialog/photo/files.vue:32
msgid "Size"
msgstr ""
@ -2301,8 +2299,8 @@ msgstr ""
msgid "Status"
msgstr ""
#: src/dialog/photo/files.vue:64
#: src/dialog/photo/files.vue:61
#: src/dialog/photo/files.vue:70
#: src/dialog/photo/files.vue:67
msgid "Storage Folder"
msgstr ""
@ -2315,7 +2313,7 @@ msgid "Style"
msgstr ""
#: src/dialog/photo/details.vue:482
#: src/model/subject.js:135
#: src/model/subject.js:136
msgid "Subject"
msgstr ""
@ -2345,7 +2343,7 @@ msgid "Teal"
msgstr ""
#: src/dialog/photo/details.vue:26
#: src/pages/people/faces.vue:50
#: src/pages/people/faces.vue:51
msgid "Text too long"
msgstr ""
@ -2436,15 +2434,15 @@ msgid "Try again using other filters or keywords."
msgstr ""
#: src/dialog/account/edit.vue:490
#: src/dialog/photo/files.vue:88
#: src/dialog/photo/files.vue:85
#: src/dialog/photo/files.vue:94
#: src/dialog/photo/files.vue:91
#: src/dialog/photo/files.vue:33
#: src/dialog/photo/info.vue:15
msgid "Type"
msgstr ""
#: src/dialog/photo/people.vue:151
#: src/pages/people/faces.vue:235
#: src/pages/people/faces.vue:229
msgid "Undo"
msgstr ""
@ -2454,7 +2452,7 @@ msgstr ""
#: src/dialog/photo/details.vue:16
#: src/dialog/photo/info.vue:21
#: src/model/album.js:146
#: src/model/album.js:136
#: src/model/photo.js:527
#: src/model/photo.js:544
#: src/model/photo.js:567
@ -2581,7 +2579,7 @@ msgstr ""
#: src/component/photo/cards.vue:225
#: src/component/photo/list.vue:198
#: src/component/photo/mosaic.vue:200
#: src/model/file.js:184
#: src/model/file.js:183
#: src/model/photo.js:618
#: src/model/photo.js:632
#: src/options/options.js:300
@ -2650,11 +2648,11 @@ msgstr ""
#: src/dialog/people/merge.vue:18
#: src/dialog/photo/archive.vue:18
#: src/dialog/photo/files.vue:103
#: src/dialog/photo/files.vue:111
#: src/dialog/photo/files.vue:109
#: src/dialog/photo/files.vue:117
#: src/dialog/photo/files.vue:157
#: src/dialog/photo/files.vue:100
#: src/dialog/photo/files.vue:108
#: src/dialog/photo/files.vue:106
#: src/dialog/photo/files.vue:114
#: src/dialog/photo/files.vue:154
#: src/dialog/photo/info.vue:284
#: src/dialog/photo/info.vue:305

View file

@ -33,13 +33,11 @@ import RestModel from "model/rest";
import { DateTime } from "luxon";
import { config } from "../session";
import { $gettext } from "common/vm";
import * as src from "../common/src";
import Api from "../common/api";
export class Face extends RestModel {
constructor(values) {
if (values && values.Marker) {
values.Marker = new Marker(values.Marker);
}
super(values);
}
@ -48,15 +46,24 @@ export class Face extends RestModel {
ID: "",
Src: "",
SubjUID: "",
SubjSrc: "",
FileUID: "",
MarkerUID: "",
Samples: 0,
SampleRadius: 0.0,
Collisions: 0,
CollisionRadius: 0.0,
Marker: new Marker(),
Hidden: false,
MatchedAt: "",
CreatedAt: "",
UpdatedAt: "",
Name: "",
FaceDist: 0.0,
Size: 0,
Score: 0,
Review: false,
Invalid: false,
Thumb: "",
};
}
@ -82,11 +89,15 @@ export class Face extends RestModel {
}
thumbnailUrl(size) {
if (!this.Marker) {
return `${config.contentUri}/svg/portrait`;
if (!size) {
size = "tile_160";
}
return this.Marker.thumbnailUrl(size);
if (this.Thumb) {
return `${config.contentUri}/t/${this.Thumb}/${config.previewToken()}/${size}`;
} else {
return `${config.contentUri}/svg/portrait`;
}
}
getDateString() {
@ -103,6 +114,33 @@ export class Face extends RestModel {
return this.update();
}
setName() {
if (!this.Name || this.Name.trim() === "") {
// Can't save an empty name.
return Promise.resolve(this);
}
this.SubjSrc = src.Manual;
const payload = { SubjSrc: this.SubjSrc, Name: this.Name };
return Api.put(Marker.getCollectionResource() + "/" + this.MarkerUID, payload).then((resp) => {
if (resp && resp.data && resp.data.Name) {
const data = resp.data;
this.setValues({
Name: data.Name,
SubjSrc: data.SubjSrc,
SubjUID: data.SubjUID,
Review: data.Review,
Invalid: data.Invalid,
Thumb: data.Thumb,
});
}
return Promise.resolve(this);
});
}
static batchSize() {
return 24;
}

View file

@ -77,7 +77,7 @@
<v-layout row wrap class="search-results album-results cards-view" :class="{'select-results': selection.length > 0}">
<v-flex
v-for="(album, index) in results"
:key="index"
:key="album.UID"
xs6 sm4 md3 xlg2 xxl1 d-flex
>
<v-card tile

View file

@ -55,7 +55,7 @@
<v-layout row wrap class="search-results label-results cards-view" :class="{'select-results': selection.length > 0}">
<v-flex
v-for="(label, index) in results"
:key="index"
:key="label.UID"
xs6 sm4 md3 lg2 xxl1 d-flex
>
<v-card tile

View file

@ -27,7 +27,7 @@
</v-container>
<v-list v-else-if="errors.length > 0" dense two-line class="transparent">
<v-list-tile
v-for="(err, index) in errors" :key="index"
v-for="err in errors" :key="err.ID"
avatar
@click="showDetails(err)"
>

View file

@ -46,7 +46,7 @@
<v-layout row wrap class="search-results file-results cards-view" :class="{'select-results': selection.length > 0}">
<v-flex
v-for="(model, index) in results"
:key="index"
:key="model.UID"
xs6 sm4 md3 lg2 xxl1 d-flex
>
<v-card tile

View file

@ -12,13 +12,19 @@
ripple @click="changePath(item.path)">
<v-icon v-if="$vuetify.breakpoint.smAndDown" :title="item.label">{{ item.icon }}</v-icon>
<template v-else>
<v-icon :size="18" :left="!rtl" :right="rtl">{{ item.icon }}</v-icon> {{ item.label }}
<v-icon :size="18" :left="!rtl" :right="rtl">{{ item.icon }}</v-icon>
<v-badge color="secondary-dark" :left="rtl" :right="!rtl">
<template #badge>
<span v-if="item.count">{{ item.count }}</span>
</template>
{{ item.label }}
</v-badge>
</template>
</v-tab>
<v-tabs-items touchless>
<v-tab-item v-for="(item, index) in tabs" :key="index" lazy>
<component :is="item.component" :static-filter="item.filter" :active="active === index"></component>
<component :is="item.component" :static-filter="item.filter" :active="active === index" @updateFaceCount="onUpdateFaceCount"></component>
</v-tab-item>
</v-tabs-items>
</v-tabs>
@ -59,6 +65,7 @@ export default {
'class': '',
'path': '/people/new',
'icon': 'person_add',
'count': 0,
},
];
@ -79,7 +86,10 @@ export default {
};
},
methods: {
changePath: function (path) {
onUpdateFaceCount(count) {
this.tabs[1].count = count;
},
changePath (path) {
if (this.$route.path !== path) {
this.$router.replace(path);
}

View file

@ -37,18 +37,17 @@
:key="model.ID"
xs12 sm6 md4 lg3 xl2 xxl1 d-flex
>
<v-card v-if="model.Marker"
:data-id="model.ID"
<v-card :data-id="model.ID"
tile style="user-select: none;"
:class="model.classes()"
class="result accent lighten-3">
<div class="card-background accent lighten-3"></div>
<v-img :src="model.Marker.thumbnailUrl('tile_320')"
<v-img :src="model.thumbnailUrl('tile_320')"
:transition="false"
aspect-ratio="1"
class="accent lighten-2 clickable"
@click.stop.prevent="$router.push(model.route(view))">
<v-btn v-if="!model.Marker.SubjUID && !model.Hidden" :ripple="false" :depressed="false" class="input-hide"
<v-btn v-if="!model.SubjUID && !model.Hidden" :ripple="false" :depressed="false" class="input-hide"
icon flat small absolute :title="$gettext('Hide')"
@click.stop.prevent="onHide(model)">
<v-icon color="white" class="action-hide">clear</v-icon>
@ -66,10 +65,10 @@
</v-btn>
</v-flex>
</v-layout>
<v-layout v-else-if="model.Marker.SubjUID" row wrap align-center>
<v-layout v-else-if="model.SubjUID" row wrap align-center>
<v-flex xs12 class="text-xs-left pa-0">
<v-text-field
v-model="model.Marker.Name"
v-model="model.Name"
:rules="[textRule]"
:disabled="busy"
:readonly="false"
@ -78,15 +77,15 @@
hide-details
single-line
solo-inverted
@change="onRename(model.Marker)"
@keyup.enter.native="onRename(model.Marker)"
@change="onRename(model)"
@keyup.enter.native="onRename(model)"
></v-text-field>
</v-flex>
</v-layout>
<v-layout v-else row wrap align-center>
<v-flex xs12 class="text-xs-left pa-0">
<v-combobox
v-model="model.Marker.Name"
v-model="model.Name"
style="z-index: 250"
:items="$config.values.people"
item-value="Name"
@ -104,8 +103,8 @@
prepend-inner-icon="person_add"
browser-autocomplete="off"
class="input-name pa-0 ma-0"
@change="onRename(model.Marker)"
@keyup.enter.native="onRename(model.Marker)"
@change="onRename(model)"
@keyup.enter.native="onRename(model)"
>
</v-combobox>
</v-flex>
@ -163,6 +162,7 @@ export default {
batchSize: 999,
offset: 0,
page: 0,
faceCount: 0,
selection: [],
settings: settings,
filter: filter,
@ -386,6 +386,8 @@ export default {
Face.search(params).then(resp => {
this.results = this.dirty ? resp.models : this.results.concat(resp.models);
this.setFaceCount(this.results.length);
if (!this.results.length) {
this.$notify.warn(this.$gettext("No people found"));
} else if (this.results.length === 1) {
@ -443,7 +445,7 @@ export default {
this.loading = true;
this.page = 0;
// this.dirty = true;
this.dirty = true;
this.scrollDisabled = false;
this.loadMore();
@ -470,6 +472,8 @@ export default {
this.offset = resp.limit;
this.results = resp.models;
this.setFaceCount(this.results.length);
if (!this.results.length) {
this.$notify.warn(this.$gettext("No people found"));
} else if (this.results.length === 1) {
@ -485,32 +489,36 @@ export default {
},
onShow(face) {
this.busy = true;
// this.dirty = true;
face.show().finally(() => this.busy = false);
face.show().finally(() => {
this.busy = false;
this.changeFaceCount(1);
});
},
onHide(face) {
this.busy = true;
// this.dirty = true;
face.hide().finally(() => this.busy = false);
},
onClearSubject(marker) {
this.busy = true;
// this.dirty = true;
this.$notify.blockUI();
marker.clearSubject(marker).finally(() => {
this.$notify.unblockUI();
face.hide().finally(() => {
this.busy = false;
this.changeFaceCount(-1);
});
},
onRename(marker) {
onRename(model) {
this.busy = true;
// this.dirty = true;
this.$notify.blockUI();
marker.rename().finally(() => {
model.setName().finally(() => {
this.$notify.unblockUI();
this.busy = false;
this.changeFaceCount(-1);
});
},
changeFaceCount(count) {
this.faceCount = this.faceCount + count;
this.$emit('updateFaceCount', this.faceCount);
},
setFaceCount(count) {
this.faceCount = count;
this.$emit('updateFaceCount', this.faceCount);
},
onUpdate(ev, data) {
if (!this.listen) return;

View file

@ -52,7 +52,7 @@
<v-layout row wrap class="search-results subject-results cards-view" :class="{'select-results': selection.length > 0}">
<v-flex
v-for="(model, index) in results"
:key="index"
:key="model.UID"
xs6 sm4 md3 lg2 xxl1 d-flex
>
<v-card tile

View file

@ -31,7 +31,7 @@
<v-layout row wrap class="search-results album-results cards-view" :class="{'select-results': selection.length > 0}">
<v-flex
v-for="(album, index) in results"
:key="index"
:key="album.UID"
xs6 sm4 md3 xlg2 xxl1 d-flex
>
<v-card tile

View file

@ -17,7 +17,7 @@
<v-layout row wrap class="search-results photo-results cards-view" :class="{'select-results': selectMode}">
<v-flex
v-for="(photo, index) in photos"
:key="index"
:key="photo.ID"
xs12 sm6 md4 lg3 xlg2 xxxl1 d-flex
>
<v-card tile

View file

@ -17,7 +17,7 @@
<v-layout row wrap class="search-results photo-results mosaic-view" :class="{'select-results': selectMode}">
<v-flex
v-for="(photo, index) in photos"
:key="index"
:key="photo.ID"
xs4 sm3 md2 lg1 d-flex
>
<v-card tile

View file

@ -61,22 +61,22 @@ describe("model/face", () => {
const values = {
ID: "f123ghytrfggd",
Samples: 5,
Marker: {
UID: "ABC123ghytr",
FileUID: "fhjouohnnmnd",
Type: "face",
Src: "image",
Thumb: "nicethumbuid",
},
MarkerUID: "ABC123ghytr",
FileUID: "fhjouohnnmnd",
Name: "",
Thumb: "7ca759a2b788cc5bcc08dbbce9854ff94a2f94d1",
};
const face = new Face(values);
const result = face.thumbnailUrl("xyz");
assert.equal(result, "/api/v1/t/nicethumbuid/public/xyz");
const values2 = { ID: "f123ghytrfggd", Samples: 5 };
assert.equal(result, "/api/v1/t/7ca759a2b788cc5bcc08dbbce9854ff94a2f94d1/public/xyz");
const values2 = { ID: "f123ghytrfggd", Samples: 5, Thumb: "7ca759a2b788cc5bcc08dbbce9854ff94a2f94d1" };
const face2 = new Face(values2);
const result2 = face2.thumbnailUrl();
assert.equal(result2, "/api/v1/svg/portrait");
assert.equal(result2, "/api/v1/t/7ca759a2b788cc5bcc08dbbce9854ff94a2f94d1/public/tile_160");
});
it("should get date string", () => {

View file

@ -119,6 +119,15 @@ func (m *Subject) Delete() error {
return Db().Delete(m).Error
}
// AfterDelete resets file and photo counters when the entity was deleted.
func (m *Subject) AfterDelete(tx *gorm.DB) (err error) {
tx.Model(m).Updates(Values{
"FileCount": 0,
"PhotoCount": 0,
})
return
}
// Deleted returns true if the entity is deleted.
func (m *Subject) Deleted() bool {
return m.DeletedAt != nil
@ -327,6 +336,14 @@ func (m *Subject) MergeWith(other *Subject) error {
return err
}
// Update file and photo counts.
if err := Db().Model(other).Updates(Values{
"FileCount": other.FileCount + m.FileCount,
"PhotoCount": other.PhotoCount + m.PhotoCount,
}).Error; err != nil {
return err
}
return m.Delete()
}

View file

@ -4,8 +4,6 @@ import (
"fmt"
"strings"
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/pkg/txt"
@ -17,8 +15,42 @@ func Faces(f form.FaceSearch) (results FaceResults, err error) {
return results, err
}
facesTable := entity.Face{}.TableName()
// Base query.
s := UnscopedDb().Table(entity.Face{}.TableName())
s := UnscopedDb().Table(facesTable)
if f.Markers {
s = s.Select(fmt.Sprintf(`%s.*, m.marker_uid, m.file_uid, m.marker_name, m.subj_src, m.marker_src,
m.marker_type, m.marker_review, m.marker_invalid, m.size, m.score, m.thumb, m.face_dist`, facesTable))
if txt.Yes(f.Unknown) {
s = s.Joins(`JOIN (
SELECT face_id, MIN(marker_uid) AS marker_uid FROM markers
WHERE face_id <> '' AND subj_uid = '' AND marker_name = '' AND marker_type = 'face' AND marker_src = 'image'
AND marker_invalid = 0 AND face_dist <= 0.64 AND size >= 80 AND score >= 15
GROUP BY face_id) fm
ON faces.id = fm.face_id`)
} else if txt.No(f.Unknown) {
s = s.Joins(`JOIN (
SELECT face_id, MIN(marker_uid) AS marker_uid FROM markers
WHERE face_id <> '' AND subj_uid <> '' AND marker_name <> '' AND marker_type = 'face' AND marker_src = 'image'
AND marker_invalid = 0 AND face_dist <= 0.64 AND size >= 80 AND score >= 15
GROUP BY face_id) fm
ON faces.id = fm.face_id`)
} else {
s = s.Joins(`JOIN (
SELECT face_id, MIN(marker_uid) AS marker_uid FROM markers
WHERE face_id <> '' AND marker_type = 'face' AND marker_src = 'image'
AND marker_invalid = 0 AND face_dist <= 0.64 AND size >= 80 AND score >= 15
GROUP BY face_id) fm
ON faces.id = fm.face_id`)
}
s = s.Joins("JOIN markers m ON m.marker_uid = fm.marker_uid")
} else {
s = s.Select(fmt.Sprintf(`%s.*`, facesTable))
}
// Limit result count.
if f.Count > 0 && f.Count <= MaxResults {
@ -30,46 +62,21 @@ func Faces(f form.FaceSearch) (results FaceResults, err error) {
// Set sort order.
switch f.Order {
case "subject":
s = s.Order("subj_uid")
s = s.Order(fmt.Sprintf("%s.subj_uid", facesTable))
case "added":
s = s.Order(fmt.Sprintf("%s.created_at DESC", entity.Face{}.TableName()))
s = s.Order(fmt.Sprintf("%s.created_at DESC", facesTable))
case "samples":
s = s.Order("samples DESC")
s = s.Order(fmt.Sprintf("%s.samples DESC, %s.id", facesTable, facesTable))
default:
s = s.Order("samples DESC")
}
// Make sure at least one marker exists.
if f.Markers || txt.Yes(f.Unknown) {
s = s.Where("id IN (SELECT face_id FROM ? WHERE "+
"face_id IS NOT NULL AND face_id <> '' AND marker_type = ? AND marker_src = ? AND marker_invalid = 0)",
gorm.Expr(entity.Marker{}.TableName()), entity.MarkerFace, entity.SrcImage)
}
// Adds markers to search results if requested.
addMarkers := func(results FaceResults) FaceResults {
r := make(FaceResults, 0, len(results))
// Add markers to results.
for i := range results {
if marker := entity.FindFaceMarker(results[i].ID); marker != nil {
m := results[i]
m.Marker = marker
r = append(r, m)
}
}
return r
s = s.Order(fmt.Sprintf("%s.samples DESC, %s.id", facesTable, facesTable))
}
// Find specific IDs?
if f.ID != "" {
s = s.Where(fmt.Sprintf("%s.id IN (?)", entity.Face{}.TableName()), strings.Split(strings.ToUpper(f.ID), txt.Or))
s = s.Where(fmt.Sprintf("%s.id IN (?)", facesTable), strings.Split(strings.ToUpper(f.ID), txt.Or))
if result := s.Scan(&results); result.Error != nil {
return results, result.Error
} else if f.Markers {
return addMarkers(results), nil
}
return results, nil
@ -77,23 +84,21 @@ func Faces(f form.FaceSearch) (results FaceResults, err error) {
// Exclude unknown faces?
if txt.Yes(f.Unknown) {
s = s.Where("subj_uid = '' OR subj_uid IS NULL")
s = s.Where(fmt.Sprintf("%s.subj_uid = '' OR %s.subj_uid IS NULL", facesTable, facesTable))
} else if txt.No(f.Unknown) {
s = s.Where("subj_uid <> '' AND subj_uid IS NOT NULL")
s = s.Where(fmt.Sprintf("%s.subj_uid <> '' AND %s.subj_uid IS NOT NULL", facesTable, facesTable))
}
// Exclude hidden faces?
if f.Hidden == "" || txt.No(f.Hidden) {
s = s.Where("face_hidden = 0 OR face_hidden IS NULL")
s = s.Where(fmt.Sprintf("%s.face_hidden = 0 OR %s.face_hidden IS NULL", facesTable, facesTable))
} else if txt.Yes(f.Hidden) {
s = s.Where("face_hidden = 1")
s = s.Where(fmt.Sprintf("%s.face_hidden = 1", facesTable))
}
// Perform query.
if res := s.Scan(&results); res.Error != nil {
return results, res.Error
} else if f.Markers {
return addMarkers(results), nil
}
return results, nil

View file

@ -2,24 +2,31 @@ package search
import (
"time"
"github.com/photoprism/photoprism/internal/entity"
)
// Face represents a face search result.
type Face struct {
ID string `json:"ID"`
FaceSrc string `json:"Src"`
FaceHidden bool `json:"Hidden"`
SubjUID string `json:"SubjUID"`
Samples int `json:"Samples"`
SampleRadius float64 `json:"SampleRadius"`
Collisions int `json:"Collisions"`
CollisionRadius float64 `json:"CollisionRadius"`
Marker *entity.Marker `json:"Marker,omitempty"`
MatchedAt *time.Time `json:"MatchedAt" yaml:"MatchedAt,omitempty"`
CreatedAt time.Time `json:"CreatedAt" yaml:"CreatedAt,omitempty"`
UpdatedAt time.Time `json:"UpdatedAt" yaml:"UpdatedAt,omitempty"`
ID string `json:"ID"`
FaceSrc string `json:"Src"`
FaceHidden bool `json:"Hidden"`
FaceDist float64 `json:"FaceDist,omitempty"`
SubjUID string `json:"SubjUID"`
SubjSrc string `json:"SubjSrc,omitempty"`
FileUID string `json:"FileUID,omitempty"`
MarkerUID string `json:"MarkerUID,omitempty"`
Samples int `json:"Samples"`
SampleRadius float64 `json:"SampleRadius"`
Collisions int `json:"Collisions"`
CollisionRadius float64 `json:"CollisionRadius"`
MarkerName string `json:"Name"`
Size int `json:"Size,omitempty"`
Score int `json:"Score,omitempty"`
MarkerReview bool `json:"Review"`
MarkerInvalid bool `json:"Invalid"`
Thumb string `json:"Thumb"`
MatchedAt *time.Time `json:"MatchedAt" yaml:"MatchedAt,omitempty"`
CreatedAt time.Time `json:"CreatedAt" yaml:"CreatedAt,omitempty"`
UpdatedAt time.Time `json:"UpdatedAt" yaml:"UpdatedAt,omitempty"`
}
// FaceResults represents face search results.

View file

@ -15,9 +15,7 @@ func TestFaces(t *testing.T) {
t.Logf("Faces: %#v", results)
if len(results) == 0 {
t.Fatal("results are empty")
} else if results[0].Marker == nil {
t.Fatal("marker is nil")
} else if results[0].Marker.MarkerUID == "" {
} else if results[0].MarkerUID == "" {
t.Fatal("marker uid is empty")
}
})
@ -25,7 +23,7 @@ func TestFaces(t *testing.T) {
results, err := Faces(form.FaceSearch{Offset: 3, Order: "subject", Markers: true})
assert.NoError(t, err)
t.Logf("Faces: %#v", results)
assert.LessOrEqual(t, 3, len(results))
assert.LessOrEqual(t, 1, len(results))
})
t.Run("Find specific id", func(t *testing.T) {
results, err := Faces(form.FaceSearch{ID: "PN6QO5INYTUSAATOFL43LL2ABAV5ACZK", Markers: true})