Skip to content

Commit

Permalink
(#4) convert perhaps the biggest slice possible to React without conv…
Browse files Browse the repository at this point in the history
…erting the entire app

Converting just the textarea and update button would have been a more
sensible step.
  • Loading branch information
effortlessmountain committed Apr 21, 2020
1 parent 42f2208 commit fa4ef5c
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 190 deletions.
46 changes: 1 addition & 45 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,52 +42,8 @@ <h3>How do I add an icon to my asset?</h3>
</div>
<div class="container">
<div class="assets"></div>
<div class="interface">
<h2>Ironsworn Asset Workbench v0.7.1</h2>
<div class="top-row-controls">
<div>
<label>Scale (also affects Download size)</label>
<select id="scale-select">
<option value="one-third">250px by 350px</option>
<option value="one-half">375px by 525px</option>
<option value="two-thirds">500px by 700px</option>
<option value="full">750px by 1050px</option>
</select>
</div>
<div>
<button id="show-help">How do I...?</button>
</div>
</div>
<div class="example-controls">
<label>Load Example Asset:</label>
<button class="show-example" id="lightbearer-example">Lightbearer</button>
<button class="show-example" id="ironclad-example">Ironclad</button>
<button class="show-example" id="cave-lion-example">Cave Lion</button>
</div>
<div class="editor-container"></div>

<div class="editor">
<textarea class="interface-input" spellcheck="false"></textarea>
<button class="update">update</button>
<div class="icon-import">
<label for="icon-fileselect">Icon to import: </label>
<input type="file" id="icon-fileselect" />
<label for="icon-author">Icon Author: </label>
<input type="text" id="icon-author" />
<button id="icon-import-button">Import</button>
</div>
<div class=" export">
<button id="preview-download">preview</button>
<button id="download">download as image</button>
</div>
</div>
<div>
<p class="credits">
Ironsworn and the official Ironsworn assets Copyright ©2019 Shawn Tomkin and used under
the Creative Commons Attribution-NonCommercial-
ShareAlike 4.0 International license.
</p>
</div>
</div>
</body>

</html>
24 changes: 13 additions & 11 deletions src/asset.tsx → src/Asset/Asset.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { scaleRatio, assetScale } from './assetScaling'
import { FontConfig, makeMergedConfig, createGoogleFontString } from './models/assetStyles'
import { scaleRatio } from '../assetScaling'
import { FontConfig, makeMergedConfig, createGoogleFontString } from '../models/assetStyles'


const WriteIn = (props: { writeIn?: string }) => {
Expand Down Expand Up @@ -150,17 +150,18 @@ interface Asset {

interface AssetProps {
asset: Asset,
scale: { value: string }
scale: string
}

export const Asset = (props: AssetProps) => {
let asset = props.asset
return (<div className={`asset ${props.scale.value}`}>
console.log("mah props scale is", props.scale)
return (<div className={`asset ${props.scale}`}>
<AssetStyles fonts={asset.fonts}></AssetStyles>
<div className="main-matter">
<div className="top">
<div className="type">{asset.type}</div>
<Icon icon={asset.icon} scale={props.scale.value} />
<Icon icon={asset.icon} scale={props.scale} />
<div className="asset-name">{asset.name}</div>
</div>
<div className="details">
Expand All @@ -171,18 +172,19 @@ export const Asset = (props: AssetProps) => {
</div>
</div>
</div>
<Track track={asset.track} scale={props.scale.value} />
<Track track={asset.track} scale={props.scale} />
</div >)
}

export const showAssetIn = (element, asset) => {
export const showAssetIn = (element, asset, scale, callback?: () => void) => {
console.log("Showing asset with scale", scale, "in", element)
// TODO: watch for state changes inside of a react component instead of re-rendering everything
ReactDOM.render(<Asset asset={asset} scale={assetScale} />,
element)
ReactDOM.render(<Asset asset={asset} scale={scale} />,
element, callback)
}

const assetContainer = document.querySelector(".assets")

export const showAsset = (asset) => {
showAssetIn(assetContainer, asset)
export const showAsset = (asset, scale) => {
showAssetIn(assetContainer, asset, scale)
}
219 changes: 219 additions & 0 deletions src/Editor/Editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import { showAsset } from '../Asset/Asset'
import { transformToLatest, transformSvgString, AssetDocument } from '../models/models'
import { calculateScale } from '../assetScaling'
import React from 'react'
import { ironclad, lightbearer, caveLion } from '../exampleAssets'
import { showScreen } from '../router'

export let currentAsset: AssetDocument

function asJSON(val) {
return JSON.stringify(val, null, 2)
}

type EditorState = {
currentAsset: AssetDocument,
editorJSON: string,
assetScale: string
}

type EditorProps = {
closeDownload(): void,
renderOnCanvas(
asset: AssetDocument,
scale: string,
callback: (canvas) => void): void
}

export class Editor extends React.Component<EditorProps, EditorState> {
constructor(props) {
super(props)
let startingAsset = transformToLatest(caveLion as AssetDocument) //This is where I learn the tragic extent of the failings of TypeScript's type inference.
let startingScale = calculateScale()
this.state = {
currentAsset: startingAsset,
editorJSON: asJSON(startingAsset),
assetScale: startingScale
}
currentAsset = transformToLatest(caveLion as AssetDocument)
showAsset(currentAsset, startingScale)
}

setCurrentAsset(asset) {
this.setState((state, props) => {
const latest = transformToLatest(asset)
currentAsset = latest
showAsset(latest, state.assetScale)
return {
currentAsset: latest,
editorJSON: asJSON(asset)
}
})
}

handleScaleChange(event) {
const newScale = event.target.value
this.setState((state) => {
showAsset(state.currentAsset, newScale)
return { assetScale: newScale }
})
}

handleTextAreaChange(event) {
this.setState({ editorJSON: event.target.value })
}

updateOnClick() {
try {
this.setCurrentAsset(JSON.parse(this.state.editorJSON))
}
catch (error) {
window.alert(error)
}
}

downloadButtonOnclick(scale) {
function saveImage(uri, filename) {
const link = document.createElement('a')
link.href = uri
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}

this.props.renderOnCanvas(currentAsset,
scale,
canvas => {
saveImage(canvas.toDataURL(), currentAsset.name + ".png")

this.props.closeDownload()
})
}

previewDownloadButtonOnclick(scale) {
this.props.renderOnCanvas(currentAsset, scale, () => showScreen('preview-download'))
}


handleIconImport() {
//todo: move away from queryselecting and use React
const iconFileInput = document.querySelector("#icon-fileselect") as HTMLInputElement
const iconAuthorInput = document.querySelector("#icon-author") as HTMLInputElement

const file = iconFileInput.files[0];
if (file) {
var fileReader = new FileReader()
fileReader.onload = (e) => {
var svg = e.target.result as string
currentAsset.icon = {
type: "svg",
name: file.name.split('.').slice(0, -1).join('.'),
author: iconAuthorInput.value,
svg: transformSvgString(svg)
}
this.setCurrentAsset(currentAsset)
}
fileReader.readAsText(file)
} else {
alert("missing file")
}
}

render() {
return (<div className="interface">
<h2>Ironsworn Asset Workbench v0.7.1</h2>
<div className="top-row-controls">
<div>
<label>Scale (also affects Download size)</label>
<select
id="scale-select"
onChange={(event) => this.handleScaleChange(event)}
value={this.state.assetScale}
>
<option value="one-third">250px by 350px</option>
<option value="one-half">375px by 525px</option>
<option value="two-thirds">500px by 700px</option>
<option value="full">750px by 1050px</option>
</select>
</div>
<div>
<button id="show-help" onClick={() => showScreen('help')}>How do I...?</button>
</div>
</div>
<div className="example-controls">
<label>Load Example Asset:</label>
<button
className="show-example"
id="lightbearer-example"
onClick={() => this.setCurrentAsset(lightbearer)}
>
Lightbearer
</button>
<button
className="show-example"
id="ironclad-example"
onClick={() => this.setCurrentAsset(ironclad)}
>
Ironclad
</button>
<button
className="show-example"
id="cave-lion-example"
onClick={() => this.setCurrentAsset(caveLion)}
>
Cave Lion
</button>
</div>

<div className="editor">
<textarea
className="interface-input"
spellCheck="false"
value={this.state.editorJSON}
onChange={(event) => this.handleTextAreaChange(event)}
>
</textarea>
<button
className="update"
onClick={() => this.updateOnClick()}
>
update
</button>
<div className="icon-import">
<label htmlFor="icon-fileselect">Icon to import: </label>
<input type="file" id="icon-fileselect" />
<label htmlFor="icon-author">Icon Author: </label>
<input type="text" id="icon-author" />
<button
id="icon-import-button"
onClick={() => this.handleIconImport()}
>
Import
</button>
</div>
<div className=" export">
<button
id="preview-download"
onClick={() => this.previewDownloadButtonOnclick(this.state.assetScale)}
>
preview
</button>
<button
id="download"
onClick={() => this.downloadButtonOnclick(this.state.assetScale)}
>
download as image
</button>
</div>
</div>
<div>
<p className="credits">
Ironsworn and the official Ironsworn assets Copyright ©2019 Shawn Tomkin and used under
the Creative Commons Attribution-NonCommercial-
ShareAlike 4.0 International license.
</p>
</div>
</div>)
}
}
19 changes: 1 addition & 18 deletions src/assetScaling.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { showAsset } from "./asset"
import { currentAsset } from "./editor"

export let scaleRatio = {
"full": 1,
"two-thirds": 2 / 3,
"one-half": 0.5,
"one-third": 1 / 3,
}

const calculateScale = () => {
export function calculateScale() {
if (window.innerHeight > 1070) {
return "full"
} else if (window.innerHeight > 750) {
Expand All @@ -18,18 +15,4 @@ const calculateScale = () => {
}
}

export let assetScale = { value: calculateScale() }


const scaleSelect: HTMLInputElement = document.querySelector("#scale-select")

const changeSize = (size) => {
assetScale.value = size
showAsset(currentAsset)
}

scaleSelect.addEventListener('change', (event) => {
changeSize((event.currentTarget as HTMLInputElement).value)
})

scaleSelect.value = assetScale.value
Loading

0 comments on commit fa4ef5c

Please sign in to comment.