add custom background for skin viewer

This commit is contained in:
Pig Fang 2020-03-04 19:37:17 +08:00
parent a804bb8d7b
commit af24dcf9f9
14 changed files with 164 additions and 30 deletions

1
.gitignore vendored
View File

@ -27,3 +27,4 @@ storage/options.php
.phpunit.result.cache
.php_cs.cache
resources/views/overrides
public/bg

View File

@ -1,9 +1,11 @@
import React, { useState, useEffect, useRef } from 'react'
import * as skinview3d from 'skinview3d'
import { trans } from '../scripts/i18n'
import { t } from '../scripts/i18n'
import styles from './Viewer.scss'
import SkinSteve from '../../../misc/textures/steve.png'
export const PICTURES_COUNT = 7
interface Props {
skin?: string
cape?: string
@ -40,15 +42,19 @@ const Viewer: React.FC<Props> = props => {
const stuffRef = useRef(emptyStuff)
const [paused, setPaused] = useState(false)
const [running, setRunning] = useState(false)
const [reset, setReset] = useState(0)
const [background, setBackground] = useState('#fff')
const [bgPicture, setBgPicture] = useState(0)
const indicator = (() => {
const { skin, cape } = props
if (skin && cape) {
return `${trans('general.skin')} & ${trans('general.cape')}`
return `${t('general.skin')} & ${t('general.cape')}`
} else if (skin) {
return trans('general.skin')
return t('general.skin')
} else if (cape) {
return trans('general.cape')
return t('general.cape')
}
return ''
})()
@ -117,12 +123,19 @@ const Viewer: React.FC<Props> = props => {
viewer.playerObject.skin.slim = !!props.isAlex
}, [props.isAlex])
useEffect(() => {
if (bgPicture !== 0) {
setBackground(`url("${blessing.base_url}/bg/${bgPicture}.png")`)
}
}, [bgPicture])
const togglePause = () => {
setPaused(paused => !paused)
viewRef.current.animationPaused = !viewRef.current.animationPaused
}
const toggleRun = () => {
setRunning(running => !running)
const { handles } = stuffRef.current
handles.run.paused = !handles.run.paused
handles.walk.paused = false
@ -137,52 +150,103 @@ const Viewer: React.FC<Props> = props => {
setReset(c => c + 1)
}
const setWhite = () => setBackground('#fff')
const setGray = () => setBackground('#6c757d')
const setBlack = () => setBackground('#000')
const setPrevPicture = () => {
if (bgPicture <= 1) {
setBgPicture(PICTURES_COUNT)
} else {
setBgPicture(bg => bg - 1)
}
}
const setNextPicture = () => {
if (bgPicture >= PICTURES_COUNT) {
setBgPicture(1)
} else {
setBgPicture(bg => bg + 1)
}
}
return (
<div className="card">
<div className="card-header">
<div className="d-flex justify-content-between">
<h3 className="card-title">
<span>{trans('general.texturePreview')}</span>
<span>{t('general.texturePreview')}</span>
{props.showIndicator && (
<span className="badge bg-olive ml-1">{indicator}</span>
)}
</h3>
<div className={styles.actions}>
<i
className="fas fa-forward"
className={`fas fa-${running ? 'walking' : 'running'}`}
data-toggle="tooltip"
data-placement="bottom"
title={`${trans('general.walk')} / ${trans('general.run')}`}
title={`${t('general.walk')} / ${t('general.run')}`}
onClick={toggleRun}
></i>
<i
className="fas fa-redo-alt"
data-toggle="tooltip"
data-placement="bottom"
title={trans('general.rotation')}
title={t('general.rotation')}
onClick={toggleRotate}
></i>
<i
className={`fas fa-${paused ? 'play' : 'pause'}`}
data-toggle="tooltip"
data-placement="bottom"
title={trans('general.pause')}
title={t('general.pause')}
onClick={togglePause}
></i>
<i
className="fas fa-stop"
data-toggle="tooltip"
data-placement="bottom"
title={trans('general.reset')}
title={t('general.reset')}
onClick={handleReset}
></i>
</div>
</div>
</div>
<div className="card-body">
<div className="card-body" style={{ background }}>
<div ref={containerRef} className={styles.viewer}></div>
</div>
{props.children && <div className="card-footer">{props.children}</div>}
<div className="card-footer">
<div className="mt-2 mb-3 d-flex">
<div
className="btn-color bg-white display-inline-block rounded-pill mr-2 mb-1 elevation-2"
title={t('colors.white')}
onClick={setWhite}
/>
<div
className="btn-color bg-black display-inline-block rounded-pill mr-2 mb-1 elevation-2"
title={t('colors.black')}
onClick={setBlack}
/>
<div
className="btn-color bg-gray display-inline-block rounded-pill mr-2 mb-1 elevation-2"
title={t('colors.gray')}
onClick={setGray}
/>
<div
className="btn-color bg-green display-inline-block rounded-pill mr-2 mb-1 elevation-2 text-center"
title={t('colors.prev')}
onClick={setPrevPicture}
>
<i className="fas fa-arrow-left"></i>
</div>
<div
className="btn-color bg-green display-inline-block rounded-pill mr-2 mb-1 elevation-2 text-center"
title={t('colors.next')}
onClick={setNextPicture}
>
<i className="fas fa-arrow-right"></i>
</div>
</div>
{props.children}
</div>
</div>
)
}

View File

@ -1,7 +1,7 @@
import React from 'react'
import { render, fireEvent } from '@testing-library/react'
import { trans } from '@/scripts/i18n'
import Viewer from '@/components/Viewer'
import { t } from '@/scripts/i18n'
import Viewer, { PICTURES_COUNT } from '@/components/Viewer'
test('custom footer', () => {
const { queryByText } = render(<Viewer>footer</Viewer>)
@ -11,25 +11,25 @@ test('custom footer', () => {
describe('indicator', () => {
it('hidden by default', () => {
const { queryByText } = render(<Viewer skin="skin" />)
expect(queryByText(trans('general.skin'))).not.toBeInTheDocument()
expect(queryByText(t('general.skin'))).not.toBeInTheDocument()
})
it('nothing', () => {
const { queryByText } = render(<Viewer showIndicator />)
expect(queryByText(trans('general.skin'))).not.toBeInTheDocument()
expect(queryByText(trans('general.cape'))).not.toBeInTheDocument()
expect(queryByText(t('general.skin'))).not.toBeInTheDocument()
expect(queryByText(t('general.cape'))).not.toBeInTheDocument()
})
it('skin only', () => {
const { queryByText } = render(<Viewer skin="skin" showIndicator />)
expect(queryByText(trans('general.skin'))).toBeInTheDocument()
expect(queryByText(trans('general.cape'))).not.toBeInTheDocument()
expect(queryByText(t('general.skin'))).toBeInTheDocument()
expect(queryByText(t('general.cape'))).not.toBeInTheDocument()
})
it('cape only', () => {
const { queryByText } = render(<Viewer cape="cape" showIndicator />)
expect(queryByText(trans('general.skin'))).not.toBeInTheDocument()
expect(queryByText(trans('general.cape'))).toBeInTheDocument()
expect(queryByText(t('general.skin'))).not.toBeInTheDocument()
expect(queryByText(t('general.cape'))).toBeInTheDocument()
})
it('skin and cape', () => {
@ -37,35 +37,94 @@ describe('indicator', () => {
<Viewer skin="skin" cape="cape" showIndicator />,
)
expect(
queryByText(`${trans('general.skin')} & ${trans('general.cape')}`),
queryByText(`${t('general.skin')} & ${t('general.cape')}`),
).toBeInTheDocument()
expect(queryByText(trans('general.skin'))).not.toBeInTheDocument()
expect(queryByText(trans('general.cape'))).not.toBeInTheDocument()
expect(queryByText(t('general.skin'))).not.toBeInTheDocument()
expect(queryByText(t('general.cape'))).not.toBeInTheDocument()
})
})
describe('actions', () => {
it('toggle run', () => {
const { getByTitle } = render(<Viewer />)
fireEvent.click(
getByTitle(`${trans('general.walk')} / ${trans('general.run')}`),
)
fireEvent.click(getByTitle(`${t('general.walk')} / ${t('general.run')}`))
})
it('toggle rotation', () => {
const { getByTitle } = render(<Viewer />)
fireEvent.click(getByTitle(trans('general.rotation')))
fireEvent.click(getByTitle(t('general.rotation')))
})
it('toggle pause', () => {
const { getByTitle } = render(<Viewer />)
const icon = getByTitle(trans('general.pause'))
const icon = getByTitle(t('general.pause'))
fireEvent.click(icon)
expect(icon).toHaveClass('fa-play')
})
it('reset', () => {
const { getByTitle } = render(<Viewer />)
fireEvent.click(getByTitle(trans('general.reset')))
fireEvent.click(getByTitle(t('general.reset')))
})
})
describe('background', () => {
it('white', () => {
const { getByTitle, baseElement } = render(<Viewer />)
fireEvent.click(getByTitle(t('colors.white')))
expect(
baseElement.querySelector<HTMLDivElement>('.card-body')!.style.background,
).toBe('rgb(255, 255, 255)')
})
it('black', () => {
const { getByTitle, baseElement } = render(<Viewer />)
fireEvent.click(getByTitle(t('colors.black')))
expect(
baseElement.querySelector<HTMLDivElement>('.card-body')!.style.background,
).toBe('rgb(0, 0, 0)')
})
it('white', () => {
const { getByTitle, baseElement } = render(<Viewer />)
fireEvent.click(getByTitle(t('colors.gray')))
expect(
baseElement.querySelector<HTMLDivElement>('.card-body')!.style.background,
).toBe('rgb(108, 117, 125)')
})
it('previous picture', () => {
const { getByTitle, baseElement } = render(<Viewer />)
fireEvent.click(getByTitle(t('colors.prev')))
expect(
baseElement.querySelector<HTMLDivElement>('.card-body')!.style.background,
).toBe(`url(/bg/${PICTURES_COUNT}.png)`)
fireEvent.click(getByTitle(t('colors.prev')))
expect(
baseElement.querySelector<HTMLDivElement>('.card-body')!.style.background,
).toBe(`url(/bg/${PICTURES_COUNT - 1}.png)`)
})
it('next picture', () => {
const { getByTitle, baseElement } = render(<Viewer />)
fireEvent.click(getByTitle(t('colors.next')))
expect(
baseElement.querySelector<HTMLDivElement>('.card-body')!.style.background,
).toBe('url(/bg/1.png)')
fireEvent.click(getByTitle(t('colors.next')))
expect(
baseElement.querySelector<HTMLDivElement>('.card-body')!.style.background,
).toBe('url(/bg/2.png)')
Array.from({ length: PICTURES_COUNT - 1 }).forEach(() => {
fireEvent.click(getByTitle(t('colors.next')))
})
expect(
baseElement.querySelector<HTMLDivElement>('.card-body')!.style.background,
).toBe('url(/bg/1.png)')
})
})

View File

@ -305,6 +305,13 @@ general:
previews: Texture Previews
last-modified: Last Modified
colors:
black: Black
white: White
gray: Gray
prev: Previous Background
next: Next Background
vendor:
datatable:
search: Search

BIN
resources/misc/backgrounds/1.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 KiB

BIN
resources/misc/backgrounds/2.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

BIN
resources/misc/backgrounds/3.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

BIN
resources/misc/backgrounds/4.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

BIN
resources/misc/backgrounds/5.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

BIN
resources/misc/backgrounds/6.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

BIN
resources/misc/backgrounds/7.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

View File

@ -23,6 +23,7 @@
- Support searching players at players page.
- Added Blessing Skin Shell.
- Support specifying "from" email address and name when sending email.
- 3D skin viewer can be with background now.
## Tweaked

View File

@ -23,6 +23,7 @@
- 角色页面可进行搜索
- 新增 Blessing Skin Shell
- 支持单独指定邮件发件人的地址和名称
- 3D 皮肤预览现在是带背景的
## 调整

View File

@ -20,6 +20,7 @@ if ($Simple) {
# Copy static files
Copy-Item -Path ./resources/assets/src/images/bg.png -Destination ./public/app
Copy-Item -Path ./resources/assets/src/images/favicon.ico -Destination ./public/app
Copy-Item -Path ./resources/misc/backgrounds/ ./public/bg -Recurse
Write-Host 'Static files copied.' -ForegroundColor Green
# Write commit ID