mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-26 08:26:51 +00:00
Complete refactor (#123)
This commit is contained in:
22
src/app/components/Dropdown.ts
Normal file
22
src/app/components/Dropdown.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Property } from '../state/Property';
|
||||
import { View } from '../views/View';
|
||||
import { Octicon } from './Octicon';
|
||||
|
||||
export const Dropdown = (view: View, icon: keyof typeof Octicon, entries: [string, string][], state: Property<string>, watcher?: (value: string) => void) => {
|
||||
const dropdown = view.register(el => {
|
||||
el.addEventListener('change', () => {
|
||||
state.set((el as HTMLSelectElement).value)
|
||||
})
|
||||
state.watchRun(v => (el as HTMLSelectElement).value = v, 'dropdown')
|
||||
watcher?.(state.get())
|
||||
})
|
||||
return `
|
||||
<div class="dropdown">
|
||||
<select data-id="${dropdown}">
|
||||
${entries.map(e => `
|
||||
<option value=${e[0]}>${e[1]}</option>
|
||||
`).join('')}
|
||||
</select>
|
||||
${Octicon[icon]}
|
||||
</div>`
|
||||
}
|
||||
53
src/app/components/Errors.ts
Normal file
53
src/app/components/Errors.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { DataModel } from '@mcschema/core';
|
||||
import { App } from '../App';
|
||||
import { locale } from '../Locales';
|
||||
import { View } from '../views/View';
|
||||
import { Octicon } from './Octicon';
|
||||
import { Toggle } from './Toggle';
|
||||
import { htmlEncode } from '../Utils'
|
||||
import { Tracker } from '../Tracker';
|
||||
|
||||
export const Errors = (view: View, model: DataModel) => {
|
||||
const getContent = () => {
|
||||
if (App.jsonError.get()) {
|
||||
return `<div class="error-list">
|
||||
<div class="error">
|
||||
${htmlEncode(App.jsonError.get()!)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="toggle" style="cursor: initial;">
|
||||
${Octicon.issue_opened}
|
||||
</div>`
|
||||
}
|
||||
if (model.errors.count() === 0) return ''
|
||||
return `${App.errorsVisible.get() ? `
|
||||
<div class="error-list">
|
||||
${model.errors.getAll().map(e => `
|
||||
<div class="error">
|
||||
<span class="error-path">${e.path.toString()}</span>
|
||||
<span>-</span>
|
||||
<span class="error-message">${htmlEncode(locale(e.error, e.params))}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
${Toggle(view, [[true, 'chevron_down'], [false, 'issue_opened']], App.errorsVisible, Tracker.toggleErrors)}`
|
||||
}
|
||||
const errors = view.register(el => {
|
||||
model.addListener({
|
||||
errors() {
|
||||
view.mount(el, getContent(), false)
|
||||
}
|
||||
})
|
||||
App.jsonError.watch(() => {
|
||||
view.mount(el, getContent(), false)
|
||||
})
|
||||
App.errorsVisible.watch(() => {
|
||||
view.mount(el, getContent(), false)
|
||||
}, 'errors')
|
||||
})
|
||||
return `
|
||||
<div class="errors" data-id="${errors}">
|
||||
${getContent()}
|
||||
</div>`
|
||||
}
|
||||
28
src/app/components/Header.ts
Normal file
28
src/app/components/Header.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { App } from '../App';
|
||||
import { View } from '../views/View';
|
||||
import { Dropdown } from './Dropdown';
|
||||
import { Octicon } from './Octicon';
|
||||
import { Toggle } from './Toggle';
|
||||
import { languages } from '../../config.json'
|
||||
import { Tracker } from '../Tracker';
|
||||
|
||||
export const Header = (view: View, title: string, homeLink = '/', panelToggleVisible = false) => `
|
||||
<header>
|
||||
<div class="header-title">
|
||||
<a data-link href="${homeLink}" class="home-link">${Octicon.three_bars}</a>
|
||||
<h2>${title}</h2>
|
||||
</div>
|
||||
<nav>
|
||||
${panelToggleVisible ? Toggle(view, [['tree', 'code'], ['source', 'note']], App.mobilePanel) : ''}
|
||||
<ul>
|
||||
<li>${Dropdown(view, 'globe', languages.map(l => [l.code, l.name]), App.language, Tracker.setLanguage)}</li>
|
||||
<li>${Toggle(view, [['dark', 'sun'], ['light', 'moon']], App.theme, Tracker.setTheme)}</li>
|
||||
<li class="dimmed">
|
||||
<a href="https://github.com/misode/misode.github.io" target="_blank">
|
||||
${Octicon.mark_github}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
`
|
||||
25
src/app/components/Octicon.ts
Normal file
25
src/app/components/Octicon.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export const Octicon = {
|
||||
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>',
|
||||
chevron_down: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M12.78 6.22a.75.75 0 010 1.06l-4.25 4.25a.75.75 0 01-1.06 0L3.22 7.28a.75.75 0 011.06-1.06L8 9.94l3.72-3.72a.75.75 0 011.06 0z"></path></svg>',
|
||||
chevron_right: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M6.22 3.22a.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.06L9.94 8 6.22 4.28a.75.75 0 010-1.06z"></path></svg>',
|
||||
clippy: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M5.75 1a.75.75 0 00-.75.75v3c0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75v-3a.75.75 0 00-.75-.75h-4.5zm.75 3V2.5h3V4h-3zm-2.874-.467a.75.75 0 00-.752-1.298A1.75 1.75 0 002 3.75v9.5c0 .966.784 1.75 1.75 1.75h8.5A1.75 1.75 0 0014 13.25v-9.5a1.75 1.75 0 00-.874-1.515.75.75 0 10-.752 1.298.25.25 0 01.126.217v9.5a.25.25 0 01-.25.25h-8.5a.25.25 0 01-.25-.25v-9.5a.25.25 0 01.126-.217z"></path></svg>',
|
||||
code: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4.72 3.22a.75.75 0 011.06 1.06L2.06 8l3.72 3.72a.75.75 0 11-1.06 1.06L.47 8.53a.75.75 0 010-1.06l4.25-4.25zm6.56 0a.75.75 0 10-1.06 1.06L13.94 8l-3.72 3.72a.75.75 0 101.06 1.06l4.25-4.25a.75.75 0 000-1.06l-4.25-4.25z"></path></svg>',
|
||||
download: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.47 10.78a.75.75 0 001.06 0l3.75-3.75a.75.75 0 00-1.06-1.06L8.75 8.44V1.75a.75.75 0 00-1.5 0v6.69L4.78 5.97a.75.75 0 00-1.06 1.06l3.75 3.75zM3.75 13a.75.75 0 000 1.5h8.5a.75.75 0 000-1.5h-8.5z"></path></svg>',
|
||||
globe: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.543 7.25h2.733c.144-2.074.866-3.756 1.58-4.948.12-.197.237-.381.353-.552a6.506 6.506 0 00-4.666 5.5zm2.733 1.5H1.543a6.506 6.506 0 004.666 5.5 11.13 11.13 0 01-.352-.552c-.715-1.192-1.437-2.874-1.581-4.948zm1.504 0h4.44a9.637 9.637 0 01-1.363 4.177c-.306.51-.612.919-.857 1.215a9.978 9.978 0 01-.857-1.215A9.637 9.637 0 015.78 8.75zm4.44-1.5H5.78a9.637 9.637 0 011.363-4.177c.306-.51.612-.919.857-1.215.245.296.55.705.857 1.215A9.638 9.638 0 0110.22 7.25zm1.504 1.5c-.144 2.074-.866 3.756-1.58 4.948-.12.197-.237.381-.353.552a6.506 6.506 0 004.666-5.5h-2.733zm2.733-1.5h-2.733c-.144-2.074-.866-3.756-1.58-4.948a11.738 11.738 0 00-.353-.552 6.506 6.506 0 014.666 5.5zM8 0a8 8 0 100 16A8 8 0 008 0z"></path></svg>',
|
||||
history: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.643 3.143L.427 1.927A.25.25 0 000 2.104V5.75c0 .138.112.25.25.25h3.646a.25.25 0 00.177-.427L2.715 4.215a6.5 6.5 0 11-1.18 4.458.75.75 0 10-1.493.154 8.001 8.001 0 101.6-5.684zM7.75 4a.75.75 0 01.75.75v2.992l2.028.812a.75.75 0 01-.557 1.392l-2.5-1A.75.75 0 017 8.25v-3.5A.75.75 0 017.75 4z"></path></svg>',
|
||||
info: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm6.5-.25A.75.75 0 017.25 7h1a.75.75 0 01.75.75v2.75h.25a.75.75 0 010 1.5h-2a.75.75 0 010-1.5h.25v-2h-.25a.75.75 0 01-.75-.75zM8 6a1 1 0 100-2 1 1 0 000 2z"></path></svg>',
|
||||
issue_opened: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zm-.25-6.25a.75.75 0 00-1.5 0v3.5a.75.75 0 001.5 0v-3.5z"></path></svg>',
|
||||
kebab_horizontal: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M8 9a1.5 1.5 0 100-3 1.5 1.5 0 000 3zM1.5 9a1.5 1.5 0 100-3 1.5 1.5 0 000 3zm13 0a1.5 1.5 0 100-3 1.5 1.5 0 000 3z"></path></svg>',
|
||||
link: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg>',
|
||||
mark_github: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg>',
|
||||
moon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M9.598 1.591a.75.75 0 01.785-.175 7 7 0 11-8.967 8.967.75.75 0 01.961-.96 5.5 5.5 0 007.046-7.046.75.75 0 01.175-.786zm1.616 1.945a7 7 0 01-7.678 7.678 5.5 5.5 0 107.678-7.678z"></path></svg>',
|
||||
note: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M0 3.75C0 2.784.784 2 1.75 2h12.5c.966 0 1.75.784 1.75 1.75v8.5A1.75 1.75 0 0114.25 14H1.75A1.75 1.75 0 010 12.25v-8.5zm1.75-.25a.25.25 0 00-.25.25v8.5c0 .138.112.25.25.25h12.5a.25.25 0 00.25-.25v-8.5a.25.25 0 00-.25-.25H1.75zM3.5 6.25a.75.75 0 01.75-.75h7a.75.75 0 010 1.5h-7a.75.75 0 01-.75-.75zm.75 2.25a.75.75 0 000 1.5h4a.75.75 0 000-1.5h-4z"></path></svg>',
|
||||
play: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zM6.379 5.227A.25.25 0 006 5.442v5.117a.25.25 0 00.379.214l4.264-2.559a.25.25 0 000-.428L6.379 5.227z"></path></svg>',
|
||||
plus_circle: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zm.75 4.75a.75.75 0 00-1.5 0v2.5h-2.5a.75.75 0 000 1.5h2.5v2.5a.75.75 0 001.5 0v-2.5h2.5a.75.75 0 000-1.5h-2.5v-2.5z"></path></svg>',
|
||||
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>',
|
||||
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>',
|
||||
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>',
|
||||
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>',
|
||||
x: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z"></path></svg>',
|
||||
}
|
||||
10
src/app/components/SplitGroup.ts
Normal file
10
src/app/components/SplitGroup.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import Split from 'split.js'
|
||||
import { View } from '../views/View';
|
||||
|
||||
export const SplitGroup = (view: View, options: Split.Options, entries: string[]) => `
|
||||
<div class="split-group ${options.direction ?? 'horizontal'}" data-id=${view.register(el => {
|
||||
Split([].slice.call(el.children), { snapOffset: 0, ...options })
|
||||
})}>
|
||||
${entries.join('')}
|
||||
</div>
|
||||
`
|
||||
16
src/app/components/Toggle.ts
Normal file
16
src/app/components/Toggle.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Property } from '../state/Property';
|
||||
import { View } from '../views/View';
|
||||
import { Octicon } from './Octicon';
|
||||
|
||||
export const Toggle = <T>(view: View, entries: [T, keyof typeof Octicon][], state: Property<T>, watcher?: (value: T) => void) => {
|
||||
const activeOcticon = () => Octicon[(entries.find(e => e[0] === state.get()) ?? entries[0])[1]]
|
||||
const toggle = view.register(el => {
|
||||
el.addEventListener('click', () => {
|
||||
const i = entries.findIndex(e => e[0] === state.get())
|
||||
state.set(entries[(i + 1) % entries.length][0])
|
||||
})
|
||||
state.watch(_ => el.innerHTML = activeOcticon(), 'toggle')
|
||||
watcher?.(state.get())
|
||||
})
|
||||
return `<div class="toggle" data-id="${toggle}">${activeOcticon()}</div>`
|
||||
}
|
||||
64
src/app/components/panels/PreviewPanel.ts
Normal file
64
src/app/components/panels/PreviewPanel.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { DataModel } from '@mcschema/core';
|
||||
import { App, Previews } from '../../App';
|
||||
import { BiomeNoisePreview } from '../../preview/BiomeNoisePreview';
|
||||
import { Tracker } from '../../Tracker';
|
||||
import { View } from '../../views/View';
|
||||
import { Octicon } from '../Octicon';
|
||||
|
||||
export const PreviewPanel = (view: View, model: DataModel) => {
|
||||
const canvas = view.register(el => {
|
||||
const redraw = () => {
|
||||
const preview = App.preview.get()
|
||||
if (preview && preview.path && preview.path.withModel(model).get()) {
|
||||
const ctx = (el as HTMLCanvasElement).getContext('2d')!
|
||||
const img = ctx.createImageData(200, 100)
|
||||
const newState = preview.path.withModel(model).get()
|
||||
preview.state = JSON.parse(JSON.stringify(newState))
|
||||
preview.draw(model, img)
|
||||
ctx.putImageData(img, 0, 0)
|
||||
} else {
|
||||
App.preview.set(null)
|
||||
}
|
||||
}
|
||||
model.addListener({
|
||||
invalidated: redraw
|
||||
})
|
||||
App.preview.watchRun((value) => {
|
||||
if (value) {
|
||||
redraw()
|
||||
}
|
||||
}, 'preview-panel')
|
||||
|
||||
;(Previews.biome_noise as BiomeNoisePreview).biomeColors.watch(() => {
|
||||
if (App.preview.get()?.getName() === 'biome-noise') {
|
||||
redraw()
|
||||
}
|
||||
}, 'preview-panel')
|
||||
|
||||
let dragStart: number[] | undefined
|
||||
(el as HTMLCanvasElement).addEventListener('mousedown', evt => {
|
||||
dragStart = [evt.offsetX, evt.offsetY]
|
||||
})
|
||||
;(el as HTMLCanvasElement).addEventListener('mousemove', evt => {
|
||||
if (dragStart === undefined) return
|
||||
if (App.preview.get()?.onDrag) {
|
||||
App.preview.get()?.onDrag(dragStart[0], dragStart[1], evt.offsetX, evt.offsetY)
|
||||
redraw()
|
||||
}
|
||||
dragStart = [evt.offsetX, evt.offsetY]
|
||||
})
|
||||
;(el as HTMLCanvasElement).addEventListener('mouseup', evt => {
|
||||
dragStart = undefined
|
||||
})
|
||||
})
|
||||
return `<div class="panel preview-panel">
|
||||
<div class="panel-controls">
|
||||
<div class="btn" data-id="${view.onClick(() => {
|
||||
Tracker.hidePreview(); App.preview.set(null)
|
||||
})}">
|
||||
${Octicon.x}
|
||||
</div>
|
||||
</div>
|
||||
<canvas width="200" height="100" data-id="${canvas}">
|
||||
</div>`
|
||||
}
|
||||
78
src/app/components/panels/SourcePanel.ts
Normal file
78
src/app/components/panels/SourcePanel.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { DataModel, ModelPath, Path } from '@mcschema/core';
|
||||
import { Tracker } from '../../Tracker';
|
||||
import { transformOutput } from '../../hooks/transformOutput';
|
||||
import { View } from '../../views/View';
|
||||
import { Octicon } from '../Octicon';
|
||||
import { App } from '../../App';
|
||||
|
||||
export const SourcePanel = (view: View, model: DataModel) => {
|
||||
const updateContent = (el: HTMLTextAreaElement) => {
|
||||
const data = model.schema.hook(transformOutput, new ModelPath(model), model.data);
|
||||
el.value = JSON.stringify(data, null, 2)
|
||||
}
|
||||
const source = view.register(el => {
|
||||
updateContent(el as HTMLTextAreaElement)
|
||||
model.addListener({
|
||||
invalidated() {
|
||||
App.jsonError.set(null)
|
||||
updateContent(el as HTMLTextAreaElement)
|
||||
}
|
||||
})
|
||||
el.addEventListener('change', () => {
|
||||
const rawSource = (el as HTMLTextAreaElement).value
|
||||
try {
|
||||
model.reset(JSON.parse(rawSource))
|
||||
App.jsonError.set(null)
|
||||
} catch (err) {
|
||||
App.jsonError.set(err.message)
|
||||
}
|
||||
})
|
||||
})
|
||||
const copySource = (el: Element) => {
|
||||
el.closest('.panel')?.getElementsByTagName('textarea')[0].select()
|
||||
document.execCommand('copy');
|
||||
Tracker.copy()
|
||||
}
|
||||
const downloadSource = (el: Element) => {
|
||||
const fileContents = encodeURIComponent(JSON.stringify(model.data, null, 2) + "\n")
|
||||
const downloadAnchor = el.lastElementChild as HTMLAnchorElement
|
||||
downloadAnchor.setAttribute('href', 'data:text/json;charset=utf-8,' + fileContents)
|
||||
downloadAnchor.setAttribute("download", "data.json")
|
||||
downloadAnchor.click()
|
||||
Tracker.download()
|
||||
}
|
||||
const shareSource = (el: Element) => {
|
||||
el.closest('.panel-controls')?.querySelector('input')
|
||||
Tracker.share()
|
||||
}
|
||||
const toggleMenu = (el: Element) => {
|
||||
el.classList.toggle('active')
|
||||
document.body.addEventListener('click', evt => {
|
||||
el.classList.remove('active')
|
||||
}, { capture: true, once: true })
|
||||
}
|
||||
return `<div class="panel source-panel">
|
||||
<div class="panel-controls">
|
||||
<input style="display: none;">
|
||||
<div class="btn" data-id="${view.onClick(copySource)}">
|
||||
${Octicon.clippy}
|
||||
<span data-i18n="copy"></span>
|
||||
</div>
|
||||
<div class="panel-menu">
|
||||
<div class="btn" data-id="${view.onClick(toggleMenu)}">
|
||||
${Octicon.kebab_horizontal}
|
||||
</div>
|
||||
<div class="panel-menu-list btn-group">
|
||||
<div class="btn" data-id="${view.onClick(downloadSource)}">
|
||||
${Octicon.download}<span data-i18n="download"></span>
|
||||
<a style="diplay: none;"></a>
|
||||
</div>
|
||||
<div class="btn" data-id="${view.onClick(shareSource)}">
|
||||
${Octicon.link}<span data-i18n="share"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<textarea class="source" data-id="${source}" spellcheck="false" autocorrect="off" autocapitalize="off"></textarea>
|
||||
</div>`
|
||||
}
|
||||
161
src/app/components/panels/TreePanel.ts
Normal file
161
src/app/components/panels/TreePanel.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { DataModel, ModelPath, Path } from '@mcschema/core';
|
||||
import { App, checkVersion, Previews } from '../../App';
|
||||
import { Tracker } from '../../Tracker'
|
||||
import { View } from '../../views/View';
|
||||
import { Octicon } from '../Octicon';
|
||||
import { Mounter } from '../../Mounter';
|
||||
import { renderHtml } from '../../hooks/renderHtml';
|
||||
import config from '../../../config.json'
|
||||
import { locale } from '../../Locales';
|
||||
import { BiomeNoisePreview } from '../../preview/BiomeNoisePreview';
|
||||
|
||||
const createPopupIcon = (type: string, icon: keyof typeof Octicon, popup: string) => {
|
||||
const div = document.createElement('div')
|
||||
div.className = `node-icon ${type}`
|
||||
div.addEventListener('click', evt => {
|
||||
div.getElementsByTagName('span')[0].classList.add('show')
|
||||
document.body.addEventListener('click', evt => {
|
||||
div.getElementsByTagName('span')[0].classList.remove('show')
|
||||
}, { capture: true, once: true })
|
||||
})
|
||||
div.insertAdjacentHTML('beforeend', `<span class="icon-popup">${popup}</span>${Octicon[icon]}`)
|
||||
return div
|
||||
}
|
||||
|
||||
const treeViewObserver = (el: Element) => {
|
||||
el.querySelectorAll('.node[data-help]').forEach(e => {
|
||||
e.querySelector('.node-header')?.appendChild(
|
||||
createPopupIcon('node-help', 'info', e.getAttribute('data-help') ?? ''))
|
||||
})
|
||||
el.querySelectorAll('.node[data-error]').forEach(e => {
|
||||
e.querySelector('.node-header')?.appendChild(
|
||||
createPopupIcon('node-error', 'issue_opened', e.getAttribute('data-error') ?? ''))
|
||||
})
|
||||
el.querySelectorAll('.collapse.closed, button.add').forEach(e => {
|
||||
e.insertAdjacentHTML('afterbegin', Octicon.plus_circle)
|
||||
})
|
||||
el.querySelectorAll('.collapse.open, button.remove').forEach(e => {
|
||||
e.insertAdjacentHTML('afterbegin', Octicon.trashcan)
|
||||
})
|
||||
}
|
||||
|
||||
const treeViewNodeInjector = (path: ModelPath, mounter: Mounter) => {
|
||||
|
||||
let res = Object.keys(Previews).map(k => Previews[k])
|
||||
.filter(v => v.active(path))
|
||||
.map(v => {
|
||||
const id = mounter.registerClick(() => {
|
||||
Tracker.setPreview(v.getName())
|
||||
v.path = path
|
||||
App.preview.set(v)
|
||||
})
|
||||
return `<button data-id=${id}>${locale('preview')} ${Octicon.play}</button>`
|
||||
}).join('')
|
||||
|
||||
if (path.pop().endsWith(new Path(['generator', 'biome_source', 'biomes']))) {
|
||||
const biomePreview = Previews.biome_noise as BiomeNoisePreview
|
||||
const biome = path.push('biome').get()
|
||||
const id = mounter.registerChange(el => {
|
||||
biomePreview.setBiomeColor(biome, (el as HTMLInputElement).value)
|
||||
biomePreview.state = {}
|
||||
})
|
||||
res += `<input type="color" value="${biomePreview.getBiomeHex(biome)}" data-id=${id}></input>`
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
export const TreePanel = (view: View, model: DataModel) => {
|
||||
const mounter = new Mounter({ nodeInjector: treeViewNodeInjector })
|
||||
const getContent = () => {
|
||||
if (App.loaded.get()) {
|
||||
const path = new ModelPath(model)
|
||||
const rendered = model.schema.hook(renderHtml, path, model.data, mounter)
|
||||
const category = model.schema.category(path)
|
||||
if (rendered[1]) {
|
||||
return `<div class="node ${model.schema.type(path)}-node" ${category ? `data-category="${category}"` : ''}>
|
||||
<div class="node-header">${rendered[1]}</div>
|
||||
<div class="node-body">${rendered[2]}</div>
|
||||
</div>`
|
||||
}
|
||||
return rendered[2]
|
||||
}
|
||||
return '<div class="spinner"></div>'
|
||||
}
|
||||
const mountContent = (el: Element) => {
|
||||
el.innerHTML = getContent()
|
||||
treeViewObserver(el)
|
||||
mounter.mount(el)
|
||||
}
|
||||
const tree = view.register(el => {
|
||||
App.loaded.watchRun((value) => {
|
||||
if (!value) {
|
||||
// If loading is taking more than 100 ms, show spinner
|
||||
new Promise(r => setTimeout(r, 100)).then(() => {
|
||||
if (!App.loaded.get()) {
|
||||
mountContent(el)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
mountContent(el)
|
||||
}
|
||||
})
|
||||
model.addListener({
|
||||
invalidated() {
|
||||
mountContent(el)
|
||||
}
|
||||
})
|
||||
;(Previews.biome_noise as BiomeNoisePreview).biomeColors.watch(() => {
|
||||
mountContent(el)
|
||||
}, 'tree-panel')
|
||||
})
|
||||
const toggleMenu = (el: Element) => {
|
||||
el.classList.toggle('active')
|
||||
document.body.addEventListener('click', evt => {
|
||||
el.classList.remove('active')
|
||||
}, { capture: true, once: true })
|
||||
}
|
||||
return `<div class="panel tree-panel">
|
||||
<div class="panel-controls">
|
||||
<div class="btn" data-id="${view.onClick(() => {
|
||||
Tracker.reset(); model.reset(model.schema.default())
|
||||
})}">
|
||||
${Octicon.history}
|
||||
<span data-i18n="reset"></span>
|
||||
</div>
|
||||
<div class="panel-menu">
|
||||
<div class="btn" data-id="${view.onClick(toggleMenu)}">
|
||||
${Octicon.tag}
|
||||
<span data-id="${view.register(el => App.version.watch(v => el.textContent = v, 'tree-controls'))}">
|
||||
${App.version.get()}
|
||||
</span>
|
||||
</div>
|
||||
<div class="panel-menu-list btn-group">
|
||||
${config.versions
|
||||
.filter(v => checkVersion(v.id, App.model.get()!.minVersion ?? '1.16'))
|
||||
.reverse()
|
||||
.map(v => `
|
||||
<div class="btn" data-id="${view.onClick(() => {
|
||||
Tracker.setVersion(v.id); App.version.set(v.id)
|
||||
})}">
|
||||
${v.id}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-menu">
|
||||
<div class="btn" data-id="${view.onClick(toggleMenu)}">
|
||||
${Octicon.kebab_horizontal}
|
||||
</div>
|
||||
<div class="panel-menu-list btn-group">
|
||||
<div class="btn" data-id="${view.onClick(() => {Tracker.undo(); model.undo()})}">
|
||||
${Octicon.arrow_left}<span data-i18n="undo"></span>
|
||||
</div>
|
||||
<div class="btn" data-id="${view.onClick(() => {Tracker.redo(); model.redo()})}">
|
||||
${Octicon.arrow_right}<span data-i18n="redo"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree" data-id="${tree}"></div>
|
||||
</div>`
|
||||
}
|
||||
Reference in New Issue
Block a user