Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 154 additions & 74 deletions app/components/FileUploader.vue
Original file line number Diff line number Diff line change
@@ -1,36 +1,59 @@
<script setup>
import { useGeodeStore } from "@ogw_front/stores/geode";
import { useTemplateRef } from "vue";

import CsvPreviewer from "@ogw_front/components/csv-preview/CsvPreviewer";
import DragAndDrop from "@ogw_front/components/DragAndDrop";

const emit = defineEmits(["files_uploaded", "decrement_step", "reset_values"]);

const {
multiple,
accept,
files,
auto_upload,
mini,
show_overlay: showOverlay,
} = defineProps({
multiple: { type: Boolean, required: true },
accept: { type: String, required: true },
files: { type: Array, required: false, default: [] },
auto_upload: { type: Boolean, required: false, default: false },
mini: { type: Boolean, required: false, default: false },
show_overlay: { type: Boolean, required: false, default: true },
const { multiple, accept, files, auto_upload, showOverlay, mini } = defineProps({
multiple: { type: Boolean, default: false },
accept: { type: String, default: "" },
files: { type: Array, default: () => [] },
auto_upload: { type: Boolean, default: true },
showOverlay: { type: Boolean, default: false },
mini: { type: Boolean, default: false },
});

const geodeStore = useGeodeStore();

const internal_files = ref(files);
const dragAndDropRef = useTemplateRef("dragAndDropRef");
const csv_dialog = ref(false);
const current_csv_file = ref(undefined);
const current_csv_index = ref(-1);
const loading = ref(false);
const files_uploaded = ref(false);
const dragAndDropRef = useTemplateRef("dragAndDropRef");

const toggle_loading = useToggle(loading);

function isCsv(file) {
return file.name.toLowerCase().endsWith(".csv");
}

function openCsvPreviewer(file, index) {
current_csv_file.value = file;
current_csv_index.value = index;
csv_dialog.value = true;
}

async function onCsvConfirm(result) {
const json_content = JSON.stringify(result, undefined, 2);
const base_name = current_csv_file.value.name.slice(
0,
current_csv_file.value.name.lastIndexOf("."),
);
const json_filename = `${base_name}.json`;

const blob = new Blob([json_content], { type: "application/json" });
const json_file = new File([blob], json_filename, {
type: "application/json",
});

current_csv_file.value.isConfigured = true;
await geodeStore.upload(json_file);
internal_files.value = [...internal_files.value];
csv_dialog.value = false;
}

function processSelectedFiles(selected_files) {
if (multiple) {
internal_files.value = [...internal_files.value, ...selected_files];
Expand All @@ -52,11 +75,32 @@ async function upload_files() {
const promise_array = internal_files.value.map((file) => geodeStore.upload(file));
await Promise.all(promise_array);
files_uploaded.value = true;
emit("files_uploaded", internal_files.value);

toggle_loading();
emit("files_uploaded", internal_files.value);
}

watch(
() => internal_files.value,
async (newFiles) => {
if (newFiles.length === 0) {
return;
}
const unconfiguredCsv = newFiles.find((file) => isCsv(file) && !file.isConfigured);

if (unconfiguredCsv) {
openCsvPreviewer(unconfiguredCsv, internal_files.value.indexOf(unconfiguredCsv));
return;
}

const allConfigured = newFiles.every((file) => !isCsv(file) || file.isConfigured);

if (auto_upload && allConfigured) {
await upload_files();
}
},
{ deep: true },
);

if (files.length > 0) {
Comment thread
SpliiT marked this conversation as resolved.
internal_files.value = files;
if (auto_upload) {
Expand All @@ -71,41 +115,34 @@ watch(
},
{ deep: true },
);

watch(internal_files, (value) => {
Comment thread
BotellaA marked this conversation as resolved.
files_uploaded.value = false;
if (auto_upload && value.length > 0) {
upload_files();
}
});
</script>

<template>
<DragAndDrop
v-if="!internal_files.length"
ref="dragAndDropRef"
:multiple
:accept
:loading
:show-extensions="false"
:inline="true"
:show-overlay="showOverlay"
:texts="{
idle: 'Select files',
drop: 'Drop files here',
loading: 'Loading...',
}"
@files-selected="processSelectedFiles"
/>

<template v-if="mini">
<v-btn
icon="mdi-plus"
variant="text"
color="primary"
:loading="loading"
class="mt-2"
@click="dragAndDropRef?.triggerFileDialog"
/>
<DragAndDrop
ref="dragAndDropRef"
class="d-none"
:multiple
:accept
@files-selected="processSelectedFiles"
/>
</template>
<DragAndDrop
v-else
ref="dragAndDropRef"
:multiple
:accept
:loading
:show-extensions="false"
:inline="false"
:inline="!internal_files.length"
:show-overlay="showOverlay"
@files-selected="processSelectedFiles"
/>
Expand All @@ -132,23 +169,50 @@ watch(internal_files, (value) => {
</v-sheet>

<v-sheet class="d-flex flex-wrap ga-2" color="transparent">
<v-chip
v-for="(file, index) in internal_files"
:key="index"
closable
size="default"
color="white"
variant="outlined"
class="font-weight-medium glass-ui border-opacity-10 px-4"
style="background: rgba(255, 255, 255, 0.05) !important"
@click:close="removeFile(index)"
>
<v-icon start size="18" color="primary">mdi-file-outline</v-icon>
<span class="text-white">{{ file.name }}</span>
<template #close>
<v-icon size="16" class="ml-2 opacity-60 hover-opacity-100">mdi-close-circle</v-icon>
</template>
</v-chip>
<template v-for="(file, index) in internal_files" :key="index">
<v-chip
closable
size="default"
color="white"
variant="outlined"
class="font-weight-medium glass-ui border-opacity-10 px-4"
style="background: rgba(255, 255, 255, 0.05) !important"
@click:close="removeFile(index)"
>
<v-icon start size="18" :color="isCsv(file) && file.isConfigured ? 'success' : 'primary'">
{{
isCsv(file)
? file.isConfigured
? "mdi-file-check"
: "mdi-file-table"
: "mdi-file-outline"
}}
</v-icon>
<span class="text-white">{{ file.displayName || file.name }}</span>

<v-tooltip v-if="isCsv(file)" text="Configure CSV" location="bottom">
<template #activator="{ props: tooltipProps }">
<v-btn
v-bind="tooltipProps"
icon="mdi-cog"
variant="flat"
:color="file.isConfigured ? 'success' : 'primary'"
:class="['ml-2', { 'pulse-animation': !file.isConfigured }]"
width="24"
height="24"
density="compact"
@click.stop="openCsvPreviewer(file, index)"
>
<v-icon size="14">mdi-cog</v-icon>
</v-btn>
</template>
</v-tooltip>

<template #close>
<v-icon size="16" class="ml-2 opacity-60 hover-opacity-100">mdi-close-circle</v-icon>
</template>
</v-chip>
</template>
</v-sheet>
</v-card-text>

Expand All @@ -164,24 +228,40 @@ watch(internal_files, (value) => {
@click="upload_files"
>
<v-icon start size="22">mdi-cloud-upload</v-icon>
Upload {{ internal_files.length }} file<span v-if="internal_files.length > 1">s</span>
Upload
{{ internal_files.length }}
file<span v-if="internal_files.length > 1">s</span>
</v-btn>
</v-card-actions>

<CsvPreviewer
v-if="current_csv_file"
v-model="csv_dialog"
:file="current_csv_file"
@confirm="onCsvConfirm"
/>
</template>

<style scoped>
.border-dashed {
border: 2px dashed rgba(255, 255, 255, 0.1) !important;
transition: all 0.3s ease;
.glass-ui {
background: rgba(255, 255, 255, 0.05) !important;
backdrop-filter: blur(10px);
}

.border-dashed:hover {
border-color: rgba(var(--v-theme-primary), 0.4) !important;
background: rgba(var(--v-theme-primary), 0.02) !important;
.hover-opacity-100:hover {
opacity: 1 !important;
}

.custom-upload-btn {
letter-spacing: 0.5px;
box-shadow: 0 4px 15px rgba(var(--v-theme-primary), 0.3);
.pulse-animation {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(var(--v-theme-primary), 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(var(--v-theme-primary), 0);
}
100% {
box-shadow: 0 0 0 0 rgba(var(--v-theme-primary), 0);
}
}
</style>
43 changes: 30 additions & 13 deletions app/components/MissingFilesSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ const schema = schemas.opengeodeweb_back.missing_files;

const emit = defineEmits(["update_values", "increment_step", "decrement_step"]);

const { multiple, geode_object_type, filenames } = defineProps({
const { multiple, geode_object_type, filenames, files } = defineProps({
multiple: { type: Boolean, required: true },
geode_object_type: { type: String, required: true },
filenames: { type: Array, required: true },
files: { type: Array, required: false, default: () => [] },
});

const accept = ref("");
Expand All @@ -34,12 +35,17 @@ async function missing_files() {
additional_files.value = [];
const geodeStore = useGeodeStore();

const promise_array = filenames.map(async (filename) => {
const response = await geodeStore.request(schema, {
geode_object_type,
filename,
});
return response;
const promise_array = filenames.map((filename) => {
const isCsvFile =
filename.toLowerCase().endsWith(".csv") || filename.toLowerCase().endsWith(".csv.json");
if (isCsvFile) {
return Promise.resolve({
has_missing_files: false,
mandatory_files: [],
additional_files: [],
});
}
return geodeStore.request(schema, { geode_object_type, filename });
});
const values = await Promise.all(promise_array);
for (const value of values) {
Expand All @@ -49,11 +55,19 @@ async function missing_files() {
mandatory_files.value = [...mandatory_files.value, ...value.mandatory_files];
additional_files.value = [...additional_files.value, ...value.additional_files];
}
if (has_missing_files.value) {
accept.value = [...mandatory_files.value, ...additional_files.value]
.map((filename) => `.${filename.split(".").pop()}`)
.join(",");
} else {
const unconfigured_csvs = files.filter(
(file) =>
(file.name.toLowerCase().endsWith(".csv") || file.name.toLowerCase().endsWith(".csv.json")) &&
!file.isConfigured,
);
if (unconfigured_csvs.length > 0) {
has_missing_files.value = true;
if (accept.value === "") {
accept.value = ".json";
}
}

if (!has_missing_files.value) {
emit("increment_step");
}
toggle_loading();
Expand Down Expand Up @@ -85,7 +99,10 @@ await missing_files();
</v-row>
<v-row>
<v-col cols="12">
<FileUploader v-bind="{ multiple, accept }" @files_uploaded="files_uploaded_event" />
<FileUploader
v-bind="{ multiple, accept, files, auto_upload: false }"
@files_uploaded="files_uploaded_event"
/>
</v-col>
</v-row>
<v-row>
Expand Down
10 changes: 9 additions & 1 deletion app/components/ObjectSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,20 @@ async function get_allowed_objects() {
const load_scores = allowed_objects_list.map((obj) => obj[key].is_loadable);
const priorities = allowed_objects_list
.map((obj) => obj[key].object_priority)
.filter((priority) => priority !== undefined && priority !== null);
.filter((priority) => priority !== undefined);
final_object[key] = { is_loadable: Math.min(...load_scores) };
if (priorities.length > 0) {
final_object[key].object_priority = Math.max(...priorities);
}
}
const isCsv = filenames.some(
(filename) =>
filename.toLowerCase().endsWith(".csv") || filename.toLowerCase().endsWith(".csv.json"),
);
if (isCsv && !final_object["PointSet3D"]) {
final_object["PointSet3D"] = { is_loadable: 1, object_priority: 100 };
}

allowed_objects.value = final_object;
const selected_object = select_geode_object(final_object);
if (selected_object) {
Expand Down
Loading
Loading