Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT only emit at the first initialization in Forge viewer - javascript

export const initForgeViewer = (urn: string, renderingHTMLElemet: HTMLElement): Promise<any> => {
const forgeOptions = getForgeOptions(urn)
return new Promise((resolve, reject) => {
Autodesk.Viewing.Initializer(forgeOptions, () => {
const viewerConfig = {
extensions: ["ToolbarExtension"],
sharedPropertyDbPath: undefined,
canvasConfig: undefined, // TODO: Needs documentation or something.
startOnInitialize: true,
experimental: []
}
const viewer = new Autodesk.Viewing.Private.GuiViewer3D(renderingHTMLElemet, viewerConfig)
const avd = Autodesk.Viewing.Document
viewer.setTheme('light-theme')
viewer.start()
avd.load(forgeOptions.urn, (doc: any) => { // Autodesk.Viewing.Document
const viewables = avd.getSubItemsWithProperties(doc.getRootItem(), { type: 'geometry', role: '3d' }, true)
if (viewables.length === 0) {
reject(viewer)
return
} else {
const initialViewable = viewables[0]
const svfUrl = doc.getViewablePath(initialViewable)
const modelOptions = { sharedPropertyDbPath: doc.getPropertyDbPath() }
viewer.loadModel(svfUrl, modelOptions, (model: any) => { // Autodesk.Viewing.Model
this.loadedModel = model
resolve(viewer)
})
}
})
})
})
}
I am using the above code to initialise Forge viewer. But I realise that Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT only emit at the first time I initialize the Forge viewer. If I clean the viewer in the following way and initialize it again. The OBJECT_TREE_CREATED_EVENT would be fired
this.viewer.finish()
this.viewer.removeEventListener(Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT,this.onObjectTreeReady)
this.viewer = null

So I can assume you're completely destroying the viewer and creating it again, including all events, right? Please use the following:
viewer.tearDown()
viewer.finish()
viewer = null
Tested using v6

Related

The video stops and does not resume after track.stop()

I'm creating simple video-chat one-to-one using PeerJS and React. Everything is working fine except the camera muting. When participant A muting camera, it's muting on both clients but when participant A unmutes it, participant B can see only the static image instead of the opponent video.
I have a global state webcamState which changed when the corresponding button was clicked:
const videoRef = useRef<any>(null);
const webcamState = useSelector(getWebcamState);
const [stream, setStream] = useState<MediaStream | null>(null);
const [isWebcamLoading, setIsWebcamLoading] = useState(true);
const loadUserMedia = () => {
setIsWebcamLoading(true);
getUserMedia(
{ video: webcamState, audio: micState },
(newStream: MediaStream) => {
videoRef.current.srcObject = newStream;
setStream(newStream);
setIsWebcamLoading(false);
},
(err: any) => {
setIsWebcamLoading(false);
},
);
};
useEffect(() => {
if (videoRef?.current?.srcObject) {
videoRef.current.srcObject.getVideoTracks().forEach((track: any) => (track.enabled = webcamState));
if (!webcamState) {
videoRef.current.srcObject.getVideoTracks().forEach((track: any) => track.stop());
videoRef.current.pause();
} else {
loadUserMedia();
}
}
}, [webcamState]);
Then this stream exporting from this hook and passed into another to initialize Peer call (and peer answer as well):
export interface IPeerOptions {
opponentVideoRef: React.MutableRefObject<null>;
currentVideoRef: React.MutableRefObject<null>;
currentStream: MediaStream;
userId: string;
}
export const initializePeerCall = (options: IPeerOptions): Peer => {
const call = options.peer.call(options.opponentId, options.currentStream);
call.on('stream', stream => {
options.opponentVideoRef = setVideo(options.opponentVideoRef, stream);
});
call.on('error', err => {
console.log(err);
});
call.on('close', () => {
console.log('Connection closed');
});
return options.peer;
};
No errors appear in the console
But if I will remove the following line: videoRef.current.srcObject.getVideoTracks().forEach((track: any) => track.stop()); everything will work fine as expected
Maybe anyone faced something similar?
UPD: I tried this but the result was the same:
useEffect(() => {
if (videoRef?.current?.srcObject) {
videoRef.current.srcObject.getVideoTracks().forEach((track: any) => (track.enabled = webcamState));
if (!webcamState) {
videoRef.current.srcObject.getVideoTracks().forEach((track: any) => track.stop());
loadUserMedia();
} else {
loadUserMedia();
}
}
}, [webcamState]);
Notification:
const onOpponentVideoStatusChanged = (newStatus: boolean) => {
setOpponentState(prev => {
return { microphoneState: !!prev?.microphoneState, webcamState: newStatus };
});
options.opponentVideoRef.current.srcObject.getVideoTracks().forEach((track: any) => (track.enabled = newStatus));
};
As I understand after long investigation, user B still getting the same stream after user A created a new one. How can I fix it?
If you turn track off using track.stop(), you can not resume it.
I did get new stream when I have to resume it.
React.useEffect(()=>{
if(mediaStream) { // mediaStream could be state locally or globally.
const videoTrack = mediaStream.getVideoTracks();
videoTrack.forEach((t) => {
t.enabled = false;
t.stop();
});
}
// get MediaStream again with contraints
}, [isAudioOnly]);

