Add Vue component for skinview3d
This commit is contained in:
parent
608f3fbe4b
commit
c120c58dda
145
resources/assets/src/components/common/Previewer.vue
Normal file
145
resources/assets/src/components/common/Previewer.vue
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
<template>
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title" style="width: 100%;">{!! $title or trans('general.texture-preview') !!}
|
||||
<span data-toggle="tooltip" class="badge bg-light-blue">{{ indicator }}</span>
|
||||
<div class="operations">
|
||||
<i
|
||||
@click="toggleRun"
|
||||
data-toggle="tooltip"
|
||||
data-placement="bottom"
|
||||
title="@lang('general.walk').' / '.trans('general.run')"
|
||||
class="fa fa-forward"
|
||||
></i>
|
||||
<i
|
||||
@click="toggleRotate"
|
||||
data-toggle="tooltip"
|
||||
data-placement="bottom"
|
||||
title="@lang('general.rotation')"
|
||||
class="fa fa-repeat"
|
||||
></i>
|
||||
<i
|
||||
@click="togglePause"
|
||||
data-toggle="tooltip"
|
||||
data-placement="bottom"
|
||||
title="@lang('general.pause')"
|
||||
class="fa"
|
||||
:class="{ 'fa-pause': !paused, 'fa-play': paused }"
|
||||
></i>
|
||||
<i
|
||||
@click="reset"
|
||||
data-toggle="tooltip"
|
||||
data-placement="bottom"
|
||||
title="@lang('general.reset')"
|
||||
class="fa fa-stop"
|
||||
></i>
|
||||
</div>
|
||||
</h3>
|
||||
</div><!-- /.box-header -->
|
||||
<div class="box-body">
|
||||
<div ref="previewer">
|
||||
<!-- Container for 3D Preview -->
|
||||
</div>
|
||||
</div><!-- /.box-body -->
|
||||
<div v-if="$slots.footer" class="box-footer">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as skinview3d from 'skinview3d';
|
||||
|
||||
export default {
|
||||
name: 'Previewer',
|
||||
props: {
|
||||
skin: String,
|
||||
cape: String,
|
||||
closetMode: Boolean,
|
||||
initPositionZ: {
|
||||
default: 70
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
/** @type {skinview3d.SkinViewer} */
|
||||
viewer: null,
|
||||
handles: {
|
||||
walk: null,
|
||||
run: null,
|
||||
rotate: null,
|
||||
},
|
||||
control: null,
|
||||
paused: false,
|
||||
}),
|
||||
computed: {
|
||||
indicator() {
|
||||
if (!this.closetMode) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (this.skin && this.cape) {
|
||||
return `${this.$t('general.skin')} & ${this.$t('general.cape')}`;
|
||||
} else if (this.skin) {
|
||||
return this.$t('general.skin');
|
||||
} else if (this.cape) {
|
||||
return this.$t('general.cape');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initPreviewer() {
|
||||
this.viewer = new skinview3d.SkinViewer({
|
||||
domElement: this.$refs.previewer,
|
||||
width: this.$refs.previewer.clientWidth,
|
||||
height: this.$refs.previewer.clientHeight,
|
||||
skinUrl: this.skin,
|
||||
capeUrl: this.cape
|
||||
});
|
||||
this.viewer.camera.position.z = this.initPositionZ;
|
||||
this.viewer.animation = new skinview3d.CompositeAnimation();
|
||||
this.handles.walk = this.viewer.animation.add(skinview3d.WalkingAnimation);
|
||||
this.handles.run = this.viewer.animation.add(skinview3d.RunningAnimation);
|
||||
this.handles.rotate = this.viewer.animation.add(skinview3d.RotatingAnimation);
|
||||
this.handles.run.paused = true;
|
||||
this.control = skinview3d.createOrbitControls(this.viewer);
|
||||
},
|
||||
togglePause() {
|
||||
this.paused = !this.paused;
|
||||
this.viewer.animationPaused = !this.viewer.animationPaused;
|
||||
},
|
||||
toggleRun() {
|
||||
this.handles.run.paused = !this.handles.run.paused;
|
||||
this.handles.walk.paused = !this.handles.walk.paused;
|
||||
},
|
||||
toggleRotate() {
|
||||
this.handles.rotate.paused = !this.handles.rotate.paused;
|
||||
},
|
||||
reset() {
|
||||
this.viewer.dispose();
|
||||
this.handles = {};
|
||||
this.control = null;
|
||||
this.initPreviewer();
|
||||
this.handles.walk.paused = true;
|
||||
this.handles.run.paused = true;
|
||||
this.handles.rotate.paused = true;
|
||||
this.viewer.camera.position.z = 70;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
skin(url) {
|
||||
this.viewer.skinUrl = url;
|
||||
},
|
||||
cape(url) {
|
||||
this.viewer.capeUrl = url;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initPreviewer();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.viewer.dispose();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
24
resources/assets/tests/__mocks__/skinview3d.js
Normal file
24
resources/assets/tests/__mocks__/skinview3d.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
export class SkinViewer {
|
||||
constructor() {
|
||||
this.animationPaused = false;
|
||||
this.camera = {
|
||||
position: {}
|
||||
};
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
export class CompositeAnimation {
|
||||
add(animation) {
|
||||
return animation;
|
||||
}
|
||||
}
|
||||
|
||||
export function createOrbitControls() {}
|
||||
|
||||
export const WalkingAnimation = { paused: false };
|
||||
export const RunningAnimation = { paused: false };
|
||||
export const RotatingAnimation = { paused: false };
|
||||
92
resources/assets/tests/components/common/Previewer.test.js
Normal file
92
resources/assets/tests/components/common/Previewer.test.js
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import Previewer from '@/common/Previewer';
|
||||
import * as mockedSkinview3d from '../../__mocks__/skinview3d';
|
||||
|
||||
jest.mock('skinview3d');
|
||||
|
||||
test('initialize skinview3d', () => {
|
||||
const wrapper = mount(Previewer);
|
||||
expect(wrapper.vm.viewer).toBeInstanceOf(mockedSkinview3d.SkinViewer);
|
||||
expect(wrapper.vm.viewer.camera.position.z).toBe(70);
|
||||
});
|
||||
|
||||
test('dispose viewer before destroy', () => {
|
||||
const wrapper = mount(Previewer);
|
||||
wrapper.destroy();
|
||||
expect(wrapper.vm.viewer.disposed).toBeTrue();
|
||||
});
|
||||
|
||||
test('skin URL should be updated', () => {
|
||||
const wrapper = mount(Previewer);
|
||||
wrapper.setProps({ skin: 'abc' });
|
||||
expect(wrapper.vm.viewer.skinUrl).toBe('abc');
|
||||
});
|
||||
|
||||
test('cape URL should be updated', () => {
|
||||
const wrapper = mount(Previewer);
|
||||
wrapper.setProps({ cape: 'abc' });
|
||||
expect(wrapper.vm.viewer.capeUrl).toBe('abc');
|
||||
});
|
||||
|
||||
test('`footer` slot', () => {
|
||||
const wrapper = mount(Previewer, {
|
||||
slots: {
|
||||
footer: '<div id="footer" />'
|
||||
}
|
||||
});
|
||||
expect(wrapper.find('#footer').exists()).toBeTrue();
|
||||
});
|
||||
|
||||
test('disable closet mode', () => {
|
||||
const wrapper = mount(Previewer);
|
||||
expect(wrapper.find('.badge').text()).toBe('');
|
||||
});
|
||||
|
||||
test('enable closet mode', () => {
|
||||
const wrapper = mount(Previewer, {
|
||||
propsData: {
|
||||
closetMode: true
|
||||
}
|
||||
});
|
||||
expect(wrapper.find('.badge').text()).toBe('');
|
||||
|
||||
wrapper.setProps({ skin: 'abc' });
|
||||
expect(wrapper.find('.badge').text()).toBe('general.skin');
|
||||
|
||||
wrapper.setProps({ cape: 'abc', skin: '' });
|
||||
expect(wrapper.find('.badge').text()).toBe('general.cape');
|
||||
|
||||
wrapper.setProps({ skin: 'abc', cape: 'abc' });
|
||||
expect(wrapper.find('.badge').text()).toBe('general.skin & general.cape');
|
||||
});
|
||||
|
||||
test('toggle pause', () => {
|
||||
const wrapper = mount(Previewer);
|
||||
const pauseButton = wrapper.find('.fa-pause');
|
||||
expect(pauseButton.exists()).toBeTrue();
|
||||
pauseButton.trigger('click');
|
||||
expect(wrapper.find('.fa-play').exists()).toBeTrue();
|
||||
expect(wrapper.find('.fa-pause').exists()).toBeFalse();
|
||||
});
|
||||
|
||||
test('toggle run', () => {
|
||||
const wrapper = mount(Previewer);
|
||||
wrapper.find('.fa-forward').trigger('click');
|
||||
expect(wrapper.vm.handles.run.paused).toBeFalse();
|
||||
expect(wrapper.vm.handles.walk.paused).toBeTrue();
|
||||
});
|
||||
|
||||
test('toggle rotate', () => {
|
||||
const wrapper = mount(Previewer);
|
||||
wrapper.find('.fa-repeat').trigger('click');
|
||||
expect(wrapper.vm.handles.rotate.paused).toBeTrue();
|
||||
});
|
||||
|
||||
test('reset', () => {
|
||||
mockedSkinview3d.SkinViewer.prototype.dispose = jest.fn(function () {
|
||||
this.disposed = true;
|
||||
}.bind(mockedSkinview3d.SkinViewer));
|
||||
const wrapper = mount(Previewer);
|
||||
wrapper.find('.fa-stop').trigger('click');
|
||||
expect(mockedSkinview3d.SkinViewer.prototype.dispose).toBeCalled();
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user