<commit_before>import WithRender from "./search-results.vue";
import Vue from "vue";
import { Component, Prop, Watch } from "vue-property-decorator";
import { FindOnMapResult, ID, LineCreate, MarkerCreate, SearchResult, Type } from "facilmap-types";
import "./search-results.scss";
import Icon from "../ui/icon/icon";
import { Client, InjectClient, InjectMapComponents, InjectMapContext } from "../../utils/decorators";
import context from "../context";
import SearchResultInfo from "../search-result-info/search-result-info";
import { MapComponents, MapContext } from "../leaflet-map/leaflet-map";
import { SelectedItem } from "../../utils/selection";
import { Point } from "geojson";
import { FileResult, FileResultObject } from "../../utils/files";
import { showErrorToast } from "../../utils/toasts";
import { lineStringToTrackPoints, mapSearchResultToType } from "./utils";
import { isFileResult, isLineResult, isMapResult, isMarkerResult } from "../../utils/search";
import { combineZoomDestinations, flyTo, getZoomDestinationForMapResult, getZoomDestinationForResults, getZoomDestinationForSearchResult } from "../../utils/zoom";
import { mapValues, pickBy, uniq } from "lodash";
import FormModal from "../ui/form-modal/form-modal";
import StringMap from "../../utils/string-map";

@WithRender
@Component({
	components: { FormModal, Icon, SearchResultInfo }
})
export default class SearchResults extends Vue {
	
	@InjectClient() client!: Client;
	@InjectMapContext() mapContext!: MapContext;
	@InjectMapComponents() mapComponents!: MapComponents;

	@Prop({ type: Array }) searchResults?: Array<SearchResult | FileResult>;
	@Prop({ type: Array }) mapResults?: FindOnMapResult[];
	@Prop({ type: Number, required: true }) layerId!: number;
	/** When clicking a search result, union zoom to it. Normal zoom is done when clicking the zoom button. */
	@Prop({ type: Boolean, default: false }) unionZoom!: boolean;
	/** When clicking or selecting a search result, zoom to it. */
	@Prop({ type: Boolean, default: false }) autoZoom!: boolean;
	@Prop({ type: Object, default: () => ({}) }) customTypes!: FileResultObject["types"];

	activeTab = 0;

	get isNarrow(): boolean {
		return context.isNarrow;
	}

	get showZoom(): boolean {
		return !this.autoZoom || this.unionZoom;
	}

	get openResult(): SearchResult | FileResult | undefined {
		if (this.activeResults.length == 1 && !isMapResult(this.activeResults[0]))
			return this.activeResults[0];
		else
			return undefined;
	}

	get activeResults(): Array<SearchResult | FileResult | FindOnMapResult> {
		return [
			...(this.searchResults || []).filter((result) => this.mapContext.selection.some((item) => item.type == "searchResult" && item.result === result)),
			...(this.mapResults || []).filter((result) => {
				if (result.kind == "marker")
					return this.mapContext.selection.some((item) => item.type == "marker" && item.id == result.id);
				else if (result.kind == "line")
					return this.mapContext.selection.some((item) => item.type == "line" && item.id == result.id);
				else
					return false;
			})
		];
	}

	get isAllSelected(): boolean {
		return !this.searchResults?.some((result) => !this.activeResults.includes(result));
	}

	get activeSearchResults(): Array<SearchResult | FileResult> {
		return this.activeResults.filter((result) => !isMapResult(result)) as any;
	}

	get activeMarkerSearchResults(): Array<SearchResult | FileResult> {
		return this.activeSearchResults.filter((result) => isMarkerResult(result)) as any;
	}

	get activeLineSearchResults(): Array<SearchResult | FileResult> {
		return this.activeSearchResults.filter((result) => isLineResult(result)) as any;
	}

	get markerTypes(): Array<Type> {
		return Object.values(this.client.types).filter((type) => type.type == "marker");
	}

	get lineTypes(): Array<Type> {
		return Object.values(this.client.types).filter((type) => type.type == "line");
	}

	get hasCustomTypes(): boolean {
		return Object.keys(this.customTypes).length > 0;
	}

	closeResult(): void {
		this.activeTab = 0;
	}

	@Watch("openResult")
	handleOpenResultChange(openResult: SearchResult | FileResult | undefined): void {
		if (!openResult && this.activeTab != 0)
			this.activeTab = 0;
	}

	handleClick(result: SearchResult | FileResult | FindOnMapResult, event: MouseEvent): void {
		const toggle = event.ctrlKey || event.shiftKey;
		this.selectResult(result, toggle);

		if (this.autoZoom)
			this.zoomToSelectedResults(this.unionZoom || toggle);
	}

	zoomToSelectedResults(unionZoom: boolean): void {
		let dest = getZoomDestinationForResults(this.activeResults);
		if (dest && unionZoom)
			dest = combineZoomDestinations([dest, { bounds: this.mapComponents.map.getBounds() }]);
		if (dest)
			flyTo(this.mapComponents.map, dest);
	}

