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
[WIP] feat: add dialog to preview output files (images only) #536
[WIP] feat: add dialog to preview output files (images only) #536
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed! Great work. Left some comments.
@@ -3,6 +3,8 @@ import { NavigationExtras, Router, UrlSerializer } from '@angular/router'; | |||
import { Location, LocationStrategy } from '@angular/common'; | |||
import { WindowRef } from '../window-ref.service'; | |||
|
|||
const omit = require('lodash.omit'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TBH, I'm a bit sceptical if we really need to pull in yet another library for this. Especially since I'm not completely sure about the footprint. The whole omit npm package is 1.5k lines of code https://github.com/lodash/lodash/blob/4.5.0-npm-packages/lodash.omit/index.js
Considering that what we really need is basically just to remove the preview
key, I'm unsure if this is really justified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It also seems depricated lodash/lodash#2930 (comment)
@@ -41,5 +41,9 @@ export class OutputFilesService { | |||
.startWith(false) | |||
.take(2) | |||
} | |||
|
|||
isImage(fileName: string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we put that somewhere into util? Basically just
export const isImage = fileName => (/\.(gif|jpg|jpeg|tiff|png)$/i).test(fileName)
this.location.go(this.urlSerializer.serialize(currentUrlTree)); | ||
} | ||
|
||
removeQueryParams(path: string, keys: Array<string> | string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seem my comment above. I realize you want a generic method and omit
seems handy but for the reasons stated above I'd vote to either:
-
Make this less generic and just go with
delete preview
as this is really all that is currently needed -
Implement this
removeQueryParams
method without pulling in lodash omit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great point! I'll change it. My idea was to make it very generic to that you can specify a range of query params to be deleted. But for now it works. Even if we later need this we can also go with a simple for
loop or something and use delete
.
|
||
import 'rxjs/add/operator/first'; | ||
|
||
export class FilePreviewDialogRef<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are introducing generic T
but you don't seem to use it in any of the APIs. Instead all these APIs are using any
. Didn't you mean to use T
there?
close(dialogResult?: any): void { | ||
this.containerInstance.animationStateChanged | ||
.filter(event => event.phaseName === 'start') | ||
.first() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should rather be take(1)
|
||
this.containerInstance.animationStateChanged | ||
.filter(event => event.phaseName === 'done' && event.toState === 'leave') | ||
.first() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should rather be take(1)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
Left some minor comments.
@@ -1,17 +1,19 @@ | |||
import { Component, OnInit, OnDestroy, OnChanges, Input } from '@angular/core'; | |||
import { Component, OnInit, OnDestroy, OnChanges, Input, ViewContainerRef, ComponentRef } from '@angular/core'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both ViewContainerRef
and ComponentRef
are imported but not used.
client/package.json
Outdated
@@ -14,19 +14,20 @@ | |||
"private": true, | |||
"dependencies": { | |||
"@angular/animations": "5.0.0-beta.6", | |||
"@angular/cdk": "2.0.0-beta.10", | |||
"@angular/cdk": "^2.0.0-beta.11", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we either
- rollback to 2.0.0-beta.10 here or
- make a separate commit that upgrades dependencies?
In fact I'd prefer to have rollback and have a single commit later for upgrading Angular Material, as it introduces a very big deprecation that I'd like to get out of our way asap: https://github.com/angular/material2/blob/master/CHANGELOG.md#200-beta11-carapace-parapet-2017-09-21
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need the update in order to import PortalInjector
.
import { Observable } from 'rxjs/Observable'; | ||
import { OutputFilesService } from '../../output-files.service'; | ||
import { OutputFile } from '../../models/output-file'; | ||
|
||
import 'rxjs/add/operator/scan'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason this has been removed? The scan operator is still being used in this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cause it was already inside the rx.operators.ts
. Do we gather the operators there or keep them in the components? Cause I see those operator imports sprinkled all over the app.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #536 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see. Makes sense.
import { Subject } from 'rxjs/Subject'; | ||
import { FilePreviewDialogComponent } from './file-preview-dialog/file-preview-dialog.component'; | ||
|
||
import 'rxjs/add/operator/first'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need filter
as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We started to have operators in a separate file rx.operators.ts
. This one just needs to be removed here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We actually moved away from using rx.operators.ts
a while ago and rather import every used operator in their dedicated files. This is because e.g. we can't lazyload operators if we put them all in one file.
The only case where we still use rx.operators.ts
is in tests.
|
||
private onImageLoaded() { | ||
this.loading = false; | ||
this.imageElement.nativeElement.src = this.outputFile.download_url; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use Renderer Apis here instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep I can do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait, why Renderer? I am using a ViewChild
here. Do I really need the Renderer APIs?
export const FILE_PREVIEW_DIALOG_SCROLL_STRATEGY_PROVIDER = { | ||
provide: FILE_PREVIEW_DIALOG_SCROLL_STRATEGY, | ||
deps: [Overlay], | ||
useFactory: FILE_PREVIEW_DIALOG_SCROLL_STRATEGY_PROVIDER_FACTORY, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing comma
export const FILE_PREVIEW_DIALOG_SCROLL_STRATEGY = | ||
new InjectionToken<() => ScrollStrategy>('mat-dialog-scroll-strategy'); | ||
|
||
export function FILE_PREVIEW_DIALOG_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay: Overlay): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason this factory function is written in uppercase letters everywhere?
Just nitpicking here, but I prefer having functions be camel cased (I know Angular core does the same in some places but I don't think we need to follow that convention)
Take a look at app.module.ts
where we have a factory provider for the firebase database, as an example
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Looks weird. Missed that.
this.location.go(this.urlSerializer.serialize(currentUrlTree)); | ||
} | ||
|
||
removeQueryParams(path: string, keys: Array<string> | string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have this method addition in a separate commit (including tests)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why in a separate commit? I mean it's part of this feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer having a separate commit for that and another one that introduces the file preview so that if you ever have to cherry-pick/rollback you don't have get back everything or nothing, but rather can pick the specific parts you need.
I agree it prolly won't be the case in this scenario, but you never know. Better safe than sorry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Left another comment regarding your change to remove generics completely. Not a show stopper just want to know if there was a specific reason why you went with a non generic / any version rather than a type safe one?
import 'rxjs/add/operator/filter'; | ||
import 'rxjs/add/operator/take'; | ||
|
||
export class FilePreviewDialogRef { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that you removed the type param here. Why did you not keep it and use it in the methods? All you methods are using any. Was there anything preventing you from using T
in those methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cause the generic was not needed as the component for the overlay will always be FilePreviewDialogComponent
. The flexibility from the MdDialog
service was not needed for this specific use case (at least not yet). At this point we don't need the ability to pass a component to the service that will be rendered inside a PortalHost
. Once we need this we can easily refactor it back to a more generic approach. Does that make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Plus I am only using any
for the Subjects and the Observable that emits the result passed to close()
. This of course can be of any
type. The generic, again, is only needed if you want to have multiple types of components that can be rendered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought the generic was meant to be for the type of the dialog result, no?
I see two options here:
- The dialog result always has the same shape -> create an interface and use that instead of
any
- The dialog result can be set dynamically -> make this class generic and use
T
in those methods.
There shouldn't be a reason to define these APIs without type safety.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK makes sense. Then I re-introduce the generic and use that for the result
. Thanks for the valuable input / feedback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The question is, do we even need a result? I mean what do we expect from such overlay? Right now I can't think of anything that may be passed as a result from the FilePreviewDialogComponent
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought the generic was meant to be for the type of the dialog result, no?
Not really. I have introduced this because I thought I make it very generic similar to the MdDialog
. Then I found that I don't need this and the generic type <T>
became a left over. There's only one component instantiated which is the FilePreviewDialogComponent
. There's no dynamic component as with the MdDialog
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, kill it with fire 💣
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I now decided to use OutputFile
as the type for the result. In Google Drive you can edit the file in the overlay which I find pretty cool and probably useful. When we add such functionality we can easily return the updated outputFile
and then pass it along to a service that updates the file in the DB using Firebase. What do you think @cburgdorf @PascalPrecht?
Alternatively I can get rid of the result completely as we don't need it so far.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just get rid off it for now
@@ -0,0 +1,37 @@ | |||
// Taken from https://github.com/angular/material2/blob/master/src/cdk/testing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole file can probably be extracted into a general testing util. These functions are really helpful when dispatching keyboard events.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not have source code copied over and separately version controlled in our own projects, but if we really needed then yes, this should be moved to src/test-helpers
Anything left to do here? I didn't follow the discussion around Angular Material. So, from my side there's nothing blocking this from getting merged any more. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool @d3lm ! We're getting there.
Left a few more comments. I think once we get those ironed out this can be merged.
} | ||
} | ||
] | ||
}).compileComponents(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need .compileComponents()
here because angular CLI takes care of compiling all the templates and styles into the components.
|
||
beforeEach(() => { | ||
viewContainerFixture = TestBed.createComponent(TestComponent); | ||
viewContainerFixture.detectChanges(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a nitpick here, but this could as well just be in the beforeEach()
before this one. There's no need to introduce a second beforeEach()
block.
size_bytes: 204607, | ||
user_id: '5Yh73eYomSfTOcHFUx5EzkN5r1B3' | ||
} | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, this can all be done in the first beforeEach
block
viewContainerFixture.detectChanges(); | ||
|
||
expect(overlayContainerElement.querySelector('.cdk-overlay-backdrop').classList).toContain('dark-backdrop'); | ||
expect(overlayContainerElement.querySelector('.cdk-overlay-pane').classList).toContain('ml-file-preview-dialog-panel'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this test any config defaults? I see you're configuring the output file for the data configuration but you're testing class names.
I'm sure I'm missing something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind, seeing it in the next spec
expect(backdropClickSpy).toHaveBeenCalledTimes(1); | ||
done(); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks to me like you're testing if Angular's emitter's do their job.
Is this really needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's right but I thought it would be good to also test this, if the streams emit when the dialog is closed. I am testing the whole process of closing the dialog and when you execute close
it doesn't necessarily mean that the streams emit values because I have certain filters on the pipeline. So it also tests if the animationState
changes and only then the stream(s) should emit a value.
@@ -0,0 +1,65 @@ | |||
import { Component, Inject, EventEmitter } from '@angular/core'; | |||
import { MD_DIALOG_DATA } from '@angular/material'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not used anywhere
loading = true; | ||
error = null; | ||
|
||
animationState: 'void' | 'enter' | 'leave' = 'enter'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just thinking out loud here: Would it make sense to make those string enums?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's only used inside the class and an enum generates more code. Using string literal types gives you type safety and less code generated (as it generates no additional code).
|
||
onError(error: ErrorEvent) { | ||
this.loading = false; | ||
this.error = 'Bummer! An error occured while loading the image.'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This message should rather be moved into the template.
@@ -0,0 +1,37 @@ | |||
// Taken from https://github.com/angular/material2/blob/master/src/cdk/testing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not have source code copied over and separately version controlled in our own projects, but if we really needed then yes, this should be moved to src/test-helpers
:host { | ||
display: flex; | ||
align-items: center; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This little bit of CSS can be inlined on the component. No need to have a separate file for that.
No description provided.