Merge branch 'feature/resize' into develop

This commit is contained in:
EnixCoda 2019-08-11 11:04:58 +08:00
commit 781c706137
No known key found for this signature in database
GPG key ID: 0C1A07377913A1DD
10 changed files with 5989 additions and 121 deletions

View file

@ -1,46 +0,0 @@
import * as React from 'react'
type Props = {
children(size: Size): React.ReactNode
}
type Size = {
width: number
height: number
}
type State = {
size: Size
}
export default class AutoSizer extends React.Component<Props, State> {
state = {
size: {
width: 0,
height: 0,
},
}
ref = React.createRef<HTMLDivElement>()
componentDidMount() {
const container = this.ref.current
if (container) {
this.setState({
size: {
width: container.offsetWidth,
height: container.offsetHeight,
},
})
}
}
render() {
const { size } = this.state
return (
<div {...this.props} ref={this.ref}>
{this.props.children(size)}
</div>
)
}
}

View file

@ -1,6 +1,9 @@
import * as React from 'react'
import ResizeHandler from 'components/ResizeHandler'
import HorizontalResizeHandler from 'components/ResizeHandler'
import cx from 'utils/cx'
import { useWindowSize, useMediaStyleSheet } from 'utils/hooks'
import { bodySpacingClassName } from 'utils/DOMHelper'
import configHelper, { configKeys } from 'utils/configHelper'
export type Size = number
type Props = {
@ -8,23 +11,54 @@ type Props = {
className?: string
}
export default class Resizable extends React.PureComponent<Props> {
state = {
size: this.props.baseSize,
}
const MINIMAL_CONTENT_VIEWPORT_WIDTH = 100
const GITHUB_WIDTH = 1020
onResize = (size: Size) => this.setState({ size: Math.max(this.props.baseSize, size) })
export default function Resizable({
baseSize,
className,
children,
}: React.PropsWithChildren<Props>) {
const [size, setSize] = React.useState(baseSize)
render() {
const { className, children } = this.props
const { size } = this.state
return (
<div className={cx('gitako-position-wrapper', className)}>
<ResizeHandler onResize={this.onResize} size={size} />
<div className={'gitako-position-content'} style={{ width: size }}>
{children}
</div>
</div>
)
}
React.useEffect(() => {
setSize(baseSize)
}, [baseSize])
useWindowSize(
width => {
if (size > width - MINIMAL_CONTENT_VIEWPORT_WIDTH)
setSize(width - MINIMAL_CONTENT_VIEWPORT_WIDTH)
},
[size],
)
React.useEffect(() => {
document.documentElement.style.setProperty('--gitako-width', size + 'px')
configHelper.setOne(configKeys.sideBarWidth, size)
}, [size])
useMediaStyleSheet(
`.${bodySpacingClassName} { margin-left: calc(var(--gitako-width) * 2 + 1020px - 100vw); }`,
size => [`min-width: ${size + GITHUB_WIDTH}px`, `max-width: ${size * 2 + GITHUB_WIDTH}px`],
size,
)
useMediaStyleSheet(
`.${bodySpacingClassName} { margin-left: var(--gitako-width); }`,
size => [`max-width: ${size + GITHUB_WIDTH}px`],
size,
)
return (
<div className={cx('gitako-position-wrapper', className)}>
<div className={'gitako-position-content'}>{children}</div>
<HorizontalResizeHandler
onResize={size => {
if (size < window.innerWidth - MINIMAL_CONTENT_VIEWPORT_WIDTH) setSize(size)
}}
size={size}
/>
</div>
)
}

View file

@ -1,19 +1,25 @@
import * as React from 'react'
import Icon from 'components/Icon'
import Resizable, { Size } from './Resizable'
import { Size } from './Resizable'
type Props = {
size: Size
onResize: Resizable['onResize']
onResize(size: Size): void
style?: React.CSSProperties
}
export default class ResizeHandler extends React.PureComponent<Props> {
export default class HorizontalResizeHandler extends React.PureComponent<Props> {
pointerDown = false
startX = 0
delta = 0
baseSize = this.props.size
componentWillReceiveProps(nextProps: Props) {
if (!this.pointerDown) {
// update baseSize when not resizing
this.baseSize = this.props.size
}
}
subscribeEvents = () => {
window.addEventListener('mousemove', this.onPointerMove)
window.addEventListener('mouseup', this.onPointerUp)
@ -24,24 +30,20 @@ export default class ResizeHandler extends React.PureComponent<Props> {
window.removeEventListener('mouseup', this.onPointerUp)
}
onPointerDown = (e: React.MouseEvent) => {
this.pointerDown = true
const { clientX } = e
onPointerDown = ({ clientX }: React.MouseEvent) => {
this.startX = clientX
this.pointerDown = true
this.subscribeEvents()
}
onPointerMove = (e: MouseEvent) => {
onPointerMove = ({ clientX }: MouseEvent) => {
if (!this.pointerDown) return
const { clientX } = e
const { onResize } = this.props
this.delta = this.startX - clientX
onResize(this.delta + this.baseSize)
this.props.onResize(clientX - this.startX + this.baseSize)
}
onPointerUp = () => {
this.pointerDown = false
this.baseSize = Math.max(this.baseSize + this.delta, this.props.size)
this.baseSize = this.props.size
this.unsubscribeEvents()
}

View file

@ -5,20 +5,14 @@
@min-screen-width: 1280px;
@github-content-width: 1020px;
@side-bar-base-width: (@min-screen-width - @github-content-width);
@width-with-gitako: (@min-screen-width + @side-bar-base-width);
@min-width-with-gitako: (@min-screen-width + @side-bar-base-width);
@github-header-z-index: 32;
@github-pull-request-float-header-z-index: 110;
@minimal-z-index: max(@github-header-z-index, @github-pull-request-float-header-z-index) + 1;
.with-@{name}-spacing {
margin-left: @side-bar-base-width;
@media (min-width: @min-screen-width) and (max-width: @width-with-gitako) {
margin-left: ~'calc(' @width-with-gitako ~' - 100vw)';
}
@media (min-width: @width-with-gitako) {
margin-left: 0;
}
:root {
--gitako-width: @side-bar-base-width;
}
.@{name}-ready {
@ -26,8 +20,9 @@
background: #24292e;
}
.Header {
width: 1012px;
max-width: 1012px;
margin: 0 auto;
overflow: auto;
}
}
@ -87,7 +82,6 @@
position: fixed;
top: 5px;
left: 0;
transform: translate((@side-bar-base-width - 30px));
z-index: @minimal-z-index;
display: inline-flex;
justify-content: center;
@ -97,35 +91,45 @@
height: 30px;
will-change: transform;
border: 1px solid transparent;
transition: all @animation-duration ease;
@media screen and (min-width: @width-with-gitako) {
transform: translate(~'calc(50vw - (' @github-content-width ~') / 2 - 30px)');
.transform-expanded {
transform: translate(calc(var(--gitako-width) - 30px));
}
.transform-collapsed {
transform: translate(5px);
}
&:hover {
.error-message {
display: inline;
}
}
.transform-expanded;
animation: toggle-show-button-wrapper-expand @animation-duration;
&.collapsed {
border-color: #999999;
border-radius: 3px;
background: #f5f5f5;
@media screen and (max-width: @width-with-gitako) {
transform: translate(5px);
}
.transform-collapsed;
animation: toggle-show-button-wrapper-collapse @animation-duration;
.action-icon {
color: #0366d6d0;
}
}
&.error {
.action-icon {
color: #cb2431;
@keyframes toggle-show-button-wrapper-expand {
from {
.transform-collapsed;
}
to {
.transform-expanded;
}
}
@keyframes toggle-show-button-wrapper-collapse {
from {
.transform-expanded;
}
to {
.transform-collapsed;
}
}
@ -137,6 +141,18 @@
transition: all @animation-duration ease;
}
&:hover {
.error-message {
display: inline;
}
}
&.error {
.action-icon {
color: #cb2431;
}
}
.error-message {
display: none;
margin: 0 4px;
@ -150,39 +166,34 @@
left: 0;
height: 100vh;
z-index: @minimal-z-index;
min-width: @side-bar-base-width;
display: flex;
justify-content: flex-end;
@media screen and (min-width: @min-screen-width) {
width: ~'calc((100vw - ' @github-content-width ~') / 2)';
}
&.hidden {
.hidden;
}
.gitako-position-content {
max-width: 100%;
width: var(--gitako-width);
}
}
.@{name}-resize-handler {
@resize-handler-width: 16px;
@side-bar-and-handler-width: (@min-screen-width + @resize-handler-width);
display: none;
width: @resize-handler-width;
background: #fafbfc;
display: flex;
align-items: center;
cursor: ew-resize;
user-select: none;
width: 0;
background: #e1e4e8;
border-left: 1px solid #e1e4e8;
@media screen and (min-width: (@min-screen-width + @side-bar-base-width + @resize-handler-width * 2)) {
display: flex;
align-items: center;
&:hover {
width: 16px;
background: #fafbfc;
border-right: 1px solid #e1e4e8;
}
.grabber-icon {
transform: rotate(90deg);
width: 12px;
font-size: 0;
}
}
.@{name}-side-bar-body {

View file

@ -71,6 +71,7 @@ const init: MethodCreator<Props, ConnectorState> = dispatch => async () => {
metaData.branchName = detectedBranchName || 'master'
dispatch.call(setMetaData, metaData)
const {
sideBarWidth,
access_token: accessToken,
shortcut,
compressSingletonFolder,
@ -79,6 +80,7 @@ const init: MethodCreator<Props, ConnectorState> = dispatch => async () => {
} = await configHelper.getAll()
DOMHelper.decorateGitHubPageContent({ copyFileButton, copySnippetButton })
dispatch.set({
baseSize: sideBarWidth,
accessToken,
toggleShowSideBarShortcut: shortcut,
compressSingletonFolder,

View file

@ -20,12 +20,12 @@ function markGitakoReadyState() {
* if should show gitako, then move body right to make space for showing gitako
* otherwise, hide the space
*/
export const bodySpacingClassName = 'with-gitako-spacing'
function setBodyIndent(shouldShowGitako: boolean) {
const spacingClassName = 'with-gitako-spacing'
if (shouldShowGitako) {
document.body.classList.add(spacingClassName)
document.body.classList.add(bodySpacingClassName)
} else {
document.body.classList.remove(spacingClassName)
document.body.classList.remove(bodySpacingClassName)
}
}

View file

@ -2,6 +2,7 @@ import storageHelper from 'utils/storageHelper'
import { pick } from 'utils/general'
type Config = {
sideBarWidth: number
shortcut: string | undefined
access_token: string | undefined
compressSingletonFolder: boolean
@ -10,6 +11,7 @@ type Config = {
}
export enum configKeys {
sideBarWidth = 'sideBarWidth',
shortcut = 'shortcut',
accessToken = 'access_token',
compressSingletonFolder = 'compressSingletonFolder',
@ -17,7 +19,8 @@ export enum configKeys {
copySnippetButton = 'copySnippetButton',
}
const defaultConfigs = {
const defaultConfigs: Config = {
sideBarWidth: 260,
shortcut: undefined,
access_token: undefined,
compressSingletonFolder: true,

View file

@ -82,3 +82,14 @@ export function findNode(
}
}
}
export function createStyleSheet(content: string) {
const style = document.createElement('style')
style.appendChild(document.createTextNode(content))
document.head.appendChild(style)
return style
}
export function setStyleSheetMedia(style: HTMLStyleElement, media: string) {
style.setAttribute('media', media)
}

41
src/utils/hooks.ts Normal file
View file

@ -0,0 +1,41 @@
import * as React from 'react'
import { createStyleSheet, setStyleSheetMedia } from './general'
export function useWindowSize(
callback: (width: number) => void,
deps?: ReadonlyArray<any> | undefined,
) {
React.useEffect(() => {
const resizeListener: (this: Window, ev: UIEvent) => void = () => callback(window.innerWidth)
window.addEventListener('resize', resizeListener)
return () => window.removeEventListener('resize', resizeListener)
}, deps)
}
export function useMediaStyleSheet(
content: string,
getMediaQuery: (width: number) => string[],
size: number,
) {
const style = React.useRef<HTMLStyleElement>()
// this order may prevent first effect actually occur at first time
React.useEffect(() => {
setSheetMedia()
}, [size])
React.useEffect(() => {
style.current = createStyleSheet(content)
setSheetMedia()
}, [])
function setSheetMedia() {
if (style.current)
setStyleSheetMedia(
style.current,
getMediaQuery(size)
.map(query => `(${query})`)
.join(' and '),
)
}
}

5810
yarn-error.log Normal file

File diff suppressed because it is too large Load diff