Now, I'm able to list the levels. I'm trying to zoom on a specific room of a given level.
I've the room dbId but when I do this :
v.fitToView(34969, v.model) (v => viewer / 34969 the dbId's room)
Camera is going to far :
I'm trying to do exactly the same camera movement than when I click on a sub item in modelStructureTool > Room
about color of a room
Here is my code with the correction :
var ulRoom = document.createElement('ul');
ulRoom.setAttribute('id','levelsList');
el.children.forEach(function(l){
var liRoom = document.createElement('li');
liRoom.setAttribute('class','room');
liRoom.setAttribute('style','cursor:pointer;color: #000; font-weigth: bold');
liRoom.onclick = function () {
console.log("l ====>", l);
viewer.fitToView( [l.dbId], viewer.model);
viewer.setThemingColor( l.dbId, viewer.model);
};
ulRoom.appendChild(liRoom);
liRoom.innerHTML = l.name;
});
viewer.fitToView is working nicely.
In my forEach, l is room :
The first argument must be an array, so your function call has to be changed to viewer.fitToView( [ 34969 ], viewer.model ). The 2nd argument model is optional, so it will become viewer.fitToView( [ 34969 ] ) in single model use case.
If you’re using the Viewer in multiple models case, you have to find the corresponding model from viewer.impl.modelQueue().getModels() for that room and the second argument of the Viewer3D#fitToView.
Hope it helps.
Edit:
The viewer API for changing element's color is viewer.setThemingColor( dbId, color ) and it only works for leaf nodes. After investigating the model you provided to Forge Helpdesk, I found the room which has dbId 34969 is not the leaf node of the Viewer instance tree. So, this's why the color didn't change while the API is event right.
function getLeafNodes( model, dbIds ) {
return new Promise( ( resolve, reject ) => {
try {
const instanceTree = model.getData().instanceTree
dbIds = dbIds || instanceTree.getRootId();
const dbIdArray = Array.isArray( dbIds ) ? dbIds : [dbIds]
let leafIds = [];
const getLeafNodesRec = ( id ) => {
let childCount = 0;
instanceTree.enumNodeChildren( id, ( childId ) => {
getLeafNodesRec( childId );
++childCount;
})
if( childCount == 0 ) {
leafIds.push( id );
}
}
for( let i = 0; i < dbIdArray.length; ++i ) {
getLeafNodesRec( dbIdArray[i] );
}
return resolve( leafIds );
} catch (ex) {
return reject(ex)
}
})
}
let color = new THREE.Vector4( 255/255, 0/255, 0/255, 1 );
getLeafNodes( viewer.model, [ 34969 ] )
.then( ( leafNodes ) => {
// Call setThemingColor for every leaf node.
for( let i = 0; i < leafNodes.length; i++ ) {
viewer.setThemingColor( leafNodes[i], color );
}
})
.catch( ( error ) => console.warn( error ) );
Here is the Viewer documentation: https://developer.autodesk.com/en/docs/viewer/v2/reference/javascript/viewer3d/
Related
I'm using Angular 6.
I have an array of links and a variable to store fetched information in same order as of array one by one.
Here is what I'm trying to do using for loop.
products: any;
processedItems: Array<any> = [];
private _processItem() {
for (let i = 0; i < this.products.length; i++) {
this.scraperService.scrapSingle(this.products[i].url).subscribe(
res => {
if (res.status.http_code === 200) {
const properties = this.scraperService.processSingleProduct(res.contents);
const p_item = {};
p_item['info'] = this.products[i];
p_item['properties'] = properties;
this.processedItems.push(p_item);
}
console.log(res);
}
);
}
console.log(this.products.length);
}
But how to wait for subscribe before moving to next index in the loop?
Just splice the p_item into your array at the required index given i.
For example instead of doing,
this.processedItems.push(p_item);
do this,
this.processedItems.splice(p_item, 0, i);
That solves your problem :)
Use promises instead of rx.js subscriptions via using toPromise method. You might need to map the res to json. res.map(item => item.json());
products: any;
processedItems: Array < any > =[];
private _processItem() {
this.products.array.forEach(async (element) => {
const res = await this.scraperService.scrapSingle(element.url).toPromise();
if (res.status.http_code === 200) {
const properties = this.scraperService.processSingleProduct(res.contents);
const p_item = {};
p_item['info'] = element
p_item['properties'] = properties;
this.processedItems.push(p_item);
}
console.log(res);
});
console.log(this.products.length);
}
I am struggling a bit with understanding and applying #Effects for my episode Download option. I got some help in another question and this is my latest concoction.
Quick overview: User clicks download which dispatches a DOWNLOAD_EPISODE action which is captured in the first Effect. The download call returns a stream of HttpEvents and a final HttpResponse.
During the event.type === 3 I want to report download progress. When event.type === 4 the body has arrived and I can call the success which for example can create a Blob.
Service episodesService:
download( episode: Episode ): Observable<HttpEvent<any>> | Observable<HttpResponse<any>> {
// const url = encodeURIComponent( episode.url.url );
const url = 'https%3A%2F%2Fwww.sample-videos.com%2Faudio%2Fmp3%2Fcrowd-cheering.mp3';
const req = new HttpRequest( 'GET', 'http://localhost:3000/episodes/' + url, {
reportProgress: true,
responseType: 'blob'
} );
return this.http.request( req );
}
downloadSuccess( response: any ): Observable<any> {
console.log( 'calling download success', response );
if ( response.body ) {
var blob = new Blob( [ response.body ], { type: response.body.type } );
console.log( 'blob', blob );
}
return of( { status: 'done' } );
}
getHttpProgress( event: HttpEvent<any> | HttpResponse<Blob> ): Observable<DownloadProgress> {
switch ( event.type ) {
case HttpEventType.DownloadProgress:
const progress = Math.round( 100 * event.loaded / event.total );
return of( { ...event, progress } );
case HttpEventType.Response:
const { body, type } = event;
return of( { body, type, progress: 100 } );
default:
return of( { ...event, progress: 0 } );
}
}
The effects:
#Effect()
downloadEpisode$ = this.actions$.pipe(
ofType<episodeActions.DownloadEpisodes>( episodeActions.DOWNLOAD_EPISODE ),
switchMap( ( { payload } ) => this.episodesService.download( payload )
.pipe(
switchMap( (response: HttpEvent<any> | HttpResponse<any>) => this.episodesService.getHttpProgress( response ) ), //merge in the progress
map( ( response: fromServices.DownloadProgress ) => {
// update the progress in the episode
//
if ( response.type <= 3 ) {
return new episodeActions.DownloadProgressEpisodes( { ...payload, download: {
progress: response.progress
} } );
// pass the Blob on the download response
//
} else if ( response.type === 4 ){
return new episodeActions.DownloadEpisodesSuccess( response );
}
} ),
catchError( error => of( new episodeActions.DownloadEpisodesFail( error ) ) ),
)
)
)
#Effect( { dispatch: false } )
processDownloadEpisodeSuccess$ = this.actions$.pipe(
ofType<any>( episodeActions.DOWNLOAD_EPISODE_SUCCESS ),
switchMap( ( { payload } ) => this.episodesService
.downloadSuccess( payload ).pipe(
tap( response => console.log( 'response', payload,response ) ),
// catchError(err => of(new episodeActions.ProcessEpisodesFail(error))),
)
)
)
I am pretty happy with this solution, however i do not like the If ELSE clause in the MAP as part of the DOWNLOAD_EPISODE Effect.
Ideally I want to split the stream there, if type is 3 I want to go route A which always dispatches episodeActions.DownloadProgressEpisodes with an Episode payload.
Whenever it is type 4, the last emitted event, I want to got the B route in the stream and call episodeActions.DownloadEpisodesSuccess with the response body.
I tried to add more effects but I always end up in a situation the the result stream of the this.episodesService.download will either be a progress update OR a response body. For the progress update, I require the Episode record to be able to call the reducer and update the Episode's progress.
I tried to use things as setting filter( response => response.type === 4 ) after the DownloadProgressEpisodes but before DownloadEpisodesSuccess hoping it would allow for an iteration before the filter to deal with the progress and then filter the rest through to the Success Action.
I then learned that is not how streams work.
I also tried last() which did work but it didn't let me dispatch the Response Actions (the console log did work), but only the DownloadEpisodesSuccess action after the last() would be dispatched.
So, if it is possible to split the stream and deal with event.type 3 differently then event.type 4 I would be interested in that.
*****UPDATE*******
I want to keep the original question, however I did run into a throttling requirement because I was dispatching a lot of actions for large files.
I tried the throttle operator but that would suppress emits but could also suppress the last emit with the response body. I tried splitting it with partition but I don't think that is possible. After a light bulb moment I decided to create 2 effects for DOWNLOAD_EPISODE, one which only captures all event.type === 3 and throttles it and another effect that uses the last operator and only deals with event.type === 4.
See code:
#Effect( )
downloadEpisode$ = this.actions$.pipe(
ofType<episodeActions.DownloadEpisodes>( episodeActions.DOWNLOAD_EPISODE ),
switchMap( ( { payload } ) => this.episodesService.download( payload )
.pipe(
switchMap( (response: HttpEvent<any> | HttpResponse<any>) => this.episodesService.getHttpProgress( response ) ),
throttleTime( 500 ),
map( ( response: fromServices.DownloadProgress ) => {
console.log('Type 3', response);
// update the progress in the episode
if ( response.type <= 3) {
return new episodeActions.DownloadProgressEpisodes( { ...payload, download: {
progress: response.progress
} } );
}
} ),
catchError( error => of( new episodeActions.DownloadEpisodesFail( error ) ) ),
)
)
)
#Effect( )
downloadEpisodeLast$ = this.actions$.pipe(
ofType<episodeActions.DownloadEpisodes>( episodeActions.DOWNLOAD_EPISODE ),
switchMap( ( { payload } ) => this.episodesService.download( payload )
.pipe(
switchMap( (response: HttpEvent<any> | HttpResponse<any>) => this.episodesService.getHttpProgress( response ) ),
last(),
map( ( response: fromServices.DownloadProgress ) => {
console.log('Type 4', response);
if ( response.type === 4 ){
return new episodeActions.DownloadEpisodesSuccess( response, { ...payload, download: {
progress: response.progress
} } );
}
} ),
catchError( error => of( new episodeActions.DownloadEpisodesFail( error ) ) ),
)
)
)
What do you think, is this the best solution? Type 3 and 4 are split and I can throttle it.
*update2:
It does create an issue where the Progress Action with progress 97% can be triggered after the Download Success action with progress 100%.
Every time I run into a new challenge...
SampleTime(500) seems to work, since it doesnt seem to throw a Type 3 event after the Type 4 event came in.
*update3: I just found out that I am calling Download twice now in the effect, sigh. I am back at square one trying to throttle the HttpEvent stream coming from episodeService.download.
I think if you don't want to have if else statement, you'll have to create different effects.
In the current one you'll dispatch a new action, e.g. DownloadEpisodeProgess, with the type in the payload.
You will then create two effects that listens to this action and filter them accordingly via the type, one effect will be used to dispatch DownloadProgressEpisodes, the other one for DownloadEpisodesSuccess.
Another possible solution would be the partition operator, but I haven't used it in combination with NgRx Effects.
It is also good to keep in mind that for each subscription you make, there will be a performance cost, personally I don't really mind the if else statement.
I'm trying to get an ElementHandle's class name using Puppeteer... is it possible? Am I using the wrong approach? In this jsBin is part of my code, so you can understand what I am trying to achieve.
CriticalCssPlugin.prototype.load = function( page, src ) {
return page.goto( src, { waitUntil: 'networkidle2' } )
.then( () => {
return page
.$$( '*' )
.then( elements => {
return Promise.all( elements.map( element => {
return element.boundingBox()
} ) )
.then( positions => {
let visible = positions.filter( ( rect, index ) => {
if ( !rect ) {
return rect
}
rect.element = elements[ index ]
return this.isAnyPartOfElementInViewport( rect, page.viewport() )
} )
this.getClasses( visible )
} )
} )
} )
}
CriticalCssPlugin.prototype.getClasses = function( visibles ) {
Promise.all( visibles.map( visible => {
return visible.element.getProperty( '' )
} ) )
.then( classes => {
console.log(classes);
} )
}
CriticalCssPlugin.prototype.isAnyPartOfElementInViewport = function( rect, viewport ) {
const windowHeight = viewport.height
const windowWidth = viewport.width
const vertInView = ( rect.y <= windowHeight ) && ( ( rect.y + rect.height ) >= 0 )
const horInView = ( rect.x <= windowWidth ) && ( ( rect.x + rect.width ) >= 0 )
return ( vertInView && horInView )
}
https://jsbin.com/kuzejoluji/edit?js,output
Thank you :D
Going to drop this here since this page is currently first result searching for "elementhandle class name"
From the docs, you should just be able to the following
const el = await page.$('.myElement')
const className = await el.getProperty('className')
// alternatively,
// page.$('.myElement')
// .then(el => el.getProperty('className'))
// .then(className => ... )
jimmyjoy's answer is right but this may help others use the elementHandle
page.$(el) // This grabs the element (returns a elementHandle)
.then((el) => el.getProperty("className")) // Returns a jsHandle of that property
.then((cn) => cn.jsonValue()) // This converts the className jsHandle to a space delimitedstring
.then((classNameString) => classNameString.split(" ") // Splits into array
.then((x) => console.log(x)
Which would log an array of classes
Note: when i tried to do a .split on the end of jsonValue() it didn't work as i believe the promise isn't resolved at that point so cn.jsonValue().split(" ") wont work
References
List of properties on elements
Puppeteer docs for ElementHandle
I found a solution that helps in parts, but it was good enough to me. I've got the class name acessing ElementHandle._remoteObject.description.
Hope this helps someone.
you can get the element variable and use evaluate function like that:
const element = await page.$(".some-class"); // for ids you can write "#some-id"
const className = await page.evaluate(el => el.className, element);
console.log('className', className) // here you can get the class name
I use this function
// JS
export async function elementHasClass(el, className) {
const classNames = (
await (await el.getProperty('className')).jsonValue()
).split(/\s+/);
return classNames.includes(className);
}
// TS
export async function elementHasClass(
el: ElementHandle,
className: string,
): Promise<boolean> {
const classNames = (
await (await el.getProperty('className')).jsonValue<string>()
).split(/\s+/);
return classNames.includes(className);
}
This is what i did:
let posts = await page.$$(".postContainer .upvote");
for (let i = 0; i < posts.length; i++) {
// log class name
console.log(await (await posts[i].getProperty('className')).jsonValue());
// click upvotes on posts
await codes[i].click();
}
When I pass an array of dbIds to be turned off the viewer is turning every node off in my model.
Autodesk.Viewing.Viewer3D.prototype.turnOff = function(dbIds) {
var node;
$(dbIds)
.each(function(index, item) {
node = viewer.model.getData().instanceTree.nodeAccess.nodes[item];
viewer.impl.visibilityManager.setNodeOff(node, true);
});
}
If you pass the id of a parent, it will turn off all its children, which is probably what happens in your case. Turning nodes off definitely works fine, you can take a look at my demo at https://forge-rcdb.autodesk.io.
Select a row in the database view or a segment in the pie chart:
What you need to do is to get the leaf node ids, only leaf nodes are represented by geometry in the viewer.
Here is some ES6 code sample, extracted from there:
static getLeafNodes (model, dbIds) {
return new Promise((resolve, reject)=>{
try {
const instanceTree = model.getData().instanceTree
dbIds = dbIds || instanceTree.getRootId()
const dbIdArray = Array.isArray(dbIds) ? dbIds : [dbIds]
let leafIds = []
const getLeafNodesRec = (id) => {
var childCount = 0;
instanceTree.enumNodeChildren(id, (childId) => {
getLeafNodesRec(childId)
++childCount
})
if (childCount == 0) {
leafIds.push(id)
}
}
for (var i = 0; i < dbIdArray.length; ++i) {
getLeafNodesRec(dbIdArray[i])
}
return resolve(leafIds)
} catch(ex){
return reject(ex)
}
})
}
static async isolateFull (viewer, dbIds = [], model = null) {
try {
model = model || viewer.activeModel || viewer.model
viewer.isolate(dbIds)
const targetIds = Array.isArray(dbIds) ? dbIds : [dbIds]
const targetLeafIds = await ViewerToolkit.getLeafNodes(
model, targetIds)
const leafIds = await ViewerToolkit.getLeafNodes (model)
const leafTasks = leafIds.map((dbId) => {
return new Promise((resolveLeaf) => {
const show = !targetLeafIds.length ||
targetLeafIds.indexOf(dbId) > -1
viewer.impl.visibilityManager.setNodeOff(
dbId, !show)
resolveLeaf()
})
})
return Promise.all(leafTasks)
} catch (ex) {
return Promise.reject(ex)
}
}
Can someone help me decypher this ES6 statement?
const {
isFetching,
lastUpdated,
items: posts
} = postsByReddit[selectedReddit] || {
isFetching: true,
items: []
}
I pulled it from the Redux async example - https://github.com/reactjs/redux/blob/master/examples/async/containers/App.js#L81
The code is simply declaring three constants, getting them from similarly named properties on an object if it is non-empty, otherwise get them from an object literal that acts as default values.
I trust that you are confused over the object like syntax rather than the const keyword.
var|let|const { ... } = ... is an object destructuring declaration.
var|let|const [ ... ] = ... is an array destructuring declaration.
Both are short hand for "break down right hand side and assign to left hand side".
Destructuring can be done on array or object using different brackets.
It can be part of a declaration or as stand-alone assignment.
const { isFetching } = obj; // Same as const isFetching = obj.isFetching
var [ a, b ] = ary; // Same as var a = ary[0], b = ary[1]
[ a ] = [ 1 ]; // Same as a = 1
For object destructuring, you can specify the property name.
For array, you can skip elements by leaving blank commas.
Destructuring can also form a hierarchy and be mixed.
const { items: posts } = obj; // Same as const posts = obj.items
var [ , , c ] = ary; // Same as var c = ary[2]
let { foo: [ { bar } ], bas } = obj; // Same as let bar = obj.foo[0].bar, bas = obj.bas
When destructuring null or undefined, or array destructure on non-iterable, it will throw TypeError.
Otherwise, if a matching part cannot be found, its value is undefined, unless a default is set.
let { err1 } = null; // TypeError
let [ err3 ] = {}; // TypeError
let [ { err2 } ] = [ undefined ]; // TypeError
let [ no ] = []; // undefined
let { body } = {}; // undefined
let { here = this } = {}; // here === this
let { valueOf } = 0; // Surprise! valueOf === Number.prototype.valueOf
Array destructuring works on any "iterable" objects, such as Map, Set, or NodeList.
Of course, these iterable objects can also be destructed as objects.
const doc = document;
let [ a0, a1, a2 ] = doc.querySelectorAll( 'a' ); // Get first three <a> into a0, a1, a2
let { 0: a, length } = doc.querySelectorAll( 'a' ); // Get first <a> and number of <a>
Finally, don't forget that destructuring can be used in any declarations, not just in function body:
function log ({ method = 'log', message }) {
console[ method ]( message );
}
log({ method: "info", message: "This calls console.info" });
log({ message: "This defaults to console.log" });
for ( let i = 0, list = frames, { length } = frames ; i < length ; i++ ) {
console.log( list[ i ] ); // Log each frame
}
Note that because destructuring depends on left hand side to specify how to destructre right hand side,
you cannot use destructring to assign to object properties.
This also excludes the usage of calculated property name in destructuring.
As you have seen, destructuring is a simple shorthand concept that will help you do more with less code.
It is well supported in Chrome, Edge, Firefox, Node.js, and Safari,
so you can start learn and use it now!
For EcmaScript5 (IE11) compatibility, Babel and Traceur transpilers
can turn most ES6/ES7 code into ES5, including destructuring.
If still unclear, feel free to come to StackOverflow JavaScript chatroom.
As the second most popular room on SO, experts are available 24/7 :)
This is an additional response to the already given. Destructuring also supports default values, which enables us to simplify the code:
const {
isFetching = true,
lastUpdated,
items = []
} = postsByReddit[selectedReddit] || {};
Basically:
var isFecthing;
var lastUpdated;
var posts;
if (postsByReddit[selectedReddit]) {
isFecthing = postsByReddit[selectedReddit].isFecthing;
lastUpdated = postsByReddit[selectedReddit].lastUpdated;
posts = postsByReddit[selectedReddit].items.posts;
} else {
isFecthing = true;
items = [];
}