How to use encrypt-storage with zustand in react?

I'm using zustand with persist plugin to store the state of my application. I want to use localstorage but the cache has to be encrypted.
For encryption, I'm using encrypt-storage. For encryption keys, I want to make an API call to the backend and initialise the encrypt storage.
The problem is while the API call is being made, the storage is still undefined. How to properly initialise zustand with encrypt-storage ?
Here is what I have tried :
import { EncryptStorage } from "encrypt-storage";
import { create } from "zustand";
import { devtools, persist, } from "zustand/middleware";
import { createJSONStorage } from "zustand/middleware"
const fake_api = (ms: number) => new Promise(resolve => {
setTimeout(resolve, ms)
})
export function KeyResolver(callback: () => void) {
const fn = async () => {
//
await fake_api(2000);
console.log("encryption key retrieved")
encryptStorage.EKEY = 'secret-key-value';
encryptStorage.storage = new EncryptStorage(encryptStorage.EKEY, {
stateManagementUse: true,
});
callback();
};
if (!encryptStorage.EKEY) {
fn();
}
}
interface IEncryptStorage {
storage: undefined | EncryptStorage,
EKEY: null | string,
}
export const encryptStorage: IEncryptStorage = {
storage: undefined,
EKEY: null,
}
const useOptimiserStore = create<IOptimiserStore>()(
devtools(
persist(
(set) => ({
...initialOtimiserStoreState,
_hasHydrated: false,
setHyderated: (val) => set({ _hasHydrated: val })
}),
{
name: "optimiser-storage",
// #ts-expect-error
storage: createJSONStorage(() => encryptStorage.storage),
onRehydrateStorage: () => {
KeyResolver(() => {
useOptimiserStore.getState().setHyderated(true)
});
}
}
),
{
name: "optimiser-storage",
}
)
);
// And i'm using it inside my component like this:
const Component = () => {
const hasHyderated = useOptimiserStore(state => state._hasHydrated);
if (!hasHyderated) {
return <>retreiving encryption keys </>
}
return <div> ... </div>
}
But I get the following error:
Uncaught TypeError: can't access property "setItem", storage is undefined
I managed to make it work by implementing a custom storage engine.
https://docs.pmnd.rs/zustand/integrations/persisting-store-data#how-can-i-use-a-custom-storage-engine

How to update RTK Query cache when Firebase RTDB change event fired (update, write, create, delete)

I am using redux-tookit, rtk-query (for querying other api's and not just Firebase) and Firebase (for authentication and db).
The code below works just fine for retrieving and caching the data but I wish to take advantage of both rtk-query caching as well as Firebase event subscribing, so that when ever a change is made in the DB (from any source even directly in firebase console) the cache is updated.
I have tried both updateQueryCache and invalidateTags but so far I am not able to find an ideal approach that works.
Any assistance in pointing me in the right direction would be greatly appreciated.
// firebase.ts
export const onRead = (
collection: string,
callback: (snapshort: DataSnapshot) => void,
options: ListenOptions = { onlyOnce: false }
) => onValue(ref(db, collection), callback, options);
export async function getCollection<T>(
collection: string,
onlyOnce: boolean = false
): Promise<T> {
let timeout: NodeJS.Timeout;
return new Promise<T>((resolve, reject) => {
timeout = setTimeout(() => reject('Request timed out!'), ASYNC_TIMEOUT);
onRead(collection, (snapshot) => resolve(snapshot.val()), { onlyOnce });
}).finally(() => clearTimeout(timeout));
}
// awards.ts
const awards = dbApi
.enhanceEndpoints({ addTagTypes: ['Themes'] })
.injectEndpoints({
endpoints: (builder) => ({
getThemes: builder.query<ThemeData[], void>({
async queryFn(arg, api) {
try {
const { auth } = api.getState() as RootState;
const programme = auth.user?.unit.guidingProgramme!;
const path = `/themes/${programme}`;
const themes = await getCollection<ThemeData[]>(path, true);
return { data: themes };
} catch (error) {
return { error: error as FirebaseError };
}
},
providesTags: ['Themes'],
keepUnusedDataFor: 1000 * 60
}),
getTheme: builder.query<ThemeData, string | undefined>({
async queryFn(slug, api) {
try {
const initiate = awards.endpoints.getThemes.initiate;
const getThemes = api.dispatch(initiate());
const { data } = (await getThemes) as ApiResponse<ThemeData[]>;
const name = slug
?.split('-')
.map(
(value) =>
value.substring(0, 1).toUpperCase() +
value.substring(1).toLowerCase()
)
.join(' ');
return { data: data?.find((theme) => theme.name === name) };
} catch (error) {
return { error: error as FirebaseError };
}
},
keepUnusedDataFor: 0
})
})
});

How to initialize App Data in node js and access it without being undefined in jest test?

i am initializing a node js app with crucial data for the app to work from a database in index.js.
index.ts
import {getInitialData} from 'initData.ts';
export let APP_DATA: AppData;
export const initializeAppData = async () => {
try {
APP_DATA = (await getInitialData()) as AppData;
if (process.env.NODE_ENV !== 'test') {
initializeMongoose();
startServer();
}
} catch (error) {
console.log(error);
}
};
initData.ts
let dbName: string = 'initialData';
if (process.env.NODE_ENV === 'test') {
dbName = 'testDb';
}
const uri = `${process.env.MONGODB_URI}/?maxPoolSize=20&w=majority`;
export async function getInitialData() {
const client = new MongoClient(uri);
try {
await client.connect();
const database = client.db(dbName);
const configCursor = database
.collection('config')
.find({}, { projection: { _id: 0 } });
const config = await configCursor.toArray();
const aaoCursor = database
.collection('aao')
.find({}, { projection: { _id: 0 } });
const aao = await aaoCursor.toArray();
return { config, aao };
} catch {
(err: Error) => console.log(err);
} finally {
await client.close();
}
}
I'm using this array in another file and import it there.
missionCreateHandler
import { APP_DATA } from '../index';
export const addMissionResources = (
alarmKeyword: AlarmKeyword,
newMission: MissionDocument
) => {
const alarmKeywordObject = APP_DATA?.aao.find(
(el) => Object.keys(el)[0] === alarmKeyword
);
const resourceCommand = Object.values(alarmKeywordObject!);
resourceCommand.forEach((el) => {
Object.entries(el).forEach(([key, value]) => {
for (let ii = 1; ii <= value; ii++) {
newMission.resources?.push({
initialType: key,
status: 'unarranged',
});
}
});
});
};
I'm setting up a mongodb-memory-server in globalSetup.ts for Jest and copy the relevant data to the database from json-files.
globalSetup.ts
export = async function globalSetup() {
const instance = await MongoMemoryServer.create({
instance: { dbName: 'testDb' },
});
const uri = instance.getUri();
(global as any).__MONGOINSTANCE = instance;
process.env.MONGODB_URI = uri.slice(0, uri.lastIndexOf('/'));
process.env.JWT_SECRET = 'testSECRET';
const client = new MongoClient(
`${process.env.MONGODB_URI}/?maxPoolSize=20&w=majority`
);
try {
await client.connect();
const database = client.db('testDb');
database.createCollection('aao');
//#ts-ignore
await database.collection('aao').insertMany(aao['default']);
} catch (error) {
console.log(error);
} finally {
await client.close();
}
};
missionCreateHandler.test.ts
test('it adds the correct mission resources to the array', async () => {
const newMission = await Mission.create({
address: {
street: 'test',
houseNr: 23,
},
alarmKeyword: 'R1',
});
const expected = {
initialType: 'rtw',
status: 'unarranged',
};
addMissionResources('R1', newMission);
expect(newMission.resources[0].initialType).toEqual(expected.initialType);
expect(newMission.resources[0].status).toEqual(expected.status);
});
When runing the test, i get an 'TypeError: Cannot convert undefined or null to object at Function.values ()'. So it seems that the APP_DATA object is not set. I checked that the mongodb-memory-server is set up correctly and feed with the needed data.
When i hardcode the content of APP_DATA in index.ts, the test runs without problems.
So my questions are: How is the best practice to set up initial data in a node js app and where to store it (global object, simple variable and import it in the files where needed)? How can the test successfully run, or is my code just untestable?
Thank you!

vscode.commands.executeCommand was not working

I'm writing an VS Code extension to help migrating React.createClass to class extends React.Component. The problem here was, I could not get vscode.commands.executeCommand('vscode.executeFormatDocumentProvider', ...) to work.
Note that the code below is pure JavaScript, but not TypeScript.
function activate(context) {
context.subscriptions.push(vscode.commands.registerCommand('migrate-to-react-es6-class', () => {
const editor = vscode.window.activeTextEditor
const document = editor.document
try {
const originalCode = document.getText()
const modifiedCode = 'do something and return new code'
if (originalCode === modifiedCode) {
vscode.window.showInformationMessage('Nothing is to be migrated.')
} else {
editor.edit(edit => {
const editingRange = document.validateRange(new vscode.Range(0, 0, Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER))
edit.replace(editingRange, modifiedCode)
})
if (document.isUntitled === false) {
vscode.commands.executeCommand('vscode.executeFormatDocumentProvider', document.uri, { insertSpaces: true, tabSize: 2 })
}
}
} catch (error) {
vscode.window.showErrorMessage(error.message)
console.error(error)
}
}))
}
After 3.25 years, you've probably figured this out by now, but for the record, I assume you hung a .then() on the editor.edit() and then moved the executeCommand to within the then(), right?
editor.edit(edit => {
const editingRange = ...
edit.replace(editingRange, modifiedCode)
}).then(editWorked => {if (editWorked && !document.isUntitled) {
vscode.commands.executeCommand('vscode.executeFormatDocumentProvider', ...) })
You must apply returned edits
private async formatDocument(): Promise<void> {
const docUri = this.textEditor.document.uri;
const textEdits = (await vscode.commands.executeCommand(
'vscode.executeFormatDocumentProvider',
docUri,
)) as vscode.TextEdit[];
const edit = new vscode.WorkspaceEdit();
for (const textEdit of textEdits) {
edit.replace(docUri, textEdit.range, textEdit.newText);
}
await vscode.workspace.applyEdit(edit);
}
I implemented it directly in the extension.ts:
commands.registerCommand(constants.commands.formatDocument, async () => {
const docUri = editor?.document.uri;
const textEdits: TextEdit[] | undefined = await commands.executeCommand(
'vscode.executeFormatDocumentProvider',
docUri
);
if (textEdits && docUri) {
const edit = new WorkspaceEdit();
for (const textEdit of textEdits) {
edit.replace(docUri, textEdit.range, textEdit.newText);
}
await workspace.applyEdit(edit);
}
});
constants.commands.formatDocument gets the value after parsing my package.json

Categories