Implement multi-scheme animation name mapper for Owen Animation System
Some checks failed
CI/CD Pipeline / Test & Lint (16.x) (push) Has been cancelled
CI/CD Pipeline / Test & Lint (18.x) (push) Has been cancelled
CI/CD Pipeline / Test & Lint (20.x) (push) Has been cancelled
CI/CD Pipeline / Security Audit (push) Has been cancelled
CI/CD Pipeline / Release (push) Has been cancelled
Release / Validate Version (push) Has been cancelled
Release / Build and Test (push) Has been cancelled
Release / Create Release (push) Has been cancelled
Release / Publish to NPM (push) Has been cancelled
Release / Deploy Demo (push) Has been cancelled
Animation Processing Pipeline / Validate Animation Names (push) Has been cancelled
Animation Processing Pipeline / Process Blender Animation Assets (push) Has been cancelled
Animation Processing Pipeline / Update Animation Documentation (push) Has been cancelled
Animation Processing Pipeline / Deploy Animation Demo (push) Has been cancelled
Some checks failed
CI/CD Pipeline / Test & Lint (16.x) (push) Has been cancelled
CI/CD Pipeline / Test & Lint (18.x) (push) Has been cancelled
CI/CD Pipeline / Test & Lint (20.x) (push) Has been cancelled
CI/CD Pipeline / Security Audit (push) Has been cancelled
CI/CD Pipeline / Release (push) Has been cancelled
Release / Validate Version (push) Has been cancelled
Release / Build and Test (push) Has been cancelled
Release / Create Release (push) Has been cancelled
Release / Publish to NPM (push) Has been cancelled
Release / Deploy Demo (push) Has been cancelled
Animation Processing Pipeline / Validate Animation Names (push) Has been cancelled
Animation Processing Pipeline / Process Blender Animation Assets (push) Has been cancelled
Animation Processing Pipeline / Update Animation Documentation (push) Has been cancelled
Animation Processing Pipeline / Deploy Animation Demo (push) Has been cancelled
- Added AnimationNameMapper class to handle conversion between different animation naming schemes (legacy, artist, hierarchical, semantic). - Included methods for initialization, pattern matching, conversion, and validation of animation names. - Developed comprehensive unit tests for the animation name converter and demo pages using Playwright. - Created a Vite configuration for the demo application, including asset handling and optimization settings. - Enhanced the demo with features for batch conversion, performance metrics, and responsive design.
This commit is contained in:
602
demo/js/demo.js
Normal file
602
demo/js/demo.js
Normal file
@ -0,0 +1,602 @@
|
||||
/**
|
||||
* Owen Animation System Demo - Main JavaScript
|
||||
*
|
||||
* This file provides the interactive functionality for the demo pages.
|
||||
* It demonstrates the core features of the Owen Animation System.
|
||||
*/
|
||||
|
||||
// Import Owen Animation System (simulated for demo)
|
||||
// In a real implementation, this would import from the actual package
|
||||
const OwenDemo = {
|
||||
// Mock AnimationNameMapper for demo purposes
|
||||
AnimationNameMapper: class {
|
||||
constructor () {
|
||||
this.animations = {
|
||||
legacy: [
|
||||
'walk_forward', 'walk_backward', 'run_fast', 'run_slow',
|
||||
'jump_high', 'jump_low', 'idle_breathing', 'idle_looking',
|
||||
'attack_sword', 'attack_bow', 'defend_shield', 'defend_dodge',
|
||||
'death_forward', 'death_backward', 'hurt_light', 'hurt_heavy',
|
||||
'climb_up', 'climb_down', 'swim_forward', 'swim_idle'
|
||||
],
|
||||
artist: [
|
||||
'WalkForward', 'WalkBackward', 'RunFast', 'RunSlow',
|
||||
'JumpHigh', 'JumpLow', 'IdleBreathing', 'IdleLooking',
|
||||
'AttackSword', 'AttackBow', 'DefendShield', 'DefendDodge',
|
||||
'DeathForward', 'DeathBackward', 'HurtLight', 'HurtHeavy',
|
||||
'ClimbUp', 'ClimbDown', 'SwimForward', 'SwimIdle'
|
||||
],
|
||||
hierarchical: [
|
||||
'character.movement.walk.forward', 'character.movement.walk.backward',
|
||||
'character.movement.run.fast', 'character.movement.run.slow',
|
||||
'character.movement.jump.high', 'character.movement.jump.low',
|
||||
'character.idle.breathing', 'character.idle.looking',
|
||||
'character.combat.attack.sword', 'character.combat.attack.bow',
|
||||
'character.combat.defend.shield', 'character.combat.defend.dodge',
|
||||
'character.state.death.forward', 'character.state.death.backward',
|
||||
'character.state.hurt.light', 'character.state.hurt.heavy',
|
||||
'character.movement.climb.up', 'character.movement.climb.down',
|
||||
'character.movement.swim.forward', 'character.movement.swim.idle'
|
||||
],
|
||||
semantic: [
|
||||
'character_walk_forward', 'character_walk_backward',
|
||||
'character_run_fast', 'character_run_slow',
|
||||
'character_jump_high', 'character_jump_low',
|
||||
'character_idle_breathing', 'character_idle_looking',
|
||||
'character_attack_sword', 'character_attack_bow',
|
||||
'character_defend_shield', 'character_defend_dodge',
|
||||
'character_death_forward', 'character_death_backward',
|
||||
'character_hurt_light', 'character_hurt_heavy',
|
||||
'character_climb_up', 'character_climb_down',
|
||||
'character_swim_forward', 'character_swim_idle'
|
||||
]
|
||||
}
|
||||
|
||||
// Create conversion mappings
|
||||
this.conversionMap = this.createConversionMap()
|
||||
}
|
||||
|
||||
createConversionMap () {
|
||||
const map = {}
|
||||
const schemes = Object.keys(this.animations)
|
||||
|
||||
schemes.forEach(scheme => {
|
||||
map[scheme] = {}
|
||||
schemes.forEach(targetScheme => {
|
||||
map[scheme][targetScheme] = {}
|
||||
this.animations[scheme].forEach((anim, index) => {
|
||||
map[scheme][targetScheme][anim] = this.animations[targetScheme][index]
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return map
|
||||
}
|
||||
|
||||
getAllAnimationsByScheme (scheme) {
|
||||
return this.animations[scheme] || []
|
||||
}
|
||||
|
||||
convert (animationName, targetScheme, sourceScheme = null) {
|
||||
// If no source scheme provided, try to detect it
|
||||
if (!sourceScheme) {
|
||||
sourceScheme = this.detectScheme(animationName)
|
||||
}
|
||||
|
||||
if (!sourceScheme) {
|
||||
throw new Error(`Unable to detect scheme for animation: ${animationName}`)
|
||||
}
|
||||
|
||||
if (!this.conversionMap[sourceScheme] || !this.conversionMap[sourceScheme][targetScheme]) {
|
||||
throw new Error(`Conversion from ${sourceScheme} to ${targetScheme} not supported`)
|
||||
}
|
||||
|
||||
const converted = this.conversionMap[sourceScheme][targetScheme][animationName]
|
||||
if (!converted) {
|
||||
throw new Error(`Animation "${animationName}" not found in ${sourceScheme} scheme`)
|
||||
}
|
||||
|
||||
return converted
|
||||
}
|
||||
|
||||
detectScheme (animationName) {
|
||||
for (const [scheme, animations] of Object.entries(this.animations)) {
|
||||
if (animations.includes(animationName)) {
|
||||
return scheme
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
convertBatch (animations, sourceScheme, targetScheme) {
|
||||
return animations.map(anim => {
|
||||
try {
|
||||
return {
|
||||
original: anim,
|
||||
converted: this.convert(anim, targetScheme, sourceScheme),
|
||||
success: true
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
original: anim,
|
||||
error: error.message,
|
||||
success: false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
suggestCorrections (animationName, scheme) {
|
||||
const animations = this.animations[scheme] || []
|
||||
return animations.filter(anim =>
|
||||
anim.toLowerCase().includes(animationName.toLowerCase()) ||
|
||||
animationName.toLowerCase().includes(anim.toLowerCase())
|
||||
).slice(0, 3)
|
||||
}
|
||||
},
|
||||
|
||||
// Mock OwenAnimationContext for demo purposes
|
||||
OwenAnimationContext: class {
|
||||
constructor (options = {}) {
|
||||
this.namingScheme = options.namingScheme || 'semantic'
|
||||
this.autoConvert = options.autoConvert !== false
|
||||
this.container = options.container
|
||||
this.currentAnimation = null
|
||||
this.isPlaying = false
|
||||
this.mapper = new OwenDemo.AnimationNameMapper()
|
||||
}
|
||||
|
||||
async loadModel (modelPath) {
|
||||
// Simulate model loading
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
console.log('Model loaded:', modelPath)
|
||||
}
|
||||
|
||||
async playAnimation (animationName) {
|
||||
try {
|
||||
// Convert animation name if needed
|
||||
let targetName = animationName
|
||||
if (this.autoConvert) {
|
||||
const detectedScheme = this.mapper.detectScheme(animationName)
|
||||
if (detectedScheme && detectedScheme !== this.namingScheme) {
|
||||
targetName = this.mapper.convert(animationName, this.namingScheme, detectedScheme)
|
||||
}
|
||||
}
|
||||
|
||||
this.currentAnimation = targetName
|
||||
this.isPlaying = true
|
||||
|
||||
console.log(`Playing animation: ${targetName} (original: ${animationName})`)
|
||||
|
||||
// Simulate animation playback
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
console.log(`Animation ${targetName} completed`)
|
||||
resolve()
|
||||
}, 2000)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Failed to play animation:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
stopAnimation () {
|
||||
this.isPlaying = false
|
||||
this.currentAnimation = null
|
||||
console.log('Animation stopped')
|
||||
}
|
||||
|
||||
dispose () {
|
||||
this.stopAnimation()
|
||||
console.log('Animation context disposed')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Demo Application State
|
||||
const DemoState = {
|
||||
currentScheme: 'semantic',
|
||||
selectedAnimation: null,
|
||||
animationContext: null,
|
||||
mapper: new OwenDemo.AnimationNameMapper(),
|
||||
|
||||
init () {
|
||||
this.setupEventListeners()
|
||||
this.updateAnimationList()
|
||||
this.setupConversionTool()
|
||||
this.setupTabSwitching()
|
||||
this.initAnimationContext()
|
||||
},
|
||||
|
||||
setupEventListeners () {
|
||||
// Naming scheme change
|
||||
const schemeSelect = document.getElementById('naming-scheme')
|
||||
if (schemeSelect) {
|
||||
schemeSelect.addEventListener('change', (e) => {
|
||||
this.currentScheme = e.target.value
|
||||
this.updateAnimationList()
|
||||
this.updateCodeExamples()
|
||||
})
|
||||
}
|
||||
|
||||
// Animation selection
|
||||
const animationSelect = document.getElementById('animation-select')
|
||||
if (animationSelect) {
|
||||
animationSelect.addEventListener('change', (e) => {
|
||||
this.selectedAnimation = e.target.value
|
||||
this.updatePlayButtons()
|
||||
})
|
||||
}
|
||||
|
||||
// Playback controls
|
||||
const playBtn = document.getElementById('play-animation')
|
||||
const pauseBtn = document.getElementById('pause-animation')
|
||||
const stopBtn = document.getElementById('stop-animation')
|
||||
|
||||
if (playBtn) {
|
||||
playBtn.addEventListener('click', () => this.playSelectedAnimation())
|
||||
}
|
||||
if (pauseBtn) {
|
||||
pauseBtn.addEventListener('click', () => this.pauseAnimation())
|
||||
}
|
||||
if (stopBtn) {
|
||||
stopBtn.addEventListener('click', () => this.stopAnimation())
|
||||
}
|
||||
|
||||
// Start demo button
|
||||
const startBtn = document.getElementById('start-demo')
|
||||
if (startBtn) {
|
||||
startBtn.addEventListener('click', () => this.startInteractiveDemo())
|
||||
}
|
||||
},
|
||||
|
||||
updateAnimationList () {
|
||||
const select = document.getElementById('animation-select')
|
||||
if (!select) return
|
||||
|
||||
const animations = this.mapper.getAllAnimationsByScheme(this.currentScheme)
|
||||
|
||||
select.innerHTML = '<option value="">Select an animation...</option>'
|
||||
animations.forEach(anim => {
|
||||
const option = document.createElement('option')
|
||||
option.value = anim
|
||||
option.textContent = anim
|
||||
select.appendChild(option)
|
||||
})
|
||||
|
||||
this.updatePlayButtons()
|
||||
},
|
||||
|
||||
updatePlayButtons () {
|
||||
const hasSelection = !!this.selectedAnimation
|
||||
const playBtn = document.getElementById('play-animation')
|
||||
const pauseBtn = document.getElementById('pause-animation')
|
||||
const stopBtn = document.getElementById('stop-animation')
|
||||
|
||||
if (playBtn) playBtn.disabled = !hasSelection
|
||||
if (pauseBtn) pauseBtn.disabled = !hasSelection
|
||||
if (stopBtn) stopBtn.disabled = !hasSelection
|
||||
},
|
||||
|
||||
setupConversionTool () {
|
||||
const input = document.getElementById('input-animation')
|
||||
const schemeSelect = document.getElementById('input-scheme')
|
||||
const convertBtn = document.getElementById('convert-btn')
|
||||
|
||||
if (!input || !schemeSelect || !convertBtn) return
|
||||
|
||||
const updateConversion = () => {
|
||||
const animationName = input.value.trim()
|
||||
const sourceScheme = schemeSelect.value
|
||||
|
||||
if (!animationName) {
|
||||
this.clearConversionResults()
|
||||
return
|
||||
}
|
||||
|
||||
this.performConversion(animationName, sourceScheme)
|
||||
}
|
||||
|
||||
input.addEventListener('input', updateConversion)
|
||||
schemeSelect.addEventListener('change', updateConversion)
|
||||
convertBtn.addEventListener('click', updateConversion)
|
||||
},
|
||||
|
||||
performConversion (animationName, sourceScheme) {
|
||||
const results = {
|
||||
legacy: '-',
|
||||
artist: '-',
|
||||
hierarchical: '-',
|
||||
semantic: '-'
|
||||
}
|
||||
|
||||
const schemes = ['legacy', 'artist', 'hierarchical', 'semantic']
|
||||
|
||||
schemes.forEach(targetScheme => {
|
||||
try {
|
||||
results[targetScheme] = this.mapper.convert(animationName, targetScheme, sourceScheme)
|
||||
} catch (error) {
|
||||
results[targetScheme] = `Error: ${error.message}`
|
||||
}
|
||||
})
|
||||
|
||||
this.displayConversionResults(results)
|
||||
},
|
||||
|
||||
displayConversionResults (results) {
|
||||
Object.entries(results).forEach(([scheme, result]) => {
|
||||
const element = document.getElementById(`result-${scheme}`)
|
||||
if (element) {
|
||||
element.textContent = result
|
||||
element.className = result.startsWith('Error:') ? 'error' : 'success'
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
clearConversionResults () {
|
||||
const schemes = ['legacy', 'artist', 'hierarchical', 'semantic']
|
||||
schemes.forEach(scheme => {
|
||||
const element = document.getElementById(`result-${scheme}`)
|
||||
if (element) {
|
||||
element.textContent = '-'
|
||||
element.className = ''
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
setupTabSwitching () {
|
||||
const tabButtons = document.querySelectorAll('.tab-button')
|
||||
const tabPanes = document.querySelectorAll('.tab-pane')
|
||||
|
||||
tabButtons.forEach(button => {
|
||||
button.addEventListener('click', (e) => {
|
||||
const targetTab = e.target.dataset.tab
|
||||
|
||||
// Update button states
|
||||
tabButtons.forEach(btn => btn.classList.remove('active'))
|
||||
e.target.classList.add('active')
|
||||
|
||||
// Update pane visibility
|
||||
tabPanes.forEach(pane => {
|
||||
pane.classList.remove('active')
|
||||
if (pane.id === `${targetTab}-tab`) {
|
||||
pane.classList.add('active')
|
||||
}
|
||||
})
|
||||
|
||||
this.updateCodeExamples()
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
updateCodeExamples () {
|
||||
// Update code examples based on current scheme and selection
|
||||
const jsOutput = document.getElementById('js-code-output')
|
||||
const reactOutput = document.getElementById('react-code-output')
|
||||
const vueOutput = document.getElementById('vue-code-output')
|
||||
|
||||
if (jsOutput) {
|
||||
jsOutput.textContent = this.generateJavaScriptExample()
|
||||
}
|
||||
if (reactOutput) {
|
||||
reactOutput.textContent = this.generateReactExample()
|
||||
}
|
||||
if (vueOutput) {
|
||||
vueOutput.textContent = this.generateVueExample()
|
||||
}
|
||||
},
|
||||
|
||||
generateJavaScriptExample () {
|
||||
const animation = this.selectedAnimation || 'character_walk_forward'
|
||||
return `import { OwenAnimationContext } from '@kjanat/owen'
|
||||
|
||||
// Initialize with ${this.currentScheme} naming scheme
|
||||
const animationContext = new OwenAnimationContext({
|
||||
namingScheme: '${this.currentScheme}',
|
||||
autoConvert: true
|
||||
})
|
||||
|
||||
// Load your character model
|
||||
await animationContext.loadModel('./path/to/character.gltf')
|
||||
|
||||
// Play animation using ${this.currentScheme} scheme
|
||||
await animationContext.playAnimation('${animation}')
|
||||
|
||||
// The system automatically handles conversions between schemes
|
||||
// You can use any naming scheme and it will convert automatically`
|
||||
},
|
||||
|
||||
generateReactExample () {
|
||||
const animation = this.selectedAnimation || 'character_walk_forward'
|
||||
return `import React, { useEffect, useRef, useState } from 'react'
|
||||
import { OwenAnimationContext } from '@kjanat/owen'
|
||||
|
||||
export function AnimatedCharacter() {
|
||||
const containerRef = useRef()
|
||||
const [animationContext, setAnimationContext] = useState(null)
|
||||
const [currentAnimation, setCurrentAnimation] = useState('${animation}')
|
||||
|
||||
useEffect(() => {
|
||||
const context = new OwenAnimationContext({
|
||||
namingScheme: '${this.currentScheme}',
|
||||
container: containerRef.current
|
||||
})
|
||||
|
||||
context.loadModel('./character.gltf').then(() => {
|
||||
setAnimationContext(context)
|
||||
})
|
||||
|
||||
return () => context?.dispose()
|
||||
}, [])
|
||||
|
||||
const playAnimation = async (animationName) => {
|
||||
if (animationContext) {
|
||||
await animationContext.playAnimation(animationName)
|
||||
setCurrentAnimation(animationName)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="animated-character">
|
||||
<div ref={containerRef} className="viewport" />
|
||||
<button onClick={() => playAnimation('${animation}')}>
|
||||
Play Animation
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}`
|
||||
},
|
||||
|
||||
generateVueExample () {
|
||||
const animation = this.selectedAnimation || 'character_walk_forward'
|
||||
return `<template>
|
||||
<div class="animated-character">
|
||||
<div ref="viewport" class="viewport"></div>
|
||||
<button @click="playAnimation('${animation}')">
|
||||
Play Animation
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { OwenAnimationContext } from '@kjanat/owen'
|
||||
|
||||
const viewport = ref(null)
|
||||
const animationContext = ref(null)
|
||||
const currentAnimation = ref('${animation}')
|
||||
|
||||
onMounted(async () => {
|
||||
const context = new OwenAnimationContext({
|
||||
namingScheme: '${this.currentScheme}',
|
||||
container: viewport.value
|
||||
})
|
||||
|
||||
await context.loadModel('./character.gltf')
|
||||
animationContext.value = context
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
animationContext.value?.dispose()
|
||||
})
|
||||
|
||||
const playAnimation = async (animationName) => {
|
||||
if (animationContext.value) {
|
||||
await animationContext.value.playAnimation(animationName)
|
||||
currentAnimation.value = animationName
|
||||
}
|
||||
}
|
||||
</script>`
|
||||
},
|
||||
|
||||
async initAnimationContext () {
|
||||
const canvas = document.getElementById('demo-canvas')
|
||||
if (!canvas) return
|
||||
|
||||
this.animationContext = new OwenDemo.OwenAnimationContext({
|
||||
namingScheme: this.currentScheme,
|
||||
container: canvas
|
||||
})
|
||||
|
||||
// Simulate model loading
|
||||
try {
|
||||
await this.animationContext.loadModel('basic-character.gltf')
|
||||
console.log('Demo character loaded successfully')
|
||||
} catch (error) {
|
||||
console.error('Failed to load demo character:', error)
|
||||
}
|
||||
},
|
||||
|
||||
async playSelectedAnimation () {
|
||||
if (!this.selectedAnimation || !this.animationContext) return
|
||||
|
||||
try {
|
||||
await this.animationContext.playAnimation(this.selectedAnimation)
|
||||
} catch (error) {
|
||||
console.error('Failed to play animation:', error)
|
||||
window.alert(`Failed to play animation: ${error.message}`)
|
||||
}
|
||||
},
|
||||
|
||||
pauseAnimation () {
|
||||
// In a real implementation, this would pause the current animation
|
||||
console.log('Animation paused')
|
||||
},
|
||||
|
||||
stopAnimation () {
|
||||
if (this.animationContext) {
|
||||
this.animationContext.stopAnimation()
|
||||
}
|
||||
},
|
||||
|
||||
startInteractiveDemo () {
|
||||
// Navigate to interactive page or start guided tour
|
||||
if (window.location.pathname.includes('index.html') || window.location.pathname === '/') {
|
||||
window.location.href = 'interactive.html'
|
||||
} else {
|
||||
// Already on a page with interactive features
|
||||
this.scrollToSection('.live-demo-section')
|
||||
}
|
||||
},
|
||||
|
||||
scrollToSection (selector) {
|
||||
const element = document.querySelector(selector)
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy code functionality
|
||||
function setupCodeCopying () {
|
||||
document.querySelectorAll('.copy-code-btn').forEach(button => {
|
||||
button.addEventListener('click', async (e) => {
|
||||
const targetId = e.target.dataset.target
|
||||
const codeElement = document.getElementById(targetId)
|
||||
|
||||
if (codeElement) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(codeElement.textContent)
|
||||
|
||||
// Visual feedback
|
||||
const originalText = e.target.textContent
|
||||
e.target.textContent = 'Copied!'
|
||||
e.target.style.background = 'var(--success-color)'
|
||||
|
||||
setTimeout(() => {
|
||||
e.target.textContent = originalText
|
||||
e.target.style.background = ''
|
||||
}, 2000)
|
||||
} catch (error) {
|
||||
console.error('Failed to copy code:', error)
|
||||
window.alert('Failed to copy code to clipboard')
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Initialize demo when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
DemoState.init()
|
||||
setupCodeCopying()
|
||||
|
||||
// Add some visual feedback for interactions
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.classList.contains('btn')) {
|
||||
e.target.style.transform = 'scale(0.98)'
|
||||
setTimeout(() => {
|
||||
e.target.style.transform = ''
|
||||
}, 150)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Export for use in other demo files
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = { OwenDemo, DemoState }
|
||||
} else {
|
||||
window.OwenDemo = OwenDemo
|
||||
window.DemoState = DemoState
|
||||
}
|
||||
Reference in New Issue
Block a user