Skip to content

Commit

Permalink
feat!: render roadmaps with d3 (#358)
Browse files Browse the repository at this point in the history
* chore: fix error when clicking roadmap item in d3 mode

* chore: render d3 roadmap with correct header

* chore: pull out date logic

* feat: get d3 mode rendering

* chore: ensure today line toggles properly

* chore: roadmap header ticks&labels are rendering

* tmp

* tmp: attempting panning impl and quit. started on collision detection

* chore: expand mode=d3 milestone items width to match text

* chore(d3): start work on collision detection

* feat: bin-packing algorithm implemented for d3 rendering

* feat: bin-packing is working really well

* feat: d3 panning works

* feat: d3 zooming + panning

* fix: d3 roadmap view width

* fix: milestone text

* fix(milestone): d3 rendering is more accurate

* fix(milestone): d3 milestones show progress bar

* fix(milestone): text padding, size, polish, truncating

* fix(drag): prevent unintentional attempt to drag milestone items

* fix: d3 roadmap height + panning and zoom polish

* fix: date granularity upon zooming out

* feat: SERIOUS polish on zoom, pan, & header labels

* tmp: temporarily force rendering of d3 roadmap for preview

* chore: fix build errors

* tmp: temporarily ignore unused RoadmapDetailed

* tmp: dont run tests for d3 preview; for now

* fix: zoom and pan controls are more intuitive

* chore: remove unused component

* fix: tickGuides stretch to full height

* fix: invalid foreignObject usage

* feat: implement detailed view in d3

NOTE: Currently a bug when switching between simple and detailed view

* fix: build

* tmp: using issueData context

* fix: binPackedGroups doesnt cause infinite re-renders

* fix: pan/zoom work when toggling views

* fix: use MMM DD, YYYY display format for milestone dates

* chore: pull out RoadmapGroupRenderer

* chore: fix build, renable tests, skip e2e

* fix: todayLine styling

* chore: remove unused code

* chore: todayLine polish

* chore: some more cleanup

* chore: set ETA always to EOD

* fix: rescale the timeScale with zoom transform

* fix: zoom and panning

* This allows us to properly recognize when an X value is within view
* Removes old hacky way of manually using panX

* feat: make zoom/pan level shareable

* feat: default zoom finding, and url parameter setting

* chore: fix build

* tests: fix unit tests

* fix: default view finding when all dates are same

fixes #369

* fix: d3-migration e2e tests

* fix(lint): remove unused legacyView

* chore: fix next+eslint

* chore: remove dead code

* feat: add legacy view button

* test: remove tests for removed files

* chore(pr-comment): remove redundant null check

* chore(pr-comment): clean up components/roadmap/header.tsx

* chore(pr-comment): move constants to svgConstants

* chore(pr-comment): refactor math into ItemContainerSvg

* chore: implement standard style

* chore(lint): sort imports

* feat: make roadmapHeader smarter

* feat: make roadmapHeader even smarter (mobile support)

* chore(pr-comment): code cleanup

* chore: use Dayjs instead of Date in NewRoadMapHeaderTick

* fix: css on hover for clickable milestones

* chore: remove RoadmapMode and it's uses

* chore: remove unused file

* fix: some viewMode issues

* fix: breadcrumb nav & zoomTransform+url bug

fixes #371

* fix: lint

* fix: 📦 Fixing box model (#375)

* fix: 📦 Fixing box model

* 🤦 lint

* fix: ⚡ reducing number of comparisons

---------

Co-authored-by: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com>

* fix: dont loop over each group of binPackedItems (#376)

* fix: dont loop over each group of binPackedItems

* fix: top/bottom y semantics

* Revert "fix: top/bottom y semantics"

This reverts commit 5d275b3.

---------

Co-authored-by: Nishant Arora <1895906+whizzzkid@users.noreply.github.com>
  • Loading branch information
SgtPooki and whizzzkid committed May 19, 2023
1 parent 44e503d commit 6c085b3
Show file tree
Hide file tree
Showing 121 changed files with 2,426 additions and 2,392 deletions.
47 changes: 32 additions & 15 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,33 @@ module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
project: ['./tsconfig.json']
},
env: {
jest: true,
jest: true
},
plugins: [
'@typescript-eslint',
'import',
'import'
],
extends: [
'plugin:@next/next/recommended',
'next',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'prettier',
'plugin:import/typescript',
'standard'
],
rules: {
camelcase: 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unused-vars': ['error', { 'destructuredArrayIgnorePattern': '^_' }],
'@typescript-eslint/no-unused-vars': ['error', { destructuredArrayIgnorePattern: '^_' }],
'@typescript-eslint/restrict-template-expressions': 'off',
'arrow-body-style': 'off',
'arrow-body-style': 'warn',
'implicit-arrow-linebreak': 'off',
'jsx-a11y/alt-text': 'off',
Expand All @@ -41,16 +42,32 @@ module.exports = {
'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off',
'import/no-unused-modules': [2, {
"unusedExports": true,
"ignoreExports": [
'pages/',
'components/roadmap/',
unusedExports: true,
ignoreExports: [
'hooks/useEffectDebugger.ts',
'lib/backend/saveIssueDataToFile.ts',
'lib/mergeStarMapsErrorGroups.ts',
'lib/addStarMapsErrorsToStarMapsErrorGroups.ts',
'playwright.config.ts',
'pages/',
'playwright.config.ts'
]
}]
}],
'import/order': [
'error',
{
groups: [
'builtin', // Built-in types are first
['external', 'unknown'],
['parent', 'sibling', 'internal', 'index']
],
'newlines-between': 'always',
alphabetize: {
order: 'asc',
caseInsensitive: true
},
warnOnUnassignedImports: true
}
]
},
settings: {
'import/parsers': {
Expand All @@ -60,8 +77,8 @@ module.exports = {
typescript: {},
node: {
extensions: ['.jsx', '.jsx', '.ts', '.tsx'],
moduleDirectory: ['node_modules', 'lib/', 'components/', 'hooks/', 'pages/'],
moduleDirectory: ['node_modules', 'lib/', 'components/', 'hooks/', 'pages/']
}
}
}
};
}
140 changes: 72 additions & 68 deletions components/RoadmapForm.tsx
Original file line number Diff line number Diff line change
@@ -1,119 +1,123 @@
import { Router, useRouter } from 'next/router';
import { Button, FormControl, FormErrorMessage, Input, InputGroup, InputLeftElement, InputRightElement, Text } from '@chakra-ui/react';
import { SearchIcon } from '@chakra-ui/icons'
import React, { useEffect, useState } from 'react';
import { Button, FormControl, FormErrorMessage, Input, InputGroup, InputLeftElement, InputRightElement, Text } from '@chakra-ui/react'
import { isEmpty } from 'lodash'
import { Router, useRouter } from 'next/router'
import React, { useEffect, useState } from 'react'

import { useGlobalLoadingState } from '../hooks/useGlobalLoadingState';
import useCheckMobileScreen from '../hooks/useCheckSmallScreen'
import { setCurrentIssueUrl, useCurrentIssueUrl } from '../hooks/useCurrentIssueUrl'
import { useGlobalLoadingState } from '../hooks/useGlobalLoadingState'
import { useViewMode } from '../hooks/useViewMode'
import { ViewMode } from '../lib/enums'
import { getValidUrlFromInput } from '../lib/getValidUrlFromInput'
import { paramsFromUrl } from '../lib/paramsFromUrl'
import styles from './RoadmapForm.module.css'
import theme from './theme/constants'
import { setCurrentIssueUrl, useCurrentIssueUrl } from '../hooks/useCurrentIssueUrl';
import { paramsFromUrl } from '../lib/paramsFromUrl';
import { getValidUrlFromInput } from '../lib/getValidUrlFromInput';
import { useViewMode } from '../hooks/useViewMode';
import { ViewMode } from '../lib/enums';
import { isEmpty } from 'lodash';
import useCheckMobileScreen from '../hooks/useCheckSmallScreen';

export function RoadmapForm() {
const router = useRouter();
const globalLoadingState = useGlobalLoadingState();
const currentIssueUrl = useCurrentIssueUrl();
const [issueUrl, setIssueUrl] = useState<string | null>();
const [error, setError] = useState<Error | null>(null);
const [isInputBlanked, setIsInputBlanked] = useState<boolean>(false);
const [isSmallScreen] = useCheckMobileScreen();
const viewMode = useViewMode() as ViewMode;
export function RoadmapForm () {
const router = useRouter()
const globalLoadingState = useGlobalLoadingState()
const currentIssueUrl = useCurrentIssueUrl()
const [issueUrl, setIssueUrl] = useState<string | null>()
const [error, setError] = useState<Error | null>(null)
const [isInputBlanked, setIsInputBlanked] = useState<boolean>(false)
const [isSmallScreen] = useCheckMobileScreen()
const viewMode = useViewMode() as ViewMode

useEffect(() => {
if (!isInputBlanked && isEmpty(currentIssueUrl) && window.location.pathname.length > 1) {
try {
const urlObj = getValidUrlFromInput(window.location.pathname.replace('/roadmap', ''));
setCurrentIssueUrl(urlObj.toString());
const urlObj = getValidUrlFromInput(window.location.pathname.replace('/roadmap', ''))
setCurrentIssueUrl(urlObj.toString())
} catch {}
}
}, [currentIssueUrl, isInputBlanked])

useEffect(() => {
const asyncFn = async () => {
if (router.isReady) {
if (!issueUrl) return;
if (!issueUrl) return
try {
const params = paramsFromUrl(issueUrl);
const params = paramsFromUrl(issueUrl)
if (params) {
const { owner, repo, issue_number } = params;
setIssueUrl(null);
const { owner, repo, issue_number } = params
setIssueUrl(null)
if (window.location.pathname.includes(`github.com/${owner}/${repo}/issues/${issue_number}`)) {
setTimeout(() => {
/**
* Clear the error after a few seconds.
*/
setError(null);
}, 5000);
throw new Error('Already viewing this issue');
setError(null)
}, 5000)
throw new Error('Already viewing this issue')
}
await router.push(`/roadmap/github.com/${owner}/${repo}/issues/${issue_number}#${viewMode}`);
globalLoadingState.stop();
await router.push(`/roadmap/github.com/${owner}/${repo}/issues/${issue_number}#view=${viewMode}`)
globalLoadingState.stop()
}
} catch (err) {
setError(err as Error);
globalLoadingState.stop();
setError(err as Error)
globalLoadingState.stop()
}
}
};
asyncFn();
}, [router, issueUrl, viewMode, globalLoadingState]);
}
asyncFn()
}, [router, issueUrl, viewMode, globalLoadingState])

