-
Notifications
You must be signed in to change notification settings - Fork 0
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
Compare since previous compare PR #4
base: initial
Are you sure you want to change the base?
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.
Nice work @aelishRollo! 🚀 🤫 This is amazing. I can't wait to be a user of this functionality
The improvements on the code are fantastic 👍
I have a few more comments regarding:
- Creating types to use as props
- Separating definitions of components to be outside of the
App
component's definition - Moving test data to its own file
- Using React
key
prop for arrays of elements
src/App.tsx
Outdated
|
||
// Components | ||
|
||
function Files() { |
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 have all of the components defined inside of this App
component, as well as the creation of the test data happening inside of the component. This has a side effect of making it so React has no context of when things "change" here, or when they have "stayed the same" between renders.
The reference to the child component definitions are changing every time, and the data being passed in as props (at least they likely will be later). For minimal refactoring to improve this, I recommend the following making the following changes:
Create types.ts
export type FileInfo = {
id: string;
// ...
}
// Other types...
Create testdata.ts
import {
FileInfo,
// Other types...
} from './types';
const files: FileInfo[] = [
{
id: '1',
title: 'my_file_name'.mp3,
// ...
},
// ...
];
const comments: Comment[] = [
];
export const testData = {
// this is shorthand for `files: files`
files,
comments,
}
Then in App.tsx
import {
FileInfo,
Comment,
} from './types';
import {testData} from './testdata';
function App() {
return (
<div className="root">
<SectionTitle sectionData={testData.sectionData} />
<ChordProgression chordProgression={testData.chordProgression} />
<Files files={testData.files} />
<Comments comments={testData.comments} />
<Submit />
</div>
);
}
// Files component
type FilesProps = {
files: FileInfo
}
function Files(props: FileProps) {
// then use `props.files` to render things
}
We define components like Files
outside of the definition of App
. Ideally these are defined in their own files, but keeping them in the same file as App
is less work for now. Separating the definitions to be on the module-level of the file is important though.
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.
Okay, so do you want me to move the components to their own files?
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.
Yeah each of the components seem separate enough to warrant living in their own individual files
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.
Also vscode and typescript make it really easy to move components into other files. You can highlight the text of the component, then right-click and select "Refactor..." (I prefer to use the command palette and type "refactor" instead of right-clicking), then select "Move to a new file". It generates a new file, which you can then rename and relocate as necessary. vscode will automatically:
- Move that component's function to a new file, and export it as a named export
- Import that component in the original file
- Move/copy and necessary imports (like React) into the new file, and remove them from this file if they are no longer used anywhere in this file
You don't have to do any of these things. It just does it for you. It's called "refactor" here since the change is made cleanly, and the functionality of the program doesn't change
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.
Yeah man, I love the refactor command! It's so useful
src/App.tsx
Outdated
function Submit() { | ||
return <div className="submit"> | ||
<textarea placeholder="make a comment"></textarea> | ||
<button>Submit</button> | ||
</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.
It probably makes sense to have the submit button trigger an update to the array of comments shown. That would make the code more ready for porting over.
We would add a prop to this Submit
component for the parent component to process the form submission.
Also, I'm thinking this component can be called CreateComment
to be more specific about what it's doing. We can then have the passed in callback prop to be called onSubmit
. What do you think?
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 that renaming the component to CreateComment is a good idea for sure, since the name is now more clear as to its purpose.
I believe that in order for this component to update the array of comments shown, there are a few options we could go for, namely:
- just adding a comment to the array (low complexity, but only a temporary soltion)
- adding the array to local storage and actually updating the array there (a bit more involved but more of a long term solution)
What are your thoughts on 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.
I'm thinking localStorage will be useful to help with mocking the actual backend now. Probably good idea to have a client.ts file that acts like we're fetching and saving to the backend. That will handle all localStorage things behind the scenes. Here, client
refers to "http client" in a way. It's our gateway to the backend's endpoints, but we can fake the client's implementation with localStorage for now.
export const fetchAllDataForSectionPage = async (sectionId: string): Promise<SectionPageAllData> => {
return getSectionDataFromLocalStorage(sectionId);
};
export const addNewCommentToSection = async (sectionId: string, commentText: string): Promise<Comment> => {
const sectionData = getSectionDataFromLocalStorage(sectionId);
const id = Math.random().toString().slice(2); // generate random string
const newComment: Comment = {
id,
message: commentText,
};
sectionData.comments.push(comment);
saveSectionDataToLocalStorage(sectionId, sectionData);
return newComment;
};
After this we can have a browse project page that will help show other aspects of the UI. It would be a view where you can select a given section in a project. Then we could also have a projects browser that allows you to see a list of projects. What do you think?
src/Comments.tsx
Outdated
@@ -0,0 +1,30 @@ | |||
import * as Types from './types'; |
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.
Currently when new comments are added, there are some rendering issues.
I think the CSS for this section needs to be made more elastic.
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.
Let's go through this part in a pair programming session
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 appreciate that! I actually already fixed it
src/CreateComment.tsx
Outdated
) | ||
} | ||
|
||
return ( |
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 should add a <textarea> or here to capture the user's comment
src/CreateComment.tsx
Outdated
|
||
return ( | ||
<div> | ||
<button onClick={() => handleAddComment({name: "New User", commentText: "This is a new 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.
This data should come from the form, rather than being hardcoded like it is now
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.
Amazing work @aelishRollo! 🚀 🧪 💩
A few more comments for discussion. This is looking so good! I'd say let's get this to be more-or-less functional on a useState
level, then we can move forward with other PRs for any backend stuff or other pages.
src/App.tsx
Outdated
function Submit() { | ||
return <div className="submit"> | ||
<textarea placeholder="make a comment"></textarea> | ||
<button>Submit</button> | ||
</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.
I'm thinking localStorage will be useful to help with mocking the actual backend now. Probably good idea to have a client.ts file that acts like we're fetching and saving to the backend. That will handle all localStorage things behind the scenes. Here, client
refers to "http client" in a way. It's our gateway to the backend's endpoints, but we can fake the client's implementation with localStorage for now.
export const fetchAllDataForSectionPage = async (sectionId: string): Promise<SectionPageAllData> => {
return getSectionDataFromLocalStorage(sectionId);
};
export const addNewCommentToSection = async (sectionId: string, commentText: string): Promise<Comment> => {
const sectionData = getSectionDataFromLocalStorage(sectionId);
const id = Math.random().toString().slice(2); // generate random string
const newComment: Comment = {
id,
message: commentText,
};
sectionData.comments.push(comment);
saveSectionDataToLocalStorage(sectionId, sectionData);
return newComment;
};
After this we can have a browse project page that will help show other aspects of the UI. It would be a view where you can select a given section in a project. Then we could also have a projects browser that allows you to see a list of projects. What do you think?
src/App.tsx
Outdated
import { Comments } from './Comments'; | ||
import { CreateComment } from './CreateComment'; | ||
import { SectionTitle } from './SectionTitle'; | ||
import { useState } from 'react'; |
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 like to add some linting to allow us to have a consistent ordering of imports. Then you can configure vscode to perform any lint fixes every time the file is saved locally
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.
Okay, so what about integrating ESLint or Prettier in my local repo? That's one option, but I'm curious what approach you think is best
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.
Yeah you can do it yourself as well. I would focus on eslint out of those two. I've been trying out biomejs but it's a bit too opinionated to me and doesn't allow for enough customization. Eslint supports all rules you could ever want, plus any styling that is supported by prettier for the most part. Eslint is king
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 about we talk about this over video? I feel like there's a fair bit to unpack in this series of comments and I want to be sure we're on the same page
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.
Here's the things I'm trying to figure out about this:
- What’s Promise <SectionPageAllData ?
How do I know what the shape of this type will need to look like when I’m writing something similar in the future?
I guess the type here is for an object which contains all the data from localstorage/backend?
-
How are these two functions related? One seems to be for adding a comment and the other is for getting a file from localstorage. I get that they’re both useful but it feels kind of arbitrary to store them both together since they don’t seem related other than both making use of our mock backend. Maybe that’s the reason
-
The first function almost feels redundant. The function call it’s returning has essentially the same name as it. So what’s the point of it?
-
I’m not sure what you mean that client refers to http client. I know what a client is, but I’m not understanding the significance of this statement
-
I'd like to know more about what you have in mind for the project page with different views
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'm thinking about this, and I think that one large appeal of the code you wrote is that we're already giving things types such they could be async, despite localStorage being synchronous, so that when we go go to an actual backend we don't have to change them.
Would love to know your thoughts!
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 that one large appeal of the code you wrote is that we're already giving things types such they could be async, despite localStorage being synchronous, so that when we go go to an actual backend we don't have to change them.
@aelishRollo Yes that's correct 👍 You got it perfectly. We should "act" like we are working with a real backend as much as possible, including the naming and usage of functions being agnostic to which "backend" we are using, so we can then swap them out from each other. It may make even more sense to create an interface
, and have a class LocalStorageBackendAPI
that implements that interface.
As far as the term "backend", you could call the localStorage
in this case a "storage backend", so it kind of already is a backend in itself.
src/types.ts
Outdated
|
||
import React, { Dispatch } from "react" | ||
|
||
export type sectionData = { |
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.
Types are typically capitalized like so
export type sectionData = { | |
export type SectionData = { |
src/types.ts
Outdated
|
||
export type chordProgression = string[] | ||
|
||
export type chordProgressionProps = { |
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.
Props are typically defined right above the component that accepts those props, and are usually not exported either. Any parent component rendering this component infers the prop types automatically. Even though the type would not be exported, it implicitly is when calling/rendering the component since it accepts that as an argument
src/testData.ts
Outdated
|
||
export const currentChordProgression: Types.chordProgression = ['C', 'Dm', 'F', 'G'] | ||
|
||
export const files: Types.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.
I think I would actually expect Types
to be written as types
. Namespaces like that are typically lowercased
src/SectionTitle.tsx
Outdated
@@ -0,0 +1,16 @@ | |||
import * as Types from './types'; | |||
|
|||
export const SectionTitle: React.FC<Types.sectionDataProps> = ({ sectionData }) => { |
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'll want this component to be editable by the user, so they can rename the section if desired
src/Files.tsx
Outdated
{files.map((file) => <div id={file.id}> | ||
{file.title} |
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 would prefer this syntax in this case. What do you think?
{files.map((file) => <div id={file.id}> | |
{file.title} | |
{files.map((file) => ( | |
<div id={file.id}> | |
{file.title} |
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.
Seems more like you're indenting it in a similar way to any other loop. It's also nice to return the JSX inside a set of parentheses like that. It seems nice and readable.
I like it and think we should use it
src/Comments.tsx
Outdated
}; | ||
|
||
|
||
|
||
// | ||
|
||
|
||
|
||
|
||
// |
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.
Nice
src/Comments.tsx
Outdated
export const Comments: React.FC<Types.commentsProps> = ({ comments }) => { | ||
return ( | ||
<div className="comments"> | ||
<span>3 Comments</span> |
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.
<span>3 Comments</span> | |
<span>{comments.length} Comments</span> |
src/Comments.tsx
Outdated
@@ -0,0 +1,30 @@ | |||
import * as Types from './types'; |
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.
Let's go through this part in a pair programming session
Leaving some notes here from a text conversation regarding backend design I think we can avoid writing Go code by using Mattermost's API for data management. Any comments or files would go through that API. Project and music related data can live in an SQL database separate from Mattermost. We can handle shared authentication with supabase. Sign up for jam tools using supabase, get soundtown thrown in for free. And vise versa So Mattermost channels would be an abstraction on the level of a project. A thread is any comment thread on any object that we want to support commenting on Then channel membership governs file and comment access directly, for free. For making them completely public, the "public profile" thing is down the road where I'll visit that then So we can have comments in "the database" for now, and for file uploads we use URLs for now. Data access control and permissions can be ignored at the moment, as that is all going to be handled by Mattermost. Everything is "public" atm since there will only be no authentication, until we set up supabase for the project. We should probably just set that up so we can get the database going So Mattermost doesn't exist for now, as we will sort of mock its behavior by other means. Setting up supabase will get our production database up and be ready for authentication You could then control data access through team and channel membership. And use public links to files for public uploads. Then fetch thread through a superuser or MM plugin What would be even more lightweight would be to use localStorage, and have an import/export feature, to mimick storing in database and having data be portable |
* create ci workflows for build and type checking * ignore empty dep array in useEffect * add linting
✅ Deploy Preview for section-view ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
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.
} | ||
|
||
|
||
export const ChordProgression: React.FC<ChordProgressionProps> = ({ chordProgression }) => { |
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.
Not necessarily a problem, but having two things named the same (this component and types.ChordProgression
, which is exported as a named export in the types.ts
file) can cause issues when you want to use your IDE to import one of those two things. I usually name this ChordProgressionView
or something like that to avoid the name collision
export const ChordProgression: React.FC<ChordProgressionProps> = ({ chordProgression }) => { | |
export const ChordProgressionView: React.FC<ChordProgressionProps> = ({ chordProgression }) => { |
src/Comments.tsx
Outdated
|
||
// Check if there are stored comments, otherwise use initial comments | ||
setComments(storedComments || initialComments); | ||
}, []); // Empty dependency array ensures this runs once on mount |
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.
Nice comment showing you know how this works. The comment itself is probably unnecessary for production code
Edit: This was signaling a linting error, so I disabled this line from the linting process
src/Comments.tsx
Outdated
|
||
|
||
export const Comments: React.FC<Types.commentsProps> = ({ comments = initialComments }) => { | ||
export const Comments: React.FC<types.commentsProps> = ({ comments = initialComments, setComments }) => { |
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.
Nit: types.commentProps
should be moved into this file as Props
or CommentsProps
src/Comments.tsx
Outdated
export const Comments: React.FC<types.commentsProps> = ({ comments = initialComments, setComments }) => { | ||
|
||
useEffect(() => { | ||
// Attempt to load comments from localStorage | ||
const storedCommentsJSON = localStorage.getItem('comments'); | ||
const storedComments = storedCommentsJSON ? JSON.parse(storedCommentsJSON) : null; | ||
|
||
// Check if there are stored comments, otherwise use initial comments | ||
setComments(storedComments || initialComments); |
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.
Eventually there will be other places where comments will be rendered, with comments on other types of things even. I think this component should receive one of these as a prop:
comments
- Array ofComment
entity
- {entity_type: string, entity_id: string}; mentioned in Comments data model #5
I like the second one more. Then the Comments
component is responsible for fetching from the store these comments.
Fetch all project data at page load from project id pointers. But lazy load comment bodies. Make use of "See comments" buttons at times to delay loading.
src/CreateComment.tsx
Outdated
const handleAddComment = () => { | ||
const newComment = { name, commentText }; | ||
|
||
// Fetch existing comments from localStorage | ||
const storedComments = localStorage.getItem('comments'); | ||
const existingComments = storedComments ? JSON.parse(storedComments) : []; | ||
|
||
// Add the new comment to the array | ||
const updatedComments = [...existingComments, newComment]; | ||
|
||
// Save the updated array back to localStorage | ||
localStorage.setItem('comments', JSON.stringify(updatedComments)); | ||
|
||
// Update the local state to reflect the new list of comments | ||
setComments(updatedComments); | ||
} |
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.
Swag 👍 🤫
src/Files.tsx
Outdated
{files.map((file) => ( | ||
<div id={file.id}> | ||
{file.title} | ||
<br></br> <br></br> | ||
{file.numComments + ' '} | ||
Comments | ||
</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.
Indentation looks off here. Should be more like this:
{files.map((file) => (
<div id={file.id}>
{file.title}
<br></br> <br></br>
{file.numComments + ' '}
Comments
</div>
))}
Also I tend to not use <br>
, and instead do vertical spacing with css. Here I think I arbitrarily would create a file-title
class that has margin-bottom
{files.map((file) => (
<div id={file.id}>
<p className='file-title'>{file.title}</p>
<p className='file-num-comments'>
{file.numComments + ' '}
Comments
</p>
</div>
))}
.file-title {
margin-bottom: 30px;
}
I set them to p
, though span
with display: inline-block
would also work
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'm sure there's a more appropriate way to make that positioned that way vertically. Also we need to make sure this looks good on mobile
src/SectionTitle.tsx
Outdated
<button> Save revision </button> | ||
<div className="section-title"> | ||
<div className='text'> | ||
<h1>{currentTitle}</h1> |
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 have an idea of user experience of how we can edit the title. I think we should be able to click the current title, or an edit button next to it, and then the title becomes an input box where you can edit its content. Then the submit button changes back to an h1
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.
For an edit button, we can use the pencil icon from FontAwesome
https://fontawesome.com/icons/pencil?f=classic&s=solid
https://fontawesome.com/search
src/SectionTitle.tsx
Outdated
const formData = new FormData(event.currentTarget); // Using event.currentTarget to reference the form | ||
const newName = formData.get('newName') as string; | ||
setCurrentTitle(newName); | ||
setShowForm(false); |
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.
Interesting to get the data from the form this way. Any particular reason you preferred this over using useState
etc.?
src/Comments.tsx
Outdated
import { useEffect} from 'react'; | ||
|
||
|
||
export const Comments: React.FC<types.commentsProps> = ({ comments = initialComments, setComments }) => { |
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 may make sense to create a src/components
folder to put components like this
* initial take on refactoring to store and client * connect section title to store * make the app use localStorage * remote IStore interface to simplify. fix workflow test command * use typed in username for comments * add import for index.css
https://section-view.netlify.app