-
Notifications
You must be signed in to change notification settings - Fork 98
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement of repo collection [OSPP 2023] (#713)
* feat: developed RepoCollection Button through antd component * feat: implement LocalStorage,SetDefault,DeleteCollection,AddCurRepo feature * style: yarn run prettier for index.scss * feat: refactor view * feat: implement storage feature * feat: implement collection modal from popup button * feat: implement collection modal using tabs and repo list beside * feat: implement collection modal from repository dropdown click * feat: solve reopen modal problem * feat: implement collection editor function (quick import need to be done) * feat: add judgment for whether there is initial data * feat: create CollectionEditor component * feat: use GitHub REST API to get repo and its description * feat: implement CollectionEditor add and edit feature * feat: solve formatting and naming issues * feat: refactor data structure * refactor: implement the collection button in the original GitHub style (step 1) * chore: update yarn.lock * fix: update module import * chore: update yarn.lock * fix: duplicate rendering after tab switches * fix: prevent duplicate finally * refactor: give CollectionModal a separate directory * refactor: CollectionList (30%) and AddToCollections (1%) * feat: support basic interactions with CollectionButton * chore: disable charts-design * chore: allow overflow when there are many exsiting collections * chore: add comments * chore: add manage button in AddToCollections * refactor: a possible final directory * chore: support to open modal from CollectionList * fix: fix typo * feat: add confirm check before collection deletion * feat: set default key for Table and add Radio for quick import (User/Organization) * refactor: move custom type definition to context.ts * refactor: use constate to better manage locally global state * feat: store and useStore for repo-collection * chore: update repo name after turbo:restore * feat: replace some part with real data * feat: AddToCollections(100%) * fix: a simple lock mechanism to prevent concurrent updates in store.ts * feat: implement CollectionDisplayModal and rename filename * refactor: rename view.tsx * feat: add divider for footer in AddToCollections * refactor: remove redundant initialization * refactor: rollback and delete displayModal * feat: implement of CollectionManageModal * feat: implement of CollectionEditor confirm * feat: use await/async function * chore: fix typo and add validator for duplicate collection name * style: maximize the collection modal size --------- Co-authored-by: Lam Tang <tangyenan@gmail.com>
- Loading branch information
1 parent
86a3357
commit e83ec15
Showing
14 changed files
with
1,299 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
189 changes: 189 additions & 0 deletions
189
src/pages/ContentScripts/features/repo-collection/CollectionButton/AddToCollections.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
import { useRepoCollectionContext } from '../context'; | ||
import { Collection } from '../context/store'; | ||
|
||
import React, { useEffect, useState } from 'react'; | ||
|
||
const CheckListItem = ( | ||
collection: Collection, | ||
onChange: (collectionId: Collection['id'], checked: boolean) => void, | ||
checked: boolean | ||
) => { | ||
const handleChange = () => { | ||
onChange(collection.id, checked); | ||
}; | ||
|
||
return ( | ||
<div | ||
key={collection.id} | ||
className="form-checkbox mr-3 ml-6 ml-sm-5 mb-2 mt-0" | ||
> | ||
<label className="f5 text-normal"> | ||
<input | ||
type="checkbox" | ||
value={collection.id} | ||
checked={checked} | ||
onChange={handleChange} | ||
/> | ||
{collection.name} | ||
</label> | ||
</div> | ||
); | ||
}; | ||
|
||
/** | ||
* The modal for quickly adding the current repository to existing collections (also for removing) | ||
*/ | ||
export const AddToCollections = () => { | ||
const { | ||
currentRepositoryId, | ||
currentRepositoryCollections, | ||
allCollections, | ||
updaters, | ||
hideAddToCollections, | ||
setHideAddToCollections, | ||
setHideCollectionList, | ||
setShowManageModal, | ||
} = useRepoCollectionContext(); | ||
|
||
const [checkedCollectionIds, setCheckedCollectionIds] = useState< | ||
Collection['id'][] | ||
>([]); | ||
|
||
const resetCheckboxes = () => { | ||
setCheckedCollectionIds(currentRepositoryCollections.map((c) => c.id)); | ||
}; | ||
|
||
// reset checkboxes when currentRepositoryCollections changes | ||
useEffect(() => { | ||
resetCheckboxes(); | ||
}, [currentRepositoryCollections]); | ||
|
||
const handleCheckChange = ( | ||
collectionId: Collection['id'], | ||
checked: boolean | ||
) => { | ||
if (checked) { | ||
setCheckedCollectionIds( | ||
checkedCollectionIds.filter((id) => id !== collectionId) | ||
); | ||
} else { | ||
setCheckedCollectionIds([...checkedCollectionIds, collectionId]); | ||
} | ||
}; | ||
|
||
const goToCollectionList = () => { | ||
setHideAddToCollections(true); | ||
setHideCollectionList(false); | ||
}; | ||
|
||
const apply = () => { | ||
// add/remove relations | ||
const toAdd = checkedCollectionIds.filter( | ||
(id) => !currentRepositoryCollections.some((c) => c.id === id) | ||
); | ||
const toRemove = currentRepositoryCollections.filter( | ||
(c) => !checkedCollectionIds.includes(c.id) | ||
); | ||
toAdd && | ||
updaters.addRelations( | ||
toAdd.map((id) => ({ | ||
collectionId: id, | ||
repositoryId: currentRepositoryId, | ||
})) | ||
); | ||
toRemove && | ||
updaters.removeRelations( | ||
toRemove.map((c) => ({ | ||
collectionId: c.id, | ||
repositoryId: currentRepositoryId, | ||
})) | ||
); | ||
|
||
goToCollectionList(); | ||
}; | ||
|
||
const cancel = () => { | ||
resetCheckboxes(); | ||
|
||
goToCollectionList(); | ||
}; | ||
|
||
const manage = () => { | ||
// open modal to manage collections | ||
setShowManageModal(true); | ||
}; | ||
|
||
// if the ids of currentRepositoryCollections are the same as the ids of selectedCollectionIds, then the "Apply" button should be disabled | ||
let isApplyDisabled: boolean; | ||
if (currentRepositoryCollections.length !== checkedCollectionIds.length) { | ||
isApplyDisabled = false; | ||
} else { | ||
isApplyDisabled = currentRepositoryCollections.every((c) => | ||
checkedCollectionIds.includes(c.id) | ||
); | ||
} | ||
|
||
return ( | ||
<div | ||
className="notifications-component-dialog" | ||
hidden={hideAddToCollections} | ||
> | ||
<div className="SelectMenu-modal notifications-component-dialog-modal overflow-visible"> | ||
<header className="d-none d-sm-flex flex-items-start pt-1"> | ||
<button | ||
className="border-0 px-2 pt-1 m-0 Link--secondary f5" | ||
style={{ backgroundColor: 'transparent' }} | ||
type="button" | ||
onClick={goToCollectionList} | ||
> | ||
<svg | ||
style={{ position: 'relative', left: '2px', top: '1px' }} | ||
aria-hidden="true" | ||
height="16" | ||
viewBox="0 0 16 16" | ||
version="1.1" | ||
width="16" | ||
className="octicon octicon-arrow-left" | ||
> | ||
<path d="M7.78 12.53a.75.75 0 0 1-1.06 0L2.47 8.28a.75.75 0 0 1 0-1.06l4.25-4.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042L4.81 7h7.44a.75.75 0 0 1 0 1.5H4.81l2.97 2.97a.75.75 0 0 1 0 1.06Z"></path> | ||
</svg> | ||
</button> | ||
|
||
<h1 className="pt-1 pr-4 pb-0 pl-0 f5 text-bold"> | ||
Add to collections | ||
</h1> | ||
</header> | ||
<legend> | ||
<div className="text-small color-fg-muted pt-0 pr-3 pb-3 pl-6 pl-sm-5 border-bottom mb-3"> | ||
Select collections you want the current repository to be added to. | ||
</div> | ||
</legend> | ||
{/* Checklist */} | ||
<div style={{ maxHeight: '200px', overflowY: 'auto' }}> | ||
{allCollections.map((collection) => { | ||
const checked = checkedCollectionIds.includes(collection.id); | ||
return CheckListItem(collection, handleCheckChange, checked); | ||
})} | ||
</div> | ||
{/* 3 buttons */} | ||
<footer className="SelectMenu-footer"> | ||
<div className="pt-2 pb-3 px-3 d-flex flex-justify-start flex-row-reverse"> | ||
<button | ||
disabled={isApplyDisabled} | ||
className="btn-primary btn-sm btn ml-2" | ||
onClick={apply} | ||
> | ||
Apply | ||
</button> | ||
<button className="btn-sm btn ml-2" onClick={cancel}> | ||
Cancel | ||
</button> | ||
<button className="btn-sm btn" onClick={manage}> | ||
Manage | ||
</button> | ||
</div> | ||
</footer> | ||
</div> | ||
</div> | ||
); | ||
}; |
127 changes: 127 additions & 0 deletions
127
src/pages/ContentScripts/features/repo-collection/CollectionButton/CollectionList.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import { useRepoCollectionContext } from '../context'; | ||
import { Collection } from '../context/store'; | ||
|
||
import React from 'react'; | ||
|
||
const ListItem = ( | ||
collection: Collection, | ||
onClick: (collectionId: Collection['id']) => void | ||
) => { | ||
const handleClick = () => { | ||
onClick(collection.id); | ||
}; | ||
|
||
return ( | ||
<div | ||
key={collection.id} | ||
className="SelectMenu-item flex-items-start btn border-0 rounded-0" | ||
onClick={handleClick} | ||
> | ||
<span className="text-small text-normal wb-break-all"> | ||
{collection.name} | ||
</span> | ||
</div> | ||
); | ||
}; | ||
|
||
/** | ||
* The modal that shows the collections that the repo belongs to | ||
*/ | ||
export const CollectionList = () => { | ||
const { | ||
currentRepositoryCollections, | ||
hideCollectionList, | ||
setHideAddToCollections, | ||
setHideCollectionList, | ||
setSelectedCollection, | ||
setShowManageModal, | ||
} = useRepoCollectionContext(); | ||
|
||
const handleCollectionClick = (collectionId: Collection['id']) => { | ||
setSelectedCollection(collectionId); | ||
setShowManageModal(true); | ||
}; | ||
|
||
const goToAddToCollections = () => { | ||
setHideAddToCollections(false); | ||
setHideCollectionList(true); | ||
}; | ||
|
||
return ( | ||
<div className="SelectMenu" hidden={hideCollectionList}> | ||
<div className="SelectMenu-modal"> | ||
<button | ||
className="SelectMenu-closeButton position-absolute right-0 m-2" | ||
type="button" | ||
data-toggle-for="collection-button-details" | ||
> | ||
<svg | ||
aria-hidden="true" | ||
height="16" | ||
viewBox="0 0 16 16" | ||
version="1.1" | ||
width="16" | ||
data-view-component="true" | ||
className="octicon octicon-x" | ||
> | ||
<path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path> | ||
</svg> | ||
</button> | ||
<div className="d-flex flex-column flex-1 overflow-hidden"> | ||
<div className="SelectMenu-list"> | ||
{/* header */} | ||
<header className="SelectMenu-header"> | ||
<h3 className="SelectMenu-title"> | ||
Contained in these collections | ||
</h3> | ||
<button | ||
className="SelectMenu-closeButton" | ||
type="button" | ||
aria-label="Close menu" | ||
data-toggle-for="collection-button-details" | ||
> | ||
<svg | ||
aria-hidden="true" | ||
height="16" | ||
viewBox="0 0 16 16" | ||
version="1.1" | ||
width="16" | ||
data-view-component="true" | ||
className="octicon octicon-x" | ||
> | ||
<path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path> | ||
</svg> | ||
</button> | ||
</header> | ||
{/* list */} | ||
<div className="overflow-y-auto" style={{ maxHeight: '340px' }}> | ||
{currentRepositoryCollections.map((collection) => | ||
ListItem(collection, handleCollectionClick) | ||
)} | ||
</div> | ||
{/* footer */} | ||
<footer className="SelectMenu-footer p-0 position-sticky"> | ||
<div | ||
className="SelectMenu-item btn rounded-0 border-bottom-0 text-normal f6" | ||
onClick={goToAddToCollections} | ||
> | ||
<svg | ||
width="20" | ||
aria-hidden="true" | ||
height="16" | ||
viewBox="0 0 16 16" | ||
version="1.1" | ||
data-view-component="true" | ||
className="octicon octicon-plus mr-2 text-center" | ||
> | ||
<path d="M7.75 2a.75.75 0 0 1 .75.75V7h4.25a.75.75 0 0 1 0 1.5H8.5v4.25a.75.75 0 0 1-1.5 0V8.5H2.75a.75.75 0 0 1 0-1.5H7V2.75A.75.75 0 0 1 7.75 2Z"></path> | ||
</svg> | ||
Add to collections | ||
</div> | ||
</footer> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; |
41 changes: 41 additions & 0 deletions
41
src/pages/ContentScripts/features/repo-collection/CollectionButton/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { CollectionList } from './CollectionList'; | ||
import { AddToCollections } from './AddToCollections'; | ||
import { useRepoCollectionContext } from '../context'; | ||
|
||
import React from 'react'; | ||
import { FundProjectionScreenOutlined } from '@ant-design/icons'; | ||
|
||
/** | ||
* The "Collections" button, which is in the left of the "Edit Pins" button | ||
*/ | ||
export const CollectionButton = () => { | ||
const { currentRepositoryCollections } = useRepoCollectionContext(); | ||
|
||
return ( | ||
<div className="f5 position-relative"> | ||
<details | ||
id="collection-button-details" | ||
className="details-reset details-overlay f5 position-relative" | ||
> | ||
<summary className="btn-sm btn" role="button"> | ||
<span> | ||
<FundProjectionScreenOutlined | ||
style={{ fontSize: '14px', color: 'var(--color-fg-muted)' }} | ||
/> | ||
{' Collections '} | ||
</span> | ||
<span | ||
data-pjax-replace="true" // not confirmed to be necessary | ||
data-turbo-replace="true" // this one, either. | ||
className="Counter" | ||
> | ||
{currentRepositoryCollections.length} | ||
</span> | ||
<span className="dropdown-caret"></span> | ||
</summary> | ||
<CollectionList /> | ||
<AddToCollections /> | ||
</details> | ||
</div> | ||
); | ||
}; |
Oops, something went wrong.