Skip to content
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-211: Rebuild Tabs #79

Merged
merged 38 commits into from
Mar 10, 2023
Merged

UT-211: Rebuild Tabs #79

merged 38 commits into from
Mar 10, 2023

Conversation

andr9528
Copy link
Contributor

Changes in this pull request are dependant on #78 being merged.

feature: creates new Tab components with accompanying CSS.
featuer: copied most of the CSS styling using by the existing framework, to maintain the same style.
feature: creates a new temporary 'App' file called 'AppNew' using the new Tab.
	- When the styling is as it should be, then the old 'App' is deleted and 'AppNew' is renamed, to take its place.
refactor: simplyfied the code of 'App' in the new 'AppNew'.
feature: adds the new 'AppNew' in index.tsx, with the old commented out, for easy toggle between under development.
fix: fixes footer showing while Cliptool is booting (i.e undefined value, which should be ignored).
fix: adds a fixed 4 pixel border to Thumbnails, where only the color now change on select. Stops the shrinking/growing of images on select.
feature: makes the new tab system the one used.
chore: cleans up some unneeded css.
chore: reorder imports to match comment, setting up where components/css are imported.
@andr9528 andr9528 marked this pull request as ready for review February 23, 2023 12:22
@andr9528
Copy link
Contributor Author

Changes in this PR should not be merged until after a new build has been performed of merged changes from #78, and a new tag has been generated for a release.
Reasoning behind this, is that we do not want to release these major refactorings (UT-211 & UT-210) in this next release.

src/client/components/Footer.tsx Outdated Show resolved Hide resolved
src/client/components/Tab/TabBar.tsx Outdated Show resolved Hide resolved
src/client/components/Tab/TabBar.tsx Outdated Show resolved Hide resolved
src/client/components/Tab/TabBar.tsx Outdated Show resolved Hide resolved
src/client/components/Tab/TabContent.tsx Outdated Show resolved Hide resolved
src/client/css/Tab.css Show resolved Hide resolved
src/client/index.tsx Outdated Show resolved Hide resolved
src/client/css/Tab.css Outdated Show resolved Hide resolved
# Conflicts:
#	src/client/components/App.tsx
#	src/client/components/Footer.tsx
#	src/client/css/Thumbnail.css
style: removes some unnecessary curly brackets.
style: undid changes in index.tsx.
style: removed a css line that had no effect.
Copy link

@RasmusAlbrektsen RasmusAlbrektsen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what i can see it looks fine. Of course there a other things that i know you are addressing in a refactor, so i won't mention those here :-)

refactor: moves location where settings is displayed to the main area.
refactor: renames and extracts 'SwitchRender' from Footer to new file called 'OperationModeFooter'.
refactor: renames 'EditVisibilityFooter' to 'OperationModeEditVisibilityFooter'.
refactor: moves html structure from 'FooterContent' to 'OperationModeEditVisibilityFooter'.
refactor: deletes 'TabItem' and moves its rendered html to 'TagContent'.
refactor: changes the test of 'Exit' under settings to 'Close Settings'.
style: updates the location of button under settings to be at the top.
style: updates the order of buttons under settings to be from least to most destructive.
style: updates the tabs to be scrollable incase they can't fit.
style: updates the header to be scrollable incase content can't fit.
# Conflicts:
#	src/client/components/Header.tsx
#	src/client/css/App.css
…urther improvements to settings.

refactor: renames 'handleToggleEditVisibilityMode' to 'handleEditVisibilityMode'.
feature: closes settings when 'edit visibility' is toggled on.
fix: fixes settings not updating live when changes where done.
chore: removes some unused code in settings.
…emoves warning/error in conole regarding causing re-render while re-rendering.

refactor: specifies more closely which properties has to change for a re-render to occour, instead of when any redux state change happens.
…under settings.