const formSubmit = (e) => {
e.preventDefault();
globalLoadingState.start();
setError(null);
e.preventDefault()
globalLoadingState.start()
setError(null)

try {
if (currentIssueUrl == null) {
throw new Error('currentIssueUrl is null');
throw new Error('currentIssueUrl is null')
}
const newUrl = getValidUrlFromInput(currentIssueUrl);
setIssueUrl(newUrl.toString());
const newUrl = getValidUrlFromInput(currentIssueUrl)
setIssueUrl(newUrl.toString())
} catch (err) {
setError(err as Error);
globalLoadingState.stop();
setError(err as Error)
globalLoadingState.stop()
}
}

const inputRightElement = (
<Button type="submit" isLoading={globalLoadingState.get()} className={styles.formSubmitButton} border="1px solid #8D8D8D" borderRadius="4px" bg="rgba(141, 141, 141, 0.3)" onClick={formSubmit}>
<Button type="submit" isLoading={globalLoadingState.get()} className={styles.formSubmitButton} border="1px solid #8D8D8D" borderRadius="4px" bg="rgba(141, 141, 141, 0.3)" onClick={formSubmit}>
<Text p="6px 10px" color="white"></Text>
</Button>
);
)

const onChangeHandler = (e) => {
setIsInputBlanked(true);
setIsInputBlanked(true)
setCurrentIssueUrl(e.target.value ?? '')
};
}

