mirror of
https://github.com/simon987/sist2.git
synced 2025-12-12 15:08:53 +00:00
refactor index schema, remove sidecar parsing, remove TS
This commit is contained in:
@@ -1,116 +1,20 @@
|
||||
import axios from "axios";
|
||||
import {ext, strUnescape, lum} from "./util";
|
||||
import {strUnescape, lum, sid} from "./util";
|
||||
import Sist2Query from "@/Sist2ElasticsearchQuery";
|
||||
import store from "@/store";
|
||||
|
||||
export interface EsTag {
|
||||
id: string
|
||||
count: number
|
||||
color: string | undefined
|
||||
isLeaf: boolean
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
style: string
|
||||
text: string
|
||||
rawText: string
|
||||
fg: string
|
||||
bg: string
|
||||
userTag: boolean
|
||||
}
|
||||
|
||||
export interface Index {
|
||||
name: string
|
||||
version: string
|
||||
id: string
|
||||
idPrefix: string
|
||||
timestamp: number
|
||||
models: []
|
||||
}
|
||||
|
||||
export interface EsHit {
|
||||
_index: string
|
||||
_id: string
|
||||
_score: number
|
||||
_type: string
|
||||
_tags: Tag[]
|
||||
_seq: number
|
||||
_source: {
|
||||
path: string
|
||||
size: number
|
||||
mime: string
|
||||
name: string
|
||||
extension: string
|
||||
index: string
|
||||
_depth: number
|
||||
mtime: number
|
||||
videoc: string
|
||||
audioc: string
|
||||
parent: string
|
||||
width: number
|
||||
height: number
|
||||
duration: number
|
||||
tag: string[]
|
||||
checksum: string
|
||||
thumbnail: string
|
||||
}
|
||||
_props: {
|
||||
isSubDocument: boolean
|
||||
isImage: boolean
|
||||
isGif: boolean
|
||||
isVideo: boolean
|
||||
isPlayableVideo: boolean
|
||||
isPlayableImage: boolean
|
||||
isAudio: boolean
|
||||
hasThumbnail: boolean
|
||||
hasVidPreview: boolean
|
||||
imageAspectRatio: number
|
||||
/** Number of thumbnails available */
|
||||
tnNum: number
|
||||
}
|
||||
highlight: {
|
||||
name: string[] | undefined,
|
||||
content: string[] | undefined,
|
||||
}
|
||||
}
|
||||
|
||||
function getIdPrefix(indices: Index[], id: string): string {
|
||||
for (let i = 4; i < 32; i++) {
|
||||
const prefix = id.slice(0, i);
|
||||
|
||||
if (indices.filter(idx => idx.id.slice(0, i) == prefix).length == 1) {
|
||||
return prefix;
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
export interface EsResult {
|
||||
took: number
|
||||
|
||||
hits: {
|
||||
// TODO: ES 6.X ?
|
||||
total: {
|
||||
value: number
|
||||
}
|
||||
hits: EsHit[]
|
||||
}
|
||||
|
||||
aggregations: any
|
||||
}
|
||||
|
||||
class Sist2Api {
|
||||
|
||||
private readonly baseUrl: string
|
||||
private sist2Info: any
|
||||
private queryfunc: () => EsResult;
|
||||
baseUrl;
|
||||
sist2Info;
|
||||
queryfunc;
|
||||
|
||||
constructor(baseUrl: string) {
|
||||
constructor(baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
init(queryFunc: () => EsResult) {
|
||||
init(queryFunc) {
|
||||
this.queryfunc = queryFunc;
|
||||
}
|
||||
|
||||
@@ -127,9 +31,9 @@ class Sist2Api {
|
||||
.filter((v, i, a) => a.findIndex(v2 => (v2.id === v.id)) === i)
|
||||
}
|
||||
|
||||
getSist2Info(): Promise<any> {
|
||||
getSist2Info() {
|
||||
return axios.get(`${this.baseUrl}i`).then(resp => {
|
||||
const indices = resp.data.indices as Index[];
|
||||
const indices = resp.data.indices;
|
||||
|
||||
resp.data.indices = indices.map(idx => {
|
||||
return {
|
||||
@@ -138,8 +42,7 @@ class Sist2Api {
|
||||
timestamp: idx.timestamp,
|
||||
version: idx.version,
|
||||
models: idx.models,
|
||||
idPrefix: getIdPrefix(indices, idx.id),
|
||||
} as Index;
|
||||
};
|
||||
});
|
||||
|
||||
this.sist2Info = resp.data;
|
||||
@@ -148,8 +51,8 @@ class Sist2Api {
|
||||
})
|
||||
}
|
||||
|
||||
setHitProps(hit: EsHit): void {
|
||||
hit["_props"] = {} as any;
|
||||
setHitProps(hit) {
|
||||
hit["_props"] = {};
|
||||
|
||||
const mimeCategory = hit._source.mime == null ? null : hit._source.mime.split("/")[0];
|
||||
|
||||
@@ -157,7 +60,7 @@ class Sist2Api {
|
||||
hit._props.isSubDocument = true;
|
||||
}
|
||||
|
||||
if ("thumbnail" in hit._source) {
|
||||
if ("thumbnail" in hit._source && hit._source.thumbnail > 0) {
|
||||
hit._props.hasThumbnail = true;
|
||||
|
||||
if (Number.isNaN(Number(hit._source.thumbnail))) {
|
||||
@@ -213,8 +116,8 @@ class Sist2Api {
|
||||
}
|
||||
}
|
||||
|
||||
setHitTags(hit: EsHit): void {
|
||||
const tags = [] as Tag[];
|
||||
setHitTags(hit) {
|
||||
const tags = [];
|
||||
|
||||
// User tags
|
||||
if ("tag" in hit._source) {
|
||||
@@ -226,10 +129,10 @@ class Sist2Api {
|
||||
hit._tags = tags;
|
||||
}
|
||||
|
||||
createUserTag(tag: string): Tag {
|
||||
createUserTag(tag) {
|
||||
const tokens = tag.split(".");
|
||||
|
||||
const colorToken = tokens.pop() as string;
|
||||
const colorToken = tokens.pop();
|
||||
|
||||
const bg = colorToken;
|
||||
const fg = lum(colorToken) > 50 ? "#000" : "#fff";
|
||||
@@ -241,23 +144,23 @@ class Sist2Api {
|
||||
text: tokens.join("."),
|
||||
rawText: tag,
|
||||
userTag: true,
|
||||
} as Tag;
|
||||
};
|
||||
}
|
||||
|
||||
search(): Promise<EsResult> {
|
||||
if (this.backend() == "sqlite") {
|
||||
search() {
|
||||
if (this.backend() === "sqlite") {
|
||||
return this.ftsQuery(this.queryfunc())
|
||||
} else {
|
||||
return this.esQuery(this.queryfunc());
|
||||
}
|
||||
}
|
||||
|
||||
esQuery(query: any): Promise<EsResult> {
|
||||
esQuery(query) {
|
||||
return axios.post(`${this.baseUrl}es`, query).then(resp => {
|
||||
const res = resp.data as EsResult;
|
||||
const res = resp.data;
|
||||
|
||||
if (res.hits?.hits) {
|
||||
res.hits.hits.forEach((hit: EsHit) => {
|
||||
res.hits.hits.forEach((hit) => {
|
||||
hit["_source"]["name"] = strUnescape(hit["_source"]["name"]);
|
||||
hit["_source"]["path"] = strUnescape(hit["_source"]["path"]);
|
||||
|
||||
@@ -270,9 +173,9 @@ class Sist2Api {
|
||||
});
|
||||
}
|
||||
|
||||
ftsQuery(query: any): Promise<EsResult> {
|
||||
ftsQuery(query) {
|
||||
return axios.post(`${this.baseUrl}fts/search`, query).then(resp => {
|
||||
const res = resp.data as any;
|
||||
const res = resp.data;
|
||||
|
||||
if (res.hits.hits) {
|
||||
res.hits.hits.forEach(hit => {
|
||||
@@ -293,7 +196,7 @@ class Sist2Api {
|
||||
});
|
||||
}
|
||||
|
||||
private getMimeTypesEs(query) {
|
||||
getMimeTypesEs(query) {
|
||||
const AGGS = {
|
||||
mimeTypes: {
|
||||
terms: {
|
||||
@@ -322,7 +225,7 @@ class Sist2Api {
|
||||
});
|
||||
}
|
||||
|
||||
private getMimeTypesSqlite(): Promise<[{ mime: string, count: number }]> {
|
||||
getMimeTypesSqlite() {
|
||||
return axios.get(`${this.baseUrl}fts/mimetypes`)
|
||||
.then(resp => {
|
||||
return resp.data;
|
||||
@@ -332,15 +235,15 @@ class Sist2Api {
|
||||
async getMimeTypes(query = undefined) {
|
||||
let buckets;
|
||||
|
||||
if (this.backend() == "sqlite") {
|
||||
if (this.backend() === "sqlite") {
|
||||
buckets = await this.getMimeTypesSqlite();
|
||||
} else {
|
||||
buckets = await this.getMimeTypesEs(query);
|
||||
}
|
||||
|
||||
const mimeMap: any[] = [];
|
||||
const mimeMap = [];
|
||||
|
||||
buckets.sort((a: any, b: any) => a.mime > b.mime).forEach((bucket: any) => {
|
||||
buckets.sort((a, b) => a.mime > b.mime).forEach((bucket) => {
|
||||
const tmp = bucket.mime.split("/");
|
||||
const category = tmp[0];
|
||||
const mime = tmp[1];
|
||||
@@ -374,7 +277,7 @@ class Sist2Api {
|
||||
return {buckets, mimeMap};
|
||||
}
|
||||
|
||||
_createEsTag(tag: string, count: number): EsTag {
|
||||
_createEsTag(tag, count) {
|
||||
const tokens = tag.split(".");
|
||||
|
||||
if (/.*\.#[0-9a-fA-F]{6}/.test(tag)) {
|
||||
@@ -394,7 +297,7 @@ class Sist2Api {
|
||||
};
|
||||
}
|
||||
|
||||
private getTagsEs() {
|
||||
getTagsEs() {
|
||||
return this.esQuery({
|
||||
aggs: {
|
||||
tags: {
|
||||
@@ -407,21 +310,21 @@ class Sist2Api {
|
||||
size: 0,
|
||||
}).then(resp => {
|
||||
return resp["aggregations"]["tags"]["buckets"]
|
||||
.sort((a: any, b: any) => a["key"].localeCompare(b["key"]))
|
||||
.map((bucket: any) => this._createEsTag(bucket["key"], bucket["doc_count"]));
|
||||
.sort((a, b) => a["key"].localeCompare(b["key"]))
|
||||
.map((bucket) => this._createEsTag(bucket["key"], bucket["doc_count"]));
|
||||
});
|
||||
}
|
||||
|
||||
private getTagsSqlite() {
|
||||
getTagsSqlite() {
|
||||
return axios.get(`${this.baseUrl}/fts/tags`)
|
||||
.then(resp => {
|
||||
return resp.data.map(tag => this._createEsTag(tag.tag, tag.count))
|
||||
});
|
||||
}
|
||||
|
||||
async getTags(): Promise<EsTag[]> {
|
||||
async getTags() {
|
||||
let tags;
|
||||
if (this.backend() == "sqlite") {
|
||||
if (this.backend() === "sqlite") {
|
||||
tags = await this.getTagsSqlite();
|
||||
} else {
|
||||
tags = await this.getTagsEs();
|
||||
@@ -430,7 +333,7 @@ class Sist2Api {
|
||||
// Remove duplicates (same tag with different color)
|
||||
const seen = new Set();
|
||||
|
||||
return tags.filter((t: EsTag) => {
|
||||
return tags.filter((t) => {
|
||||
if (seen.has(t.id)) {
|
||||
return false;
|
||||
}
|
||||
@@ -439,31 +342,29 @@ class Sist2Api {
|
||||
});
|
||||
}
|
||||
|
||||
saveTag(tag: string, hit: EsHit) {
|
||||
return axios.post(`${this.baseUrl}tag/` + hit["_source"]["index"], {
|
||||
saveTag(tag, hit) {
|
||||
return axios.post(`${this.baseUrl}tag/${sid(hit)}`, {
|
||||
delete: false,
|
||||
name: tag,
|
||||
doc_id: hit["_id"]
|
||||
});
|
||||
}
|
||||
|
||||
deleteTag(tag: string, hit: EsHit) {
|
||||
return axios.post(`${this.baseUrl}tag/` + hit["_source"]["index"], {
|
||||
deleteTag(tag, hit) {
|
||||
return axios.post(`${this.baseUrl}tag/${sid(hit)}`, {
|
||||
delete: true,
|
||||
name: tag,
|
||||
doc_id: hit["_id"]
|
||||
});
|
||||
}
|
||||
|
||||
searchPaths(indexId, minDepth, maxDepth, prefix = null) {
|
||||
if (this.backend() == "sqlite") {
|
||||
if (this.backend() === "sqlite") {
|
||||
return this.searchPathsSqlite(indexId, minDepth, minDepth, prefix);
|
||||
} else {
|
||||
return this.searchPathsEs(indexId, minDepth, maxDepth, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
private searchPathsSqlite(indexId, minDepth, maxDepth, prefix) {
|
||||
searchPathsSqlite(indexId, minDepth, maxDepth, prefix) {
|
||||
return axios.post(`${this.baseUrl}fts/paths`, {
|
||||
indexId, minDepth, maxDepth, prefix
|
||||
}).then(resp => {
|
||||
@@ -471,7 +372,7 @@ class Sist2Api {
|
||||
});
|
||||
}
|
||||
|
||||
private searchPathsEs(indexId, minDepth, maxDepth, prefix): Promise<[{ path: string, count: number }]> {
|
||||
searchPathsEs(indexId, minDepth, maxDepth, prefix) {
|
||||
|
||||
const query = {
|
||||
query: {
|
||||
@@ -516,7 +417,7 @@ class Sist2Api {
|
||||
});
|
||||
}
|
||||
|
||||
private getDateRangeSqlite() {
|
||||
getDateRangeSqlite() {
|
||||
return axios.get(`${this.baseUrl}fts/dateRange`)
|
||||
.then(resp => ({
|
||||
min: resp.data.dateMin,
|
||||
@@ -524,15 +425,15 @@ class Sist2Api {
|
||||
}));
|
||||
}
|
||||
|
||||
getDateRange(): Promise<{ min: number, max: number }> {
|
||||
if (this.backend() == "sqlite") {
|
||||
getDateRange() {
|
||||
if (this.backend() === "sqlite") {
|
||||
return this.getDateRangeSqlite();
|
||||
} else {
|
||||
return this.getDateRangeEs();
|
||||
}
|
||||
}
|
||||
|
||||
private getDateRangeEs() {
|
||||
getDateRangeEs() {
|
||||
return this.esQuery({
|
||||
// TODO: filter current selected indices
|
||||
aggs: {
|
||||
@@ -549,7 +450,7 @@ class Sist2Api {
|
||||
if (range.min == null) {
|
||||
range.min = 0;
|
||||
range.max = 1;
|
||||
} else if (range.min == range.max) {
|
||||
} else if (range.min === range.max) {
|
||||
range.max += 1;
|
||||
}
|
||||
|
||||
@@ -557,7 +458,7 @@ class Sist2Api {
|
||||
});
|
||||
}
|
||||
|
||||
private getPathSuggestionsSqlite(text: string) {
|
||||
getPathSuggestionsSqlite(text) {
|
||||
return axios.post(`${this.baseUrl}fts/paths`, {
|
||||
prefix: text,
|
||||
minDepth: 1,
|
||||
@@ -567,7 +468,7 @@ class Sist2Api {
|
||||
})
|
||||
}
|
||||
|
||||
private getPathSuggestionsEs(text) {
|
||||
getPathSuggestionsEs(text) {
|
||||
return this.esQuery({
|
||||
suggest: {
|
||||
path: {
|
||||
@@ -585,31 +486,31 @@ class Sist2Api {
|
||||
});
|
||||
}
|
||||
|
||||
getPathSuggestions(text: string): Promise<string[]> {
|
||||
if (this.backend() == "sqlite") {
|
||||
getPathSuggestions(text) {
|
||||
if (this.backend() === "sqlite") {
|
||||
return this.getPathSuggestionsSqlite(text);
|
||||
} else {
|
||||
return this.getPathSuggestionsEs(text)
|
||||
}
|
||||
}
|
||||
|
||||
getTreemapStat(indexId: string) {
|
||||
getTreemapStat(indexId) {
|
||||
return `${this.baseUrl}s/${indexId}/TMAP`;
|
||||
}
|
||||
|
||||
getMimeStat(indexId: string) {
|
||||
getMimeStat(indexId) {
|
||||
return `${this.baseUrl}s/${indexId}/MAGG`;
|
||||
}
|
||||
|
||||
getSizeStat(indexId: string) {
|
||||
getSizeStat(indexId) {
|
||||
return `${this.baseUrl}s/${indexId}/SAGG`;
|
||||
}
|
||||
|
||||
getDateStat(indexId: string) {
|
||||
getDateStat(indexId) {
|
||||
return `${this.baseUrl}s/${indexId}/DAGG`;
|
||||
}
|
||||
|
||||
private getDocumentEs(docId: string, highlight: boolean, fuzzy: boolean) {
|
||||
getDocumentEs(sid, highlight, fuzzy) {
|
||||
const query = Sist2Query.searchQuery();
|
||||
|
||||
if (highlight) {
|
||||
@@ -648,7 +549,7 @@ class Sist2Api {
|
||||
query.query.bool.must = [query.query.bool.must];
|
||||
}
|
||||
|
||||
query.query.bool.must.push({match: {_id: docId}});
|
||||
query.query.bool.must.push({match: {_id: sid}});
|
||||
|
||||
delete query["sort"];
|
||||
delete query["aggs"];
|
||||
@@ -669,35 +570,35 @@ class Sist2Api {
|
||||
});
|
||||
}
|
||||
|
||||
private getDocumentSqlite(docId: string): Promise<EsHit> {
|
||||
return axios.get(`${this.baseUrl}/fts/d/${docId}`)
|
||||
getDocumentSqlite(sid) {
|
||||
return axios.get(`${this.baseUrl}/fts/d/${sid}`)
|
||||
.then(resp => ({
|
||||
_source: resp.data
|
||||
} as EsHit));
|
||||
}));
|
||||
}
|
||||
|
||||
getDocument(docId: string, highlight: boolean, fuzzy: boolean): Promise<EsHit | null> {
|
||||
if (this.backend() == "sqlite") {
|
||||
return this.getDocumentSqlite(docId);
|
||||
getDocument(sid, highlight, fuzzy) {
|
||||
if (this.backend() === "sqlite") {
|
||||
return this.getDocumentSqlite(sid);
|
||||
} else {
|
||||
return this.getDocumentEs(docId, highlight, fuzzy);
|
||||
return this.getDocumentEs(sid, highlight, fuzzy);
|
||||
}
|
||||
}
|
||||
|
||||
getTagSuggestions(prefix: string): Promise<string[]> {
|
||||
if (this.backend() == "sqlite") {
|
||||
getTagSuggestions(prefix) {
|
||||
if (this.backend() === "sqlite") {
|
||||
return this.getTagSuggestionsSqlite(prefix);
|
||||
} else {
|
||||
return this.getTagSuggestionsEs(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
private getTagSuggestionsSqlite(prefix): Promise<string[]> {
|
||||
getTagSuggestionsSqlite(prefix) {
|
||||
return axios.post(`${this.baseUrl}/fts/suggestTags`, prefix)
|
||||
.then(resp => (resp.data));
|
||||
}
|
||||
|
||||
private getTagSuggestionsEs(prefix): Promise<string[]> {
|
||||
getTagSuggestionsEs(prefix) {
|
||||
return this.esQuery({
|
||||
suggest: {
|
||||
tag: {
|
||||
@@ -723,8 +624,8 @@ class Sist2Api {
|
||||
});
|
||||
}
|
||||
|
||||
getEmbeddings(indexId, docId, modelId) {
|
||||
return axios.post(`${this.baseUrl}/e/${indexId}/${docId}/${modelId.toString().padStart(3, '0')}`)
|
||||
getEmbeddings(sid, modelId) {
|
||||
return axios.post(`${this.baseUrl}/e/${sid}/${modelId.toString().padStart(3, '0')}`)
|
||||
.then(resp => (resp.data));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import store from "./store";
|
||||
import sist2Api, {EsHit, Index} from "@/Sist2Api";
|
||||
import store from "@/store";
|
||||
import Sist2Api from "@/Sist2Api";
|
||||
|
||||
const SORT_MODES = {
|
||||
score: {
|
||||
@@ -7,62 +7,62 @@ const SORT_MODES = {
|
||||
{_score: {order: "desc"}},
|
||||
{_tie: {order: "asc"}}
|
||||
],
|
||||
key: (hit: EsHit) => hit._score
|
||||
key: (hit) => hit._score
|
||||
},
|
||||
random: {
|
||||
mode: [
|
||||
{_score: {order: "desc"}},
|
||||
{_tie: {order: "asc"}}
|
||||
],
|
||||
key: (hit: EsHit) => hit._score
|
||||
key: (hit) => hit._score
|
||||
},
|
||||
dateAsc: {
|
||||
mode: [
|
||||
{mtime: {order: "asc"}},
|
||||
{_tie: {order: "asc"}}
|
||||
],
|
||||
key: (hit: EsHit) => hit._source.mtime
|
||||
key: (hit) => hit._source.mtime
|
||||
},
|
||||
dateDesc: {
|
||||
mode: [
|
||||
{mtime: {order: "desc"}},
|
||||
{_tie: {order: "asc"}}
|
||||
],
|
||||
key: (hit: EsHit) => hit._source.mtime
|
||||
key: (hit) => hit._source.mtime
|
||||
},
|
||||
sizeAsc: {
|
||||
mode: [
|
||||
{size: {order: "asc"}},
|
||||
{_tie: {order: "asc"}}
|
||||
],
|
||||
key: (hit: EsHit) => hit._source.size
|
||||
key: (hit) => hit._source.size
|
||||
},
|
||||
sizeDesc: {
|
||||
mode: [
|
||||
{size: {order: "desc"}},
|
||||
{_tie: {order: "asc"}}
|
||||
],
|
||||
key: (hit: EsHit) => hit._source.size
|
||||
key: (hit) => hit._source.size
|
||||
},
|
||||
nameAsc: {
|
||||
mode: [
|
||||
{name: {order: "asc"}},
|
||||
{_tie: {order: "asc"}}
|
||||
],
|
||||
key: (hit: EsHit) => hit._source.name
|
||||
key: (hit) => hit._source.name
|
||||
},
|
||||
nameDesc: {
|
||||
mode: [
|
||||
{name: {order: "desc"}},
|
||||
{_tie: {order: "asc"}}
|
||||
],
|
||||
key: (hit: EsHit) => hit._source.name
|
||||
key: (hit) => hit._source.name
|
||||
}
|
||||
} as any;
|
||||
};
|
||||
|
||||
class Sist2ElasticsearchQuery {
|
||||
|
||||
searchQuery(blankSearch: boolean = false): any {
|
||||
searchQuery(blankSearch = false) {
|
||||
|
||||
const getters = store.getters;
|
||||
|
||||
@@ -76,7 +76,7 @@ class Sist2ElasticsearchQuery {
|
||||
const fuzzy = getters.fuzzy;
|
||||
const size = getters.size;
|
||||
const after = getters.lastDoc;
|
||||
const selectedIndexIds = getters.selectedIndices.map((idx: Index) => idx.id)
|
||||
const selectedIndexIds = getters.selectedIndices.map((idx) => idx.id)
|
||||
const selectedMimeTypes = getters.selectedMimeTypes;
|
||||
const selectedTags = getters.selectedTags;
|
||||
const sortMode = getters.embedding ? "score" : getters.sortMode;
|
||||
@@ -86,7 +86,7 @@ class Sist2ElasticsearchQuery {
|
||||
|
||||
const filters = [
|
||||
{terms: {index: selectedIndexIds}}
|
||||
] as any[];
|
||||
];
|
||||
|
||||
const fields = [
|
||||
"name^8",
|
||||
@@ -138,7 +138,7 @@ class Sist2ElasticsearchQuery {
|
||||
if (getters.optTagOrOperator) {
|
||||
filters.push({terms: {"tag": selectedTags}});
|
||||
} else {
|
||||
selectedTags.forEach((tag: string) => filters.push({term: {"tag": tag}}));
|
||||
selectedTags.forEach((tag) => filters.push({term: {"tag": tag}}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,7 +173,7 @@ class Sist2ElasticsearchQuery {
|
||||
},
|
||||
sort: SORT_MODES[sortMode].mode,
|
||||
size: size,
|
||||
} as any;
|
||||
};
|
||||
|
||||
if (!after) {
|
||||
q.aggs = {
|
||||
@@ -193,7 +193,7 @@ class Sist2ElasticsearchQuery {
|
||||
if (getters.embedding) {
|
||||
delete q.query;
|
||||
|
||||
const field = "emb." + sist2Api.models().find(m => m.id == getters.embeddingsModel).path;
|
||||
const field = "emb." + Sist2Api.models().find(m => m.id === getters.embeddingsModel).path;
|
||||
|
||||
if (hasKnn) {
|
||||
// Use knn (8.8+)
|
||||
@@ -1,5 +1,4 @@
|
||||
import store from "./store";
|
||||
import {EsHit, Index} from "@/Sist2Api";
|
||||
import store from "@/store";
|
||||
|
||||
const SORT_MODES = {
|
||||
score: {
|
||||
@@ -29,18 +28,12 @@ const SORT_MODES = {
|
||||
"sort": "name",
|
||||
"sortAsc": false
|
||||
}
|
||||
} as any;
|
||||
|
||||
interface SortMode {
|
||||
text: string
|
||||
mode: any[]
|
||||
key: (hit: EsHit) => any
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class Sist2ElasticsearchQuery {
|
||||
|
||||
searchQuery(): any {
|
||||
searchQuery() {
|
||||
|
||||
const getters = store.getters;
|
||||
|
||||
@@ -52,7 +45,7 @@ class Sist2ElasticsearchQuery {
|
||||
const dateMax = getters.dateMax;
|
||||
const size = getters.size;
|
||||
const after = getters.lastDoc;
|
||||
const selectedIndexIds = getters.selectedIndices.map((idx: Index) => idx.id)
|
||||
const selectedIndexIds = getters.selectedIndices.map((idx) => idx.id)
|
||||
const selectedMimeTypes = getters.selectedMimeTypes;
|
||||
const selectedTags = getters.selectedTags;
|
||||
|
||||
@@ -95,7 +88,7 @@ class Sist2ElasticsearchQuery {
|
||||
if (selectedTags.length > 0) {
|
||||
q["tags"] = selectedTags
|
||||
}
|
||||
if (getters.sortMode == "random") {
|
||||
if (getters.sortMode === "random") {
|
||||
q["seed"] = getters.seed;
|
||||
}
|
||||
if (getters.optHighlight) {
|
||||
@@ -108,7 +101,7 @@ class Sist2ElasticsearchQuery {
|
||||
q["embedding"] = getters.embedding;
|
||||
q["sort"] = "embedding";
|
||||
q["sortAsc"] = false;
|
||||
} else if (getters.sortMode == "embedding") {
|
||||
} else if (getters.sortMode === "embedding") {
|
||||
q["sort"] = "sort"
|
||||
q["sortAsc"] = true;
|
||||
}
|
||||
@@ -9,8 +9,7 @@
|
||||
|
||||
<script>
|
||||
import * as d3 from "d3";
|
||||
import {burrow} from "@/util-js"
|
||||
import {humanFileSize} from "@/util";
|
||||
import {humanFileSize, burrow} from "@/util";
|
||||
import Sist2Api from "@/Sist2Api";
|
||||
import domtoimage from "dom-to-image";
|
||||
|
||||
|
||||
@@ -31,8 +31,7 @@
|
||||
<script>
|
||||
import noUiSlider from 'nouislider';
|
||||
import 'nouislider/dist/nouislider.css';
|
||||
import {humanDate} from "@/util";
|
||||
import {mergeTooltips} from "@/util-js";
|
||||
import {humanDate, mergeTooltips} from "@/util";
|
||||
|
||||
export default {
|
||||
name: "DateSlider",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<!-- Audio player-->
|
||||
<audio v-if="doc._props.isAudio" ref="audio" preload="none" class="audio-fit fit" controls
|
||||
:type="doc._source.mime"
|
||||
:src="`f/${doc._source.index}/${doc._id}`"
|
||||
:src="`f/${sid(doc)}`"
|
||||
@play="onAudioPlay()"></audio>
|
||||
|
||||
<b-card-body class="padding-03">
|
||||
@@ -43,7 +43,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ext, humanFileSize, humanTime} from "@/util";
|
||||
import {ext, humanFileSize, humanTime, sid} from "@/util";
|
||||
import TagContainer from "@/components/TagContainer.vue";
|
||||
import DocFileTitle from "@/components/DocFileTitle.vue";
|
||||
import DocInfoModal from "@/components/DocInfoModal.vue";
|
||||
@@ -69,13 +69,14 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
sid: sid,
|
||||
humanFileSize: humanFileSize,
|
||||
humanTime: humanTime,
|
||||
onInfoClick() {
|
||||
this.showInfo = true;
|
||||
},
|
||||
onEmbeddingClick() {
|
||||
Sist2Api.getEmbeddings(this.doc._source.index, this.doc._id, this.$store.state.embeddingsModel).then(embeddings => {
|
||||
Sist2Api.getEmbeddings(sid(this.doc), this.$store.state.embeddingsModel).then(embeddings => {
|
||||
this.$store.commit("setEmbeddingText", "");
|
||||
this.$store.commit("setEmbedding", embeddings);
|
||||
this.$store.commit("setEmbeddingDoc", this.doc);
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<template>
|
||||
<GridLayout
|
||||
ref="grid-layout"
|
||||
:options="gridOptions"
|
||||
@append="append"
|
||||
@layout-complete="$emit('layout-complete')"
|
||||
>
|
||||
<DocCard v-for="doc in docs" :key="doc._id" :doc="doc" :width="width"></DocCard>
|
||||
</GridLayout>
|
||||
<GridLayout
|
||||
ref="grid-layout"
|
||||
:options="gridOptions"
|
||||
@append="append"
|
||||
@layout-complete="$emit('layout-complete')"
|
||||
>
|
||||
<DocCard v-for="doc in docs" :key="sid(doc)" :doc="doc" :width="width"></DocCard>
|
||||
</GridLayout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {sid} from "@/util";
|
||||
import Vue from "vue";
|
||||
import DocCard from "@/components/DocCard";
|
||||
|
||||
@@ -18,50 +19,53 @@ import VueInfiniteGrid from "@egjs/vue-infinitegrid";
|
||||
Vue.use(VueInfiniteGrid);
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
DocCard,
|
||||
},
|
||||
props: ["docs", "append"],
|
||||
data() {
|
||||
return {
|
||||
width: 0,
|
||||
gridOptions: {
|
||||
align: "center",
|
||||
margin: 0,
|
||||
transitionDuration: 0,
|
||||
isOverflowScroll: false,
|
||||
isConstantSize: false,
|
||||
useFit: false,
|
||||
// Indicates whether keep the number of DOMs is maintained. If the useRecycle value is 'true', keep the number
|
||||
// of DOMs is maintained. If the useRecycle value is 'false', the number of DOMs will increase as card elements
|
||||
// are added.
|
||||
useRecycle: false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
colCount() {
|
||||
const columns = this.$store.getters["optColumns"];
|
||||
|
||||
if (columns === "auto") {
|
||||
return Math.round(this.$refs["grid-layout"].$el.scrollWidth / 300)
|
||||
}
|
||||
return columns;
|
||||
components: {
|
||||
DocCard,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.width = this.$refs["grid-layout"].$el.scrollWidth / this.colCount;
|
||||
props: ["docs", "append"],
|
||||
data() {
|
||||
return {
|
||||
width: 0,
|
||||
gridOptions: {
|
||||
align: "center",
|
||||
margin: 0,
|
||||
transitionDuration: 0,
|
||||
isOverflowScroll: false,
|
||||
isConstantSize: false,
|
||||
useFit: false,
|
||||
// Indicates whether keep the number of DOMs is maintained. If the useRecycle value is 'true', keep the number
|
||||
// of DOMs is maintained. If the useRecycle value is 'false', the number of DOMs will increase as card elements
|
||||
// are added.
|
||||
useRecycle: false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
sid,
|
||||
},
|
||||
computed: {
|
||||
colCount() {
|
||||
const columns = this.$store.getters["optColumns"];
|
||||
|
||||
if (this.colCount === 1) {
|
||||
this.$refs["grid-layout"].$el.classList.add("grid-single-column");
|
||||
}
|
||||
if (columns === "auto") {
|
||||
return Math.round(this.$refs["grid-layout"].$el.scrollWidth / 300)
|
||||
}
|
||||
return columns;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.width = this.$refs["grid-layout"].$el.scrollWidth / this.colCount;
|
||||
|
||||
this.$store.subscribe((mutation) => {
|
||||
if (mutation.type === "busUpdateWallItems" && this.$refs["grid-layout"]) {
|
||||
this.$refs["grid-layout"].updateItems();
|
||||
}
|
||||
});
|
||||
},
|
||||
if (this.colCount === 1) {
|
||||
this.$refs["grid-layout"].$el.classList.add("grid-single-column");
|
||||
}
|
||||
|
||||
this.$store.subscribe((mutation) => {
|
||||
if (mutation.type === "busUpdateWallItems" && this.$refs["grid-layout"]) {
|
||||
this.$refs["grid-layout"].updateItems();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<a :href="`f/${doc._source.index}/${doc._id}`"
|
||||
<a :href="`f/${sid(doc)}`"
|
||||
:class="doc._source.embedding ? 'file-title-anchor-with-embedding' : 'file-title-anchor'" target="_blank">
|
||||
<div class="file-title" :title="doc._source.path + '/' + doc._source.name + ext(doc)"
|
||||
v-html="fileName() + ext(doc)"></div>
|
||||
@@ -7,12 +7,13 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ext} from "@/util";
|
||||
import {ext, sid} from "@/util";
|
||||
|
||||
export default {
|
||||
name: "DocFileTitle",
|
||||
props: ["doc"],
|
||||
methods: {
|
||||
sid: sid,
|
||||
ext: ext,
|
||||
fileName() {
|
||||
if (!this.doc.highlight) {
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
<template>
|
||||
<b-modal :visible="show" size="lg" :hide-footer="true" static lazy @close="$emit('close')" @hide="$emit('close')"
|
||||
>
|
||||
<template #modal-title>
|
||||
<h5 class="modal-title" :title="doc._source.name + ext(doc)">
|
||||
{{ doc._source.name + ext(doc) }}
|
||||
<router-link :to="`/file?byId=${doc._id}`">#</router-link>
|
||||
</h5>
|
||||
</template>
|
||||
<b-modal :visible="show" size="lg" :hide-footer="true" static lazy @close="$emit('close')" @hide="$emit('close')"
|
||||
>
|
||||
<template #modal-title>
|
||||
<h5 class="modal-title" :title="doc._source.name + ext(doc)">
|
||||
{{ doc._source.name + ext(doc) }}
|
||||
<router-link :to="`/file?byId=${doc._id}`">#</router-link>
|
||||
</h5>
|
||||
</template>
|
||||
|
||||
<img v-if="doc._props.hasThumbnail" :src="`t/${doc._source.index}/${doc._id}`" alt="" class="fit card-img-top">
|
||||
<img v-if="doc._props.hasThumbnail" :src="`t/${sid(doc)}`" alt="" class="fit card-img-top">
|
||||
|
||||
<InfoTable :doc="doc"></InfoTable>
|
||||
<InfoTable :doc="doc"></InfoTable>
|
||||
|
||||
<LazyContentDiv :doc-id="doc._id"></LazyContentDiv>
|
||||
</b-modal>
|
||||
<LazyContentDiv :sid="sid(doc)"></LazyContentDiv>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ext} from "@/util";
|
||||
import {ext, sid} from "@/util";
|
||||
import InfoTable from "@/components/InfoTable";
|
||||
import LazyContentDiv from "@/components/LazyContentDiv";
|
||||
|
||||
export default {
|
||||
name: "DocInfoModal",
|
||||
components: {LazyContentDiv, InfoTable},
|
||||
props: ["doc", "show"],
|
||||
methods: {
|
||||
ext: ext,
|
||||
}
|
||||
name: "DocInfoModal",
|
||||
components: {LazyContentDiv, InfoTable},
|
||||
props: ["doc", "show"],
|
||||
methods: {
|
||||
ext: ext,
|
||||
sid: sid
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,45 +1,49 @@
|
||||
<template>
|
||||
<b-list-group class="mt-3">
|
||||
<DocListItem v-for="doc in docs" :key="doc._id" :doc="doc"></DocListItem>
|
||||
</b-list-group>
|
||||
<b-list-group class="mt-3">
|
||||
<DocListItem v-for="doc in docs" :key="sid(doc)" :doc="doc"></DocListItem>
|
||||
</b-list-group>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script>
|
||||
import {sid} from "@/util";
|
||||
import DocListItem from "@/components/DocListItem.vue";
|
||||
import Vue from "vue";
|
||||
|
||||
export default Vue.extend({
|
||||
name: "DocList",
|
||||
components: {DocListItem},
|
||||
props: ["docs", "append"],
|
||||
mounted() {
|
||||
window.addEventListener("scroll", () => {
|
||||
const threshold = 400;
|
||||
const app = document.getElementById("app");
|
||||
name: "DocList",
|
||||
components: {DocListItem},
|
||||
props: ["docs", "append"],
|
||||
mounted() {
|
||||
window.addEventListener("scroll", () => {
|
||||
const threshold = 400;
|
||||
const app = document.getElementById("app");
|
||||
|
||||
if ((window.innerHeight + window.scrollY) >= app.offsetHeight - threshold) {
|
||||
this.append();
|
||||
}
|
||||
});
|
||||
}
|
||||
if ((window.innerHeight + window.scrollY) >= app.offsetHeight - threshold) {
|
||||
this.append();
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
sid: sid
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
.theme-black .list-group-item {
|
||||
background: #212121;
|
||||
color: #e0e0e0;
|
||||
background: #212121;
|
||||
color: #e0e0e0;
|
||||
|
||||
border-bottom: none;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-radius: 0;
|
||||
padding: .25rem 0.5rem;
|
||||
border-bottom: none;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-radius: 0;
|
||||
padding: .25rem 0.5rem;
|
||||
}
|
||||
|
||||
.theme-black .list-group-item:first-child {
|
||||
border-top: none;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -17,10 +17,10 @@
|
||||
</div>
|
||||
|
||||
<img v-if="doc._props.isPlayableImage || doc._props.isPlayableVideo"
|
||||
:src="(doc._props.isGif && hover) ? `f/${doc._source.index}/${doc._id}` : `t/${doc._source.index}/${doc._id}`"
|
||||
:src="(doc._props.isGif && hover) ? `f/${sid(doc)}` : `t/${sid(doc)}`"
|
||||
alt=""
|
||||
class="pointer fit-sm" @click="onThumbnailClick()">
|
||||
<img v-else :src="`t/${doc._source.index}/${doc._id}`" alt=""
|
||||
<img v-else :src="`t/${sid(doc)}`" alt=""
|
||||
class="fit-sm">
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,6 +70,7 @@ import FileIcon from "@/components/icons/FileIcon";
|
||||
import FeaturedFieldsLine from "@/components/FeaturedFieldsLine";
|
||||
import MLIcon from "@/components/icons/MlIcon.vue";
|
||||
import Sist2Api from "@/Sist2Api";
|
||||
import {sid} from "@/util";
|
||||
|
||||
export default {
|
||||
name: "DocListItem",
|
||||
@@ -82,12 +83,13 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
sid: sid,
|
||||
async onThumbnailClick() {
|
||||
this.$store.commit("setUiLightboxSlide", this.doc._seq);
|
||||
await this.$store.dispatch("showLightbox");
|
||||
},
|
||||
onEmbeddingClick() {
|
||||
Sist2Api.getEmbeddings(this.doc._source.index, this.doc._id, this.$store.state.embeddingsModel).then(embeddings => {
|
||||
Sist2Api.getEmbeddings(sid(this.doc), this.$store.state.embeddingsModel).then(embeddings => {
|
||||
this.$store.commit("setEmbeddingText", "");
|
||||
this.$store.commit("setEmbedding", embeddings);
|
||||
this.$store.commit("setEmbeddingDoc", this.doc);
|
||||
|
||||
@@ -1,188 +1,189 @@
|
||||
<template>
|
||||
<div v-if="doc._props.hasThumbnail" class="img-wrapper" @mouseenter="onTnEnter()" @mouseleave="onTnLeave()"
|
||||
@touchstart="onTouchStart()">
|
||||
<div v-if="doc._props.isAudio" class="card-img-overlay" :class="{'small-badge': smallBadge}">
|
||||
<span class="badge badge-resolution">{{ humanTime(doc._source.duration) }}</span>
|
||||
<div v-if="doc._props.hasThumbnail" class="img-wrapper" @mouseenter="onTnEnter()" @mouseleave="onTnLeave()"
|
||||
@touchstart="onTouchStart()">
|
||||
<div v-if="doc._props.isAudio" class="card-img-overlay" :class="{'small-badge': smallBadge}">
|
||||
<span class="badge badge-resolution">{{ humanTime(doc._source.duration) }}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="doc._props.isImage && doc._props.imageAspectRatio < 5"
|
||||
class="card-img-overlay"
|
||||
:class="{'small-badge': smallBadge}">
|
||||
<span class="badge badge-resolution">{{ `${doc._source.width}x${doc._source.height}` }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="(doc._props.isVideo || doc._props.isGif) && doc._source.duration > 0"
|
||||
class="card-img-overlay"
|
||||
:class="{'small-badge': smallBadge}">
|
||||
<span class="badge badge-resolution">{{ humanTime(doc._source.duration) }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="doc._props.isPlayableVideo" class="play">
|
||||
<svg viewBox="0 0 494.942 494.942" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m35.353 0 424.236 247.471-424.236 247.471z"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<img ref="tn"
|
||||
v-if="doc._props.isPlayableImage || doc._props.isPlayableVideo"
|
||||
:src="tnSrc"
|
||||
alt=""
|
||||
:style="{height: (doc._props.isGif && hover) ? `${tnHeight()}px` : undefined}"
|
||||
class="pointer fit card-img-top" @click="onThumbnailClick()">
|
||||
<img v-else :src="tnSrc" alt=""
|
||||
class="fit card-img-top">
|
||||
|
||||
<ThumbnailProgressBar v-if="hover && doc._props.hasVidPreview"
|
||||
:progress="(currentThumbnailNum + 1) / (doc._props.tnNum)"
|
||||
></ThumbnailProgressBar>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="doc._props.isImage && doc._props.imageAspectRatio < 5"
|
||||
class="card-img-overlay"
|
||||
:class="{'small-badge': smallBadge}">
|
||||
<span class="badge badge-resolution">{{ `${doc._source.width}x${doc._source.height}` }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="(doc._props.isVideo || doc._props.isGif) && doc._source.duration > 0"
|
||||
class="card-img-overlay"
|
||||
:class="{'small-badge': smallBadge}">
|
||||
<span class="badge badge-resolution">{{ humanTime(doc._source.duration) }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="doc._props.isPlayableVideo" class="play">
|
||||
<svg viewBox="0 0 494.942 494.942" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m35.353 0 424.236 247.471-424.236 247.471z"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<img ref="tn"
|
||||
v-if="doc._props.isPlayableImage || doc._props.isPlayableVideo"
|
||||
:src="tnSrc"
|
||||
alt=""
|
||||
:style="{height: (doc._props.isGif && hover) ? `${tnHeight()}px` : undefined}"
|
||||
class="pointer fit card-img-top" @click="onThumbnailClick()">
|
||||
<img v-else :src="tnSrc" alt=""
|
||||
class="fit card-img-top">
|
||||
|
||||
<ThumbnailProgressBar v-if="hover && doc._props.hasVidPreview"
|
||||
:progress="(currentThumbnailNum + 1) / (doc._props.tnNum)"
|
||||
></ThumbnailProgressBar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {humanTime} from "@/util";
|
||||
import {humanTime, sid} from "@/util";
|
||||
import ThumbnailProgressBar from "@/components/ThumbnailProgressBar";
|
||||
|
||||
export default {
|
||||
name: "FullThumbnail",
|
||||
props: ["doc", "smallBadge"],
|
||||
components: {ThumbnailProgressBar},
|
||||
data() {
|
||||
return {
|
||||
hover: false,
|
||||
currentThumbnailNum: 0,
|
||||
timeoutId: null
|
||||
name: "FullThumbnail",
|
||||
props: ["doc", "smallBadge"],
|
||||
components: {ThumbnailProgressBar},
|
||||
data() {
|
||||
return {
|
||||
hover: false,
|
||||
currentThumbnailNum: 0,
|
||||
timeoutId: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$store.subscribe((mutation) => {
|
||||
if (mutation.type === "busTnTouchStart" && mutation.payload !== this.doc._id) {
|
||||
this.onTnLeave();
|
||||
}
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
tnSrc() {
|
||||
return this.getThumbnailSrc(this.currentThumbnailNum);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
sid: sid,
|
||||
getThumbnailSrc(thumbnailNum) {
|
||||
const doc = this.doc;
|
||||
const props = doc._props;
|
||||
if (props.isGif && this.hover) {
|
||||
return `f/${sid(doc)}`;
|
||||
}
|
||||
return (this.currentThumbnailNum === 0)
|
||||
? `t/${sid(doc)}`
|
||||
: `t/${sid(doc)}/${String(thumbnailNum).padStart(4, "0")}`;
|
||||
},
|
||||
humanTime: humanTime,
|
||||
onThumbnailClick() {
|
||||
this.$emit("onThumbnailClick");
|
||||
},
|
||||
tnHeight() {
|
||||
return this.$refs.tn.height;
|
||||
},
|
||||
tnWidth() {
|
||||
return this.$refs.tn.width;
|
||||
},
|
||||
onTnEnter() {
|
||||
this.hover = true;
|
||||
const start = Date.now()
|
||||
if (this.doc._props.hasVidPreview) {
|
||||
let img = new Image();
|
||||
img.src = this.getThumbnailSrc(this.currentThumbnailNum + 1);
|
||||
img.onload = () => {
|
||||
this.currentThumbnailNum += 1;
|
||||
this.scheduleNextTnNum(Date.now() - start);
|
||||
}
|
||||
}
|
||||
},
|
||||
onTnLeave() {
|
||||
this.currentThumbnailNum = 0;
|
||||
this.hover = false;
|
||||
if (this.timeoutId !== null) {
|
||||
window.clearTimeout(this.timeoutId);
|
||||
this.timeoutId = null;
|
||||
}
|
||||
},
|
||||
scheduleNextTnNum(offset = 0) {
|
||||
const INTERVAL = (this.$store.state.optVidPreviewInterval ?? 700) - offset;
|
||||
this.timeoutId = window.setTimeout(() => {
|
||||
const start = Date.now();
|
||||
if (!this.hover) {
|
||||
return;
|
||||
}
|
||||
if (this.currentThumbnailNum === this.doc._props.tnNum - 1) {
|
||||
this.currentThumbnailNum = 0;
|
||||
this.scheduleNextTnNum();
|
||||
} else {
|
||||
let img = new Image();
|
||||
img.src = this.getThumbnailSrc(this.currentThumbnailNum + 1);
|
||||
img.onload = () => {
|
||||
this.currentThumbnailNum += 1;
|
||||
this.scheduleNextTnNum(Date.now() - start);
|
||||
}
|
||||
}
|
||||
}, INTERVAL);
|
||||
},
|
||||
onTouchStart() {
|
||||
this.$store.commit("busTnTouchStart", this.doc._id);
|
||||
if (!this.hover) {
|
||||
this.onTnEnter()
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$store.subscribe((mutation) => {
|
||||
if (mutation.type === "busTnTouchStart" && mutation.payload !== this.doc._id) {
|
||||
this.onTnLeave();
|
||||
}
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
tnSrc() {
|
||||
return this.getThumbnailSrc(this.currentThumbnailNum);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getThumbnailSrc(thumbnailNum) {
|
||||
const doc = this.doc;
|
||||
const props = doc._props;
|
||||
if (props.isGif && this.hover) {
|
||||
return `f/${doc._source.index}/${doc._id}`;
|
||||
}
|
||||
return (this.currentThumbnailNum === 0)
|
||||
? `t/${doc._source.index}/${doc._id}`
|
||||
: `t/${doc._source.index}/${doc._id}/${String(thumbnailNum).padStart(4, "0")}`;
|
||||
},
|
||||
humanTime: humanTime,
|
||||
onThumbnailClick() {
|
||||
this.$emit("onThumbnailClick");
|
||||
},
|
||||
tnHeight() {
|
||||
return this.$refs.tn.height;
|
||||
},
|
||||
tnWidth() {
|
||||
return this.$refs.tn.width;
|
||||
},
|
||||
onTnEnter() {
|
||||
this.hover = true;
|
||||
const start = Date.now()
|
||||
if (this.doc._props.hasVidPreview) {
|
||||
let img = new Image();
|
||||
img.src = this.getThumbnailSrc(this.currentThumbnailNum + 1);
|
||||
img.onload = () => {
|
||||
this.currentThumbnailNum += 1;
|
||||
this.scheduleNextTnNum(Date.now() - start);
|
||||
}
|
||||
}
|
||||
},
|
||||
onTnLeave() {
|
||||
this.currentThumbnailNum = 0;
|
||||
this.hover = false;
|
||||
if (this.timeoutId !== null) {
|
||||
window.clearTimeout(this.timeoutId);
|
||||
this.timeoutId = null;
|
||||
}
|
||||
},
|
||||
scheduleNextTnNum(offset = 0) {
|
||||
const INTERVAL = (this.$store.state.optVidPreviewInterval ?? 700) - offset;
|
||||
this.timeoutId = window.setTimeout(() => {
|
||||
const start = Date.now();
|
||||
if (!this.hover) {
|
||||
return;
|
||||
}
|
||||
if (this.currentThumbnailNum === this.doc._props.tnNum - 1) {
|
||||
this.currentThumbnailNum = 0;
|
||||
this.scheduleNextTnNum();
|
||||
} else {
|
||||
let img = new Image();
|
||||
img.src = this.getThumbnailSrc(this.currentThumbnailNum + 1);
|
||||
img.onload = () => {
|
||||
this.currentThumbnailNum += 1;
|
||||
this.scheduleNextTnNum(Date.now() - start);
|
||||
}
|
||||
}
|
||||
}, INTERVAL);
|
||||
},
|
||||
onTouchStart() {
|
||||
this.$store.commit("busTnTouchStart", this.doc._id);
|
||||
if (!this.hover) {
|
||||
this.onTnEnter()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.img-wrapper {
|
||||
position: relative;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.img-wrapper:hover svg {
|
||||
fill: rgba(0, 0, 0, 1);
|
||||
fill: rgba(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
.card-img-top {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
.play {
|
||||
position: absolute;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.play svg {
|
||||
fill: rgba(0, 0, 0, 0.7);
|
||||
fill: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.badge-resolution {
|
||||
color: #c6c6c6;
|
||||
background-color: #272727CC;
|
||||
padding: 2px 3px;
|
||||
color: #c6c6c6;
|
||||
background-color: #272727CC;
|
||||
padding: 2px 3px;
|
||||
}
|
||||
|
||||
.card-img-overlay {
|
||||
pointer-events: none;
|
||||
padding: 2px 6px;
|
||||
bottom: 4px;
|
||||
top: unset;
|
||||
left: unset;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
padding: 2px 6px;
|
||||
bottom: 4px;
|
||||
top: unset;
|
||||
left: unset;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.small-badge {
|
||||
padding: 1px 3px;
|
||||
font-size: 70%;
|
||||
padding: 1px 3px;
|
||||
font-size: 70%;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -27,11 +27,12 @@
|
||||
@click.shift="shiftClick(idx, $event)"
|
||||
class="d-flex justify-content-between align-items-center list-group-item-action pointer"
|
||||
:class="{active: lastClickIndex === idx}"
|
||||
:key="idx.id"
|
||||
>
|
||||
<div class="d-flex">
|
||||
<b-checkbox style="pointer-events: none" :checked="isSelected(idx)"></b-checkbox>
|
||||
{{ idx.name }}
|
||||
<div style="vertical-align: center; margin-left: 5px">
|
||||
<div v-if="hasEmbeddings(idx)" style="vertical-align: center; margin-left: 5px">
|
||||
<MLIcon small style="top: -1px; position: relative"></MLIcon>
|
||||
</div>
|
||||
<span class="text-muted timestamp-text ml-2"
|
||||
@@ -43,7 +44,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script>
|
||||
import SmallBadge from "./SmallBadge.vue"
|
||||
import {mapActions, mapGetters} from "vuex";
|
||||
import Vue from "vue";
|
||||
@@ -111,7 +112,7 @@ export default Vue.extend({
|
||||
onSelect(value) {
|
||||
this.setSelectedIndices(this.indices.filter(idx => value.includes(idx.id)));
|
||||
},
|
||||
formatIdxDate(timestamp: number): string {
|
||||
formatIdxDate(timestamp) {
|
||||
return format(new Date(timestamp * 1000), "yyyy-MM-dd");
|
||||
},
|
||||
toggleIndex(index, e) {
|
||||
@@ -121,14 +122,17 @@ export default Vue.extend({
|
||||
|
||||
this.lastClickIndex = index;
|
||||
if (this.isSelected(index)) {
|
||||
this.setSelectedIndices(this.selectedIndices.filter(idx => idx.id != index.id));
|
||||
this.setSelectedIndices(this.selectedIndices.filter(idx => idx.id !== index.id));
|
||||
} else {
|
||||
this.setSelectedIndices([index, ...this.selectedIndices]);
|
||||
}
|
||||
},
|
||||
isSelected(index) {
|
||||
return this.selectedIndices.find(idx => idx.id == index.id) != null;
|
||||
}
|
||||
return this.selectedIndices.find(idx => idx.id === index.id) != null;
|
||||
},
|
||||
hasEmbeddings(index) {
|
||||
return index.models.length > 0;
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
>{{ $t("ml.analyzeText") }}
|
||||
</b-button>
|
||||
<b-select :disabled="mlPredictionsLoading || mlLoading" class="ml-2" v-model="nerModel">
|
||||
<b-select-option :value="opt.value" v-for="opt of ModelsRepo.getOptions()">{{ opt.text }}
|
||||
<b-select-option :value="opt.value" v-for="opt of ModelsRepo.getOptions()" :key="opt.value">{{ opt.text }}
|
||||
</b-select-option>
|
||||
</b-select>
|
||||
</b-form>
|
||||
@@ -46,7 +46,7 @@ import {mapGetters, mapMutations} from "vuex";
|
||||
export default {
|
||||
name: "LazyContentDiv",
|
||||
components: {AnalyzedContentSpansContainer, Preloader},
|
||||
props: ["docId"],
|
||||
props: ["sid"],
|
||||
data() {
|
||||
return {
|
||||
ModelsRepo,
|
||||
@@ -70,7 +70,7 @@ export default {
|
||||
}
|
||||
|
||||
Sist2Api
|
||||
.getDocument(this.docId, this.$store.state.optHighlight, this.$store.state.fuzzy)
|
||||
.getDocument(this.sid, this.$store.state.optHighlight, this.$store.state.fuzzy)
|
||||
.then(doc => {
|
||||
this.loading = false;
|
||||
|
||||
|
||||
@@ -43,8 +43,7 @@
|
||||
</b-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Sist2Api, {EsResult} from "@/Sist2Api";
|
||||
<script>
|
||||
import Vue from "vue";
|
||||
import {humanFileSize} from "@/util";
|
||||
import DisplayModeToggle from "@/components/DisplayModeToggle.vue";
|
||||
@@ -52,6 +51,7 @@ import SortSelect from "@/components/SortSelect.vue";
|
||||
import Preloader from "@/components/Preloader.vue";
|
||||
import Sist2Query from "@/Sist2ElasticsearchQuery";
|
||||
import ClipboardIcon from "@/components/icons/ClipboardIcon.vue";
|
||||
import Sist2Api from "@/Sist2Api";
|
||||
|
||||
export default Vue.extend({
|
||||
name: "ResultsCard",
|
||||
@@ -64,7 +64,7 @@ export default Vue.extend({
|
||||
return this.$store.state.lastQueryResults != null;
|
||||
},
|
||||
hitCount() {
|
||||
return (this.$store.state.firstQueryResults as EsResult).aggregations.total_count.value;
|
||||
return (this.$store.state.firstQueryResults).aggregations.total_count.value;
|
||||
},
|
||||
tableItems() {
|
||||
const items = [];
|
||||
@@ -79,10 +79,10 @@ export default Vue.extend({
|
||||
},
|
||||
methods: {
|
||||
took() {
|
||||
return (this.$store.state.lastQueryResults as EsResult).took + "ms";
|
||||
return (this.$store.state.lastQueryResults).took + "ms";
|
||||
},
|
||||
totalSize() {
|
||||
return humanFileSize((this.$store.state.firstQueryResults as EsResult).aggregations.total_size.value);
|
||||
return humanFileSize((this.$store.state.firstQueryResults).aggregations.total_size.value);
|
||||
},
|
||||
onToggle() {
|
||||
const show = !document.getElementById("collapse-1").classList.contains("show");
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
<script>
|
||||
import noUiSlider from 'nouislider';
|
||||
import 'nouislider/dist/nouislider.css';
|
||||
import {humanFileSize} from "@/util";
|
||||
import {mergeTooltips} from "@/util-js";
|
||||
import {humanFileSize, mergeTooltips} from "@/util";
|
||||
|
||||
export default {
|
||||
name: "SizeSlider",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<b-badge variant="secondary" :pill="pill">{{ text }}</b-badge>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script>
|
||||
import Vue from "vue";
|
||||
|
||||
export default Vue.extend({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="thumbnail-progress-bar" :style="{width: `${percentProgress}%`}"></div>
|
||||
<div class="thumbnail_count-progress-bar" :style="{width: `${percentProgress}%`}"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -16,7 +16,7 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
|
||||
.thumbnail-progress-bar {
|
||||
.thumbnail_count-progress-bar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
@@ -27,11 +27,11 @@ export default {
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.theme-black .thumbnail-progress-bar {
|
||||
.theme-black .thumbnail_count-progress-bar {
|
||||
background: rgba(0, 188, 212, 0.95);
|
||||
}
|
||||
|
||||
.sub-document .thumbnail-progress-bar {
|
||||
.sub-document .thumbnail_count-progress-bar {
|
||||
max-width: calc(100% - 8px);
|
||||
left: 4px;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ const authGuard = (to, from, next) => {
|
||||
next();
|
||||
}
|
||||
|
||||
const routes: Array<RouteConfig> = [
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
name: "SearchPage",
|
||||
@@ -1,20 +1,18 @@
|
||||
import Vue from "vue"
|
||||
import Vuex from "vuex"
|
||||
import VueRouter, {Route} from "vue-router";
|
||||
import {EsHit, EsResult, EsTag, Index} from "@/Sist2Api";
|
||||
import {deserializeMimes, randomSeed, serializeMimes} from "@/util";
|
||||
import {getInstance} from "@/plugins/auth0.js";
|
||||
|
||||
const CONF_VERSION = 3;
|
||||
|
||||
Vue.use(Vuex)
|
||||
Vue.use(Vuex);
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
seed: 0,
|
||||
indices: [] as Index[],
|
||||
tags: [] as EsTag[],
|
||||
sist2Info: null as any,
|
||||
indices: [],
|
||||
tags: [],
|
||||
sist2Info: null,
|
||||
|
||||
sizeMin: undefined,
|
||||
sizeMax: undefined,
|
||||
@@ -64,12 +62,12 @@ export default new Vuex.Store({
|
||||
optAutoAnalyze: false,
|
||||
optMlDefaultModel: null,
|
||||
|
||||
_onLoadSelectedIndices: [] as string[],
|
||||
_onLoadSelectedMimeTypes: [] as string[],
|
||||
_onLoadSelectedTags: [] as string[],
|
||||
selectedIndices: [] as Index[],
|
||||
selectedMimeTypes: [] as string[],
|
||||
selectedTags: [] as string[],
|
||||
_onLoadSelectedIndices: [],
|
||||
_onLoadSelectedMimeTypes: [],
|
||||
_onLoadSelectedTags: [],
|
||||
selectedIndices: [],
|
||||
selectedMimeTypes: [],
|
||||
selectedTags: [],
|
||||
|
||||
lastQueryResults: null,
|
||||
firstQueryResults: null,
|
||||
@@ -80,10 +78,10 @@ export default new Vuex.Store({
|
||||
uiSqliteMode: false,
|
||||
uiLightboxIsOpen: false,
|
||||
uiShowLightbox: false,
|
||||
uiLightboxSources: [] as string[],
|
||||
uiLightboxThumbs: [] as string[],
|
||||
uiLightboxCaptions: [] as any[],
|
||||
uiLightboxTypes: [] as string[],
|
||||
uiLightboxSources: [],
|
||||
uiLightboxThumbs: [],
|
||||
uiLightboxCaptions: [],
|
||||
uiLightboxTypes: [],
|
||||
uiLightboxKey: 0,
|
||||
uiLightboxSlide: 0,
|
||||
uiReachedScrollEnd: false,
|
||||
@@ -91,7 +89,7 @@ export default new Vuex.Store({
|
||||
uiDetailsMimeAgg: null,
|
||||
uiShowDetails: false,
|
||||
|
||||
uiMimeMap: [] as any[],
|
||||
uiMimeMap: [],
|
||||
|
||||
auth0Token: null,
|
||||
nerModel: {
|
||||
@@ -122,7 +120,7 @@ export default new Vuex.Store({
|
||||
if (state._onLoadSelectedIndices.length > 0) {
|
||||
|
||||
state.selectedIndices = val.filter(
|
||||
(idx: Index) => state._onLoadSelectedIndices.some(prefix => idx.id.startsWith(prefix))
|
||||
(idx) => state._onLoadSelectedIndices.some(id => id === idx.id.toString(16))
|
||||
);
|
||||
} else {
|
||||
state.selectedIndices = val;
|
||||
@@ -145,18 +143,18 @@ export default new Vuex.Store({
|
||||
setSelectedIndices: (state, val) => state.selectedIndices = val,
|
||||
setSelectedMimeTypes: (state, val) => state.selectedMimeTypes = val,
|
||||
setSelectedTags: (state, val) => state.selectedTags = val,
|
||||
setUiLightboxIsOpen: (state, val: boolean) => state.uiLightboxIsOpen = val,
|
||||
_setUiShowLightbox: (state, val: boolean) => state.uiShowLightbox = val,
|
||||
setUiLightboxKey: (state, val: number) => state.uiLightboxKey = val,
|
||||
_setKeySequence: (state, val: number) => state.keySequence = val,
|
||||
_setQuerySequence: (state, val: number) => state.querySequence = val,
|
||||
setUiLightboxIsOpen: (state, val) => state.uiLightboxIsOpen = val,
|
||||
_setUiShowLightbox: (state, val) => state.uiShowLightbox = val,
|
||||
setUiLightboxKey: (state, val) => state.uiLightboxKey = val,
|
||||
_setKeySequence: (state, val) => state.keySequence = val,
|
||||
_setQuerySequence: (state, val) => state.querySequence = val,
|
||||
addLightboxSource: (state, {source, thumbnail, caption, type}) => {
|
||||
state.uiLightboxSources.push(source);
|
||||
state.uiLightboxThumbs.push(thumbnail);
|
||||
state.uiLightboxCaptions.push(caption);
|
||||
state.uiLightboxTypes.push(type);
|
||||
},
|
||||
setUiLightboxSlide: (state, val: number) => state.uiLightboxSlide = val,
|
||||
setUiLightboxSlide: (state, val) => state.uiLightboxSlide = val,
|
||||
|
||||
setUiLightboxSources: (state, val) => state.uiLightboxSources = val,
|
||||
setUiLightboxThumbs: (state, val) => state.uiLightboxThumbs = val,
|
||||
@@ -230,7 +228,7 @@ export default new Vuex.Store({
|
||||
store.commit("setOptLang", val.lang);
|
||||
}
|
||||
},
|
||||
loadFromArgs({commit}, route: Route) {
|
||||
loadFromArgs({commit}, route) {
|
||||
|
||||
if (route.query.q) {
|
||||
commit("setSearchText", route.query.q);
|
||||
@@ -265,11 +263,11 @@ export default new Vuex.Store({
|
||||
}
|
||||
|
||||
if (route.query.m) {
|
||||
commit("_setOnLoadSelectedMimeTypes", deserializeMimes(route.query.m as string));
|
||||
commit("_setOnLoadSelectedMimeTypes", deserializeMimes(route.query.m));
|
||||
}
|
||||
|
||||
if (route.query.t) {
|
||||
commit("_setOnLoadSelectedTags", (route.query.t as string).split(","));
|
||||
commit("_setOnLoadSelectedTags", (route.query.t).split(","));
|
||||
}
|
||||
|
||||
if (route.query.sort) {
|
||||
@@ -280,7 +278,7 @@ export default new Vuex.Store({
|
||||
commit("setSeed", Number(route.query.seed));
|
||||
}
|
||||
},
|
||||
async updateArgs({state}, router: VueRouter) {
|
||||
async updateArgs({state}, router) {
|
||||
|
||||
if (router.currentRoute.path !== "/") {
|
||||
return;
|
||||
@@ -290,14 +288,14 @@ export default new Vuex.Store({
|
||||
query: {
|
||||
q: state.searchText.trim() ? state.searchText.trim().replace(/\s+/g, " ") : undefined,
|
||||
fuzzy: state.fuzzy ? null : undefined,
|
||||
i: state.selectedIndices ? state.selectedIndices.map((idx: Index) => idx.idPrefix) : undefined,
|
||||
i: state.selectedIndices ? state.selectedIndices.map((idx) => idx.id.toString(16)) : undefined,
|
||||
dMin: state.dateMin,
|
||||
dMax: state.dateMax,
|
||||
sMin: state.sizeMin,
|
||||
sMax: state.sizeMax,
|
||||
path: state.pathText ? state.pathText : undefined,
|
||||
m: serializeMimes(state.selectedMimeTypes),
|
||||
t: state.selectedTags.length == 0 ? undefined : state.selectedTags.join(","),
|
||||
t: state.selectedTags.length === 0 ? undefined : state.selectedTags.join(","),
|
||||
sort: state.sortMode === "score" ? undefined : state.sortMode,
|
||||
seed: state.sortMode === "random" ? state.seed.toString() : undefined
|
||||
}
|
||||
@@ -306,11 +304,11 @@ export default new Vuex.Store({
|
||||
});
|
||||
},
|
||||
updateConfiguration({state}) {
|
||||
const conf = {} as any;
|
||||
const conf = {};
|
||||
|
||||
Object.keys(state).forEach((key) => {
|
||||
if (key.startsWith("opt")) {
|
||||
conf[key] = (state as any)[key];
|
||||
conf[key] = (state)[key];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -323,14 +321,14 @@ export default new Vuex.Store({
|
||||
if (confString) {
|
||||
const conf = JSON.parse(confString);
|
||||
|
||||
if (!("version" in conf) || conf["version"] != CONF_VERSION) {
|
||||
if (!("version" in conf) || conf["version"] !== CONF_VERSION) {
|
||||
localStorage.removeItem("sist2_configuration");
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
Object.keys(state).forEach((key) => {
|
||||
if (key.startsWith("opt")) {
|
||||
(state as any)[key] = conf[key];
|
||||
(state)[key] = conf[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -386,7 +384,7 @@ export default new Vuex.Store({
|
||||
indices: state => state.indices,
|
||||
sist2Info: state => state.sist2Info,
|
||||
indexMap: state => {
|
||||
const map = {} as any;
|
||||
const map = {};
|
||||
state.indices.forEach(idx => map[idx.id] = idx);
|
||||
return map;
|
||||
},
|
||||
@@ -405,12 +403,12 @@ export default new Vuex.Store({
|
||||
size: state => state.optSize,
|
||||
sortMode: state => state.sortMode,
|
||||
lastQueryResult: state => state.lastQueryResults,
|
||||
lastDoc: function (state): EsHit | null {
|
||||
lastDoc: function (state) {
|
||||
if (state.lastQueryResults == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (state.lastQueryResults as unknown as EsResult).hits.hits.slice(-1)[0];
|
||||
return (state.lastQueryResults).hits.hits.slice(-1)[0];
|
||||
},
|
||||
uiShowLightbox: state => state.uiShowLightbox,
|
||||
uiLightboxSources: state => state.uiLightboxSources,
|
||||
@@ -451,7 +449,7 @@ export default new Vuex.Store({
|
||||
optMlRepositories: state => state.optMlRepositories,
|
||||
mlRepositoryList: state => {
|
||||
const repos = state.optMlRepositories.split("\n")
|
||||
return repos[0] == "" ? [] : repos;
|
||||
return repos[0] === "" ? [] : repos;
|
||||
},
|
||||
optMlDefaultModel: state => state.optMlDefaultModel,
|
||||
optAutoAnalyze: state => state.optAutoAnalyze,
|
||||
@@ -1,139 +0,0 @@
|
||||
export function mergeTooltips(slider, threshold, separator, fixTooltips) {
|
||||
|
||||
const isMobile = window.innerWidth <= 650;
|
||||
if (isMobile) {
|
||||
threshold = 25;
|
||||
}
|
||||
|
||||
const textIsRtl = getComputedStyle(slider).direction === 'rtl';
|
||||
const isRtl = slider.noUiSlider.options.direction === 'rtl';
|
||||
const isVertical = slider.noUiSlider.options.orientation === 'vertical';
|
||||
const tooltips = slider.noUiSlider.getTooltips();
|
||||
const origins = slider.noUiSlider.getOrigins();
|
||||
|
||||
// Move tooltips into the origin element. The default stylesheet handles this.
|
||||
tooltips.forEach(function (tooltip, index) {
|
||||
if (tooltip) {
|
||||
origins[index].appendChild(tooltip);
|
||||
}
|
||||
});
|
||||
|
||||
slider.noUiSlider.on('update', function (values, handle, unencoded, tap, positions) {
|
||||
|
||||
const pools = [[]];
|
||||
const poolPositions = [[]];
|
||||
const poolValues = [[]];
|
||||
let atPool = 0;
|
||||
|
||||
// Assign the first tooltip to the first pool, if the tooltip is configured
|
||||
if (tooltips[0]) {
|
||||
pools[0][0] = 0;
|
||||
poolPositions[0][0] = positions[0];
|
||||
poolValues[0][0] = values[0];
|
||||
}
|
||||
|
||||
for (let i = 1; i < positions.length; i++) {
|
||||
if (!tooltips[i] || (positions[i] - positions[i - 1]) > threshold) {
|
||||
atPool++;
|
||||
pools[atPool] = [];
|
||||
poolValues[atPool] = [];
|
||||
poolPositions[atPool] = [];
|
||||
}
|
||||
|
||||
if (tooltips[i]) {
|
||||
pools[atPool].push(i);
|
||||
poolValues[atPool].push(values[i]);
|
||||
poolPositions[atPool].push(positions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
pools.forEach(function (pool, poolIndex) {
|
||||
const handlesInPool = pool.length;
|
||||
|
||||
for (let j = 0; j < handlesInPool; j++) {
|
||||
const handleNumber = pool[j];
|
||||
|
||||
if (j === handlesInPool - 1) {
|
||||
let offset = 0;
|
||||
|
||||
poolPositions[poolIndex].forEach(function (value) {
|
||||
offset += 1000 - 10 * value;
|
||||
});
|
||||
|
||||
const direction = isVertical ? 'bottom' : 'right';
|
||||
const last = isRtl ? 0 : handlesInPool - 1;
|
||||
const lastOffset = 1000 - 10 * poolPositions[poolIndex][last];
|
||||
offset = (textIsRtl && !isVertical ? 100 : 0) + (offset / handlesInPool) - lastOffset;
|
||||
|
||||
// Center this tooltip over the affected handles
|
||||
tooltips[handleNumber].innerHTML = poolValues[poolIndex].join(separator);
|
||||
tooltips[handleNumber].style.display = 'block';
|
||||
|
||||
tooltips[handleNumber].style[direction] = offset + '%';
|
||||
} else {
|
||||
// Hide this tooltip
|
||||
tooltips[handleNumber].style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (fixTooltips) {
|
||||
const isMobile = window.innerWidth <= 650;
|
||||
const len = isMobile ? 20 : 5;
|
||||
|
||||
if (positions[0] < len) {
|
||||
tooltips[0].style.right = `${(1 - ((positions[0]) / len)) * -35}px`
|
||||
} else {
|
||||
tooltips[0].style.right = "0"
|
||||
}
|
||||
|
||||
if (positions[1] > (100 - len)) {
|
||||
tooltips[1].style.right = `${((positions[1] - (100 - len)) / len) * 35}px`
|
||||
} else {
|
||||
tooltips[1].style.right = "0"
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function burrow(table, addSelfDir, rootName) {
|
||||
const root = {};
|
||||
table.forEach(row => {
|
||||
let layer = root;
|
||||
|
||||
row.taxonomy.forEach(key => {
|
||||
layer[key] = key in layer ? layer[key] : {};
|
||||
layer = layer[key];
|
||||
});
|
||||
if (Object.keys(layer).length === 0) {
|
||||
layer["$size$"] = row.size;
|
||||
} else if (addSelfDir) {
|
||||
layer["."] = {
|
||||
"$size$": row.size,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const descend = function (obj, depth) {
|
||||
return Object.keys(obj).filter(k => k !== "$size$").map(k => {
|
||||
const child = {
|
||||
name: k,
|
||||
depth: depth,
|
||||
value: 0,
|
||||
children: descend(obj[k], depth + 1)
|
||||
};
|
||||
if ("$size$" in obj[k]) {
|
||||
child.value = obj[k]["$size$"];
|
||||
}
|
||||
return child;
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
name: rootName,
|
||||
children: descend(root, 1),
|
||||
value: 0,
|
||||
depth: 0,
|
||||
}
|
||||
}
|
||||
329
sist2-vue/src/util.js
Normal file
329
sist2-vue/src/util.js
Normal file
@@ -0,0 +1,329 @@
|
||||
export function mergeTooltips(slider, threshold, separator, fixTooltips) {
|
||||
|
||||
const isMobile = window.innerWidth <= 650;
|
||||
if (isMobile) {
|
||||
threshold = 25;
|
||||
}
|
||||
|
||||
const textIsRtl = getComputedStyle(slider).direction === 'rtl';
|
||||
const isRtl = slider.noUiSlider.options.direction === 'rtl';
|
||||
const isVertical = slider.noUiSlider.options.orientation === 'vertical';
|
||||
const tooltips = slider.noUiSlider.getTooltips();
|
||||
const origins = slider.noUiSlider.getOrigins();
|
||||
|
||||
// Move tooltips into the origin element. The default stylesheet handles this.
|
||||
tooltips.forEach(function (tooltip, index) {
|
||||
if (tooltip) {
|
||||
origins[index].appendChild(tooltip);
|
||||
}
|
||||
});
|
||||
|
||||
slider.noUiSlider.on('update', function (values, handle, unencoded, tap, positions) {
|
||||
|
||||
const pools = [[]];
|
||||
const poolPositions = [[]];
|
||||
const poolValues = [[]];
|
||||
let atPool = 0;
|
||||
|
||||
// Assign the first tooltip to the first pool, if the tooltip is configured
|
||||
if (tooltips[0]) {
|
||||
pools[0][0] = 0;
|
||||
poolPositions[0][0] = positions[0];
|
||||
poolValues[0][0] = values[0];
|
||||
}
|
||||
|
||||
for (let i = 1; i < positions.length; i++) {
|
||||
if (!tooltips[i] || (positions[i] - positions[i - 1]) > threshold) {
|
||||
atPool++;
|
||||
pools[atPool] = [];
|
||||
poolValues[atPool] = [];
|
||||
poolPositions[atPool] = [];
|
||||
}
|
||||
|
||||
if (tooltips[i]) {
|
||||
pools[atPool].push(i);
|
||||
poolValues[atPool].push(values[i]);
|
||||
poolPositions[atPool].push(positions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
pools.forEach(function (pool, poolIndex) {
|
||||
const handlesInPool = pool.length;
|
||||
|
||||
for (let j = 0; j < handlesInPool; j++) {
|
||||
const handleNumber = pool[j];
|
||||
|
||||
if (j === handlesInPool - 1) {
|
||||
let offset = 0;
|
||||
|
||||
poolPositions[poolIndex].forEach(function (value) {
|
||||
offset += 1000 - 10 * value;
|
||||
});
|
||||
|
||||
const direction = isVertical ? 'bottom' : 'right';
|
||||
const last = isRtl ? 0 : handlesInPool - 1;
|
||||
const lastOffset = 1000 - 10 * poolPositions[poolIndex][last];
|
||||
offset = (textIsRtl && !isVertical ? 100 : 0) + (offset / handlesInPool) - lastOffset;
|
||||
|
||||
// Center this tooltip over the affected handles
|
||||
tooltips[handleNumber].innerHTML = poolValues[poolIndex].join(separator);
|
||||
tooltips[handleNumber].style.display = 'block';
|
||||
|
||||
tooltips[handleNumber].style[direction] = offset + '%';
|
||||
} else {
|
||||
// Hide this tooltip
|
||||
tooltips[handleNumber].style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (fixTooltips) {
|
||||
const isMobile = window.innerWidth <= 650;
|
||||
const len = isMobile ? 20 : 5;
|
||||
|
||||
if (positions[0] < len) {
|
||||
tooltips[0].style.right = `${(1 - ((positions[0]) / len)) * -35}px`
|
||||
} else {
|
||||
tooltips[0].style.right = "0"
|
||||
}
|
||||
|
||||
if (positions[1] > (100 - len)) {
|
||||
tooltips[1].style.right = `${((positions[1] - (100 - len)) / len) * 35}px`
|
||||
} else {
|
||||
tooltips[1].style.right = "0"
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function burrow(table, addSelfDir, rootName) {
|
||||
const root = {};
|
||||
table.forEach(row => {
|
||||
let layer = root;
|
||||
|
||||
row.taxonomy.forEach(key => {
|
||||
layer[key] = key in layer ? layer[key] : {};
|
||||
layer = layer[key];
|
||||
});
|
||||
if (Object.keys(layer).length === 0) {
|
||||
layer["$size$"] = row.size;
|
||||
} else if (addSelfDir) {
|
||||
layer["."] = {
|
||||
"$size$": row.size,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const descend = function (obj, depth) {
|
||||
return Object.keys(obj).filter(k => k !== "$size$").map(k => {
|
||||
const child = {
|
||||
name: k,
|
||||
depth: depth,
|
||||
value: 0,
|
||||
children: descend(obj[k], depth + 1)
|
||||
};
|
||||
if ("$size$" in obj[k]) {
|
||||
child.value = obj[k]["$size$"];
|
||||
}
|
||||
return child;
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
name: rootName,
|
||||
children: descend(root, 1),
|
||||
value: 0,
|
||||
depth: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function ext(hit) {
|
||||
return srcExt(hit._source)
|
||||
}
|
||||
|
||||
export function srcExt(src) {
|
||||
return Object.prototype.hasOwnProperty.call(src, "extension")
|
||||
&& src["extension"] !== "" ? "." + src["extension"] : "";
|
||||
}
|
||||
|
||||
export function strUnescape(str) {
|
||||
let result = "";
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const c = str[i];
|
||||
const next = str[i + 1];
|
||||
|
||||
if (c === "]") {
|
||||
if (next === "]") {
|
||||
result += c;
|
||||
i += 1;
|
||||
} else {
|
||||
result += String.fromCharCode(parseInt(str.slice(i, i + 2), 16));
|
||||
i += 2;
|
||||
}
|
||||
} else {
|
||||
result += c;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const thresh = 1000;
|
||||
const units = ["k", "M", "G", "T", "P", "E", "Z", "Y"];
|
||||
|
||||
export function humanFileSize(bytes) {
|
||||
if (bytes === 0) {
|
||||
return "0 B"
|
||||
}
|
||||
|
||||
if (Math.abs(bytes) < thresh) {
|
||||
return bytes + ' B';
|
||||
}
|
||||
let u = -1;
|
||||
do {
|
||||
bytes /= thresh;
|
||||
++u;
|
||||
} while (Math.abs(bytes) >= thresh && u < units.length - 1);
|
||||
|
||||
return bytes.toFixed(1) + units[u];
|
||||
}
|
||||
|
||||
export function humanTime(sec_num) {
|
||||
sec_num = Math.floor(sec_num);
|
||||
const hours = Math.floor(sec_num / 3600);
|
||||
const minutes = Math.floor((sec_num - (hours * 3600)) / 60);
|
||||
const seconds = sec_num - (hours * 3600) - (minutes * 60);
|
||||
|
||||
if (sec_num < 60) {
|
||||
return `${sec_num}s`
|
||||
}
|
||||
|
||||
if (sec_num < 3600) {
|
||||
return `${minutes < 10 ? "0" : ""}${minutes}:${seconds < 10 ? "0" : ""}${seconds}`;
|
||||
}
|
||||
|
||||
return `${hours < 10 ? "0" : ""}${hours}:${minutes < 10 ? "0" : ""}${minutes}:${seconds < 10 ? "0" : ""}${seconds}`;
|
||||
}
|
||||
|
||||
export function humanDate(numMilis) {
|
||||
const date = (new Date(numMilis * 1000));
|
||||
return date.getUTCFullYear() + "-" + ("0" + (date.getUTCMonth() + 1)).slice(-2) + "-" + ("0" + date.getUTCDate()).slice(-2)
|
||||
|
||||
}
|
||||
|
||||
export function lum(c) {
|
||||
c = c.substring(1);
|
||||
const rgb = parseInt(c, 16);
|
||||
const r = (rgb >> 16) & 0xff;
|
||||
const g = (rgb >> 8) & 0xff;
|
||||
const b = (rgb >> 0) & 0xff;
|
||||
|
||||
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
}
|
||||
|
||||
|
||||
export function getSelectedTreeNodes(tree) {
|
||||
const selectedNodes = new Set();
|
||||
|
||||
const selected = tree.selected();
|
||||
|
||||
for (let i = 0; i < selected.length; i++) {
|
||||
|
||||
if (selected[i].id === "any") {
|
||||
return ["any"]
|
||||
}
|
||||
|
||||
//Only get children
|
||||
if (selected[i].text.indexOf("(") !== -1) {
|
||||
if (selected[i].values) {
|
||||
selectedNodes.add(selected[i].values.slice(-1)[0]);
|
||||
} else {
|
||||
selectedNodes.add(selected[i].id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(selectedNodes);
|
||||
}
|
||||
|
||||
export function getTreeNodeAttributes(tree) {
|
||||
const nodes = tree.selectable();
|
||||
const attributes = {};
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
|
||||
let id = null;
|
||||
|
||||
if (nodes[i].text.indexOf("(") !== -1 && nodes[i].values) {
|
||||
id = nodes[i].values.slice(-1)[0];
|
||||
} else {
|
||||
id = nodes[i].id
|
||||
}
|
||||
|
||||
attributes[id] = {
|
||||
checked: nodes[i].itree.state.checked,
|
||||
collapsed: nodes[i].itree.state.collapsed,
|
||||
}
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
|
||||
export function serializeMimes(mimes) {
|
||||
if (mimes.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return mimes.map(mime => compressMime(mime)).join("");
|
||||
}
|
||||
|
||||
export function deserializeMimes(mimeString) {
|
||||
return mimeString
|
||||
.replaceAll(/([IVATUF])/g, "$$$&")
|
||||
.split("$")
|
||||
.map(mime => decompressMime(mime))
|
||||
.slice(1) // Ignore the first (empty) token
|
||||
}
|
||||
|
||||
export function compressMime(mime) {
|
||||
return mime
|
||||
.replace("image/", "I")
|
||||
.replace("video/", "V")
|
||||
.replace("application/", "A")
|
||||
.replace("text/", "T")
|
||||
.replace("audio/", "U")
|
||||
.replace("font/", "F")
|
||||
.replace("+", ",")
|
||||
.replace("x-", "X")
|
||||
}
|
||||
|
||||
export function decompressMime(mime) {
|
||||
return mime
|
||||
.replace("I", "image/")
|
||||
.replace("V", "video/")
|
||||
.replace("A", "application/")
|
||||
.replace("T", "text/")
|
||||
.replace("U", "audio/")
|
||||
.replace("F", "font/")
|
||||
.replace(",", "+")
|
||||
.replace("X", "x-")
|
||||
}
|
||||
|
||||
export function randomSeed() {
|
||||
return Math.round(Math.random() * 100000);
|
||||
}
|
||||
|
||||
export function sid(doc) {
|
||||
if (doc._id.includes(".")) {
|
||||
return doc._id
|
||||
}
|
||||
|
||||
const num = BigInt(doc._id);
|
||||
|
||||
const indexId = (num >> BigInt(32));
|
||||
const docId = num & BigInt(0xFFFFFFFF);
|
||||
|
||||
return indexId.toString(16).padStart(8, "0") + "." + docId.toString(16).padStart(8, "0");
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
import {EsHit} from "@/Sist2Api";
|
||||
|
||||
export function ext(hit: EsHit) {
|
||||
return srcExt(hit._source)
|
||||
}
|
||||
|
||||
export function srcExt(src) {
|
||||
return Object.prototype.hasOwnProperty.call(src, "extension")
|
||||
&& src["extension"] !== "" ? "." + src["extension"] : "";
|
||||
}
|
||||
|
||||
export function strUnescape(str: string): string {
|
||||
let result = "";
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const c = str[i];
|
||||
const next = str[i + 1];
|
||||
|
||||
if (c === "]") {
|
||||
if (next === "]") {
|
||||
result += c;
|
||||
i += 1;
|
||||
} else {
|
||||
result += String.fromCharCode(parseInt(str.slice(i, i + 2), 16));
|
||||
i += 2;
|
||||
}
|
||||
} else {
|
||||
result += c;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const thresh = 1000;
|
||||
const units = ["k", "M", "G", "T", "P", "E", "Z", "Y"];
|
||||
|
||||
export function humanFileSize(bytes: number): string {
|
||||
if (bytes === 0) {
|
||||
return "0 B"
|
||||
}
|
||||
|
||||
if (Math.abs(bytes) < thresh) {
|
||||
return bytes + ' B';
|
||||
}
|
||||
let u = -1;
|
||||
do {
|
||||
bytes /= thresh;
|
||||
++u;
|
||||
} while (Math.abs(bytes) >= thresh && u < units.length - 1);
|
||||
|
||||
return bytes.toFixed(1) + units[u];
|
||||
}
|
||||
|
||||
export function humanTime(sec_num: number): string {
|
||||
sec_num = Math.floor(sec_num);
|
||||
const hours = Math.floor(sec_num / 3600);
|
||||
const minutes = Math.floor((sec_num - (hours * 3600)) / 60);
|
||||
const seconds = sec_num - (hours * 3600) - (minutes * 60);
|
||||
|
||||
if (sec_num < 60) {
|
||||
return `${sec_num}s`
|
||||
}
|
||||
|
||||
if (sec_num < 3600) {
|
||||
return `${minutes < 10 ? "0" : ""}${minutes}:${seconds < 10 ? "0" : ""}${seconds}`;
|
||||
}
|
||||
|
||||
return `${hours < 10 ? "0" : ""}${hours}:${minutes < 10 ? "0" : ""}${minutes}:${seconds < 10 ? "0" : ""}${seconds}`;
|
||||
}
|
||||
|
||||
export function humanDate(numMilis: number): string {
|
||||
const date = (new Date(numMilis * 1000));
|
||||
return date.getUTCFullYear() + "-" + ("0" + (date.getUTCMonth() + 1)).slice(-2) + "-" + ("0" + date.getUTCDate()).slice(-2)
|
||||
|
||||
}
|
||||
|
||||
export function lum(c: string) {
|
||||
c = c.substring(1);
|
||||
const rgb = parseInt(c, 16);
|
||||
const r = (rgb >> 16) & 0xff;
|
||||
const g = (rgb >> 8) & 0xff;
|
||||
const b = (rgb >> 0) & 0xff;
|
||||
|
||||
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
}
|
||||
|
||||
|
||||
export function getSelectedTreeNodes(tree: any) {
|
||||
const selectedNodes = new Set();
|
||||
|
||||
const selected = tree.selected();
|
||||
|
||||
for (let i = 0; i < selected.length; i++) {
|
||||
|
||||
if (selected[i].id === "any") {
|
||||
return ["any"]
|
||||
}
|
||||
|
||||
//Only get children
|
||||
if (selected[i].text.indexOf("(") !== -1) {
|
||||
if (selected[i].values) {
|
||||
selectedNodes.add(selected[i].values.slice(-1)[0]);
|
||||
} else {
|
||||
selectedNodes.add(selected[i].id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(selectedNodes);
|
||||
}
|
||||
|
||||
export function getTreeNodeAttributes(tree: any) {
|
||||
const nodes = tree.selectable();
|
||||
const attributes = {};
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
|
||||
let id = null;
|
||||
|
||||
if (nodes[i].text.indexOf("(") !== -1 && nodes[i].values) {
|
||||
id = nodes[i].values.slice(-1)[0];
|
||||
} else {
|
||||
id = nodes[i].id
|
||||
}
|
||||
|
||||
attributes[id] = {
|
||||
checked: nodes[i].itree.state.checked,
|
||||
collapsed: nodes[i].itree.state.collapsed,
|
||||
}
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
|
||||
export function serializeMimes(mimes: string[]): string | undefined {
|
||||
if (mimes.length == 0) {
|
||||
return undefined;
|
||||
}
|
||||
return mimes.map(mime => compressMime(mime)).join("");
|
||||
}
|
||||
|
||||
export function deserializeMimes(mimeString: string): string[] {
|
||||
return mimeString
|
||||
.replaceAll(/([IVATUF])/g, "$$$&")
|
||||
.split("$")
|
||||
.map(mime => decompressMime(mime))
|
||||
.slice(1) // Ignore the first (empty) token
|
||||
}
|
||||
|
||||
export function compressMime(mime: string): string {
|
||||
return mime
|
||||
.replace("image/", "I")
|
||||
.replace("video/", "V")
|
||||
.replace("application/", "A")
|
||||
.replace("text/", "T")
|
||||
.replace("audio/", "U")
|
||||
.replace("font/", "F")
|
||||
.replace("+", ",")
|
||||
.replace("x-", "X")
|
||||
}
|
||||
|
||||
export function decompressMime(mime: string): string {
|
||||
return mime
|
||||
.replace("I", "image/")
|
||||
.replace("V", "video/")
|
||||
.replace("A", "application/")
|
||||
.replace("T", "text/")
|
||||
.replace("U", "audio/")
|
||||
.replace("F", "font/")
|
||||
.replace(",", "+")
|
||||
.replace("X", "x-")
|
||||
}
|
||||
|
||||
export function randomSeed(): number {
|
||||
return Math.round(Math.random() * 100000);
|
||||
}
|
||||
@@ -1,142 +1,142 @@
|
||||
<template>
|
||||
<div style="margin-left: auto; margin-right: auto;" class="container">
|
||||
<Preloader v-if="loading"></Preloader>
|
||||
<b-card v-else-if="!loading && found">
|
||||
<b-card-title :title="doc._source.name + ext(doc)">
|
||||
{{ doc._source.name + ext(doc) }}
|
||||
</b-card-title>
|
||||
<div style="margin-left: auto; margin-right: auto;" class="container">
|
||||
<Preloader v-if="loading"></Preloader>
|
||||
<b-card v-else-if="!loading && found">
|
||||
<b-card-title :title="doc._source.name + ext(doc)">
|
||||
{{ doc._source.name + ext(doc) }}
|
||||
</b-card-title>
|
||||
|
||||
<!-- Thumbnail-->
|
||||
<div style="position: relative; margin-left: auto; margin-right: auto; text-align: center">
|
||||
<FullThumbnail :doc="doc" :small-badge="false" @onThumbnailClick="onThumbnailClick()"></FullThumbnail>
|
||||
</div>
|
||||
<!-- Thumbnail-->
|
||||
<div style="position: relative; margin-left: auto; margin-right: auto; text-align: center">
|
||||
<FullThumbnail :doc="doc" :small-badge="false" @onThumbnailClick="onThumbnailClick()"></FullThumbnail>
|
||||
</div>
|
||||
|
||||
<!-- Audio player-->
|
||||
<audio v-if="doc._props.isAudio" ref="audio" preload="none" class="audio-fit fit" controls
|
||||
:type="doc._source.mime"
|
||||
:src="`f/${doc._source.index}/${doc._id}`"></audio>
|
||||
<!-- Audio player-->
|
||||
<audio v-if="doc._props.isAudio" ref="audio" preload="none" class="audio-fit fit" controls
|
||||
:type="doc._source.mime"
|
||||
:src="`f/${sid(doc)}`"></audio>
|
||||
|
||||
<InfoTable :doc="doc" v-if="doc"></InfoTable>
|
||||
<InfoTable :doc="doc" v-if="doc"></InfoTable>
|
||||
|
||||
<div v-if="doc._source.content" class="content-div">{{ doc._source.content }}</div>
|
||||
</b-card>
|
||||
<div v-else>
|
||||
<b-card>
|
||||
<b-card-title>{{ $t("filePage.notFound") }}</b-card-title>
|
||||
</b-card>
|
||||
<div v-if="doc._source.content" class="content-div">{{ doc._source.content }}</div>
|
||||
</b-card>
|
||||
<div v-else>
|
||||
<b-card>
|
||||
<b-card-title>{{ $t("filePage.notFound") }}</b-card-title>
|
||||
</b-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Preloader from "@/components/Preloader.vue";
|
||||
import InfoTable from "@/components/InfoTable.vue";
|
||||
import Sist2Api from "@/Sist2Api";
|
||||
import {ext} from "@/util";
|
||||
import {ext, sid} from "@/util";
|
||||
import Vue from "vue";
|
||||
import sist2 from "@/Sist2Api";
|
||||
import FullThumbnail from "@/components/FullThumbnail";
|
||||
|
||||
export default Vue.extend({
|
||||
name: "FilePage",
|
||||
components: {
|
||||
FullThumbnail,
|
||||
Preloader,
|
||||
InfoTable
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
found: false,
|
||||
doc: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
ext: ext,
|
||||
onThumbnailClick() {
|
||||
window.open(`/f/${this.doc.index}/${this.doc._id}`, "_blank");
|
||||
name: "FilePage",
|
||||
components: {
|
||||
FullThumbnail,
|
||||
Preloader,
|
||||
InfoTable
|
||||
},
|
||||
findByCustomField(field, id) {
|
||||
return {
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
match: {
|
||||
[field]: id
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
size: 1
|
||||
}
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
found: false,
|
||||
doc: null
|
||||
}
|
||||
},
|
||||
findById(id) {
|
||||
return {
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
match: {
|
||||
"_id": id
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
methods: {
|
||||
ext: ext,
|
||||
sid: sid,
|
||||
onThumbnailClick() {
|
||||
window.open(`/f/${sid(this.doc)}`, "_blank");
|
||||
},
|
||||
size: 1
|
||||
}
|
||||
},
|
||||
findByName(name) {
|
||||
return {
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
match: {
|
||||
"name": name
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
findByCustomField(field, id) {
|
||||
return {
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
match: {
|
||||
[field]: id
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
size: 1
|
||||
}
|
||||
},
|
||||
size: 1
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
mounted() {
|
||||
let query = null;
|
||||
if (this.$route.query.byId) {
|
||||
query = this.findById(this.$route.query.byId);
|
||||
} else if (this.$route.query.byName) {
|
||||
query = this.findByName(this.$route.query.byName);
|
||||
} else if (this.$route.query.by && this.$route.query.q) {
|
||||
query = this.findByCustomField(this.$route.query.by, this.$route.query.q)
|
||||
}
|
||||
|
||||
if (query) {
|
||||
Sist2Api.esQuery(query).then(result => {
|
||||
if (result.hits.hits.length === 0) {
|
||||
this.found = false;
|
||||
} else {
|
||||
this.doc = result.hits.hits[0];
|
||||
this.found = true;
|
||||
findById(id) {
|
||||
return {
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
match: {
|
||||
"_id": id
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
size: 1
|
||||
}
|
||||
},
|
||||
findByName(name) {
|
||||
return {
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
match: {
|
||||
"name": name
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
size: 1
|
||||
}
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
});
|
||||
} else {
|
||||
this.loading = false;
|
||||
this.found = false;
|
||||
},
|
||||
mounted() {
|
||||
let query = null;
|
||||
if (this.$route.query.byId) {
|
||||
query = this.findById(this.$route.query.byId);
|
||||
} else if (this.$route.query.byName) {
|
||||
query = this.findByName(this.$route.query.byName);
|
||||
} else if (this.$route.query.by && this.$route.query.q) {
|
||||
query = this.findByCustomField(this.$route.query.by, this.$route.query.q)
|
||||
}
|
||||
|
||||
if (query) {
|
||||
Sist2Api.esQuery(query).then(result => {
|
||||
if (result.hits.hits.length === 0) {
|
||||
this.found = false;
|
||||
} else {
|
||||
this.doc = result.hits.hits[0];
|
||||
this.found = true;
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
});
|
||||
} else {
|
||||
this.loading = false;
|
||||
this.found = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.img-wrapper {
|
||||
display: inline-block;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
@@ -60,6 +60,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {sid} from "@/util";
|
||||
import Preloader from "@/components/Preloader.vue";
|
||||
import {mapActions, mapGetters, mapMutations} from "vuex";
|
||||
import SearchBar from "@/components/SearchBar.vue";
|
||||
@@ -92,7 +93,6 @@ export default Vue.extend({
|
||||
SizeSlider, PathTree, ResultsCard, MimePicker, Lightbox, DocCardWall, IndexPicker, SearchBar, Preloader
|
||||
},
|
||||
data: () => ({
|
||||
loading: false,
|
||||
uiLoading: true,
|
||||
search: undefined,
|
||||
docs: [],
|
||||
@@ -105,6 +105,9 @@ export default Vue.extend({
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters(["indices", "optDisplay"]),
|
||||
hasEmbeddings() {
|
||||
return Sist2Api.models().length > 0;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// Handle touch events
|
||||
@@ -172,12 +175,6 @@ export default Vue.extend({
|
||||
setDateBoundsMax: "setDateBoundsMax",
|
||||
setTags: "setTags",
|
||||
}),
|
||||
hasEmbeddings() {
|
||||
if (!this.loading) {
|
||||
return false;
|
||||
}
|
||||
return Sist2Api.models().some();
|
||||
},
|
||||
showErrorToast() {
|
||||
this.$bvToast.toast(
|
||||
this.$t("toast.esConnErr"),
|
||||
@@ -248,9 +245,9 @@ export default Vue.extend({
|
||||
if (hit._props.isPlayableImage || hit._props.isPlayableVideo) {
|
||||
hit._seq = await this.$store.dispatch("getKeySequence");
|
||||
this.$store.commit("addLightboxSource", {
|
||||
source: `f/${hit._source.index}/${hit._id}`,
|
||||
thumbnail: hit._props.hasThumbnail
|
||||
? `t/${hit._source.index}/${hit._id}`
|
||||
source: `f/${sid(hit)}`,
|
||||
thumbnail_count: hit._props.hasThumbnail
|
||||
? `t/${sid(hit)}`
|
||||
: null,
|
||||
caption: {
|
||||
component: LightboxCaption,
|
||||
|
||||
Reference in New Issue
Block a user