feature: adds correction of the structure of the save file, if any of the contained arrays are missing values.
refactor: extracts a duplicate value retrieval to a constant, just before use.
…gs is done. Fixes thumbnails not being scrollable in channel view.
# Conflicts:
#	src/client/components/Settings.tsx
chore: adds cleanup of the hiddenFile data on load and save, preventing tally files from entering.
	- I had an instance of a tally file being hidden, somehow, even though that was already being checked for.
	- Not entirely sure how it happened, so just chose to add more safeguards.
chore: adds early return for event handler of 'TOGGLE_THUMBNAIL_VISIBILITY' to help prevent hidding a tally file.
refactor: renamed a type to not include 'I' in name.
chore: includes a new packed version from 'yarn package'
Copy link
Contributor

@KvelaGorrrrnio KvelaGorrrrnio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have made some good changes. I have some comments for the swipe feature and a few other things.

src/client/components/Footer/OperationModeFooter.tsx Outdated Show resolved Hide resolved
src/client/components/Tab/TabContent.tsx Outdated Show resolved Hide resolved
src/client/components/Tab/TabContent.tsx Outdated Show resolved Hide resolved
src/client/components/Tab/TabContent.tsx Outdated Show resolved Hide resolved
src/client/components/Tab/TabContent.tsx Outdated Show resolved Hide resolved
src/server/utils/HiddenFilesStorage.ts Outdated Show resolved Hide resolved
src/server/utils/HiddenFilesStorage.ts Outdated Show resolved Hide resolved
Comment on lines 43 to 48
const isInvalidHiddenFiles: boolean =
validateHiddenFilesContent(hiddenFiles)
const cleanHiddenFiles: Record<string, HiddenFileInfo> =
!isInvalidHiddenFiles
? hiddenFiles
: clearInvalidHiddenFiles(hiddenFiles)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are calling validateHiddenFilesContent which on is stored in isInvalidHiddenFiles. That seems contradictory.
What is the intent of validateHiddenFilesContent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validateHiddenFilesContent checks if any of the outputs' tallyfiles are in the record of hidden files. It returns true when there are one or more in the record, meaning the record is invalid, and in need of updating.

Maybe some function naming need to change?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can consider removing the validate check and just clean the hidden files each time – as far as I see it will yield the same result.

Comment on lines +19 to 21
const settingsFromFile: IGenericSettings = JSON.parse(
fs.readFileSync(path.resolve('storage', 'settings.json'))
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having the type is better than not having it. But be aware that you are casting any to IGenericSettings. At runtime this might crash if the the structure of settings.json is different from what we imagined.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Afaik, it is being cast to IGenericSettings as soon as Redux gets it anyways.

Would it also not only crash, if a non-existent function is called?
In the case of an non-existent attribute being called a undefined would just be returned, or assigned to depending on the action.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would also crash if you use methods, like settingsFromFile.stringAttribute.length, where stringAttribute is undefined.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be updated during UT-210 to check the data read by use of Zod.

src/server/utils/SettingsStorage.ts Show resolved Hide resolved
refactor: reworks logic of changing tabs using swipe.
refactor: extracts swipe logic to new file called swipeService.ts, for easier reuse in the future.
refactor: simplifies the check for if the web page is currently on a 'channel' view.
…torage

style: changes color of text on the footer to match other "information" styles.
chore: removes some outcommented css.
refactor: extract duplicate code in 'saveHiddenFiles()' and 'loadHiddenFiles()' to a common function.
@andr9528
Copy link
Contributor Author

andr9528 commented Mar 9, 2023

Note that the 'Footer' changes that has been commented on previously here, has been cherry picked to UT-206/softDelete, as it was on that branch that the footer originally was created. As such it also makes sense that any improvements made, are there, as they should be released regardless of if Keven want the tab changes too.

Base automatically changed from UT-206/softDelete to develop March 9, 2023 08:43
Copy link
Contributor

@KvelaGorrrrnio KvelaGorrrrnio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job :) There are a few comments I would like you to have a look at before merging.
Most of them are okay to do in the refactor task.

import { TOGGLE_SHOW_SETTINGS } from '../../model/reducers/appNavAction'

import * as IO from '../../model/SocketIoConstants'
import { findThumbnail, getCleanTallyFile } from './Thumbnail'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that this will do refactored in the refactor task. The Header should not depend on the Thumbnail component. If it needs thumbnail specific information, then it should get if from a service instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes has already been solved in #81.

Comment on lines +35 to +37
try {
cleanTallyFile = getCleanTallyFile(output)
} catch {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this wrapped in a try-catch?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cliptool manages to render 1-2 timers before output has a non-undefined value.
Adding a '?' to the accessor of tallyFile inside the method might do the trick, but that will be done during UT-210, where the function has been extracted to a new location.

@@ -56,8 +68,8 @@ const handleLoopStatus = () => {
)
}

const loopStateStyle = () => {
return reduxState.media[0].output[reduxState.appNav[0].activeTab]?.loopState
const loopStateStyle = (loopState: boolean) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition 👍

@@ -0,0 +1,56 @@
const MIN_SWIPE_DISTANCE = 50
const MAX_SLOPE = 0.5
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency this could be renamed to MAX_SWIPE_SLOPE.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

return false
}
const verticalDistance = Math.abs(end.y - start.y)
if (verticalDistance / horizontalDistance > MAX_SLOPE) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracting this to a constant prior to the condition will make the condition slightly more understandable.

        const slope = verticalDistance / horizontalDistance
        if (slope > MAX_SLOPE) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And as you mentioned yourself yesterday, the condition could be inverted and returned directly. But that is up to you 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm don't remeber saying that, but it is true, so i have have inverted and returned directly.


