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
2 changes: 1 addition & 1 deletion .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
"max-lines": [
"error",
{
"max": 350,
"max": 200,
"skipBlankLines": true,
"skipComments": true
}
Expand Down
98 changes: 16 additions & 82 deletions app/components/ClippingPlanes.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
<script setup lang="ts">
import { DEBOUNCE_DELAY, DEFAULT_NORMALS } from "@ogw_front/utils/clipping_planes";
import {
DEFAULT_SLICE_AXIS,
SLICE_AXES,
type SliceAxis,
areAllGrids,
} from "@ogw_front/utils/slice";
import type { ClippingPlane } from "@ogw_front/composables/clipping_planes_widget_entries";
import ClippingPlaneCard from "@ogw_front/components/ClippingPlaneCard.vue";
import SliceCard from "@ogw_front/components/SliceCard.vue";
import ToolPanel from "@ogw_front/components/ToolPanel.vue";
import { useClippingPlanesWidget } from "@ogw_front/composables/clipping_planes_widget";
import { useClippingSlices } from "@ogw_front/composables/clipping_slices";
import { useDataStore } from "@ogw_front/stores/data";
import { useDebounceFn } from "@vueuse/core";
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
Expand All @@ -33,19 +28,12 @@ function newItemId(): number {
}
const planes = ref<ClippingPlane[]>([{ id: newItemId(), origin: undefined, normal: [1, 0, 0] }]);
const allItems = dataStore.refRegisteredItems();
const sliceEnabled = ref<boolean>(false);
const slices = ref<{ id: number; axis: SliceAxis; index: number }[]>([
{ id: newItemId(), axis: DEFAULT_SLICE_AXIS, index: 0 },
]);
const sliceMaxIndices = ref<[number, number, number]>([0, 0, 0]);
const targetIds = computed<string[]>(() => {
if (targetAllVisible.value) {
return allItems.value.map((item) => item.id);
}
return selectedDatasetIds.value;
});
const isAllGrid = computed<boolean>(() => areAllGrids(allItems.value, targetIds.value));
const isSliceActive = computed<boolean>(() => isAllGrid.value && sliceEnabled.value);
const availableDatasets = computed<{ title: string; value: string }[]>(() =>
allItems.value.map((item) => ({
title: item.name || item.id,
Expand All @@ -54,8 +42,6 @@ const availableDatasets = computed<{ title: string; value: string }[]>(() =>
);
const widgetContainer = useTemplateRef("widgetContainer");
let debouncedApply: ((...args: unknown[]) => void) | undefined = undefined;
let areSlicesApplied = false;
let pendingSlices: Promise<void> = Promise.resolve();

const {
getSceneCenter,
Expand All @@ -79,6 +65,19 @@ function getUntargetedIds(): string[] {
return allItems.value.map((item) => item.id).filter((id) => !targetIds.value.includes(id));
}

const {
sliceEnabled,
slices,
sliceMaxIndices,
isAllGrid,
isSliceActive,
applySlices,
addSlice,
removeSlice,
resetSlices,
removeSlices,
} = useClippingSlices({ allItems, targetIds, getUntargetedIds, newItemId, hybridViewerStore });

async function applyClippingPlanes(): Promise<void> {
if (allItems.value.length === 0) {
return;
Expand All @@ -100,52 +99,6 @@ async function applyClippingPlanes(): Promise<void> {
}
}

function getSlicesPayload(isActive: boolean): { axis: SliceAxis; index: number }[] {
if (!isActive) {
return [];
}
return slices.value.map(({ axis, index }) => ({ axis, index }));
}

async function sendSlices(): Promise<void> {
if (!isAllGrid.value && !areSlicesApplied) {
return;
}
const isActive = isSliceActive.value;
const untargetedIds = getUntargetedIds();
if (targetIds.value.length > 0) {
const maxIndices = await hybridViewerStore.setSlice(
targetIds.value,
getSlicesPayload(isActive),
);
if (isAllGrid.value) {
sliceMaxIndices.value = maxIndices;
}
if (isActive) {
for (const slice of slices.value) {
slice.index = Math.min(slice.index, maxIndices[slice.axis]);
}
}
}
if (areSlicesApplied && untargetedIds.length > 0) {
await hybridViewerStore.setSlice(untargetedIds, []);
}
areSlicesApplied = isActive;
}

function queueSliceTask(task: () => Promise<void>): Promise<void> {
const previous = pendingSlices;
pendingSlices = (async (): Promise<void> => {
await Promise.allSettled([previous]);
await task();
})();
return pendingSlices;
}

function applySlices(): Promise<void> {
return queueSliceTask(sendSlices);
}

async function applyAll(): Promise<void> {
await applyClippingPlanes();
await applySlices();
Expand All @@ -166,19 +119,6 @@ function removePlane(index: number): void {
planes.value.splice(index, 1);
}

function addSlice(): void {
const lastSlice = slices.value.at(-1);
slices.value.push({
id: newItemId(),
axis: lastSlice ? SLICE_AXES[lastSlice.axis].next : DEFAULT_SLICE_AXIS,
index: 0,
});
}

function removeSlice(index: number): void {
slices.value.splice(index, 1);
}

function flipNormal(plane: { normal: number[] }): void {
plane.normal = plane.normal.map((component) => -component);
syncWidgets();
Expand All @@ -188,8 +128,7 @@ function flipNormal(plane: { normal: number[] }): void {
async function resetClippingPlanes(): Promise<void> {
setFromWidget(true);
planes.value = [{ id: newItemId(), origin: undefined, normal: [1, 0, 0] }];
sliceEnabled.value = false;
slices.value = [{ id: newItemId(), axis: DEFAULT_SLICE_AXIS, index: 0 }];
resetSlices();
updateWidgetPlacement({ isReset: true });
setFromWidget(false);
await applyAll();
Expand All @@ -198,12 +137,7 @@ async function resetClippingPlanes(): Promise<void> {
async function removeClippingPlanes(): Promise<void> {
const allIds = allItems.value.map((item) => item.id);
await hybridViewerStore.setClippingPlanes(allIds, []);
await queueSliceTask(async () => {
if (areSlicesApplied) {
await hybridViewerStore.setSlice(allIds, []);
areSlicesApplied = false;
}
});
await removeSlices(allIds);
}

watch(widgetContainer, (container) => {
Expand Down
75 changes: 1 addition & 74 deletions app/components/DragAndDrop.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { filesFromEntry, isFileAccepted } from "@ogw_front/utils/drag_and_drop";
import DragAndDropInline from "./DragAndDropInternal/DragAndDropInline.vue";
import DragAndDropOverlay from "./DragAndDropInternal/DragAndDropOverlay.vue";
import type { UploadFile } from "@ogw_front/utils/upload_path";
Expand Down Expand Up @@ -56,59 +57,13 @@ const isInternalDrag = ref(false);
const dragCounter = ref(0);
const fileInput = ref<HTMLInputElement | undefined>(undefined);

const WILDCARD_SUFFIX_LENGTH = 2;

function getErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message;
}
return String(error);
}

function isFileAccepted(file: File, acceptValue: string | string[] | undefined): boolean {
const fileName = (file.name || "").toLowerCase();
const fileType = (file.type || "").toLowerCase();
const isVext = fileName.endsWith(".vext");

if (!acceptValue) {
return !isVext;
}
let rules: string[] = [];
if (Array.isArray(acceptValue)) {
rules = acceptValue;
} else if (typeof acceptValue === "string") {
rules = acceptValue.split(",");
}
rules = rules.map((rule) => String(rule).trim()).filter(Boolean);
if (rules.length === 0) {
return !isVext;
}

return rules.some((rule) => {
let cleanRule = rule.toLowerCase();
if (!cleanRule) {
return !isVext;
}
if (cleanRule === "*" || cleanRule === "*.*") {
return !isVext;
}
if (cleanRule.startsWith("*.")) {
cleanRule = cleanRule.slice(1);
}
if (cleanRule.startsWith(".")) {
return fileName.endsWith(cleanRule);
}
if (cleanRule.includes("/")) {
if (cleanRule.endsWith("/*")) {
const typeGroup = cleanRule.slice(0, -WILDCARD_SUFFIX_LENGTH);
return fileType.startsWith(typeGroup);
}
return fileType === cleanRule;
}
return fileName.endsWith(`.${cleanRule}`);
});
}

function triggerFileDialog(): void {
fileInput.value?.click();
}
Expand All @@ -134,34 +89,6 @@ function onDragOver(event: DragEvent): void {
}
}

async function readAllEntries(reader: FileSystemDirectoryReader): Promise<FileSystemEntry[]> {
// oxlint-disable-next-line promise/avoid-new
const batch = await new Promise<FileSystemEntry[]>((resolve, reject) => {
reader.readEntries(resolve, reject);
});
if (batch.length === 0) {
return [];
}
return [...batch, ...(await readAllEntries(reader))];
}

async function filesFromEntry(entry: FileSystemEntry, path: string): Promise<UploadFile[]> {
if (entry.isFile) {
// oxlint-disable-next-line promise/avoid-new
const file = await new Promise<File>((resolve, reject) => {
// oxlint-disable-next-line no-unsafe-type-assertion -- isFile guarantees a FileSystemFileEntry
(entry as FileSystemFileEntry).file(resolve, reject);
});
return [Object.assign(file, { relativePath: path })];
}
// oxlint-disable-next-line no-unsafe-type-assertion -- not a file, so a FileSystemDirectoryEntry
const children = await readAllEntries((entry as FileSystemDirectoryEntry).createReader());
const nested = await Promise.all(
children.map((child) => filesFromEntry(child, `${path}/${child.name}`)),
);
return nested.flat();
}

async function emitDroppedEntries(entries: FileSystemEntry[]): Promise<void> {
try {
const groups = await Promise.all(
Expand Down
56 changes: 2 additions & 54 deletions app/components/ThresholdFilter.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
<script setup lang="ts">
import {
MESH_ELEMENT_KINDS,
MODEL_COMPONENT_ATTRIBUTE_SCHEMAS,
MODEL_COMPONENT_KINDS,
} from "@ogw_front/utils/attributes";
import type { Threshold, ThresholdAttribute } from "@ogw_internal/stores/hybrid_viewer/filters";
import type { AttributeSource } from "@ogw_front/utils/threshold";
import type { JsonRpcSchema } from "@ogw_shared/utils/types.js";
import ThresholdCard from "@ogw_front/components/ThresholdCard.vue";
import ToolPanel from "@ogw_front/components/ToolPanel.vue";
import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_typed_schemas.js";
import { fetchSources } from "@ogw_front/utils/threshold_sources";
import { useDataStore } from "@ogw_front/stores/data";
import { useDebounceFn } from "@vueuse/core";
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
Expand Down Expand Up @@ -43,52 +37,6 @@ const availableDatasets = computed<{ title: string; value: string }[]>(() =>
})),
);

function attributeSchema(kind: string, isModel: boolean, route: "names" | "range"): JsonRpcSchema {
const prefix = isModel ? "model_component_" : "";
return back_schemas.opengeodeweb_back[
`${prefix}${kind}_attribute_${route}` as keyof typeof back_schemas.opengeodeweb_back
] as JsonRpcSchema;
}

function attributeSource(
kind: string,
title: string,
params: { id: string; component_ids?: string[] },
modelType?: string,
): AttributeSource {
const isModel = modelType !== undefined;
const schema = attributeSchema(kind, isModel, "names");
return {
title: `${title}${kind} attribute`,
schemas: { names: schema, range: attributeSchema(kind, isModel, "range") },
timeStepKey:
modelType === undefined
? schema.$id
: (MODEL_COMPONENT_ATTRIBUTE_SCHEMAS[modelType]?.[kind]?.$id ?? schema.$id),
params,
location: kind === "vertex" ? "point" : "cell",
};
}

async function fetchSources(id: string): Promise<AttributeSource[]> {
const item = await dataStore.item(id);
if (item.viewer_type !== "model") {
return ["vertex", MESH_ELEMENT_KINDS[item.geode_object_type]]
.filter((kind): kind is string => kind !== undefined)
.map((kind) => attributeSource(kind, "", { id }));
}
const modelSources = await Promise.all(
Object.entries(MODEL_COMPONENT_KINDS).map(async ([type, kinds]) => {
const component_ids = await dataStore.getMeshComponentGeodeIds(id, type);
if (component_ids.length === 0) {
return [];
}
return kinds.map((kind) => attributeSource(kind, `${type} `, { id, component_ids }, type));
}),
);
return modelSources.flat();
}

async function applyThresholds(): Promise<void> {
if (!selectedDatasetId.value) {
return;
Expand Down Expand Up @@ -136,7 +84,7 @@ watch(selectedDatasetId, async (id, previousId) => {
await clearThresholds(previousId);
}
if (id) {
sources.value = await fetchSources(id);
sources.value = await fetchSources(dataStore, id);
} else {
sources.value = [];
}
Expand Down
Loading
Loading