implement import editing

This commit is contained in:
Cyberes 2024-09-09 17:53:09 -06:00
parent 2d5525ccce
commit 613210beaa
13 changed files with 248 additions and 46 deletions

View File

@ -1 +1,2 @@
Recall Recall
Centerline

View File

@ -1,3 +1,3 @@
Test Accounts: Test Accounts:
admin:hei8iWae admin1:hei8iWae

View File

@ -1,10 +1,11 @@
from django.urls import path from django.urls import path
from data.views.import_item import upload_item, fetch_import_queue, fetch_queued, delete_import_queue from data.views.import_item import upload_item, fetch_import_queue, fetch_queued, delete_import_queue, update_imported_item
urlpatterns = [ urlpatterns = [
path('item/import/upload/', upload_item, name='upload_file'), path('item/import/upload/', upload_item, name='upload_file'),
path('item/import/get/<int:id>', fetch_import_queue, name='fetch_import_queue'), path('item/import/get/<int:item_id>', fetch_import_queue, name='fetch_import_queue'),
path('item/import/get/mine', fetch_queued, name='fetch_queued'), path('item/import/get/mine', fetch_queued, name='fetch_queued'),
path('item/import/delete/<int:id>', delete_import_queue, name='delete_import_queue'), path('item/import/delete/<int:id>', delete_import_queue, name='delete_import_queue'),
path('item/import/update/<int:item_id>', update_imported_item),
] ]

View File

@ -6,10 +6,14 @@ from django import forms
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
from django.db import IntegrityError from django.db import IntegrityError
from django.http import HttpResponse, JsonResponse from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.http import require_http_methods
from data.models import ImportQueue from data.models import ImportQueue
from geo_lib.daemon.database.locking import DBLockManager from geo_lib.daemon.database.locking import DBLockManager
from geo_lib.daemon.workers.workers_lib.importer.kml import kmz_to_kml from geo_lib.daemon.workers.workers_lib.importer.kml import kmz_to_kml
from geo_lib.daemon.workers.workers_lib.importer.tagging import generate_auto_tags
from geo_lib.types.feature import GeoPoint, GeoLineString, GeoPolygon
from geo_lib.website.auth import login_required_401 from geo_lib.website.auth import login_required_401
@ -63,12 +67,12 @@ def upload_item(request):
@login_required_401 @login_required_401
def fetch_import_queue(request, id): def fetch_import_queue(request, item_id):
if id is None: if item_id is None:
return JsonResponse({'success': False, 'msg': 'ID not provided', 'code': 400}, status=400) return JsonResponse({'success': False, 'msg': 'ID not provided', 'code': 400}, status=400)
lock_manager = DBLockManager() lock_manager = DBLockManager()
try: try:
queue = ImportQueue.objects.get(id=id) queue = ImportQueue.objects.get(id=item_id)
if queue.user_id != request.user.id: if queue.user_id != request.user.id:
return JsonResponse({'success': False, 'msg': 'not authorized to view this item', 'code': 403}, status=400) return JsonResponse({'success': False, 'msg': 'not authorized to view this item', 'code': 403}, status=400)
if not lock_manager.is_locked('data_importqueue', queue.id) and (len(queue.geofeatures) or len(queue.log)): if not lock_manager.is_locked('data_importqueue', queue.id) and (len(queue.geofeatures) or len(queue.log)):
@ -80,7 +84,7 @@ def fetch_import_queue(request, id):
@login_required_401 @login_required_401
def fetch_queued(request): def fetch_queued(request):
user_items = ImportQueue.objects.filter(user=request.user).values('id', 'geofeatures', 'original_filename', 'raw_kml_hash', 'data', 'log', 'timestamp') user_items = ImportQueue.objects.exclude(geofeatures__len=0).filter(user=request.user).values('id', 'geofeatures', 'original_filename', 'raw_kml_hash', 'data', 'log', 'timestamp')
data = json.loads(json.dumps(list(user_items), cls=DjangoJSONEncoder)) data = json.loads(json.dumps(list(user_items), cls=DjangoJSONEncoder))
lock_manager = DBLockManager() lock_manager = DBLockManager()
for i, item in enumerate(data): for i, item in enumerate(data):
@ -103,6 +107,50 @@ def delete_import_queue(request, id):
return HttpResponse(status=405) return HttpResponse(status=405)
@login_required_401
@csrf_protect # TODO: put this on all routes
@require_http_methods(["PUT"])
def update_imported_item(request, item_id):
try:
queue = ImportQueue.objects.get(id=item_id)
except ImportQueue.DoesNotExist:
return JsonResponse({'success': False, 'msg': 'ID does not exist', 'code': 404}, status=400)
if queue.user_id != request.user.id:
return JsonResponse({'success': False, 'msg': 'not authorized to edit this item', 'code': 403}, status=403)
try:
data = json.loads(request.body)
if not isinstance(data, list):
raise ValueError('Invalid data format. Expected a list.')
except (json.JSONDecodeError, ValueError) as e:
return JsonResponse({'success': False, 'msg': str(e), 'code': 400}, status=400)
parsed_data = []
for feature in data:
match feature['type'].lower():
case 'point':
c = GeoPoint(**feature)
case 'linestring':
c = GeoLineString(**feature)
case 'polygon':
c = GeoPolygon(**feature)
case _:
continue
# Generate the tags after the user has made their changes.
c.properties.tags = generate_auto_tags(c)
parsed_data.append(json.loads(c.model_dump_json()))
# Erase the geofeatures column
queue.geofeatures = []
# Update the data column with the new data
queue.data = parsed_data
queue.save()
return JsonResponse({'success': True, 'msg': 'Item updated successfully'})
def _hash_kml(b: str): def _hash_kml(b: str):
if not isinstance(b, bytes): if not isinstance(b, bytes):
b = b.encode() b = b.encode()

