Skip to content

fix: OPTIC-1125: Label config preview always showing a step behind when making updates #6417

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Sep 25, 2024
Merged
79 changes: 37 additions & 42 deletions web/apps/labelstudio/src/pages/CreateProject/Config/Preview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,25 @@ import { useAPI } from "../../../providers/ApiProvider";

const configClass = cn("configure");

const loadDependencies = async () => import("@humansignal/editor");
// Lazy load Label Studio with a single promise to avoid multiple loads
// and enable as early as possible to load the dependencies once this component is mounted for the first time
let dependencies;
const loadDependencies = async () => {
if (!dependencies) {
dependencies = import("@humansignal/editor");
}
return dependencies;
};

export const Preview = ({ config, data, error, loading, project }) => {
// @see comment about dependencies above
loadDependencies();

const [storeReady, setStoreReady] = useState(false);
const lsf = useRef(null);
const resolvingEditor = useMemo(loadDependencies);
const rootRef = useRef();
const projectRef = useRef(project);
const api = useAPI();

const projectRef = useRef(project);
projectRef.current = project;

const currentTask = useMemo(() => {
Expand Down Expand Up @@ -53,12 +63,14 @@ export const Preview = ({ config, data, error, loading, project }) => {
}, [config]);

const initLabelStudio = useCallback(async (config, task) => {
if (!task.data) return;
// wait for dependencies to load, the promise is resolved only once
// and is started when the component is mounted for the first time
await loadDependencies();

await resolvingEditor;
if (lsf.current || !task.data) return;

try {
const lsf = new window.LabelStudio(rootRef.current, {
lsf.current = new window.LabelStudio(rootRef.current, {
config,
task,
interfaces: ["side-column"],
Expand All @@ -71,6 +83,7 @@ export const Preview = ({ config, data, error, loading, project }) => {
const c = as.createAnnotation();

as.selectAnnotation(c.id);
setStoreReady(true);
};

if (isFF(FF_DEV_3617)) {
Expand All @@ -82,12 +95,9 @@ export const Preview = ({ config, data, error, loading, project }) => {
},
});

lsf.on("presignUrlForProject", onPresignUrlForProject);

return lsf;
lsf.current.on("presignUrlForProject", onPresignUrlForProject);
} catch (err) {
console.error(err);
return null;
}
}, []);

Expand All @@ -99,45 +109,30 @@ export const Preview = ({ config, data, error, loading, project }) => {
}, [loading, error]);

useEffect(() => {
if (!lsf.current) {
initLabelStudio(currentConfig, currentTask).then((ls) => {
lsf.current = ls;
});
}
}, [currentConfig, currentTask]);

useEffect(() => {
if (lsf.current?.store) {
lsf.current.store.assignConfig(currentConfig);
console.log("LSF config updated");
}
}, [currentConfig]);

useEffect(() => {
if (lsf.current?.store) {
const store = lsf.current.store;
initLabelStudio(currentConfig, currentTask).then(() => {
if (storeReady && lsf.current?.store) {
const store = lsf.current.store;

store.resetState();
store.assignTask(currentTask);
store.initializeStore(currentTask);
store.resetState();
store.assignTask(currentTask);
store.assignConfig(currentConfig);
store.initializeStore(currentTask);

const c = store.annotationStore.addAnnotation({
userGenerate: true,
});
const c = store.annotationStore.addAnnotation({
userGenerate: true,
});

store.annotationStore.selectAnnotation(c.id);
console.log("LSF task updated");
}
}, [currentTask]);
store.annotationStore.selectAnnotation(c.id);
console.log("LSF updated");
}
});
}, [currentConfig, currentTask, storeReady]);

useEffect(() => {
return () => {
if (lsf.current) {
console.info("Destroying LSF");
// there can be weird error from LSF, but we can just skip it for now
try {
lsf.current.destroy();
} catch (e) {}
lsf.current.destroy();
lsf.current = null;
}
};
Expand Down
2 changes: 1 addition & 1 deletion web/libs/editor/src/tags/object/Image/ImageEntity.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const ImageEntity = types
}))
.actions((self) => ({
preload() {
if (self.ensurePreloaded()) return;
if (self.ensurePreloaded() || !self.src) return;

self.setDownloading(true);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ describe("Rect3Point tool", () => {
ImageView.clickAtRelative(0.5, 0.5);

LabelStudio.serialize().then(([result]) => {
expect(result.value.x).to.be.closeTo(50, 0.1);
expect(result.value.y).to.be.closeTo(50, 0.1);
expect(result.value.x).to.be.closeTo(50, 0.2);
expect(result.value.y).to.be.closeTo(50, 0.2);
expect(result.value.rotation).to.be.eq(0);
});
});
Expand Down
Loading