.tab.active {
background-color: #4175e6;
/* border-bottom-color: #4175e6; */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I tend to split the CSS code into individual files when I'm working on projects that are component based. By splitting, it will be more obvious if make couplings between different components when styling.

Comment on lines +19 to 21
const settingsFromFile: IGenericSettings = JSON.parse(
fs.readFileSync(path.resolve('storage', 'settings.json'))
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would also crash if you use methods, like settingsFromFile.stringAttribute.length, where stringAttribute is undefined.

Comment on lines 43 to 48
const isInvalidHiddenFiles: boolean =
validateHiddenFilesContent(hiddenFiles)
const cleanHiddenFiles: Record<string, HiddenFileInfo> =
!isInvalidHiddenFiles
? hiddenFiles
: clearInvalidHiddenFiles(hiddenFiles)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can consider removing the validate check and just clean the hidden files each time – as far as I see it will yield the same result.

'utf8',
(error) => {
if (error) {
logger.data(error).error('Error writing file:')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It failed writing to what file?
The log won't give much value in e.g. Kibana without a bit more information.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added 'hiddenFiles' before 'file'. That should make it clear what failed.
Can / Should the log also log what it tried to save?

import { RenderFullHeader } from './Header'
import { OperationModeFooter } from './Footer/OperationModeFooter'
import Tabs from './Tab/Tabs'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't know where else to comment on folder names, but folder names should not be uppercased

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woops, you are right. However this is not a easy change to perform - I will be doing it for UT-210.

isSettingsOpen
? <SettingsPage />
: specificChannel
? <Thumbnail/>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't nest ternaries. They become too messy too fast.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fan of this one either, but not sure how to do it more cleanly at the moment.
If a change is to be performed, it will be done during UT-210.


const OFF_COLOR = { backgroundColor: 'grey' }
const ON_COLOR = { backgroundColor: 'rgb(28, 115, 165)' }

const RenderTime = () => {
const RenderTime = () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a component. No need to prefix component names with "Render" (it happens quite a lot in here, but I assume it's from before you started working on Cliptool)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, there is more than one component in this file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That structure is from before i started, and has already been fixed in #81

? ON_COLOR
: OFF_COLOR
}

const webStateStyle = () => {
return reduxState.media[0].output[reduxState.appNav[0].activeTab]?.webState
const webStateStyle = (webState: boolean) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loopStateStyle, mixStateStyle and webStateStyle are identical. Should be refactored to just be one function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has already been extracted to CSS for UT-210.

}

function emitSetOperationMode() {
function handleEditVisibilityMode(): void {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handle... refers to where the function is called from, not how the function is implemented. How does it handle it? That's the question the function name need to answer.


export default function TabContent(props: TabBarProps) {
const [touchStart, setTouchStart] = useState<Point | null>(null)
const [touchEnd, setTouchEnd] = useState<Point | null>(null)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does TabContent have to know about this? This seems like something that doesn't belong in a "Content" component.

Also, as I mentioned last week "Content" is a concept and you should not make xContent components.
In the concept of Tabs you would have your Tabbar where you have your Tabs and each Tab take some content to display when selected.
A rough pseudo-code example could looke like:

<TabBar>
  <Tab>
    <FrontPage/>
  </Tab>
  <Tab>
    <UserPage/>
  </Tab>
  <Tab>
    <SettingsPage/>
  </Tab>
</TabBar>

Here FrontPage, UserPage and SettingsPage are content of the Tab but they are not called content because they can be used outside of the Tab component. They just do whatever they are supposed to do regardless if they are being parsed as "content" or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commented on lines are used to support swipe actions to change tabs.

As for the name, this components is the one to wrap the content being displayed by Thumbnail of the selected tab.
What other name would you suggest?


return (
<div className='tabs'>
<TabBar tabData={tabData} selectedTab={selectedTab}/>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What' the difference between the Tabs component and the TabBar component? Both names indicate that has the same responsibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm 'Tabs' propably need a better name, not sure what however at the moment.
Fell free to let me know if you have good suggestion, then i will apply it during UT-210.

display: flex;
justify-content: center;
align-items: center;
padding: 20px;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary?
Can't remember the code for Footer but if you wrap it in a div you automatically get padding

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't wrapped in a div. That css is applied to the <footer> tag.

): IGenericSettings {
let generics = { ...originalGenerics }

// It must be possible to do the following in a smaller way... Not sure how currently though.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For each if-statement you do:

function yourFunction(...) {
  generics.outputLabels = getGenericOutputLabels(generics, defaultGenerics)
  generics.outputFolders = getGenericsOutputFolders(generics, defaultGenerics)
  ...
}

function getGenericOutputLabels(generics, defaults) OutputLabels[] { // or whatever the return type is
  if (generics.outputLabels.length === defaultGenerics.outputLabels.length) {
    return []
   }
  return getCombinedArray(
            generics.outputLabels,
            defaultGenerics.outputLabels
        )
}

Consider if defaults generics needs to be parse to the function and not just fecthed in the individual get.. methods

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better yet. Delete this since you said in Anders comment that this isn't needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current function structure will be gutted during UT-210, and the method changed to update the structure of an old settings file, to the new structure being implemented.

function getCombinedArray<T>(originalArray: T[], defaultArray: T[]) {
return [
...originalArray,
...defaultArray.slice(originalArray.length - defaultArray.length),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this does what you want it to do, atleast it doesn't delete duplicates.
If I have original = ['Hello', 'World'] and default = ['Hello', 'Foo', 'Bar', 'World'] it's going to return ['Hello', 'World', 'Bar', 'World']

If you want to remove duplicates then consider using Set

const arrayOne = [...]
const arrayTwo =[...]
new Set(arrayOne.combine(arrayTwo))

Copy link
Contributor Author

@andr9528 andr9528 Mar 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to take what has already been saved, and if the array doesn't have enough elements to match the length of the default, fill in the remaining 'slots' from the default.

The method in questions will also be gone with the refactor during UT-210, as the whole settings structure is getting rebuild more or less from scratch.

refactor: build a .exe file of the newest version.
refactor: changes close button of settings to say 'CLOSE SETTINGS' instead of 'DISCARD CHANGES', as the check if changes has been made has not been implemented for this branch, but will be during UT-210.
refactor: improves the logic return of 'isValidSwipe()' for readability.
refactor: improves log made if Cliptool fails to load a file for hiddenFiles.
@andr9528 andr9528 merged commit 39e3826 into develop Mar 10, 2023
@andr9528 andr9528 deleted the UT-211/reimplementTabs branch March 10, 2023 13:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants