-
Notifications
You must be signed in to change notification settings - Fork 3
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
UT-210: Rebuilding of the code structure for Cliptool. #81
Conversation
refactor: divides down Thumbnail.tsx into files such that there is maximum one component per file. refactor: extracts much of the common logic used between thumbnail components to mediaService.ts. refactor: updates references to the Thumbnail.tsx to its new locations. refactor: adds explicit types on constants and functions' arguments and return.
refactor: divides down Header.tsx into files such that there is maximum one component per file. refactor: renames 'manualstartState' to 'manualStartState'. fix: fixes header not showing the thumbnail as it was suppose too.
# Conflicts: # src/client/components/Tab/TabContent.tsx
refactor: splits last parts of Settings.tsx into other files. refactor: renames the constant being exported from mediaService.ts to mediaService, so Intellisense is able to aid with adding import for it. refactor: moves some styling applied inline to CSS.
…l crash at the moment, and toggling settings doesn't work.. refactor: renames interfaces in settingsReducer to not have 'I' in their names. refactor adds placeholder user confirmations when saving/discarding settings changes. refactor: extracts input layouts for settings to a reusable component. refactor: fixes typo in 'transistionTime' to 'transitionTime'. refactor: changes arrays of 'GenericSettings' into a single array of 'OutputSettings', each containing an attribute for the previous arrays. refactor: removed most code in 'correctStructure(...)' and verifyStructure(...), as they have to be redone with the new settings structure.
# Conflicts: # src/client/components/Header.tsx # src/client/components/Settings.tsx # src/client/components/Thumbnail.tsx
…ved in redux refactor: adds and implements Zod, to handle validation of the settings file loaded from drive. refactor: updates used version of typescript. refactor: reimplements validation of loaded settings, by use of Zod. refactor: shifts many mediaActions over to settingsAction as the data they handle going to be stored in there instead. refactor: create few service classes with shared logic. refactor: updates a number of places to use the new service classes. refactor: created type for reduxState, and applies it in a few dozen places.
refactor: renamed 'IThumbnailFile' to 'ThumbnailFile'. refactor: renamed 'IMediaFile' to 'MediaFile'. refactor: renamed 'IOutput' to 'Output'. refactor: renamed 'IMedia' to 'Media'. refactor: moves and renames Output.tallyFile to OutputSettings.selectedFile. refactor: moves service classes from being client only to model level, as backend want many of the same functions.
…circular dependency.
refactor: extracts interfaces from mediaReducer to mediaModels, for consistencies sake. refactor: implements the settings view sufficiently to not class cliptool. refactor: makes SingleOutput only render, if it isn't undefined.
refactor: changes useState in SingleOutput to be one per attribute. refactor: adds 'deep-copy-ts' to create a deep copy of settings, to allow discarding changes, should the user not want them anyways.
…g on if there is changes or not. refactor: changes 'deep-copy-ts' for 'lodash' as lodash has other functions i need anyways.
…header buttons not having their colour.
…iminates some duplicate code. refactor: changes functions defined as constants to function definitions. refactor: removes/merges duplicate code to a single implementation(s). refactor: adds return type to most, if not all, functions.
…eived. Applies changes for GeneralSettings that was missed. refactor: updates GeneralSettings to support the new structure for allowing discarding changes. refactor: adds a half dozen or so temp console.log used to try and track down why selectedFile is not set. refactor: removes 'I' from interface name of 'TimeTallyPayload'. refactor: renames 'handleOscMessage' to 'processOscMessage'. refactor: renames 'updateThumbFileList' to 'updateThumbnailFileList' along with associated constants.
…sue they are used for has been fixed.
If any horses are found let me know. In that case i have propably missed renaming something back to it correct name, while doing the renaming. |
…der settings being swappped. Woops.
… attributes. refactor: changes types in reduxState to be ordinary objects, instead of array of said objects. refactor: changes many commonly accessed attributes from reduxState to be retrieved via the service classes. refactor: renamed one file i missed in the mass renaming to camelCase. refactor: removes a few left over console.logs.
refactor: renames checkbox-input.tsx to checkbox.tsx. refactor: renames css class 'save-button' to 'c-settings-actions-button'. refactor: changes type of child input for button to a string. refactor: changes used button in thumbnail-button.tsx to a 'div'. refactor: extracts thumbnail file name display paragraph to new component - file-name-display.tsx.
…ails for list of changes. refactor: renames 'models' layer to 'shared'. refactor: moves action files from 'reducer' folder to new 'actions' folder. refactor: moves model files from 'reducer' folder to new 'models' folder. refactor: renames thumbnail-button to media-card. refactor: renames image-thumbnail to image-media-card. refactor: renames text-thumbnail to text-media-card. refactor: renames thumbnails to media-overview. refactor: renames client-handler-service to backend-observer. refactor: renames CcgConfigChannel to CasparcgConfigChannel. refactor: renames toggle to switch. refactor: renames thumbnail-overlay-display to card-overlay-display. refactor: renames thumbnail-overlay to card-overlay. refactor: merges thumbnail-picture into image-media-card. refactor: removes event-service by moving its usage to inline. refactor: splits emits from socket-service out to multiple backend api services. refactor: makes tab-title into a dumb component.
…t details for changes. refactor: moves client specific logic of time-service to new client-time-service. refactor: moves type definition for 'HiddenFiles' to media-models. refactor: changes types related to HiddenFIles from 'Record<string, HiddenFileInfo>' to 'HiddenFiles'. refactor: splits settings-persistence-service.load() into multiple methods to improve readability/flow. refactor: splits hidden-files-persistence-service.load() into multiple methods to improve readability/flow. refactor: renames a few arguements/enums to better match their content. refactor: updates some enum values to use kebab-case. fix: fixes casing of one argument of the old-settings-schema. fix: fixes grayscale not being applied to hidden images. fix: fixes backend-observer never being called, and thus nothing received from backend.
…owers duplications..
…at didn't match content.
…gs-service. refactor: renames tab-panel to main-page. refactor: renames settings to settings-page. refactor: moves logic in changing-settings-service to settings-actions and settings-page. refactor: moves the task of displaying the header and footer from the overall app, to the individual pages, which is only main-page at the moment.
…nents and changes calls for events to be performed via bindings.
…ic attribute on each service and changes the import of the services to not use default import.
…ue instead of via css class name.
…type. refactor: changes to checks for new layout structure before old. refactor: removes defaults from new-settings-schema. refactor: lowers amount of OutputSettings in the fallback default. refactor: makes startup sequence fill in extra default OutputSettings, if file contained too few compared to configured channels. refactor: reduces redux dispactches by changing used action, during startup. refactor: updates usage of 'fs' to use promise based versions. refactor: moves main part of forEach to new function for readability.
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.
There is a lot of things that needs to be addressed that I skipped for the sake of getting this PR merged.
There are however some things that must be addressed. I have commented many of them, but I need to emphasize two things:
The first thing, and by far the worst one: Your usage of singletons.
For some reason you have made an abundance of the classes singletons. I counted one place where a singleton might be suitable. Only one place.
A singleton is used to ensure that all consumers of a class calls the same data/state. This is not a need you have in almost all of your classses. Besides, this project used Redux which accomplies the same thing, so one more reason not to use singletons.
Many of the classes you have made singletons are "utility" classes. I.e. classes that just need an input and then provides an output. Classes that don't care about state at all. Since singletons is for classes that cares about state, then these "utility" classes should not be singletons.
What's even worse is that you call the instance method of your singletons over and over again in the classes that you use them in. This is no better than just making each method static and call the static method (especially because your classes allready don't care about state).
The second thing I need to address is the folder structure. It seems like you are on the right track, but haven't fully understood how it should be.
As we talked about, we want to separate our folders by feature/pages. That means that the folder structure should look like this:
-src/
-featureX/
-components/
-componentA/
-componentB/
-services/
-featureY/
-components/
-componentC/
-componentD/
-services/
-someService/
-someOtherService/
-shared/
-components/
-componentE/
-componentF/
-services/
What this would like in this project would be something like this:
-src/
-frontPage/
-components/
-FrontPageComponent
...
-settings/
-components/
-SettingsPageComponent
...
-shared/
-components/
-HeaderComponent
-FooterComponent
import { reduxStore } from '../../../../shared/store' | ||
import { toggleSettingsVisibility } from '../../../../shared/actions/app-navigation-action' | ||
|
||
export default function ApplicationHeader(): JSX.Element { |
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 call it Header
. What information do Application
give that is not allready in Header
?
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.
@LindvedKrvang Personally I think Header
is too generic. I would expect some sort of generic header
element if the component is called Header
. Application
might not be the best fit, but then we could have a look at what kind of header this is and give it an appropriate name. Since the primary focus of the header is to display information about what media is playing and how it is played, then perhaps MediaHeader
, MediaPlayerHeader
or MediaControlHeader
? Other suggestions are welcomed 😁
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.
Renamed it back to Header
. Can't fully remember why Application
was added, just that it was Anders who suggested it during our talk about a number of comments.
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.
After Anders late comment, i broke the Header appart such that:
1.Tthe header parts that should always be there are in a Header
component under shared.
2. A more specific version exist under main-page
called MediaControlHeader
, which build upon the base and adds whats needed for a header specific to main-page
.
Hope this satifies both parties.
const isHidden: boolean = props.file.name in hiddenFiles | ||
|
||
return ( | ||
<div className={`c-image-media-card ${isHidden ? 'hidden' : ''}`}> |
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 do you need a CSS class to hide the component? You could just not render it if it shouldn't be show:
return (
{!isHidden && (
<div>...</div>
)}
)
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.
But this is also ties into the discussion: "It's weird that you render a component that then decides not to render itself." It should be the calling component that figures out if it should render its child components or not.
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.
hidden
is this case applies some css to indicate that the card in question is hidden, while not Editting Visibility of cards. For this PR, it is only some sizing, but already in #84 / UT-216 this is expanded to add more css styling.
Maybe the constant needs a better name, just not sure what...
} from '../../shared/actions/settings-action' | ||
import { SocketService } from './socket-service' | ||
|
||
export class BackendObserver { |
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 is this called Backend
? It's a service that lives in the frontend so it shouldn't be named "Backend" (it also should not be named "Frontend").
What does it observe?
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 observers, or maybe a better word listens, for event from the backend.
The new name was created following 1 or more comments made during the long PR review. You might have left before they left that/those comment(s) though...
|
||
public startBackendObserver(): void { | ||
console.log('Socket Initialized', SocketService.instance.getSocket()) | ||
console.log('BackendObserver: Monitoring messages from socket...') |
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 indicated that a more apt name for this class would be SocketObserver
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 class makes use of a socket for the communication at the moment, which does support your comment, but it is also a implementation detail, which afaik should not be part of the name.
import { ClientToServerCommand } from '../../shared/socket-io-constants' | ||
import { SocketService } from './socket-service' | ||
|
||
export class BackendPlayApi { |
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 should not be naming your files in the frontend BackendX
. (goes for all files that you have named backend
)
Instead you should have a form of service that does what you want.
In this case BackendPlayApi
would be more aptly named PlayService
.
First of, this is not the backend. The file tries to call something on the backend, but that just not justify calling this backend
.
What if you don't need to call the backend at all in order to play files? Then you would have to rename your class.
It's an implementation detail whether or not this class calls the backend and we don't expose implementation details.
Second: This is not an API, so it shouldn't be called Api
.
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 get what is being said, and will gladly make that change, but i would like a talk about it together with Anders too, as the names was sorta his suggestion, and also matches with how the domain communication/naming should be when UT-203 is eventually done.
import { SocketService } from './socket-service' | ||
|
||
export class BackendPlayApi { | ||
static readonly instance = new BackendPlayApi( |
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 is this a singleton?
I get that you want to reuse the Socket connection, but you are allready using a singleton for the Socket, so there is absolutely no need for this also being a singleton (this applies to basically all the singletons you made).
Also, when you make a singleton you must make the constructor private as well.
There is currently nothing stopping me from writing new BackendPlayApi(SocketService.instance.getSocket())
and completely bypass your singleton.
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.
All but one service have been made not singleton.
'File containing hidden files not found, or not yet stored.' | ||
) | ||
} | ||
PersistenceService.instance |
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 should not be calling you singletons instance methods every single time you need them. This is a very bad practice and this is basically the same as you just making all the methods on the class static.
How are you going to mock away the dependency in a unit test?
How are you going to provide a different implementation without having to change everywhere you use it?
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.
UT-221 exists to specifically address this.
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.
All but one service is no longer singleton.
class PersistanceService { | ||
public loadFile(fileName: string): any { | ||
return fs.readFileSync(path.resolve('storage', fileName)) | ||
export class PersistenceService { |
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 class is reading and writing to files. There is no state saved in the class. There is no need for this class to be a singleton.
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.
No longer a singleton.
src/shared/services/array-service.ts
Outdated
@@ -0,0 +1,19 @@ | |||
export class ArrayService { | |||
static readonly instance = new ArrayService() | |||
fillWithDefault<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.
This class is completely redundant. The class does one thing and that one thing is allready provided by default in Typescript as Array.fill()
.
This class should be deleted.
Also, you made it a singleton. Singleton should only be used when you want to ensure all consumers accesses the same data/state. This is a utility service, so there is neither class/nor state in here, so this should not be a singleton.
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.
First, it is no longer a singleton.
Secondly, does the Array.fill()
work to fill up an existing array with more/new values?
Say you have an array with 3 numbers (eg. 1, 2 and 3) and then wants to fill in more numbers up to 8, with some default, does it work for that?
From what i can tell Array.fill()
will not expand an array when used on an existing array, it will only overwrite existing slots.
refactor: removes singleton usage for all but one services. refactor: renames backend-play-api to socket-play-service. refactor: renames backend-settings-api to socket-settings-service. refactor: renames backend-operation-api to socket-operation-service. refactor: moves backend-observer to new observer folder, and splits it into connection-observer, settings-observer, operation-observer and play-observer.
…ggested by Lindved.
refactor: renames array-service to utility-service. refactor: renames fillWithDefault to expandArrayWithDefaultsIfNeeded refactor: adds function on utility-service to convert Pascal case to Screaming Snake case and vice-versa. refactor: changes the 'edit visibility' button to be a dropdown per output. refactor: renames a function argument to better describe its content.
…footer not being saved.
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 approve under the assumption that the comments I have provided are addressed.
Some of them might be outdated, in which case you can just ignore them. I had some comments I made some time ago, that I forgot to commit – sorry about that :/
src/client/components/application-router/application-router.tsx
Outdated
Show resolved
Hide resolved
import { reduxStore } from '../../../../shared/store' | ||
import { toggleSettingsVisibility } from '../../../../shared/actions/app-navigation-action' | ||
|
||
export default function ApplicationHeader(): JSX.Element { |
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.
@LindvedKrvang Personally I think Header
is too generic. I would expect some sort of generic header
element if the component is called Header
. Application
might not be the best fit, but then we could have a look at what kind of header this is and give it an appropriate name. Since the primary focus of the header is to display information about what media is playing and how it is played, then perhaps MediaHeader
, MediaPlayerHeader
or MediaControlHeader
? Other suggestions are welcomed 😁
src/client/components/header/timer-thumbnail/timer-thumbnail.scss
Outdated
Show resolved
Hide resolved
@@ -1,4 +1,4 @@ | |||
.c-thumbnail-overlay-display { | |||
.c-card-overlay-display { |
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 class names are inconsistent. Sometimes card
is used as a prefix and at other times it is used as a suffix. This should be consistent.
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.
So we have the actual cards using card
as suffix (image-media-card
, text-media-card
& media-card
). Last of those is the base component used by the other two.
Then we have the ones that use card
as prefix (card-overlay
, card-overlay-display
, and kinda selected-card-overlay
).
card-overlay
and selected-card-overlay
are displayed by card-overlay-display
when the correct conditions are met.
card-overlay-display
are displayed on image-media-card
and text-media-card
.
When used as a prefix, it it to indicate that they are to be used one af card
. Whereas when used as a suffix, it is to define the components as a card.
Not sure if the names still needs to change, and if so, to what...
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 "card" flow seems a bit too complex. It will be a good candidate for a round 2 refactor at some later point :D
if (new ReduxSettingsService().isCardSelectedOnAnyOutput(fileName, state.settings) | ||
|| new ReduxSettingsService().isCardCuedOnAnyOutput(fileName, state.settings)) { |
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.
In this case it would make sense to store the ReduxSettingsService instance in a local variable, since it might be used multiple times.
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 resonse as this: #81 (comment)
const cleanFileName: string = new ReduxSettingsService().getCleanSelectedFileName(settingsOutput, state.settings) | ||
|| new ReduxSettingsService().getCleanCuedFileName(settingsOutput, state.settings) |
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 makes sense to store the ReduxSettingsService in a local variable, since it might get used multiple times in this scope.
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.
On one hand yes to reduce how many times it gets created.
But on the other hand no, cause then it will hardly ever get gc'ed, which was one of the reason for it not being singleton.
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.
If you instantiate it as a local variable, then at the end of the scope, the variable along with the instance should be collected :)
@@ -1,19 +1,18 @@ | |||
import React from "react" | |||
import './number-input.scss' | |||
import '../label/label.scss' | |||
import '../shared.scss' |
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 is the file called shared.scss?
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.
Because the css defined in it is shared between a handful of the different settings page components.
Not sure what other name might fit better.
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.
Calling it shared and having multiple different parts defined in here invites for other unrelated stuff to crawl in. In my opinion it could be split. I can see that a dedicated file for input styling was removed and the contents moved to shared.scss.
I will leave it be for this PR, but it would be nice at some point to have the CSS properly organized :)
src/client/settings-page/components/settings-actions/settings-actions.scss
Outdated
Show resolved
Hide resolved
refactor: updates a number of css classes to better follow BEMIT refactor: updates a number of arguemnts to better explain their content. refactor: moves many end parentheses to new line. refactor: split header component in two. One for base, and one specific for main-page. refactor: deletes tab.tsx by moving what it did to main-page.tsx. refactor: fixes typo.
refactor: extracts creation of most services to start of component/function/file. refactor: re-adds switch to convert enum value to css class.
…al mass preventing startup. refactor: converts express-handler into express-service. refactor: converts socket-io-server-handler into socket-io-server-handler-service. refactor: converts parts of caspar-cg-handler into casparcg-handler-service.
A PR for changes related to UT-210 and UT-178.