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

Compare since previous compare PR #4

Open
wants to merge 48 commits into
base: initial
Choose a base branch
from
Open

Compare since previous compare PR #4

wants to merge 48 commits into from

Conversation

mickmister
Copy link
Member

@mickmister mickmister commented Apr 15, 2024

Copy link
Member Author

@mickmister mickmister left a 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 Show resolved Hide resolved
src/App.tsx Outdated Show resolved Hide resolved
src/App.tsx Outdated Show resolved Hide resolved
src/App.tsx Outdated Show resolved Hide resolved
src/App.tsx Outdated

// Components

function Files() {
Copy link
Member Author

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.

Copy link
Member

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?

Copy link
Member Author

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

Copy link
Member Author

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

Copy link
Member

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 Show resolved Hide resolved
src/App.tsx Outdated
Comment on lines 93 to 97
function Submit() {
return <div className="submit">
<textarea placeholder="make a comment"></textarea>
<button>Submit</button>
</div>
Copy link
Member Author

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?

Copy link
Member

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?

Copy link
Member Author

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';
Copy link
Member

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.

Copy link
Member Author

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

Copy link
Member

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

)
}

return (
Copy link
Member

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


return (
<div>
<button onClick={() => handleAddComment({name: "New User", commentText: "This is a new comment."})}>
Copy link
Member

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

Copy link
Member Author

@mickmister mickmister left a 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
Comment on lines 93 to 97
function Submit() {
return <div className="submit">
<textarea placeholder="make a comment"></textarea>
<button>Submit</button>
</div>
Copy link
Member Author

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';
Copy link
Member Author

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

Copy link
Member

@aelishRollo aelishRollo May 1, 2024

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

Copy link
Member Author

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

Copy link
Member

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

Copy link
Member

@aelishRollo aelishRollo May 3, 2024

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:

  1. 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?

  1. 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

  2. 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?

  3. 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

  4. I'd like to know more about what you have in mind for the project page with different views

Copy link
Member

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!

Copy link
Member Author

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 = {
Copy link
Member Author

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

Suggested change
export type sectionData = {
export type SectionData = {

src/types.ts Outdated

export type chordProgression = string[]

export type chordProgressionProps = {
Copy link
Member Author

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[] = [
Copy link
Member Author

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

@@ -0,0 +1,16 @@
import * as Types from './types';

export const SectionTitle: React.FC<Types.sectionDataProps> = ({ sectionData }) => {
Copy link
Member Author

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
Comment on lines 7 to 8
{files.map((file) => <div id={file.id}>
{file.title}
Copy link
Member Author

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?

Suggested change
{files.map((file) => <div id={file.id}>
{file.title}
{files.map((file) => (
<div id={file.id}>
{file.title}

Copy link
Member

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
Comment on lines 21 to 30
};



//




//
Copy link
Member Author

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>
Copy link
Member Author

Choose a reason for hiding this comment

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

Suggested change
<span>3 Comments</span>
<span>{comments.length} Comments</span>

src/Comments.tsx Outdated
@@ -0,0 +1,30 @@
import * as Types from './types';
Copy link
Member Author

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

@mickmister
Copy link
Member Author

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

Copy link

netlify bot commented May 12, 2024

Deploy Preview for section-view ready!

Name Link
🔨 Latest commit bc5af78
🔍 Latest deploy log https://app.netlify.com/sites/section-view/deploys/665b7316b28a480008968330
😎 Deploy Preview https://deploy-preview-4--section-view.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link
Member Author

@mickmister mickmister left a comment

Choose a reason for hiding this comment

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

This code is awesome 🎉 🤫 Coming along very well. Adding some more comments for discussion

I think setting up with a "backend" (localStorage being used as a key -> db table store), we can start building out a working prototype

}


export const ChordProgression: React.FC<ChordProgressionProps> = ({ chordProgression }) => {
Copy link
Member Author

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

Suggested change
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
Copy link
Member Author

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 }) => {
Copy link
Member Author

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
Comment on lines 8 to 16
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);
Copy link
Member Author

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 of Comment
  • 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.

Comment on lines 18 to 33
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);
}
Copy link
Member Author

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
Comment on lines 11 to 17
{files.map((file) => (
<div id={file.id}>
{file.title}
<br></br> <br></br>
{file.numComments + ' '}
Comments
</div>)}
</div>))}
Copy link
Member Author

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

Copy link
Member Author

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

<button> Save revision </button>
<div className="section-title">
<div className='text'>
<h1>{currentTitle}</h1>
Copy link
Member Author

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

Copy link
Member Author

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

Comment on lines 23 to 26
const formData = new FormData(event.currentTarget); // Using event.currentTarget to reference the form
const newName = formData.get('newName') as string;
setCurrentTitle(newName);
setShowForm(false);
Copy link
Member Author

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 }) => {
Copy link
Member Author

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

aelishRollo and others added 2 commits May 24, 2024 16:18
* 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
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.

2 participants