View File

@ -10,7 +10,6 @@ from geo_lib.daemon.database.connection import CursorFromConnectionFromPool
from geo_lib.daemon.database.locking import DBLockManager from geo_lib.daemon.database.locking import DBLockManager
from geo_lib.daemon.workers.workers_lib.importer.kml import kml_to_geojson from geo_lib.daemon.workers.workers_lib.importer.kml import kml_to_geojson
from geo_lib.daemon.workers.workers_lib.importer.logging import create_import_log_msg from geo_lib.daemon.workers.workers_lib.importer.logging import create_import_log_msg
from geo_lib.daemon.workers.workers_lib.importer.tagging import generate_auto_tags
from geo_lib.logging.database import log_to_db, DatabaseLogLevel, DatabaseLogSource from geo_lib.logging.database import log_to_db, DatabaseLogLevel, DatabaseLogSource
from geo_lib.time import get_time_ms from geo_lib.time import get_time_ms
from geo_lib.types.feature import geojson_to_geofeature from geo_lib.types.feature import geojson_to_geofeature
@ -49,8 +48,6 @@ def import_worker():
messages.extend(kml_conv_messages) messages.extend(kml_conv_messages)
geofetures, typing_messages = geojson_to_geofeature(geojson_data) geofetures, typing_messages = geojson_to_geofeature(geojson_data)
messages.extend(typing_messages) messages.extend(typing_messages)
for feature in geofetures:
feature.tags = generate_auto_tags(feature)
success = True success = True
except Exception as e: except Exception as e:
err_name = e.__class__.__name__ err_name = e.__class__.__name__

View File

@ -10,6 +10,6 @@ def generate_auto_tags(feature: GeoFeatureSupported) -> List[str]:
] ]
now = datetime.now() now = datetime.now()
tags.append(f'year:{now.year}') tags.append(f'import-year:{now.year}')
tags.append(f'month:{now.strftime("%B")}') tags.append(f'import-month:{now.strftime("%B")}')
return [str(x) for x in tags] return [str(x) for x in tags]

View File

@ -0,0 +1,2 @@
SOFTWARE_NAME = 'geo-backend'
SOFTWARE_VERSION = '0.0.0'

View File

@ -1,9 +1,12 @@
from datetime import datetime
from enum import Enum from enum import Enum
from typing import Optional, List, Union, Tuple from typing import Optional, List, Union, Tuple
import pytz
from pydantic import Field, BaseModel from pydantic import Field, BaseModel
from geo_lib.daemon.workers.workers_lib.importer.logging import create_import_log_msg from geo_lib.daemon.workers.workers_lib.importer.logging import create_import_log_msg
from geo_lib.geo_backend import SOFTWARE_NAME, SOFTWARE_VERSION
class GeoFeatureType(str, Enum): class GeoFeatureType(str, Enum):
@ -12,6 +15,13 @@ class GeoFeatureType(str, Enum):
POLYGON = 'Polygon' POLYGON = 'Polygon'
class GeoFeatureProperties(BaseModel):
tags: List[str] = Field(default_factory=list)
created: datetime = datetime.utcnow().replace(tzinfo=pytz.utc)
software: str = Field(SOFTWARE_NAME, frozen=True)
software_version: str = Field(SOFTWARE_VERSION, frozen=True)
class GeoFeature(BaseModel): class GeoFeature(BaseModel):
""" """
A thing that's shown on the map. A thing that's shown on the map.
@ -21,8 +31,8 @@ class GeoFeature(BaseModel):
id: int # From the database id: int # From the database
type: GeoFeatureType type: GeoFeatureType
description: Optional[str] = None description: Optional[str] = None
tags: List[str] = Field(default_factory=list)
geometry: List geometry: List
properties: GeoFeatureProperties = Field(default_factory=GeoFeatureProperties)
class GeoPoint(GeoFeature): class GeoPoint(GeoFeature):

View File

@ -9,3 +9,4 @@ pydantic==2.7.3
sqlalchemy==2.0.30 sqlalchemy==2.0.30
redis==5.0.5 redis==5.0.5
async_timeout==4.0.3 async_timeout==4.0.3
pytz

View File

@ -11,8 +11,10 @@
"@types/geojson": "^7946.0.14", "@types/geojson": "^7946.0.14",
"axios": "^1.7.2", "axios": "^1.7.2",
"dropzone-vue": "^0.1.11", "dropzone-vue": "^0.1.11",
"flatpickr": "^4.6.13",
"geojson": "^0.5.0", "geojson": "^0.5.0",
"vue": "^3.4.21", "vue": "^3.4.21",
"vue-flatpickr-component": "^11.0.5",
"vue-router": "^4.3.2", "vue-router": "^4.3.2",
"vuex": "^4.1.0" "vuex": "^4.1.0"
}, },
@ -943,9 +945,10 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.7.2", "version": "1.7.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
"integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
"license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.6", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.0",
@ -1330,6 +1333,12 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/flatpickr": {
"version": "4.6.13",
"resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.13.tgz",
"integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==",
"license": "MIT"
},
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.6", "version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
@ -1606,10 +1615,11 @@
} }
}, },
"node_modules/micromatch": { "node_modules/micromatch": {
"version": "4.0.7", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"braces": "^3.0.3", "braces": "^3.0.3",
"picomatch": "^2.3.1" "picomatch": "^2.3.1"
@ -2434,6 +2444,21 @@
} }
} }
}, },
"node_modules/vue-flatpickr-component": {
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/vue-flatpickr-component/-/vue-flatpickr-component-11.0.5.tgz",
"integrity": "sha512-Vfwg5uVU+sanKkkLzUGC5BUlWd5wlqAMq/UpQ6lI2BCZq0DDrXhOMX7hrevt8bEgglIq2QUv0K2Nl84Me/VnlA==",
"license": "MIT",
"dependencies": {
"flatpickr": "^4.6.13"
},
"engines": {
"node": ">=14.13.0"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/vue-router": { "node_modules/vue-router": {
"version": "4.3.2", "version": "4.3.2",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.2.tgz", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.2.tgz",

View File

@ -12,8 +12,10 @@
"@types/geojson": "^7946.0.14", "@types/geojson": "^7946.0.14",
"axios": "^1.7.2", "axios": "^1.7.2",
"dropzone-vue": "^0.1.11", "dropzone-vue": "^0.1.11",
"flatpickr": "^4.6.13",
"geojson": "^0.5.0", "geojson": "^0.5.0",
"vue": "^3.4.21", "vue": "^3.4.21",
"vue-flatpickr-component": "^11.0.5",
"vue-router": "^4.3.2", "vue-router": "^4.3.2",
"vuex": "^4.1.0" "vuex": "^4.1.0"
}, },

View File