	zoomToResult(result: SearchResult | FileResult | FindOnMapResult): void {
		const dest = isMapResult(result) ? getZoomDestinationForMapResult(result) : getZoomDestinationForSearchResult(result);
		if (dest)
			flyTo(this.mapComponents.map, dest);
	}

	handleOpen(result: SearchResult | FileResult | FindOnMapResult, event: MouseEvent): void {
		this.selectResult(result, false);

		setTimeout(async () => {
			if (isMapResult(result)) {
				if (result.kind == "marker" && !this.client.markers[result.id])
					await this.client.getMarker({ id: result.id });
				this.mapContext.$emit("fm-search-box-show-tab", "fm-marker-info-tab", false);
			} else
				this.activeTab = 1;
		}, 0);
	}

	selectResult(result: SearchResult | FileResult | FindOnMapResult, toggle: boolean): void {
		const item: SelectedItem = isMapResult(result) ? { type: result.kind, id: result.id } : { type: "searchResult", result, layerId: this.layerId };
		if (toggle)
			this.mapComponents.selectionHandler.toggleItem(item);
		else
			this.mapComponents.selectionHandler.setSelectedItems([item]);
	}

	toggleSelectAll(): void {
		if (!this.searchResults)
			return;

		if (this.isAllSelected)
			this.mapComponents.selectionHandler.setSelectedItems([]);
		else {
			this.mapComponents.selectionHandler.setSelectedItems(this.searchResults.map((result) => ({ type: "searchResult", result, layerId: this.layerId })));

			if (this.autoZoom)
				this.zoomToSelectedResults(true);
		}
	}