Router.events.on('routeChangeStart', (...events) => {
globalLoadingState.start();
const path = events[0];
if (path === '/') {
setIsInputBlanked(true);
setCurrentIssueUrl('');
return;
useEffect(() => {
const handleRouteChange = (...events) => {
globalLoadingState.start()
const path = events[0]
if (path === '/') {
setIsInputBlanked(true)
setCurrentIssueUrl('')
return
}
const currentUrl = getValidUrlFromInput(path.split('#')[0].replace('/roadmap/', ''))
currentUrl.searchParams.delete('crumbs')
setCurrentIssueUrl(currentUrl.toString())
}
const currentUrl = getValidUrlFromInput(path.split('#')[0].replace('/roadmap/', ''));
currentUrl.searchParams.delete('crumbs');
setCurrentIssueUrl(currentUrl.toString());
});
Router.events.on('routeChangeStart', handleRouteChange)

return () => {
Router.events.off('routeChangeStart', handleRouteChange)
}
}, [globalLoadingState])

return (
isSmallScreen ?
<SearchIcon color='#FFFFFF' /> :
<form onSubmit={formSubmit}>
isSmallScreen
? <SearchIcon color='#FFFFFF' />
: <form onSubmit={formSubmit}>
<FormControl isInvalid={error != null} isDisabled={globalLoadingState.get()}>
<InputGroup>
<InputLeftElement
pointerEvents='none'
children={<SearchIcon color='#FFFFFF' />}
/>
<InputLeftElement pointerEvents='none'><SearchIcon color='#FFFFFF' /></InputLeftElement>
<Input
type="text"
value={currentIssueUrl}
Expand All @@ -128,10 +132,10 @@ export function RoadmapForm() {
borderColor={theme.light.header.input.border.color}
borderRadius={theme.light.header.input.border.radius}
/>
<InputRightElement cursor="pointer" children={inputRightElement} />
<InputRightElement cursor="pointer">{inputRightElement}</InputRightElement>
</InputGroup>
<FormErrorMessage>{error?.message}</FormErrorMessage>
</FormControl>
</form>
);
)
}
5 changes: 3 additions & 2 deletions components/RoadmapList/BulletConnector.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import styles from './BulletConnector.module.css';
import React from 'react'

import styles from './BulletConnector.module.css'

/**
* The vertical line connecting each bulletIcon across all rows
Expand Down
1 change: 1 addition & 0 deletions components/RoadmapList/BulletIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CircularProgress } from '@chakra-ui/react'
import React from 'react'

import styles from './BulletIcon.module.css'

export default function BulletIcon ({ completion_rate }: {completion_rate: number}) {
Expand Down
36 changes: 18 additions & 18 deletions components/RoadmapList/RoadmapListItemDefault.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { Grid, GridItem, Center, Link, HStack, Text, Skeleton } from '@chakra-ui/react';
import { LinkIcon } from '@chakra-ui/icons';
import NextLink from 'next/link';
import { useRouter } from 'next/router';
import React from 'react';
import { LinkIcon } from '@chakra-ui/icons'
import { Grid, GridItem, Center, Link, HStack, Text, Skeleton } from '@chakra-ui/react'
import NextLink from 'next/link'
import { useRouter } from 'next/router'
import React from 'react'

import SvgGitHubLogo from '../icons/svgr/SvgGitHubLogo';
import BulletConnector from './BulletConnector';
import BulletIcon from './BulletIcon';
import { paramsFromUrl } from '../../lib/paramsFromUrl';
import { dayjs } from '../../lib/client/dayjs';
import { getLinkForRoadmapChild } from '../../lib/client/getLinkForRoadmapChild';
import { ViewMode } from '../../lib/enums';
import { useGlobalLoadingState } from '../../hooks/useGlobalLoadingState';
import { ListIssueViewModel } from './types';
import { useGlobalLoadingState } from '../../hooks/useGlobalLoadingState'
import { dayjs } from '../../lib/client/dayjs'
import { getLinkForRoadmapChild } from '../../lib/client/getLinkForRoadmapChild'
import { ViewMode } from '../../lib/enums'
import { paramsFromUrl } from '../../lib/paramsFromUrl'
import SvgGitHubLogo from '../icons/svgr/SvgGitHubLogo'
import BulletConnector from './BulletConnector'
import BulletIcon from './BulletIcon'
import { ListIssueViewModel } from './types'

interface RoadmapListItemDefaultProps {
issue: ListIssueViewModel
Expand All @@ -22,7 +22,7 @@ interface RoadmapListItemDefaultProps {

function TitleText ({ hasChildren, issue }: Pick<RoadmapListItemDefaultProps, 'issue'> & {hasChildren: boolean}) {
return (
<Text fontWeight="semibold" color="linkBlue" fontSize={"xl"} lineHeight="32px">
<Text fontWeight="semibold" color="linkBlue" fontSize={'xl'} lineHeight="32px">
{hasChildren ? <LinkIcon lineHeight="32px" boxSize="10px" /> : null} {issue.title}
</Text>
)
Expand All @@ -45,7 +45,7 @@ function TitleTextMaybeLink ({ issue, hasChildren, index, childLink }: Pick<Road
export default function RoadmapListItemDefault ({ issue, index, issues }: RoadmapListItemDefaultProps) {
const { owner, repo, issue_number } = paramsFromUrl(issue.html_url)
const childLink = getLinkForRoadmapChild({ issueData: issue, query: useRouter().query, viewMode: ViewMode.List })
const globalLoadingState = useGlobalLoadingState();
const globalLoadingState = useGlobalLoadingState()
const hasChildren = childLink !== '#'
const issueDueDate = issue.due_date ? dayjs(issue.due_date).format('MMM D, YYYY') : 'unknown'

Expand Down Expand Up @@ -75,8 +75,8 @@ export default function RoadmapListItemDefault ({ issue, index, issues }: Roadma
<HStack gap={0} alignItems="flex-start">
<Link href={issue.html_url} lineHeight="32px" isExternal>
<Skeleton isLoaded={!globalLoadingState.get()}>
<HStack gap={0} alignItems="center" wrap={"nowrap"}>
<SvgGitHubLogo color="text" style={{ display:'inline', color: '#313239' }} fill="#313239" />
<HStack gap={0} alignItems="center" wrap={'nowrap'}>
<SvgGitHubLogo color="text" style={{ display: 'inline', color: '#313239' }} fill="#313239" />
<Text color="text" style={{ whiteSpace: 'nowrap' }} fontSize="large">{owner}/{repo}#{issue_number}</Text>
</HStack>
</Skeleton>
Expand Down
Loading

1 comment on commit 6c085b3

@vercel
Copy link

@vercel vercel bot commented on 6c085b3 May 19, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.