@ -4,13 +4,20 @@ enum GeoFeatureType {
POLYGON = 'Polygon' POLYGON = 'Polygon'
} }
interface GeoFeatureProperties {
created: Date;
software: string;
software_version: string;
tags: string[];
}
interface GeoFeatureProps { interface GeoFeatureProps {
name: string; name: string;
id: number; id: number;
type: GeoFeatureType; type: GeoFeatureType;
description?: string; description?: string;
tags?: string[];
geometry: any[]; geometry: any[];
properties: GeoFeatureProperties;
} }
class GeoFeature { class GeoFeature {
@ -20,40 +27,29 @@ class GeoFeature {
description?: string; description?: string;
tags: string[] = []; tags: string[] = [];
geometry: any[]; geometry: any[];
properties: GeoFeatureProperties;
constructor(props: GeoFeatureProps) { constructor(props: GeoFeatureProps) {
this.name = props.name; this.name = props.name;
this.id = props.id; this.id = props.id;
this.type = props.type; this.type = props.type;
this.description = props.description; this.description = props.description;
this.tags = props.tags || [];
this.geometry = props.geometry || []; this.geometry = props.geometry || [];
this.properties = props.properties;
} }
} }
export class GeoPoint extends GeoFeature { export class GeoPoint extends GeoFeature {
type: GeoFeatureType = GeoFeatureType.POINT; type: GeoFeatureType = GeoFeatureType.POINT;
geometry: number[]; geometry: number[];
constructor(props: GeoFeatureProps) {
super({...props, type: GeoFeatureType.POINT});
}
} }
export class GeoLineString extends GeoFeature { export class GeoLineString extends GeoFeature {
type: GeoFeatureType = GeoFeatureType.LINESTRING; type: GeoFeatureType = GeoFeatureType.LINESTRING;
geometry: number[][]; geometry: number[][];
constructor(props: GeoFeatureProps) {
super({...props, type: GeoFeatureType.LINESTRING});
}
} }
export class GeoPolygon extends GeoFeature { export class GeoPolygon extends GeoFeature {
type: GeoFeatureType = GeoFeatureType.POLYGON; type: GeoFeatureType = GeoFeatureType.POLYGON;
geometry: number[][][]; geometry: number[][][];
constructor(props: GeoFeatureProps) {
super({...props, type: GeoFeatureType.POLYGON});
}
} }

View File

@ -11,14 +11,71 @@
<p class="font-bold">{{ item }}</p> <p class="font-bold">{{ item }}</p>
</li> </li>
</div> </div>
<div> <div>
<li v-for="(item, index) in itemsForUser" :key="`item-${index}`"> <ul class="space-y-4">
<h2>{{ item.name }}</h2> <li v-for="(item, index) in itemsForUser" :key="`item-${index}`" class="bg-white shadow-md rounded-md p-4">
<pre> <div class="mb-4">
{{ item }} <label class="block text-gray-700 font-bold mb-2">Name:</label>
</pre> <div class="flex items-center">
</li> <input v-model="item.name" :placeholder="originalItems[index].name"
class="border border-gray-300 rounded-md px-3 py-2 w-full"/>
<button class="ml-2 bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold py-2 px-4 rounded"
@click="resetField(index, 'name')">Reset
</button>
</div> </div>
</div>
<div class="mb-4">
<label class="block text-gray-700 font-bold mb-2">Description:</label>
<div class="flex items-center">
<input v-model="item.description" :placeholder="originalItems[index].description"
class="border border-gray-300 rounded-md px-3 py-2 w-full"/>
<button class="ml-2 bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold py-2 px-4 rounded"
@click="resetField(index, 'description')">Reset
</button>
</div>
</div>
<div class="">
<label class="block text-gray-700 font-bold mb-2">Created:</label>
<div class="flex items-center">
<flat-pickr :config="flatpickrConfig" :value="item.properties.created"
class="border border-gray-300 rounded-md px-3 py-2 w-full"
@on-change="updateDate(index, $event)"></flat-pickr>
<button class="ml-2 bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold py-2 px-4 rounded"
@click="resetNestedField(index, 'properties', 'created')">Reset
</button>
</div>
<div>
<label class="block text-gray-700 font-bold mb-2">Tags:</label>
<div v-for="(tag, tagIndex) in item.properties.tags" :key="`tag-${tagIndex}`" class="mb-2">
<div class="flex items-center">
<input v-model="item.properties.tags[tagIndex]" :placeholder="getTagPlaceholder(index, tag)"
class="border rounded-md px-3 py-2 w-full bg-white"/>
<button class="ml-2 bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded"
@click="removeTag(index, tagIndex)">Remove
</button>
</div>
</div>
</div>
<div class="flex items-center mt-2">
<button :class="{ 'opacity-50 cursor-not-allowed': isLastTagEmpty(index) }"
:disabled="isLastTagEmpty(index)"
class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded"
@click="addTag(index)">Add Tag
</button>
<button class="ml-2 bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold py-2 px-4 rounded"
@click="resetTags(index)">Reset Tags
</button>
</div>
</div>
</li>
</ul>
</div>
<button class="m-2 bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded"
@click="saveChanges">Save
</button>
<div class="hidden"> <div class="hidden">
<!-- Load the queue to populate it. --> <!-- Load the queue to populate it. -->
@ -34,6 +91,9 @@ import {capitalizeFirstLetter} from "@/assets/js/string.js";
import Importqueue from "@/components/import/parts/importqueue.vue"; import Importqueue from "@/components/import/parts/importqueue.vue";
import {GeoFeatureTypeStrings} from "@/assets/js/types/geofeature-strings"; import {GeoFeatureTypeStrings} from "@/assets/js/types/geofeature-strings";
import {GeoPoint, GeoLineString, GeoPolygon} from "@/assets/js/types/geofeature-types"; import {GeoPoint, GeoLineString, GeoPolygon} from "@/assets/js/types/geofeature-types";
import {getCookie} from "@/assets/js/auth.js";
import flatPickr from 'vue-flatpickr-component';
import 'flatpickr/dist/flatpickr.css';
// TODO: for each feature, query the DB and check if there is a duplicate. For points that's duplicate coords, for linestrings and polygons that's duplicate points // TODO: for each feature, query the DB and check if there is a duplicate. For points that's duplicate coords, for linestrings and polygons that's duplicate points
// TODO: auto-refresh if still processing // TODO: auto-refresh if still processing
@ -42,13 +102,20 @@ export default {
computed: { computed: {
...mapState(["userInfo"]), ...mapState(["userInfo"]),
}, },
components: {Importqueue}, components: {Importqueue, flatPickr},
data() { data() {
return { return {
msg: "", msg: "",
currentId: null, currentId: null,
itemsForUser: [], itemsForUser: [],
workerLog: [] originalItems: [],
workerLog: [],
flatpickrConfig: {
enableTime: true,
time_24hr: true,
dateFormat: 'Y-m-d H:i',
timezone: 'UTC',
},
} }
}, },
mixins: [authMixin], mixins: [authMixin],
@ -69,14 +136,63 @@ export default {
default: default:
throw new Error(`Invalid feature type: ${item.type}`); throw new Error(`Invalid feature type: ${item.type}`);
} }
},
resetField(index, fieldName) {
this.itemsForUser[index][fieldName] = this.originalItems[index][fieldName];
},
resetNestedField(index, nestedField, fieldName) {
this.itemsForUser[index][nestedField][fieldName] = this.originalItems[index][nestedField][fieldName];
},
addTag(index) {
if (!this.isLastTagEmpty(index)) {
this.itemsForUser[index].tags.push('');
} }
}, },
getTagPlaceholder(index, tag) {
const originalTagIndex = this.originalItems[index].tags.indexOf(tag);
return originalTagIndex !== -1 ? this.originalItems[index].tags[originalTagIndex] : '';
},
isLastTagEmpty(index) {
const tags = this.itemsForUser[index].tags;
return tags.length > 0 && tags[tags.length - 1].trim().length === 0;
},
resetTags(index) {
this.itemsForUser[index].tags = [...this.originalItems[index].tags];
},
removeTag(index, tagIndex) {
this.itemsForUser[index].tags.splice(tagIndex, 1);
},
updateDate(index, selectedDates) {
this.itemsForUser[index].properties.created = selectedDates[0];
},
saveChanges() {
const csrftoken = getCookie('csrftoken');
axios.put('/api/data/item/import/update/' + this.id, this.itemsForUser, {
headers: {
'X-CSRFToken': csrftoken
}
}).then(response => {
if (response.data.success) {
this.msg = 'Changes saved successfully';
window.alert(this.msg);
} else {
this.msg = 'Error saving changes: ' + response.data.msg;
window.alert(this.msg);
}
}).catch(error => {
this.msg = 'Error saving changes: ' + error.message;
window.alert(this.msg);
});
},
}
,
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
next(async vm => { next(async vm => {
if (vm.currentId !== vm.id) { if (vm.currentId !== vm.id) {
vm.msg = "" vm.msg = ""
vm.messages = [] vm.messages = []
vm.itemsForUser = [] vm.itemsForUser = []
vm.originalItems = []
vm.currentId = null vm.currentId = null
axios.get('/api/data/item/import/get/' + vm.id).then(response => { axios.get('/api/data/item/import/get/' + vm.id).then(response => {
if (!response.data.success) { if (!response.data.success) {
@ -87,6 +203,7 @@ export default {
response.data.geofeatures.forEach((item) => { response.data.geofeatures.forEach((item) => {
vm.itemsForUser.push(vm.parseGeoJson(item)) vm.itemsForUser.push(vm.parseGeoJson(item))
}) })
vm.originalItems = JSON.parse(JSON.stringify(vm.itemsForUser))
} }
vm.msg = response.data.msg vm.msg = response.data.msg
vm.workerLog = response.data.log vm.workerLog = response.data.log
@ -96,8 +213,10 @@ export default {
}) })
} }
}) })
}, }
}; ,
}
</script> </script>
<style scoped> <style scoped>