mirror of
https://github.com/EnixCoda/Gitako.git
synced 2026-03-11 08:54:44 +00:00
Merge branch 'feature/resize' into develop
This commit is contained in:
commit
781c706137
10 changed files with 5989 additions and 121 deletions
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
41
src/utils/hooks.ts
Normal 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
5810
yarn-error.log
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue