mirror of
https://github.com/misode/misode.github.io.git
synced 2026-05-02 21:52:54 +00:00
Add sounds explorer tool
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import type { CollectionRegistry } from '@mcschema/core'
|
||||
import config from '../config.json'
|
||||
import type { VersionAssets, VersionManifest } from './Manifest'
|
||||
import type { BlockStateRegistry, VersionId } from './Schemas'
|
||||
import { checkVersion } from './Schemas'
|
||||
import { message } from './Utils'
|
||||
@@ -21,6 +22,9 @@ declare var __VANILLA_DATAPACK_SUMMARY_HASH__: string
|
||||
|
||||
const mcdataUrl = 'https://raw.githubusercontent.com/Arcensoth/mcdata'
|
||||
const vanillaDatapackUrl = 'https://raw.githubusercontent.com/SPGoding/vanilla-datapack'
|
||||
const manifestUrl = 'https://launchermeta.mojang.com/mc/game/version_manifest.json'
|
||||
const resourceUrl = 'https://resources.download.minecraft.net/'
|
||||
const corsUrl = 'https://misode-cors-anywhere.herokuapp.com/'
|
||||
|
||||
const refs: {
|
||||
id: VersionRef,
|
||||
@@ -166,6 +170,40 @@ export async function fetchPreset(version: VersionId, registry: string, id: stri
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchManifest() {
|
||||
try {
|
||||
const res = await fetch(manifestUrl)
|
||||
return await res.json()
|
||||
} catch (e) {
|
||||
throw new Error(`Error occurred while fetching version manifest: ${message(e)}`)
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchAssets(versionId: VersionId, manifest: VersionManifest) {
|
||||
const version = config.versions.find(v => v.id === versionId)
|
||||
const id = version?.latest ?? manifest.latest.snapshot
|
||||
try {
|
||||
const versionMeta = await getData(manifest.versions.find(v => v.id === id)!.url)
|
||||
|
||||
return (await getData(versionMeta.assetIndex.url)).objects
|
||||
} catch (e) {
|
||||
throw new Error(`Error occurred while fetching assets for ${version}: ${message(e)}`)
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchSounds(version: VersionId, assets: VersionAssets) {
|
||||
try {
|
||||
const hash = assets['minecraft/sounds.json'].hash
|
||||
return await getData(getResourceUrl(hash))
|
||||
} catch (e) {
|
||||
throw new Error(`Error occurred while fetching sounds for ${version}: ${message(e)}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function getResourceUrl(hash: string) {
|
||||
return `${corsUrl}${resourceUrl}${hash.slice(0, 2)}/${hash}`
|
||||
}
|
||||
|
||||
async function getData<T = any>(url: string, fn: (v: any) => T = (v: any) => v): Promise<T> {
|
||||
try {
|
||||
const cache = await caches.open(CACHE_NAME)
|
||||
|
||||
+3
-2
@@ -8,7 +8,7 @@ import '../styles/nodes.css'
|
||||
import { Analytics } from './Analytics'
|
||||
import { Header } from './components'
|
||||
import { loadLocale, locale, Locales } from './Locales'
|
||||
import { Generator, Home, Worldgen } from './pages'
|
||||
import { Generator, Home, Sounds, Worldgen } from './pages'
|
||||
import type { VersionId } from './Schemas'
|
||||
import { Store } from './Store'
|
||||
import { cleanUrl } from './Utils'
|
||||
@@ -71,7 +71,8 @@ function Main() {
|
||||
<Router onChange={changeRoute}>
|
||||
<Home path="/" {...{lang, changeTitle}} />
|
||||
<Worldgen path="/worldgen" {...{lang, changeTitle}} />
|
||||
<Generator default {...{lang, version, changeTitle}} onChangeVersion={changeVersion} />
|
||||
<Sounds path="/sounds" {...{lang, version, changeTitle, changeVersion}} />
|
||||
<Generator default {...{lang, version, changeTitle, changeVersion}} />
|
||||
</Router>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { fetchAssets, fetchManifest, fetchSounds } from './DataFetcher'
|
||||
import type { VersionId } from './Schemas'
|
||||
|
||||
export type VersionManifest = {
|
||||
latest: {
|
||||
release: string,
|
||||
snapshot: string,
|
||||
},
|
||||
versions: {
|
||||
id: string,
|
||||
type: string,
|
||||
url: string,
|
||||
}[],
|
||||
}
|
||||
let Manifest: VersionManifest | Promise<VersionManifest> | null = null
|
||||
|
||||
export type VersionAssets = {
|
||||
[key: string]: {
|
||||
hash: string,
|
||||
},
|
||||
}
|
||||
const VersionAssets: Record<string, VersionAssets | Promise<VersionAssets>> = {}
|
||||
|
||||
export type SoundEvents = {
|
||||
[key: string]: {
|
||||
sounds: (string | { name: string })[],
|
||||
},
|
||||
}
|
||||
const SoundEvents: Record<string, SoundEvents | Promise<SoundEvents>> = {}
|
||||
|
||||
export async function getManifest() {
|
||||
if (!Manifest) {
|
||||
Manifest = fetchManifest()
|
||||
}
|
||||
return Manifest
|
||||
}
|
||||
|
||||
export async function getAssets(version: VersionId) {
|
||||
if (!VersionAssets[version]) {
|
||||
VersionAssets[version] = (async () => {
|
||||
const manifest = await getManifest()
|
||||
return await fetchAssets(version, manifest)
|
||||
})()
|
||||
}
|
||||
return VersionAssets[version]
|
||||
}
|
||||
|
||||
export async function getSounds(version: VersionId) {
|
||||
if (!SoundEvents[version]) {
|
||||
SoundEvents[version] = (async () => {
|
||||
const assets = await getAssets(version)
|
||||
return await fetchSounds(version, assets)
|
||||
})()
|
||||
}
|
||||
return SoundEvents[version]
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export namespace Store {
|
||||
export const ID_THEME = 'theme'
|
||||
export const ID_VERSION = 'schema_version'
|
||||
export const ID_INDENT = 'indentation'
|
||||
export const ID_SOUNDS_VERSION = 'minecraft_sounds_version'
|
||||
|
||||
export function getLanguage() {
|
||||
return localStorage.getItem(ID_LANGUAGE) ?? 'en'
|
||||
@@ -27,6 +28,10 @@ export namespace Store {
|
||||
return localStorage.getItem(ID_INDENT) ?? '2_spaces'
|
||||
}
|
||||
|
||||
export function getSoundsVersion() {
|
||||
return localStorage.getItem(ID_SOUNDS_VERSION) ?? 'latest'
|
||||
}
|
||||
|
||||
export function setLanguage(language: string | undefined) {
|
||||
if (language) localStorage.setItem(ID_LANGUAGE, language)
|
||||
}
|
||||
@@ -42,4 +47,8 @@ export namespace Store {
|
||||
export function setIndent(indent: string | undefined) {
|
||||
if (indent) localStorage.setItem(ID_INDENT, indent)
|
||||
}
|
||||
|
||||
export function setSoundsVersion(version: string | undefined) {
|
||||
if (version) localStorage.setItem(ID_SOUNDS_VERSION, version)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export const Octicon = {
|
||||
alert: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8.22 1.754a.25.25 0 00-.44 0L1.698 13.132a.25.25 0 00.22.368h12.164a.25.25 0 00.22-.368L8.22 1.754zm-1.763-.707c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0114.082 15H1.918a1.75 1.75 0 01-1.543-2.575L6.457 1.047zM9 11a1 1 0 11-2 0 1 1 0 012 0zm-.25-5.25a.75.75 0 00-1.5 0v2.5a.75.75 0 001.5 0v-2.5z"></path></svg>,
|
||||
archive: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.75 2.5a.25.25 0 00-.25.25v1.5c0 .138.112.25.25.25h12.5a.25.25 0 00.25-.25v-1.5a.25.25 0 00-.25-.25H1.75zM0 2.75C0 1.784.784 1 1.75 1h12.5c.966 0 1.75.784 1.75 1.75v1.5A1.75 1.75 0 0114.25 6H1.75A1.75 1.75 0 010 4.25v-1.5zM1.75 7a.75.75 0 01.75.75v5.5c0 .138.112.25.25.25h10.5a.25.25 0 00.25-.25v-5.5a.75.75 0 111.5 0v5.5A1.75 1.75 0 0113.25 15H2.75A1.75 1.75 0 011 13.25v-5.5A.75.75 0 011.75 7zm4.5 1a.75.75 0 000 1.5h3.5a.75.75 0 100-1.5h-3.5z"></path></svg>,
|
||||
arrow_left: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.78 12.53a.75.75 0 01-1.06 0L2.47 8.28a.75.75 0 010-1.06l4.25-4.25a.75.75 0 011.06 1.06L4.81 7h7.44a.75.75 0 010 1.5H4.81l2.97 2.97a.75.75 0 010 1.06z"></path></svg>,
|
||||
arrow_right: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8.22 2.97a.75.75 0 011.06 0l4.25 4.25a.75.75 0 010 1.06l-4.25 4.25a.75.75 0 01-1.06-1.06l2.97-2.97H3.75a.75.75 0 010-1.5h7.44L8.22 4.03a.75.75 0 010-1.06z"></path></svg>,
|
||||
@@ -30,6 +31,7 @@ export const Octicon = {
|
||||
sun: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 10.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5zM8 12a4 4 0 100-8 4 4 0 000 8zM8 0a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0V.75A.75.75 0 018 0zm0 13a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 018 13zM2.343 2.343a.75.75 0 011.061 0l1.06 1.061a.75.75 0 01-1.06 1.06l-1.06-1.06a.75.75 0 010-1.06zm9.193 9.193a.75.75 0 011.06 0l1.061 1.06a.75.75 0 01-1.06 1.061l-1.061-1.06a.75.75 0 010-1.061zM16 8a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0116 8zM3 8a.75.75 0 01-.75.75H.75a.75.75 0 010-1.5h1.5A.75.75 0 013 8zm10.657-5.657a.75.75 0 010 1.061l-1.061 1.06a.75.75 0 11-1.06-1.06l1.06-1.06a.75.75 0 011.06 0zm-9.193 9.193a.75.75 0 010 1.06l-1.06 1.061a.75.75 0 11-1.061-1.06l1.06-1.061a.75.75 0 011.061 0z"></path></svg>,
|
||||
sync: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 2.5a5.487 5.487 0 00-4.131 1.869l1.204 1.204A.25.25 0 014.896 6H1.25A.25.25 0 011 5.75V2.104a.25.25 0 01.427-.177l1.38 1.38A7.001 7.001 0 0114.95 7.16a.75.75 0 11-1.49.178A5.501 5.501 0 008 2.5zM1.705 8.005a.75.75 0 01.834.656 5.501 5.501 0 009.592 2.97l-1.204-1.204a.25.25 0 01.177-.427h3.646a.25.25 0 01.25.25v3.646a.25.25 0 01-.427.177l-1.38-1.38A7.001 7.001 0 011.05 8.84a.75.75 0 01.656-.834z"></path></svg>,
|
||||
tag: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.5 7.775V2.75a.25.25 0 01.25-.25h5.025a.25.25 0 01.177.073l6.25 6.25a.25.25 0 010 .354l-5.025 5.025a.25.25 0 01-.354 0l-6.25-6.25a.25.25 0 01-.073-.177zm-1.5 0V2.75C1 1.784 1.784 1 2.75 1h5.025c.464 0 .91.184 1.238.513l6.25 6.25a1.75 1.75 0 010 2.474l-5.026 5.026a1.75 1.75 0 01-2.474 0l-6.25-6.25A1.75 1.75 0 011 7.775zM6 5a1 1 0 100 2 1 1 0 000-2z"></path></svg>,
|
||||
terminal: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M0 2.75C0 1.784.784 1 1.75 1h12.5c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0114.25 15H1.75A1.75 1.75 0 010 13.25V2.75zm1.75-.25a.25.25 0 00-.25.25v10.5c0 .138.112.25.25.25h12.5a.25.25 0 00.25-.25V2.75a.25.25 0 00-.25-.25H1.75zM7.25 8a.75.75 0 01-.22.53l-2.25 2.25a.75.75 0 11-1.06-1.06L5.44 8 3.72 6.28a.75.75 0 111.06-1.06l2.25 2.25c.141.14.22.331.22.53zm1.5 1.5a.75.75 0 000 1.5h3a.75.75 0 000-1.5h-3z"></path></svg>,
|
||||
three_bars: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1 2.75A.75.75 0 011.75 2h12.5a.75.75 0 110 1.5H1.75A.75.75 0 011 2.75zm0 5A.75.75 0 011.75 7h12.5a.75.75 0 110 1.5H1.75A.75.75 0 011 7.75zM1.75 12a.75.75 0 100 1.5h12.5a.75.75 0 100-1.5H1.75z"></path></svg>,
|
||||
trashcan: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M6.5 1.75a.25.25 0 01.25-.25h2.5a.25.25 0 01.25.25V3h-3V1.75zm4.5 0V3h2.25a.75.75 0 010 1.5H2.75a.75.75 0 010-1.5H5V1.75C5 .784 5.784 0 6.75 0h2.5C10.216 0 11 .784 11 1.75zM4.496 6.675a.75.75 0 10-1.492.15l.66 6.6A1.75 1.75 0 005.405 15h5.19c.9 0 1.652-.681 1.741-1.576l.66-6.6a.75.75 0 00-1.492-.149l-.66 6.6a.25.25 0 01-.249.225h-5.19a.25.25 0 01-.249-.225l-.66-6.6z"></path></svg>,
|
||||
unfold: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M8.177.677l2.896 2.896a.25.25 0 01-.177.427H8.75v1.25a.75.75 0 01-1.5 0V4H5.104a.25.25 0 01-.177-.427L7.823.677a.25.25 0 01.354 0zM7.25 10.75a.75.75 0 011.5 0V12h2.146a.25.25 0 01.177.427l-2.896 2.896a.25.25 0 01-.354 0l-2.896-2.896A.25.25 0 015.104 12H7.25v-1.25zm-5-2a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5zM6 8a.75.75 0 01-.75.75h-.5a.75.75 0 010-1.5h.5A.75.75 0 016 8zm2.25.75a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5zM12 8a.75.75 0 01-.75.75h-.5a.75.75 0 010-1.5h.5A.75.75 0 0112 8zm2.25.75a.75.75 0 000-1.5h-.5a.75.75 0 000 1.5h.5z"></path></svg>,
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { JSXInternal } from 'preact/src/jsx'
|
||||
|
||||
type InputProps = JSXInternal.HTMLAttributes<HTMLInputElement>
|
||||
|
||||
type BaseInputProps<T> = Omit<InputProps, 'onChange' | 'type'> & {
|
||||
onChange?: (value: T) => unknown,
|
||||
onEnter?: (value: T) => unknown,
|
||||
}
|
||||
function BaseInput<T>(name: string, type: string, fn: (value: string) => T) {
|
||||
const component = (props: BaseInputProps<T>) => {
|
||||
const onChange = props.onChange && ((evt: Event) => {
|
||||
const value = (evt.target as HTMLInputElement).value
|
||||
props.onChange?.(fn(value))
|
||||
})
|
||||
const onKeyDown = props.onEnter && ((evt: KeyboardEvent) => {
|
||||
if (evt.key === 'Enter') {
|
||||
const value = (evt.target as HTMLInputElement).value
|
||||
props.onEnter?.(fn(value))
|
||||
}
|
||||
})
|
||||
return <input {...props} {...{ type, onChange, onKeyDown }} />
|
||||
}
|
||||
component.displayName = name
|
||||
return component
|
||||
}
|
||||
|
||||
export const TextInput = BaseInput('TextInput', 'text', v => v)
|
||||
|
||||
export const NumberInput = BaseInput('NumberInput', 'number', v => Number(v))
|
||||
|
||||
export const RangeInput = BaseInput('RangeInput', 'range', v => Number(v))
|
||||
@@ -0,0 +1 @@
|
||||
export * from './Input'
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { DataModel } from '@mcschema/core'
|
||||
import { Path } from '@mcschema/core'
|
||||
import { useState } from 'preact/hooks'
|
||||
import { useModel } from '../hooks'
|
||||
import type { VersionId } from '../Schemas'
|
||||
import { BiomeSourcePreview, DecoratorPreview, NoiseSettingsPreview } from './previews'
|
||||
import { useModel } from '../../hooks'
|
||||
import type { VersionId } from '../../Schemas'
|
||||
import { BiomeSourcePreview, DecoratorPreview, NoiseSettingsPreview } from '../previews'
|
||||
|
||||
export const HasPreview = ['dimension', 'worldgen/noise_settings', 'worldgen/configured_feature']
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { DataModel, ModelPath } from '@mcschema/core'
|
||||
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
||||
import { Btn, BtnMenu } from '.'
|
||||
import { useModel } from '../hooks'
|
||||
import { locale } from '../Locales'
|
||||
import { transformOutput } from '../schema/transformOutput'
|
||||
import type { BlockStateRegistry } from '../Schemas'
|
||||
import { Store } from '../Store'
|
||||
import { message } from '../Utils'
|
||||
import { Btn, BtnMenu } from '..'
|
||||
import { useModel } from '../../hooks'
|
||||
import { locale } from '../../Locales'
|
||||
import { transformOutput } from '../../schema/transformOutput'
|
||||
import type { BlockStateRegistry } from '../../Schemas'
|
||||
import { Store } from '../../Store'
|
||||
import { message } from '../../Utils'
|
||||
|
||||
const OUTPUT_CHARS_LIMIT = 10000
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { DataModel } from '@mcschema/core'
|
||||
import { useErrorBoundary, useState } from 'preact/hooks'
|
||||
import { useModel } from '../hooks'
|
||||
import { FullNode } from '../schema/renderHtml'
|
||||
import type { BlockStateRegistry, VersionId } from '../Schemas'
|
||||
import { useModel } from '../../hooks'
|
||||
import { FullNode } from '../../schema/renderHtml'
|
||||
import type { BlockStateRegistry, VersionId } from '../../Schemas'
|
||||
|
||||
type TreePanelProps = {
|
||||
lang: string,
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './PreviewPanel'
|
||||
export * from './SourcePanel'
|
||||
export * from './Tree'
|
||||
@@ -3,10 +3,11 @@ export * from './Btn'
|
||||
export * from './BtnInput'
|
||||
export * from './BtnMenu'
|
||||
export * from './ErrorPanel'
|
||||
export * from './forms'
|
||||
export * from './generator'
|
||||
export * from './Header'
|
||||
export * from './Icons'
|
||||
export * from './Octicon'
|
||||
export * from './PreviewPanel'
|
||||
export * from './SourcePanel'
|
||||
export * from './previews'
|
||||
export * from './sounds'
|
||||
export * from './ToolCard'
|
||||
export * from './Tree'
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import { Howl } from 'howler'
|
||||
import { useEffect, useRef, useState } from 'preact/hooks'
|
||||
import { Btn, NumberInput, RangeInput, TextInput } from '..'
|
||||
import { getResourceUrl } from '../../DataFetcher'
|
||||
import { locale } from '../../Locales'
|
||||
import type { SoundEvents, VersionAssets } from '../../Manifest'
|
||||
|
||||
export interface SoundConfig {
|
||||
id: string,
|
||||
sound: string,
|
||||
delay: number,
|
||||
pitch: number,
|
||||
volume: number,
|
||||
}
|
||||
type SoundConfigProps = SoundConfig & {
|
||||
lang: string,
|
||||
assets: VersionAssets,
|
||||
sounds: SoundEvents,
|
||||
onEdit: (changes: Partial<SoundConfig>) => unknown,
|
||||
onDelete: () => unknown,
|
||||
delayedPlay?: number,
|
||||
}
|
||||
export function SoundConfig({ lang, assets, sounds, sound, delay, pitch, volume, onEdit, onDelete, delayedPlay }: SoundConfigProps) {
|
||||
const loc = locale.bind(null, lang)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [playing, setPlaying] = useState(false)
|
||||
const [invalid, setInvalid] = useState(false)
|
||||
const howls = useRef<Howl[]>([])
|
||||
const command = `playsound minecraft:${sound} master @s ~ ~ ~ ${volume} ${pitch}`
|
||||
|
||||
useEffect(() => {
|
||||
const soundEvent = sounds[sound]
|
||||
setInvalid((soundEvent?.sounds?.length ?? 0) === 0)
|
||||
howls.current.forEach(h => h.stop())
|
||||
howls.current = (soundEvent?.sounds ?? []).map(entry => {
|
||||
const soundPath = typeof entry === 'string' ? entry : entry.name
|
||||
const hash = assets[`minecraft/sounds/${soundPath}.ogg`].hash
|
||||
const url = getResourceUrl(hash)
|
||||
const howl = new Howl({
|
||||
src: [url],
|
||||
format: ['ogg'],
|
||||
volume,
|
||||
rate: pitch,
|
||||
})
|
||||
howl.on('end', () => {
|
||||
setPlaying(false)
|
||||
})
|
||||
const completed = () => {
|
||||
if (loading && howls.current.every(h => h.state() === 'loaded')) {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
if (howl.state() === 'loaded') {
|
||||
setTimeout(() => completed())
|
||||
} else {
|
||||
howl.on('load', () => {
|
||||
completed()
|
||||
})
|
||||
}
|
||||
return howl
|
||||
})
|
||||
setLoading(true)
|
||||
}, [sound, sounds])
|
||||
|
||||
useEffect(() => {
|
||||
howls.current.forEach(h => h.rate(pitch))
|
||||
}, [pitch])
|
||||
|
||||
useEffect(() => {
|
||||
howls.current.forEach(h => h.volume(volume))
|
||||
}, [volume])
|
||||
|
||||
const play = () => {
|
||||
if (loading || invalid) return
|
||||
stop()
|
||||
const howl = Math.floor(Math.random() * howls.current.length)
|
||||
howls.current[howl].play()
|
||||
setPlaying(true)
|
||||
}
|
||||
const stop = () => {
|
||||
howls.current.forEach(h => h.stop())
|
||||
}
|
||||
useEffect(() => {
|
||||
if (delayedPlay) setTimeout(() => play(), delay * 50)
|
||||
}, [delayedPlay])
|
||||
|
||||
useEffect(() => {
|
||||
return () => stop()
|
||||
}, [])
|
||||
|
||||
const [copyActive, setCopyActive] = useState(false)
|
||||
const copyTimeout = useRef<number | undefined>(undefined)
|
||||
const copy = () => {
|
||||
navigator.clipboard.writeText(command)
|
||||
setCopyActive(true)
|
||||
if (copyTimeout.current !== undefined) clearTimeout(copyTimeout.current)
|
||||
copyTimeout.current = setTimeout(() => {
|
||||
setCopyActive(false)
|
||||
}, 2000) as any
|
||||
}
|
||||
|
||||
return <div class={`sound-config${loading ? ' loading' : playing ? ' playing' : ''}${invalid ? ' invalid' : ''}`}>
|
||||
<Btn class="play" icon={invalid ? 'alert' : loading ? 'sync' : 'play'} label={loc('sounds.play')} onClick={play} tooltip={invalid ? loc('sounds.unknown_sound') : loading ? loc('sounds.loading_sound') : loc('sounds.play_sound')} tooltipLoc="se" />
|
||||
<TextInput class="btn btn-input sound" list="sound-list" spellcheck={false}
|
||||
value={sound} onChange={sound => onEdit({ sound })} />
|
||||
<label class="delay-label">{loc('sounds.delay')}: </label>
|
||||
<NumberInput class="btn btn-input delay" min={0}
|
||||
value={delay} onChange={delay => onEdit({ delay })} />
|
||||
<label class="pitch-label">{loc('sounds.pitch')}: </label>
|
||||
<RangeInput class="pitch tooltipped tip-s" min={0.5} max={2} step={0.01}
|
||||
aria-label={pitch.toFixed(2)} style={`--x: ${(pitch - 0.5) * (100 / 1.5)}%`}
|
||||
value={pitch} onChange={pitch => onEdit({ pitch })} />
|
||||
<label class="volume-label">{loc('sounds.volume')}: </label>
|
||||
<RangeInput class="volume tooltipped tip-s" min={0} max={1} step={0.01}
|
||||
aria-label={volume.toFixed(2)} style={`--x: ${volume * 100}%`}
|
||||
value={volume} onChange={volume => onEdit({ volume })} />
|
||||
<Btn class={`copy${copyActive ? ' active' : ''}`} icon={copyActive ? 'check' : 'terminal'} label={loc('copy')} tooltip={copyActive ? loc('copied') : loc('sounds.copy_command')}
|
||||
onClick={copy} />
|
||||
<Btn class="remove" icon="trashcan" tooltip={loc('sounds.remove_sound')}
|
||||
onClick={() => {onDelete(); stop()}} />
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './SoundConfig'
|
||||
@@ -9,16 +9,16 @@ import { useModel } from '../hooks'
|
||||
import { locale } from '../Locales'
|
||||
import type { BlockStateRegistry, VersionId } from '../Schemas'
|
||||
import { checkVersion, getBlockStates, getCollections, getModel } from '../Schemas'
|
||||
import { getGenerator } from '../Utils'
|
||||
import { getGenerator, message } from '../Utils'
|
||||
|
||||
type GeneratorProps = {
|
||||
lang: string,
|
||||
changeTitle: (title: string, versions?: VersionId[]) => unknown,
|
||||
version: VersionId,
|
||||
onChangeVersion: (version: VersionId) => unknown,
|
||||
changeVersion: (version: VersionId) => unknown,
|
||||
default?: true,
|
||||
}
|
||||
export function Generator({ lang, changeTitle, version, onChangeVersion }: GeneratorProps) {
|
||||
export function Generator({ lang, changeTitle, version, changeVersion }: GeneratorProps) {
|
||||
const loc = locale.bind(null, lang)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [errorBoundary, errorRetry] = useErrorBoundary()
|
||||
@@ -53,7 +53,7 @@ export function Generator({ lang, changeTitle, version, onChangeVersion }: Gener
|
||||
.then(b => setBlockStates(b))
|
||||
getModel(version, gen.id)
|
||||
.then(m => setModel(m))
|
||||
.catch(e => { console.error(e); setError(e.message) })
|
||||
.catch(e => { console.error(e); setError(message(e)) })
|
||||
}, [version, gen.id])
|
||||
|
||||
useModel(model, () => {
|
||||
@@ -183,7 +183,7 @@ export function Generator({ lang, changeTitle, version, onChangeVersion }: Gener
|
||||
</BtnMenu>
|
||||
<BtnMenu icon="tag" label={version}>
|
||||
{allowedVersions.reverse().map(v =>
|
||||
<Btn label={v} active={v === version} onClick={() => onChangeVersion(v)} />
|
||||
<Btn label={v} active={v === version} onClick={() => changeVersion(v)} />
|
||||
)}
|
||||
</BtnMenu>
|
||||
<BtnMenu icon="kebab_horizontal" tooltip={loc('more')}>
|
||||
|
||||
@@ -21,7 +21,7 @@ export function Home({ lang, changeTitle }: HomeProps) {
|
||||
<ToolCard title="Report Inspector" icon="report" link="https://misode.github.io/report/">
|
||||
<p>Analyse your performance reports</p>
|
||||
</ToolCard>
|
||||
<ToolCard title="Minecraft Sounds" icon="sounds" link="https://misode.github.io/sounds/">
|
||||
<ToolCard title="Minecraft Sounds" icon="sounds" link="/sounds/">
|
||||
<p>Browse through and mix all the vanilla sounds</p>
|
||||
</ToolCard>
|
||||
<ToolCard title="Data Pack Upgrader" link="https://misode.github.io/upgrader/">
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { useEffect, useRef, useState } from 'preact/hooks'
|
||||
import config from '../../config.json'
|
||||
import { Ad, Btn, BtnMenu, ErrorPanel, SoundConfig, TextInput } from '../components'
|
||||
import { locale } from '../Locales'
|
||||
import type { SoundEvents, VersionAssets } from '../Manifest'
|
||||
import { getAssets, getSounds } from '../Manifest'
|
||||
import type { VersionId } from '../Schemas'
|
||||
import { hexId, message } from '../Utils'
|
||||
|
||||
type SoundsProps = {
|
||||
path?: string,
|
||||
lang: string,
|
||||
changeTitle: (title: string, versions?: VersionId[]) => unknown,
|
||||
version: VersionId,
|
||||
changeVersion: (version: VersionId) => unknown,
|
||||
}
|
||||
export function Sounds({ lang, changeTitle, version, changeVersion }: SoundsProps) {
|
||||
const loc = locale.bind(null, lang)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
changeTitle(loc('title.sounds'))
|
||||
|
||||
const [assets, setAssets] = useState<VersionAssets>({})
|
||||
const [sounds, setSounds] = useState<SoundEvents>({})
|
||||
const soundKeys = Object.keys(sounds ?? {})
|
||||
useEffect(() => {
|
||||
getAssets(version)
|
||||
.then(assets => { setAssets(assets); return getSounds(version) })
|
||||
.then(sounds => { if (sounds) setSounds(sounds) })
|
||||
.catch(e => { console.error(e); setError(message(e)) })
|
||||
}, [version])
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
const [configs, setConfigs] = useState<SoundConfig[]>([])
|
||||
const addConfig = () => {
|
||||
setConfigs([{ id: hexId(), sound: search, delay: 0, pitch: 1, volume: 1 }, ...configs])
|
||||
}
|
||||
const editConfig = (id: string) => (changes: Partial<SoundConfig>) => {
|
||||
setConfigs(configs.map(c => c.id === id ? { ...c, ...changes } : c))
|
||||
}
|
||||
const deleteConfig = (id: string) => () => {
|
||||
setConfigs(configs.filter(c => c.id !== id))
|
||||
}
|
||||
|
||||
const [delayedPlay, setDelayedPlay] = useState(0)
|
||||
const playAll = () => {
|
||||
setDelayedPlay(delayedPlay + 1)
|
||||
}
|
||||
|
||||
const download = useRef<HTMLAnchorElement>(null)
|
||||
const downloadFunction = () => {
|
||||
const hasDelay = configs.some(c => c.delay > 0)
|
||||
const content = configs
|
||||
.sort((a, b) => a.delay - b.delay)
|
||||
.map(c => `${hasDelay ? `execute if score @s delay matches ${c.delay} run ` : ''}playsound minecraft:${c.sound} master @s ~ ~ ~ ${c.volume} ${c.pitch}`)
|
||||
.join('\n')
|
||||
download.current.setAttribute('href', 'data:text/plain;charset=utf-8,' + content + '%0A')
|
||||
download.current.setAttribute('download', 'sounds.mcfunction')
|
||||
download.current.click()
|
||||
}
|
||||
|
||||
return <main>
|
||||
<Ad type="text" id="sounds" />
|
||||
{error && <ErrorPanel error={error} onDismiss={() => setError(null)} />}
|
||||
{soundKeys.length > 0 && <>
|
||||
<div class="controls sounds-controls">
|
||||
<div class="sound-search-group">
|
||||
<TextInput class="btn btn-input sound-search" list="sound-list" placeholder={loc('sounds.search')}
|
||||
value={search} onChange={setSearch} onEnter={addConfig} />
|
||||
<Btn icon="plus" tooltip={loc('sounds.add_sound')} class="add-sound" onClick={addConfig} />
|
||||
</div>
|
||||
{configs.length > 1 && <Btn icon="play" label={ loc('sounds.play_all')} class="play-all-sounds" onClick={playAll} />}
|
||||
<div class="spacer"></div>
|
||||
<Btn icon="download" label={loc('download')} tooltip={loc('sounds.download_function')} class="download-sounds" onClick={downloadFunction} />
|
||||
<BtnMenu icon="tag" label={version}>
|
||||
{config.versions.reverse().map(v =>
|
||||
<Btn label={v.id} active={v.id === version} onClick={() => changeVersion(v.id as VersionId)} />
|
||||
)}
|
||||
</BtnMenu>
|
||||
</div>
|
||||
<div class="sounds">
|
||||
{configs.map(c => <SoundConfig key={c.id} {...c} {...{ lang, assets, sounds, delayedPlay }} onEdit={editConfig(c.id)} onDelete={deleteConfig(c.id)} />)}
|
||||
</div>
|
||||
<a ref={download} style="display: none;"></a>
|
||||
</>}
|
||||
<datalist id="sound-list">
|
||||
{soundKeys.map(s => <option key={s} value={s} />)}
|
||||
</datalist>
|
||||
</main>
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './Generator'
|
||||
export * from './Home'
|
||||
export * from './Sounds'
|
||||
export * from './Worldgen'
|
||||
|
||||
@@ -49,12 +49,14 @@
|
||||
"versions": [
|
||||
{
|
||||
"id": "1.15",
|
||||
"latest": "1.15.2",
|
||||
"refs": {
|
||||
"mcdata_master": "13355f7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1.16",
|
||||
"latest": "1.16.5",
|
||||
"refs": {
|
||||
"mcdata_master": "1.16.4",
|
||||
"vanilla_datapack_data": "1.16.4-data",
|
||||
@@ -63,6 +65,7 @@
|
||||
},
|
||||
{
|
||||
"id": "1.17",
|
||||
"latest": "1.17.1",
|
||||
"refs": {
|
||||
"mcdata_master": "1.17.1",
|
||||
"vanilla_datapack_data": "1.17.1-data",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"title.generator": "%0% Generator",
|
||||
"title.generator_category": "%0% Generators",
|
||||
"title.home": "Data Pack Generators",
|
||||
"title.sounds": "Sound Explorer",
|
||||
"presets": "Presets",
|
||||
"preview": "Visualize",
|
||||
"preview.scale": "Scale",
|
||||
@@ -66,6 +67,19 @@
|
||||
"search": "Search",
|
||||
"show_output": "Show JSON output",
|
||||
"show_preview": "Show preview",
|
||||
"sounds.play": "Play",
|
||||
"sounds.play_sound": "Play sound",
|
||||
"sounds.play_all": "Play all",
|
||||
"sounds.search": "Search sounds",
|
||||
"sounds.download_function": "Download Mcfunction",
|
||||
"sounds.delay": "Delay",
|
||||
"sounds.pitch": "Pitch",
|
||||
"sounds.volume": "Volume",
|
||||
"sounds.copy_command": "Copy command",
|
||||
"sounds.add_sound": "Add sound",
|
||||
"sounds.remove_sound": "Remove sound",
|
||||
"sounds.unknown_sound": "Unknown sound",
|
||||
"sounds.loading_sound": "Loading sound",
|
||||
"source_placeholder": "Paste JSON content here",
|
||||
"switch_generator": "Switch generator",
|
||||
"terrain_settings": "Terrain settings",
|
||||
|
||||
+268
-32
@@ -10,6 +10,10 @@
|
||||
--text-3: #c3c3c3;
|
||||
--accent-primary: #50baf9;
|
||||
--accent-success: #3eb84f;
|
||||
--accent-sounds-1: #451475;
|
||||
--accent-sounds-2: #39155e;
|
||||
--accent-sounds-3: #6a08a3;
|
||||
--accent-sounds-4: #d1a5e6;
|
||||
--nav: #91908f;
|
||||
--nav-hover: #b4b3b0;
|
||||
--nav-faded: #4d4c4c;
|
||||
@@ -17,6 +21,7 @@
|
||||
--selection: #6786dd99;
|
||||
--errors-background: #62190f;
|
||||
--errors-text: #ffffffcc;
|
||||
--invalid-text: #fd7951;
|
||||
}
|
||||
|
||||
:root[data-theme=light] {
|
||||
@@ -31,6 +36,10 @@
|
||||
--text-3: #494949;
|
||||
--accent-primary: #088cdb;
|
||||
--accent-success: #1a7f37;
|
||||
--accent-sounds-1: #b481e7;
|
||||
--accent-sounds-2: #c18df5;
|
||||
--accent-sounds-3: #af72d3;
|
||||
--accent-sounds-4: #efd3fd;
|
||||
--nav: #343a40;
|
||||
--nav-hover: #565d64;
|
||||
--nav-faded: #9fa2a7;
|
||||
@@ -38,6 +47,7 @@
|
||||
--selection: #6786dd99;
|
||||
--errors-background: #f66653;
|
||||
--errors-text: #000000cc;
|
||||
--invalid-text: #a32600;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
@@ -53,6 +63,10 @@
|
||||
--text-3: #494949;
|
||||
--accent-primary: #088cdb;
|
||||
--accent-success: #1a7f37;
|
||||
--accent-sounds-1: #b481e7;
|
||||
--accent-sounds-2: #c18df5;
|
||||
--accent-sounds-3: #af72d3;
|
||||
--accent-sounds-4: #efd3fd;
|
||||
--nav: #343a40;
|
||||
--nav-hover: #565d64;
|
||||
--nav-faded: #9fa2a7;
|
||||
@@ -60,6 +74,7 @@
|
||||
--selection: #6786dd99;
|
||||
--errors-background: #f66653;
|
||||
--errors-text: #000000cc;
|
||||
--invalid-text: #a32600;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +95,7 @@ a svg {
|
||||
body {
|
||||
font-size: 18px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
background-color: var(--background-1);
|
||||
}
|
||||
@@ -206,6 +222,7 @@ main {
|
||||
position: fixed;
|
||||
top: 12px;
|
||||
right: 16px;
|
||||
left: 16px;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -213,7 +230,7 @@ main {
|
||||
main > .controls {
|
||||
position: sticky;
|
||||
margin-right: 16px;
|
||||
right: 16px;
|
||||
margin-left: 16px;
|
||||
top: 68px;
|
||||
}
|
||||
|
||||
@@ -502,48 +519,55 @@ main.has-preview {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.tooltipped.tip-nw::after,
|
||||
.tooltipped.tip-ne::after {
|
||||
bottom: 100%;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.tooltipped.tip-se::after,
|
||||
.tooltipped.tip-s::after,
|
||||
.tooltipped.tip-sw::after {
|
||||
top: 100%;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.tooltipped.tip-ne::after
|
||||
.tooltipped.tip-se::after {
|
||||
left: 50%;
|
||||
margin-left: -16px;
|
||||
}
|
||||
|
||||
.tooltipped.tip-nw::after {
|
||||
bottom: 100%;
|
||||
margin-bottom: 6px;
|
||||
.tooltipped.tip-nw::after,
|
||||
.tooltipped.tip-sw::after {
|
||||
right: 50%;
|
||||
margin-right: -16px;
|
||||
}
|
||||
|
||||
.tooltipped.tip-ne::before,
|
||||
.tooltipped.tip-n::before,
|
||||
.tooltipped.tip-nw::before {
|
||||
bottom: auto;
|
||||
top: -7px;
|
||||
border-top-color: var(--background-6);
|
||||
}
|
||||
|
||||
.tooltipped.tip-se::after {
|
||||
top: 100%;
|
||||
margin-top: 6px;
|
||||
left: 50%;
|
||||
margin-left: -16px;
|
||||
}
|
||||
|
||||
.tooltipped.tip-sw::after {
|
||||
top: 100%;
|
||||
margin-top: 6px;
|
||||
right: 50%;
|
||||
margin-right: -16px;
|
||||
}
|
||||
|
||||
.tooltipped.tip-se::before,
|
||||
.tooltipped.tip-s::before,
|
||||
.tooltipped.tip-sw::before {
|
||||
top: auto;
|
||||
bottom: -7px;
|
||||
border-bottom-color: var(--background-6);
|
||||
}
|
||||
|
||||
.tooltipped.tip-s::after,
|
||||
.tooltipped.tip-n::after,
|
||||
.tooltipped.tip-s::before,
|
||||
.tooltipped.tip-n::before {
|
||||
left: var(--x, 50%);
|
||||
transform: translate(-50%, 8px);
|
||||
}
|
||||
|
||||
.tooltipped::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
@@ -768,12 +792,236 @@ hr {
|
||||
color: var(--text-3) !important;
|
||||
}
|
||||
|
||||
@keyframes spinner {
|
||||
.sounds {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.sound-search-group {
|
||||
flex-basis: 350px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 7px -2px #000;
|
||||
}
|
||||
|
||||
.sound-search {
|
||||
flex-basis: 100%;
|
||||
padding: 8px;
|
||||
color: var(--text-1);
|
||||
background-color: var(--background-2);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
margin-right: 0 !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn.add-sound {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
background-color: var(--accent-sounds-1);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn.add-sound:hover {
|
||||
background-color: var(--accent-sounds-2);
|
||||
}
|
||||
|
||||
.spacer {
|
||||
margin-right: auto !important;
|
||||
}
|
||||
|
||||
.sound-config {
|
||||
display: grid;
|
||||
grid-template-columns: min-content 2fr min-content min-content min-content 1fr min-content 1fr min-content min-content;
|
||||
align-items: center;
|
||||
gap: 12px 8px;
|
||||
padding: 10px;
|
||||
background-color: var(--background-2);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.sound-config:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.sound-config .btn {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.sound-config .sound {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sound-config label {
|
||||
color: var(--text-2);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sound-config .delay {
|
||||
width: 50px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.sound-config input[type=range] {
|
||||
-webkit-appearance: none;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.sound-config input[type=range]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.sound-config input[type=range]:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.sound-config input[type=range]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
border: none;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
border-radius: 50%;
|
||||
background: var(--text-3);
|
||||
cursor: pointer;
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
.sound-config input[type=range]::-moz-range-thumb {
|
||||
border: none;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
border-radius: 50%;
|
||||
background: var(--text-3);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sound-config input[type=range]::-webkit-slider-runnable-track {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
cursor: pointer;
|
||||
background: var(--background-4);
|
||||
border-radius: 2px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.sound-config input[type=range]:focus::-webkit-slider-runnable-track {
|
||||
background: var(--background-5);
|
||||
}
|
||||
|
||||
.sound-config input[type=range]::-moz-range-track {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
cursor: pointer;
|
||||
background: var(--background-4);
|
||||
border-radius: 2px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.sound-config input[type=range]:focus::-moz-range-track {
|
||||
background: var(--background-5);
|
||||
}
|
||||
|
||||
.sound-config .copy[data-command] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sound-config .copy[data-command]::after {
|
||||
content: attr(data-command);
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
margin-top: 6px;
|
||||
padding: 8px 12px;
|
||||
background-color: var(--background-3);
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 4px var(--background-1);
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
.sound-config.invalid .play,
|
||||
.sound-config.loading .play {
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
.sound-config.playing {
|
||||
background-color: var(--background-3);
|
||||
}
|
||||
|
||||
.sound-config.playing .play {
|
||||
background-image: linear-gradient(110deg, var(--accent-sounds-3), var(--accent-sounds-3) 45%, var(--accent-sounds-4) 47%, var(--accent-sounds-4) 53%, var(--accent-sounds-3) 55%);
|
||||
background-size: 300%;
|
||||
background-position: right;
|
||||
animation: playing 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes playing {
|
||||
0% {
|
||||
background-position: left;
|
||||
}
|
||||
100% {
|
||||
background-position: right;
|
||||
}
|
||||
}
|
||||
|
||||
.sound-config.loading:not(.invalid) .play svg {
|
||||
animation: spinning 2s infinite linear;
|
||||
}
|
||||
|
||||
.sound-config.invalid .sound {
|
||||
color: var(--invalid-text);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 720px) {
|
||||
.sound-search-group {
|
||||
margin-bottom: 8px;
|
||||
flex-basis: 100%;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
.sounds-controls {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.sounds .btn {
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.sounds .btn svg {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
.sounds .btn span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sound-config {
|
||||
grid-template-columns: min-content min-content 1fr min-content 1fr min-content;
|
||||
grid-template-areas:
|
||||
"play sound sound sound sound copy"
|
||||
"pitch-label pitch-label pitch volume-label volume remove";
|
||||
}
|
||||
.sound-config .play { grid-area: play; }
|
||||
.sound-config .sound { grid-area: sound; }
|
||||
.sound-config .delay-label { display: none; }
|
||||
.sound-config .delay { display: none; }
|
||||
.sound-config .pitch-label { grid-area: pitch-label; }
|
||||
.sound-config .pitch { grid-area: pitch; }
|
||||
.sound-config .volume-label { grid-area: volume-label; }
|
||||
.sound-config .volume { grid-area: volume; }
|
||||
.sound-config .copy { grid-area: copy; }
|
||||
.sound-config .remove { grid-area: remove; }
|
||||
}
|
||||
|
||||
@keyframes spinning {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
transform: rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -849,16 +1097,4 @@ hr {
|
||||
.generator-picker {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.field-list li {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.field-prop {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.field-prop input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user