	async _addToMap(data: Array<{ result: SearchResult | FileResult; type: Type }>): Promise<boolean> {
		this.$bvToast.hide("fm-search-result-info-add-error");

		try {
			const selection: SelectedItem[] = [];

			for (const { result, type } of data) {
				const obj: Partial<MarkerCreate<StringMap> & LineCreate<StringMap>> = {
					name: result.short_name
				};

				if("fmProperties" in result && result.fmProperties) { // Import GeoJSON
					Object.assign(obj, result.fmProperties);
					delete obj.typeId;
				} else {
					obj.data = mapSearchResultToType(result, type)
				}

				if(type.type == "marker") {
					const marker = await this.client.addMarker({
						...obj,
						lat: result.lat ?? (result.geojson as Point).coordinates[1],
						lon: result.lon ?? (result.geojson as Point).coordinates[0],
						typeId: type.id
					});
<commit_msg>Fix error when importing objects<commit_after>import WithRender from "./search-results.vue";
import Vue from "vue";
import { Component, Prop, Watch } from "vue-property-decorator";
import { FindOnMapResult, ID, LineCreate, MarkerCreate, SearchResult, Type } from "facilmap-types";
import "./search-results.scss";
import Icon from "../ui/icon/icon";
import { Client, InjectClient, InjectMapComponents, InjectMapContext } from "../../utils/decorators";
import context from "../context";
import SearchResultInfo from "../search-result-info/search-result-info";
import { MapComponents, MapContext } from "../leaflet-map/leaflet-map";
import { SelectedItem } from "../../utils/selection";
import { Point } from "geojson";
import { FileResult, FileResultObject } from "../../utils/files";
import { showErrorToast } from "../../utils/toasts";
import { lineStringToTrackPoints, mapSearchResultToType } from "./utils";
import { isFileResult, isLineResult, isMapResult, isMarkerResult } from "../../utils/search";
import { combineZoomDestinations, flyTo, getZoomDestinationForMapResult, getZoomDestinationForResults, getZoomDestinationForSearchResult } from "../../utils/zoom";
import { mapValues, pickBy, uniq } from "lodash";
import FormModal from "../ui/form-modal/form-modal";
import StringMap from "../../utils/string-map";

@WithRender
@Component({
	components: { FormModal, Icon, SearchResultInfo }
})
export default class SearchResults extends Vue {
	
	@InjectClient() client!: Client;
	@InjectMapContext() mapContext!: MapContext;
	@InjectMapComponents() mapComponents!: MapComponents;

	@Prop({ type: Array }) searchResults?: Array<SearchResult | FileResult>;
	@Prop({ type: Array }) mapResults?: FindOnMapResult[];
	@Prop({ type: Number, required: true }) layerId!: number;
	/** When clicking a search result, union zoom to it. Normal zoom is done when clicking the zoom button. */
	@Prop({ type: Boolean, default: false }) unionZoom!: boolean;
	/** When clicking or selecting a search result, zoom to it. */
	@Prop({ type: Boolean, default: false }) autoZoom!: boolean;
	@Prop({ type: Object, default: () => ({}) }) customTypes!: FileResultObject["types"];

	activeTab = 0;

	get isNarrow(): boolean {
		return context.isNarrow;
	}

	get showZoom(): boolean {
		return !this.autoZoom || this.unionZoom;
	}

	get openResult(): SearchResult | FileResult | undefined {
		if (this.activeResults.length == 1 && !isMapResult(this.activeResults[0]))
			return this.activeResults[0];
		else
			return undefined;
	}

	get activeResults(): Array<SearchResult | FileResult | FindOnMapResult> {
		return [
			...(this.searchResults || []).filter((result) => this.mapContext.selection.some((item) => item.type == "searchResult" && item.result === result)),
			...(this.mapResults || []).filter((result) => {
				if (result.kind == "marker")
					return this.mapContext.selection.some((item) => item.type == "marker" && item.id == result.id);
				else if (result.kind == "line")
					return this.mapContext.selection.some((item) => item.type == "line" && item.id == result.id);
				else
					return false;
			})
		];
	}

	get isAllSelected(): boolean {
		return !this.searchResults?.some((result) => !this.activeResults.includes(result));
	}

	get activeSearchResults(): Array<SearchResult | FileResult> {
		return this.activeResults.filter((result) => !isMapResult(result)) as any;
	}

	get activeMarkerSearchResults(): Array<SearchResult | FileResult> {
		return this.activeSearchResults.filter((result) => isMarkerResult(result)) as any;
	}

	get activeLineSearchResults(): Array<SearchResult | FileResult> {
		return this.activeSearchResults.filter((result) => isLineResult(result)) as any;
	}

	get markerTypes(): Array<Type> {
		return Object.values(this.client.types).filter((type) => type.type == "marker");
	}

	get lineTypes(): Array<Type> {
		return Object.values(this.client.types).filter((type) => type.type == "line");
	}

	get hasCustomTypes(): boolean {
		return Object.keys(this.customTypes).length > 0;
	}

	closeResult(): void {
		this.activeTab = 0;
	}

	@Watch("openResult")
	handleOpenResultChange(openResult: SearchResult | FileResult | undefined): void {
		if (!openResult && this.activeTab != 0)
			this.activeTab = 0;
	}

	handleClick(result: SearchResult | FileResult | FindOnMapResult, event: MouseEvent): void {
		const toggle = event.ctrlKey || event.shiftKey;
		this.selectResult(result, toggle);

		if (this.autoZoom)
			this.zoomToSelectedResults(this.unionZoom || toggle);
	}

	zoomToSelectedResults(unionZoom: boolean): void {
		let dest = getZoomDestinationForResults(this.activeResults);
		if (dest && unionZoom)
			dest = combineZoomDestinations([dest, { bounds: this.mapComponents.map.getBounds() }]);
		if (dest)
			flyTo(this.mapComponents.map, dest);
	}

	zoomToResult(result: SearchResult | FileResult | FindOnMapResult): void {
		const dest = isMapResult(result) ? getZoomDestinationForMapResult(result) : getZoomDestinationForSearchResult(result);
		if (dest)
			flyTo(this.mapComponents.map, dest);
	}

	handleOpen(result: SearchResult | FileResult | FindOnMapResult, event: MouseEvent): void {
		this.selectResult(result, false);

		setTimeout(async () => {
			if (isMapResult(result)) {
				if (result.kind == "marker" && !this.client.markers[result.id])
					await this.client.getMarker({ id: result.id });
				this.mapContext.$emit("fm-search-box-show-tab", "fm-marker-info-tab", false);
			} else
				this.activeTab = 1;
		}, 0);
	}

	selectResult(result: SearchResult | FileResult | FindOnMapResult, toggle: boolean): void {
		const item: SelectedItem = isMapResult(result) ? { type: result.kind, id: result.id } : { type: "searchResult", result, layerId: this.layerId };
		if (toggle)
			this.mapComponents.selectionHandler.toggleItem(item);
		else
			this.mapComponents.selectionHandler.setSelectedItems([item]);
	}

	toggleSelectAll(): void {
		if (!this.searchResults)
			return;

		if (this.isAllSelected)
			this.mapComponents.selectionHandler.setSelectedItems([]);
		else {
			this.mapComponents.selectionHandler.setSelectedItems(this.searchResults.map((result) => ({ type: "searchResult", result, layerId: this.layerId })));

			if (this.autoZoom)
				this.zoomToSelectedResults(true);
		}
	}

	async _addToMap(data: Array<{ result: SearchResult | FileResult; type: Type }>): Promise<boolean> {
		this.$bvToast.hide("fm-search-result-info-add-error");

		try {
			const selection: SelectedItem[] = [];

			for (const { result, type } of data) {
				const obj: Partial<MarkerCreate<StringMap> & LineCreate<StringMap>> = {
					name: result.short_name
				};

				if("fmProperties" in result && result.fmProperties) { // Import GeoJSON
					Object.assign(obj, result.fmProperties);
					if (result.fmProperties.data)
						obj.data = new StringMap(result.fmProperties.data);
					delete obj.typeId;
				} else {
					obj.data = mapSearchResultToType(result, type)
				}

				if(type.type == "marker") {
					const marker = await this.client.addMarker({
						...obj,
						lat: result.lat ?? (result.geojson as Point).coordinates[1],
						lon: result.lon ?? (result.geojson as Point).coordinates[0],
						typeId: type.id
					});

{'fraction_non_alphanumeric': 0.15129177197416457, 'fraction_numerical': 0.0016849199663016006, 'mean_word_length': 4.5191786129407205, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 57, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4434450', 'n_tokens_mistral': 4703, 'n_tokens_neox': 4288, 'n_words': 1003}