Disambiguate TypeScript with tsx extension. (#3464)

Using the technique as discussed in #2761.
This commit is contained in:
Eloy Durán
2017-02-20 11:17:18 +01:00
committed by Colin Seymour
parent b66fcb2529
commit f1be771611
7 changed files with 1868 additions and 27 deletions

View File

@@ -465,5 +465,13 @@ module Linguist
Language["Scilab"] Language["Scilab"]
end end
end end
disambiguate ".tsx" do |data|
if /^\s*(import.+(from\s+|require\()['"]react|\/\/\/\s*<reference\s)/.match(data)
Language["TypeScript"]
elsif /^\s*<\?xml\s+version/i.match(data)
Language["XML"]
end
end
end end
end end

View File

@@ -0,0 +1,384 @@
// Fixture taken from https://github.com/graphcool/console/blob/dev/src/components/onboarding/PlaygroundCPopup/PlaygroundCPopup.tsx
import * as React from 'react'
import {withRouter} from 'react-router'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {nextStep, selectExample} from '../../../actions/gettingStarted'
import {classnames} from '../../../utils/classnames'
import Loading from '../../Loading/Loading'
import {GettingStartedState} from '../../../types/gettingStarted'
import {Example} from '../../../types/types'
const classes: any = require('./PlaygroundCPopup.scss')
import {$p} from 'graphcool-styles'
import * as cx from 'classnames'
interface Tutorial {
title: string
description: string
image: string
link: string
}
const guides: Tutorial[] = [
{
title: 'Learnrelay.org',
description: 'A comprehensive, interactive introduction to Relay',
link: 'https://learnrelay.org/',
image: require('../../../assets/graphics/relay.png'),
},
{
title: 'GraphQL and the amazing Apollo Client',
description: 'Explore an Application built using React and Angular 2',
link: 'https://medium.com/google-developer-experts/graphql-and-the-amazing-apollo-client-fe57e162a70c',
image: require('../../../assets/graphics/apollo.png'),
},
{
title: 'Introducing Lokka',
description: 'A Simple JavaScript Client for GraphQL',
link: 'https://voice.kadira.io/introducing-lokka-a-simple-javascript-client-for-graphql-e0802695648c',
image: require('../../../assets/graphics/lokka.png'),
},
]
const examples = {
ReactRelay: {
path: 'react-relay-instagram-example',
description: 'React + Relay',
},
ReactApollo: {
path: 'react-apollo-instagram-example',
description: 'React + Apollo',
},
ReactNativeApollo: {
path: 'react-native-apollo-instagram-example',
description: 'React Native + Apollo',
},
AngularApollo: {
path: 'angular-apollo-instagram-example',
description: 'Angular + Apollo',
},
}
interface Props {
id: string
projectId: string
nextStep: () => Promise<void>
selectExample: (selectedExample: Example) => any
gettingStartedState: GettingStartedState
}
interface State {
mouseOver: boolean
}
class PlaygroundCPopup extends React.Component<Props, State> {
state = {
mouseOver: false,
}
refs: {
[key: string]: any
exampleAnchor: HTMLDivElement
congratsAnchor: HTMLDivElement
scroller: HTMLDivElement,
}
componentDidUpdate(prevProps: Props, prevState: State) {
if (prevProps.gettingStartedState.selectedExample !== this.props.gettingStartedState.selectedExample) {
this.refs.scroller.scrollTop += this.refs.exampleAnchor.getBoundingClientRect().top
}
if (prevProps.gettingStartedState.isCurrentStep('STEP5_WAITING')
&& this.props.gettingStartedState.isCurrentStep('STEP5_DONE')) {
this.refs.scroller.scrollTop += this.refs.congratsAnchor.getBoundingClientRect().top
const snd = new Audio(require('../../../assets/success.mp3') as string)
snd.volume = 0.5
snd.play()
}
}
render() {
const {mouseOver} = this.state
const {selectedExample} = this.props.gettingStartedState
const hovering = !this.props.gettingStartedState.isCurrentStep('STEP4_CLICK_TEASER_STEP5')
const downloadUrl = (example) => `${__BACKEND_ADDR__}/resources/getting-started-example?repository=${examples[example].path}&project_id=${this.props.projectId}&user=graphcool-examples` // tslint:disable-line
return (
<div
className='flex justify-center items-start w-100 h-100'
style={{
transition: 'background-color 0.3s ease',
backgroundColor: hovering ? 'rgba(255,255,255,0.7)' : 'transparent',
width: 'calc(100% - 266px)',
pointerEvents: 'none',
overflow: 'hidden',
}}
>
<div
ref='scroller'
className='flex justify-center w-100'
style={{
transition: 'height 0.5s ease',
height: hovering ? '100%' : mouseOver ? '190%' : '210%',
pointerEvents: hovering ? 'all' : 'none',
cursor: hovering ? 'auto' : 'pointer',
overflow: hovering ? 'auto' : 'hidden',
alignItems: selectedExample ? 'flex-start' : 'center',
}}
>
<div
className='bg-white br-2 shadow-2 mv-96'
style={{
minWidth: 600,
maxWidth: 800,
pointerEvents: 'all',
}}
onMouseLeave={() => this.setState({ mouseOver: false })}
onMouseEnter={() => {
this.setState({ mouseOver: true })
}}
onMouseOver={(e: any) => {
if (this.props.gettingStartedState.isCurrentStep('STEP4_CLICK_TEASER_STEP5')) {
this.props.nextStep()
}
}}
>
<div className='ma-16 tc pb-25'>
<div className='fw3 ma-38 f-38'>
You did it! Time to run an example.
</div>
<div className='fw2 f-16 mh-96 lh-1-4'>
You have successfully set up your own Instagram backend.{' '}
When building an app with Graphcool you can easily explore queries in the{' '}
playground and "copy &amp; paste" selected queries into your code.{' '}
Of course, to do so, you need to implement the frontend first.
</div>
<div className='fw2 f-16 mh-96 lh-1-4 mt-16'>
<div>We put together a simple app to show and add posts</div>
<div>using the backend you just built, to test and run it locally.</div>
</div>
</div>
<div className='ma-16 tc pb-25'>
<div className='fw3 ma-38 f-25'>
Select your preferred technology to download the example.
</div>
<div className='flex justify-between items-center w-100' ref='exampleAnchor'>
<div
className={classnames(
classes.exampleButton,
selectedExample === 'ReactRelay' ? classes.active : '',
)}
onClick={() => this.props.selectExample('ReactRelay')}
>
React + Relay
</div>
<div
className={classnames(
classes.exampleButton,
selectedExample === 'ReactApollo' ? classes.active : '',
)}
onClick={() => this.props.selectExample('ReactApollo')}
>
React + Apollo
</div>
<div
className={classnames(
classes.exampleButton,
selectedExample === 'ReactNativeApollo' ? classes.active : '',
)}
onClick={() => this.props.selectExample('ReactNativeApollo')}
>
React Native + Apollo
</div>
<div
className={classnames(
classes.exampleButton,
selectedExample === 'AngularApollo' ? classes.active : '',
)}
onClick={() => this.props.selectExample('AngularApollo')}
>
Angular + Apollo
</div>
</div>
</div>
{selectedExample &&
<div>
<div className='w-100'>
<iframe
className='w-100'
height='480'
allowFullScreen
frameBorder='0'
src={`https://www.youtube.com/embed/${this.getExampleVideoUrl(selectedExample)}`}
/>
</div>
<div
className='w-100 pa-25'
style={{
backgroundColor: '#FEF5D2',
}}
>
<div className='mt-25 mb-38 w-100 flex justify-center'>
<a
href={downloadUrl(selectedExample)}
className='pa-16 white'
style={{
backgroundColor: '#4A90E2',
}}
>
Download example
</a>
</div>
<div className='code dark-gray'>
<div className='black-50'>
# To see the example in action, run the following commands:
</div>
<div className='mv-16'>
<div className='black'>
npm install
</div>
<div className='black'>
npm start
</div>
</div>
<div className='black-50'>
# You can now open the app on localhost:3000
</div>
<div className='black-50'>
# Please come back to this page once you're done. We're waiting here. 💤
</div>
<div className={cx($p.w100, $p.flex, $p.flexRow, $p.justifyCenter, $p.mt25)}>
<a href='#' onClick={
(e: any) => {
e.preventDefault()
// we need to skip the 'STEP5_WAITING' step
this.props.nextStep()
this.props.nextStep()
this.props.nextStep()
}
}>
Skip
</a>
</div>
</div>
{this.props.gettingStartedState.isCurrentStep('STEP5_WAITING') &&
<div className='w-100 mv-96 flex justify-center'>
<Loading />
</div>
}
</div>
</div>
}
{this.props.gettingStartedState.isCurrentStep('STEP5_DONE') &&
<div className='w-100 mb-96' ref='congratsAnchor'>
<div className='flex items-center flex-column pv-60 fw1'>
<div className='f-96'>
🎉
</div>
<div className='f-38 mt-38'>
Congratulations!
</div>
<div className='f-38 mt-16'>
We knew you had it in you.
</div>
<div className='f-16 mv-38'>
Now go out there and build amazing things!
</div>
</div>
<div className='flex justify-between ph-25 pv-16'>
<div className='w-50 pr-16'>
<div className='ttu ls-2 f-16 fw1 lh-1-4'>
Get started on your own<br />with those excellent tutorials
</div>
<div className='mv-38'>
{guides.map(guide => this.renderBox(guide))}
</div>
</div>
<div className='w-50 pl-16'>
<div className='ttu ls-2 f-16 fw1 lh-1-4'>
Get more out of Graphcool<br />with our guides
</div>
<div className={`h-100 justify-start flex flex-column mv-38 ${classes.guides}`}>
<a
href='https://graph.cool/docs/tutorials/quickstart-2-daisheeb9x'
className={`${classes.one} fw4 black db flex items-center mb-25`}
target='_blank'
>
Declaring Relations
</a>
<a
href='https://graph.cool/docs/tutorials/quickstart-3-saigai7cha'
className={`${classes.two} fw4 black db flex items-center mb-25`}
target='_blank'
>
Implementing Business Logic
</a>
<a
href='https://graph.cool/docs/tutorials/thinking-in-terms-of-graphs-ahsoow1ool'
target='_blank'
className={`${classes.three} fw4 black db flex items-center mb-25`}
>
Thinking in terms of graphs
</a>
</div>
</div>
</div>
<div className='flex w-100 justify-center'>
<div
className='f-25 mv-16 pv-16 ph-60 ls-1 ttu pointer bg-accent white dim'
onClick={this.props.nextStep}
>
Finish Onboarding
</div>
</div>
</div>
}
</div>
</div>
</div>
)
}
private renderBox = (tutorial: Tutorial) => {
return (
<div key={tutorial.title} className='pa-16 mb-16 lh-1-4' style={{background: 'rgba(0,0,0,0.03)'}}>
<a className='flex items-center' href={tutorial.link} target='_blank'>
<div className='flex items-center justify-center' style={{ flex: '0 0 60px', height: 60 }}>
<img src={tutorial.image}/>
</div>
<div className='flex flex-column space-between ml-38'>
<div className='mb-6 dark-gray f-16'>
{tutorial.title}
</div>
<div className='fw1 mid-gray'>
{tutorial.description}
</div>
</div>
</a>
</div>
)
}
private getExampleVideoUrl = (example: Example): string => {
switch (example) {
case 'ReactRelay': return '_dj9Os2ev4M'
case 'ReactApollo': return '9nlwyPUPXjQ'
case 'ReactNativeApollo': return '9nlwyPUPXjQ'
case 'AngularApollo': return 'EzD5fJ-uniI'
}
}
}
const mapStateToProps = (state) => {
return {
gettingStartedState: state.gettingStarted.gettingStartedState,
}
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({nextStep, selectExample}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(PlaygroundCPopup))

View File

@@ -0,0 +1,366 @@
// Fixture taken from https://github.com/bgrieder/RNTSExplorer/blob/master/typescript/components/TextExample.ios.tsx
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Typescript rewrite by Bruno Grieder
*/
'use strict'
import * as React from 'react-native';
import RNTSExample from '../RNTSExample'
import RNTSExampleModule from '../RNTSExampleModule'
const {
StyleSheet,
Text,
View,
} = React
const styles = StyleSheet.create(
{
backgroundColorText: {
left: 5,
backgroundColor: 'rgba(100, 100, 100, 0.3)'
},
entity: {
fontWeight: '500',
color: '#527fe4',
},
}
)
class Entity extends React.Component<any,any> {
render() {
return (
<Text style={styles.entity}>
{this.props.children}
</Text>
)
}
}
interface AttrTogglerState {
fontWeight?: string
fontSize?: number
}
class AttributeToggler extends React.Component<any, AttrTogglerState> {
componentWillMount() {
this.setState(
{
fontWeight: '500',
fontSize: 15
}
)
}
private increaseSize = (): void => {
this.setState( {
fontSize: this.state.fontSize + 1
} )
}
render() {
const curStyle: React.TextStyle = { fontSize: this.state.fontSize }
return (
<Text>
<Text style={curStyle}>
Tap the controls below to change attributes.
</Text>
<Text>
See how it will even work on{' '}
<Text style={curStyle}>
this nested text
</Text>
<Text onPress={this.increaseSize}>
{'>> Increase Size <<'}
</Text>
</Text>
</Text>
)
}
}
export default {
title: '<Text>',
description: 'Base component for rendering styled text.',
displayName: 'TextExample',
examples: [
{
title: 'Wrap',
render: function () {
return (
<Text>
The text should wrap if it goes on multiple lines. See, this is going to
the next line.
</Text>
)
},
},
{
title: 'Padding',
render: function () {
return (
<Text style={{padding: 10}}>
This text is indented by 10px padding on all sides.
</Text>
)
},
},
{
title: 'Font Family',
render: function () {
return (
<View>
<Text style={{fontFamily: 'Cochin'}}>
Cochin
</Text>
<Text style={{fontFamily: 'Cochin', fontWeight: 'bold'}}>
Cochin bold
</Text>
<Text style={{fontFamily: 'Helvetica'}}>
Helvetica
</Text>
<Text style={{fontFamily: 'Helvetica', fontWeight: 'bold'}}>
Helvetica bold
</Text>
<Text style={{fontFamily: 'Verdana'}}>
Verdana
</Text>
<Text style={{fontFamily: 'Verdana', fontWeight: 'bold'}}>
Verdana bold
</Text>
</View>
)
},
},
{
title: 'Font Size',
render: function () {
return (
<View>
<Text style={{fontSize: 23}}>
Size 23
</Text>
<Text style={{fontSize: 8}}>
Size 8
</Text>
</View>
)
},
},
{
title: 'Color',
render: function () {
return (
<View>
<Text style={{color: 'red'}}>
Red color
</Text>
<Text style={{color: 'blue'}}>
Blue color
</Text>
</View>
)
},
},
{
title: 'Font Weight',
render: function () {
return (
<View>
<Text style={{fontWeight: '100'}}>
Move fast and be ultralight
</Text>
<Text style={{fontWeight: '200'}}>
Move fast and be light
</Text>
<Text style={{fontWeight: 'normal'}}>
Move fast and be normal
</Text>
<Text style={{fontWeight: 'bold'}}>
Move fast and be bold
</Text>
<Text style={{fontWeight: '900'}}>
Move fast and be ultrabold
</Text>
</View>
)
},
},
{
title: 'Font Style',
render: function () {
return (
<View>
<Text style={{fontStyle: 'normal'}}>
Normal text
</Text>
<Text style={{fontStyle: 'italic'}}>
Italic text
</Text>
</View>
)
},
},
{
title: 'Nested',
description: 'Nested text components will inherit the styles of their ' +
'parents (only backgroundColor is inherited from non-Text parents). ' +
'<Text> only supports other <Text> and raw text (strings) as children.',
render: function () {
return (
<View>
<Text>
(Normal text,
<Text style={{fontWeight: 'bold'}}>
(and bold
<Text style={{fontSize: 11, color: '#527fe4'}}>
(and tiny inherited bold blue)
</Text>
)
</Text>
)
</Text>
<Text style={{fontSize: 12}}>
<Entity>Entity Name</Entity>
</Text>
</View>
)
},
},
{
title: 'Text Align',
render: function () {
return (
<View>
<Text style={{textAlign: 'left'}}>
left left left left left left left left left left left left left left left
</Text>
<Text style={{textAlign: 'center'}}>
center center center center center center center center center center center
</Text>
<Text style={{textAlign: 'right'}}>
right right right right right right right right right right right right right
</Text>
</View>
)
},
},
{
title: 'Spaces',
render: function () {
return (
<Text>
A {'generated'} {' '} {'string'} and some &nbsp&nbsp&nbsp spaces
</Text>
)
},
},
{
title: 'Line Height',
render: function () {
return (
<Text>
<Text style={{lineHeight: 35}}>
A lot of space between the lines of this long passage that should
wrap once.
</Text>
</Text>
)
},
},
{
title: 'Empty Text',
description: 'It\'s ok to have Text with zero or null children.',
render: function () {
return (
<Text />
)
},
},
{
title: 'Toggling Attributes',
render: (): React.ReactElement<any> => {
return <AttributeToggler />
},
},
{
title: 'backgroundColor attribute',
description: 'backgroundColor is inherited from all types of views.',
render: function () {
return (
<View style={{backgroundColor: 'yellow'}}>
<Text>
Yellow background inherited from View parent,
<Text style={{backgroundColor: '#ffaaaa'}}>
{' '}red background,
<Text style={{backgroundColor: '#aaaaff'}}>
{' '}blue background,
<Text>
{' '}inherited blue background,
<Text style={{backgroundColor: '#aaffaa'}}>
{' '}nested green background.
</Text>
</Text>
</Text>
</Text>
</Text>
</View>
)
},
},
{
title: 'containerBackgroundColor attribute',
render: function () {
return (
<View>
<View style={{flexDirection: 'row', height: 85}}>
<View style={{backgroundColor: '#ffaaaa', width: 150}}/>
<View style={{backgroundColor: '#aaaaff', width: 150}}/>
</View>
<Text style={[styles.backgroundColorText, {top: -80}]}>
Default containerBackgroundColor (inherited) + backgroundColor wash
</Text>
<Text style={[
styles.backgroundColorText,
{top: -70, containerBackgroundColor: 'transparent'}
]}>
{"containerBackgroundColor: 'transparent' + backgroundColor wash"}
</Text>
</View>
)
},
},
{
title: 'numberOfLines attribute',
render: function () {
return (
<View>
<Text numberOfLines={1}>
Maximum of one line no matter now much I write here. If I keep writing it{"'"}ll just truncate after one line
</Text>
<Text numberOfLines={2} style={{marginTop: 20}}>
Maximum of two lines no matter now much I write here. If I keep writing it{"'"}ll just truncate after two lines
</Text>
<Text style={{marginTop: 20}}>
No maximum lines specified no matter now much I write here. If I keep writing it{"'"}ll just keep going and going
</Text>
</View>
)
}
} ] as RNTSExample[]
} as RNTSExampleModule

View File

@@ -0,0 +1,240 @@
// Fixture taken from https://github.com/jcingroup/C551608_Roki/blob/master/Work.WebProj/Scripts/src/tsx/m-parm.tsx
import $ = require('jquery');
import React = require('react');
import ReactDOM = require('react-dom');
import Moment = require('moment');
import ReactBootstrap = require("react-bootstrap");
import CommCmpt = require('comm-cmpt');
import CommFunc = require('comm-func');
namespace Parm {
interface ParamData {
Email?: string;
PurchaseTotal?: number;
HomoiothermyFee?: number;
RefrigerFee?: number;
AccountName?: string;
BankName?: string;
BankCode?: string;
AccountNumber?: string;
Fee?: number;
}
export class GridForm extends React.Component<any, { param?: ParamData }>{
constructor() {
super();
this.queryInitData = this.queryInitData.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.componentDidMount = this.componentDidMount.bind(this);
this.setInputValue = this.setInputValue.bind(this);
this.render = this.render.bind(this);
this.state = {
param: {
Email: null,
PurchaseTotal: 0,
HomoiothermyFee: 0,
RefrigerFee:0,
AccountName: null,
BankName: null,
BankCode: null,
AccountNumber: null,
Fee: 0
}
}
}
static defaultProps = {
apiInitPath: gb_approot + 'Active/ParmData/aj_ParamInit',
apiPath: gb_approot + 'api/GetAction/PostParamData'
}
componentDidMount() {
this.queryInitData();
}
queryInitData() {
CommFunc.jqGet(this.props.apiInitPath, {})
.done((data, textStatus, jqXHRdata) => {
this.setState({ param: data });
})
.fail((jqXHR, textStatus, errorThrown) => {
CommFunc.showAjaxError(errorThrown);
});
}
handleSubmit(e: React.FormEvent) {
e.preventDefault();
CommFunc.jqPost(this.props.apiPath, this.state.param)
.done((data, textStatus, jqXHRdata) => {
if (data.result) {
CommFunc.tosMessage(null, '修改完成', 1);
} else {
alert(data.message);
}
})
.fail((jqXHR, textStatus, errorThrown) => {
CommFunc.showAjaxError(errorThrown);
});
return;
}
handleOnBlur(date) {
}
setInputValue(name: string, e: React.SyntheticEvent) {
let input: HTMLInputElement = e.target as HTMLInputElement;
let obj = this.state.param;
obj[name] = input.value;
this.setState({ param: obj });
}
render() {
var outHtml: JSX.Element = null;
let param = this.state.param;
let InputDate = CommCmpt.InputDate;
outHtml = (
<div>
<ul className="breadcrumb">
<li><i className="fa-list-alt"></i>
{this.props.menuName}
</li>
</ul>
<h4 className="title"> {this.props.caption} </h4>
<form className="form-horizontal" onSubmit={this.handleSubmit}>
<div className="col-xs-12">
<div className="item-box">
{/*--email--*/}
<div className="item-title text-center">
<h5>Email信箱設定</h5>
</div>
<div className="alert alert-warning" role="alert">
<ol>
<li><strong className="text-danger">, </strong><br />ex.<strong>user1 @demo.com.tw, user2 @demo.com.tw</strong></li>
<li>Email <strong className="text-danger">: </strong><br />ex.<strong>收件人A: user1 @demo.com.tw, 收件人B: user2 @demo.com.tw</strong></li>
</ol>
</div>
<div className="form-group">
<label className="col-xs-1 control-label"></label>
<div className="col-xs-9">
<input className="form-control" type="text"
value={param.Email}
onChange={this.setInputValue.bind(this, 'Email') }
maxLength={500}
required/>
</div>
</div>
{/*--email end--*/}
{/*--shoppingCost--*/}
<div className="item-title text-center">
<h5></h5>
</div>
<div className="form-group">
<label className="col-xs-3 control-label">NT$</label>
<div className="col-xs-1">
<input className="form-control" type="number"
value={param.PurchaseTotal}
onChange={this.setInputValue.bind(this, 'PurchaseTotal') }
min={0}
required/>
</div>
<label className="col-xs-2 control-label">NT$</label>
<div className="col-xs-1">
<input className="form-control" type="number"
value={param.HomoiothermyFee}
onChange={this.setInputValue.bind(this, 'HomoiothermyFee') }
min={0}
required/>
</div>
<label className="col-xs-2 control-label">()NT$</label>
<div className="col-xs-1">
<input className="form-control" type="number"
value={param.RefrigerFee}
onChange={this.setInputValue.bind(this, 'RefrigerFee') }
min={0}
required/>
</div>
<label className="col-xs-1 control-label"></label>
</div>
{/*--shoppingCost end--*/}
{/*--Payment--*/}
<div className="item-title text-center">
<h5></h5>
</div>
<div className="form-group">
<label className="col-xs-4 control-label">ATM轉帳: </label>
</div>
<div className="form-group">
<label className="col-xs-2 control-label">: </label>
<div className="col-xs-3">
<input className="form-control" type="text"
value={param.AccountName}
onChange={this.setInputValue.bind(this, 'AccountName') }
maxLength={16}
required/>
</div>
</div>
<div className="form-group">
<label className="col-xs-2 control-label">: </label>
<div className="col-xs-3">
<input className="form-control" type="text"
value={param.BankName}
onChange={this.setInputValue.bind(this, 'BankName') }
maxLength={16}
required/>
</div>
</div>
<div className="form-group">
<label className="col-xs-2 control-label">: </label>
<div className="col-xs-3">
<input className="form-control" type="text"
value={param.BankCode}
onChange={this.setInputValue.bind(this, 'BankCode') }
maxLength={5}
required/>
</div>
</div>
<div className="form-group">
<label className="col-xs-2 control-label">: </label>
<div className="col-xs-3">
<input className="form-control" type="text"
value={param.AccountNumber}
onChange={this.setInputValue.bind(this, 'AccountNumber') }
maxLength={16}
required/>
</div>
</div>
{/*<div className="form-group">
<label className="col-xs-4 control-label">當付款方式選擇『貨到付款』時須加NT$ </label>
<div className="col-xs-1">
<input className="form-control" type="number"
value={param.Fee}
onChange={this.setInputValue.bind(this, 'Fee') }
min={0}
required/>
</div>
<label className="control-label">元手續費</label>
</div>*/}
{/*--Payment end--*/}
</div>
<div className="form-action">
<div className="col-xs-4 col-xs-offset-5">
<button type="submit" className="btn-primary"><i className="fa-check"></i> </button>
</div>
</div>
</div>
</form>
</div>
);
return outHtml;
}
}
}
var dom = document.getElementById('page_content');
ReactDOM.render(<Parm.GridForm caption={gb_caption} menuName={gb_menuname} iconClass="fa-list-alt" />, dom);

View File

@@ -0,0 +1,863 @@
/// <reference path="../DefinitelyTyped/react/react-global.d.ts" />
// Fixture taken from https://github.com/RyanCavanaugh/koany/blob/master/koany.tsx
interface Garden {
colors: Gardens.RockColor[];
shapes: Gardens.RockShape[];
}
namespace Gardens {
export enum RockShape {
Empty,
Circle,
Triangle,
Square,
Max
}
export const RockShapes = [RockShape.Circle, RockShape.Triangle, RockShape.Square];
export const RockShapesAndEmpty = RockShapes.concat(RockShape.Empty);
export enum RockColor {
Empty,
White,
Red,
Black,
Max
}
export const RockColors = [RockColor.White, RockColor.Red, RockColor.Black];
export const RockColorsAndEmpty = RockColors.concat(RockColor.Empty);
export const Size = 9;
// 012
// 345
// 678
export const adjacencies = [
[1, 3], [0, 4, 2], [1, 5],
[0, 4, 6], [3, 1, 7, 5], [2, 4, 8],
[3, 7], [6, 4, 8], [7, 5]
];
}
module Koan {
export enum DescribeContext {
// every "white stone" is ...
Singular,
// all "white stones" are
Plural,
// every stone in the top row is "white"
Adjectival
}
export enum PartType {
Selector,
Aspect
}
export enum StateTestResult {
Fail = 0,
WeakPass = 1,
Pass = 2
}
/// A general format for producing a Statement
export interface StatementTemplate<T> {
holes: PartType[];
describe(args: T): string;
test(g: Garden, args: T): StateTestResult;
}
/// A completed rule that can be used to test a Garden
export interface ProducedStatement<T> {
test(g: Garden): StateTestResult;
description: string;
children: T;
hasPassedAndFailed(): boolean;
}
function rnd(max: number) {
return Math.floor(Math.random() * max);
}
function randomColor(): Gardens.RockColor {
return Math.floor(Math.random() * (Gardens.RockColor.Max - 1)) + 1
}
function randomShape(): Gardens.RockShape {
return Math.floor(Math.random() * (Gardens.RockShape.Max - 1)) + 1
}
/* New Impl Here */
interface SelectorSpec<T> {
childTypes?: PartType[];
precedence: number;
weight: number;
test(args: T, g: Garden, index: number): string|number|boolean;
describe(args: T, context: DescribeContext): string;
isAllValues(values: Array<string>|Array<number>): boolean;
}
interface ProducedSelector {
test(g: Garden, index: number): string|number|boolean;
getDescription(plural: DescribeContext): string;
seenAllValues(): boolean;
}
export function buildSelector<T>(spec: SelectorSpec<T>, args: T): ProducedSelector {
let seenResults: { [s: string]: boolean;} = {};
return {
test: (g: Garden, index: number) => {
var result = spec.test(args, g, index);
seenResults[result + ''] = true;
return result;
},
getDescription: (context) => {
return spec.describe(args, context);
},
seenAllValues: () => {
return spec.isAllValues(Object.keys(seenResults));
}
}
}
export var SelectorTemplates: Array<SelectorSpec<{}>> = [];
module LetsMakeSomeSelectors {
// Is rock
SelectorTemplates.push({
test: (args, g, i) => g.colors[i] !== Gardens.RockColor.Empty,
describe: (args, context) => {
switch(context) {
case DescribeContext.Plural:
return 'Stones';
case DescribeContext.Adjectival:
return 'not empty';
case DescribeContext.Singular:
return 'Stone';
}
},
isAllValues: items => items.length === 2,
precedence: 0,
weight: 1
});
// Is of a certain color and/or shape
Gardens.RockColorsAndEmpty.forEach(color => {
let colorName = Gardens.RockColor[color];
let colorWeight = color === Gardens.RockColor.Empty ? 1 : 0.33;
Gardens.RockShapesAndEmpty.forEach(shape => {
let shapeName = Gardens.RockShape[shape];
let shapeWeight = shape === Gardens.RockShape.Empty ? 1 : 0.33;
SelectorTemplates.push({
test: (args, g, i) => {
if(color === Gardens.RockColor.Empty) {
if (shape === Gardens.RockShape.Empty) {
return g.colors[i] === Gardens.RockColor.Empty;
} else {
return g.shapes[i] === shape;
}
} else {
if (shape === Gardens.RockShape.Empty) {
return g.colors[i] === color;
} else {
return g.shapes[i] === shape && g.colors[i] === color;
}
}
},
describe: (args, context) => {
if(color === Gardens.RockColor.Empty) {
if (shape === Gardens.RockShape.Empty) {
switch(context) {
case DescribeContext.Plural:
return 'Empty Cells';
case DescribeContext.Adjectival:
return 'Empty';
case DescribeContext.Singular:
return 'Empty Cell';
}
} else {
switch(context) {
case DescribeContext.Plural:
return shapeName + 's';
case DescribeContext.Adjectival:
return 'a ' + shapeName;
case DescribeContext.Singular:
return shapeName;
}
}
} else {
if (shape === Gardens.RockShape.Empty) {
switch(context) {
case DescribeContext.Plural:
return colorName + ' Stones';
case DescribeContext.Adjectival:
return colorName;
case DescribeContext.Singular:
return colorName + ' Stone';
}
} else {
switch(context) {
case DescribeContext.Plural:
return colorName + ' ' + shapeName + 's';
case DescribeContext.Adjectival:
return 'a ' + colorName + ' ' + shapeName;
case DescribeContext.Singular:
return colorName + ' ' + shapeName;
}
}
}
},
isAllValues: items => items.length === 2,
precedence: 3,
weight: (shapeWeight + colorWeight === 2) ? 0.3 : shapeWeight * colorWeight
});
});
});
// [?] in the [top|middle|bottom] [row|column]
[0, 1, 2].forEach(rowCol => {
[true, false].forEach(isRow => {
var name = (isRow ? ['top', 'middle', 'bottom'] : ['left', 'middle', 'right'])[rowCol] + ' ' + (isRow ? 'row' : 'column');
var spec: SelectorSpec<[ProducedSelector]> = {
childTypes: [PartType.Selector],
test: (args, g, i) => {
var c = isRow ? Math.floor(i / 3) : i % 3;
if (c === rowCol) {
return args[0].test(g, i);
} else {
return false;
}
},
describe: (args, plural) => args[0].getDescription(plural) + ' in the ' + name,
isAllValues: items => items.length === 2,
precedence: 4,
weight: 1 / 6
};
SelectorTemplates.push(spec);
});
});
// [?] next to a [?]
SelectorTemplates.push({
childTypes: [PartType.Selector, PartType.Selector],
test: (args, g, i) => {
if (args[0].test(g, i)) {
return Gardens.adjacencies[i].some(x => !!args[1].test(g, x));
} else {
return false;
}
},
describe: (args, plural) => {
return args[0].getDescription(plural) + ' next to a ' + args[1].getDescription(DescribeContext.Singular);
},
isAllValues: items => items.length === 2,
precedence: 4,
weight: 1
} as SelectorSpec<[ProducedSelector, ProducedSelector]>);
}
export function buildStatement<T>(s: StatementTemplate<T>, args: T): ProducedStatement<T> {
let hasPassed = false;
let hasFailed = false;
let result: ProducedStatement<T> = {
children: args,
description: s.describe(args),
test: (g) => {
let r = s.test(g, args);
if (r === StateTestResult.Pass) {
hasPassed = true;
} else if(r === StateTestResult.Fail) {
hasFailed = true;
}
return r;
},
hasPassedAndFailed: () => {
return hasPassed && hasFailed && (args as any as ProducedSelector[]).every(c => c.seenAllValues());
}
};
return result;
}
export let StatementList: StatementTemplate<any>[] = [];
module LetsMakeSomeStatements {
// Every [?] is a [?]
StatementList.push({
holes: [PartType.Selector, PartType.Selector],
test: (g: Garden, args: [ProducedSelector, ProducedSelector]) => {
let didAnyTests = false;
for (var i = 0; i < Gardens.Size; i++) {
if (args[0].test(g, i)) {
if(!args[1].test(g, i)) return StateTestResult.Fail;
didAnyTests = true;
}
}
return didAnyTests ? StateTestResult.Pass : StateTestResult.WeakPass;
},
describe: args => {
return 'Every ' + args[0].getDescription(DescribeContext.Singular) + ' is ' + args[1].getDescription(DescribeContext.Adjectival);
}
});
// There is exactly 1 [?]
StatementList.push({
holes: [PartType.Selector],
test: (g: Garden, args: [ProducedSelector, ProducedSelector]) => {
var count = 0;
for (var i = 0; i < Gardens.Size; i++) {
if (args[0].test(g, i)) count++;
}
return count === 1 ? StateTestResult.Pass : StateTestResult.Fail;
},
describe: args => {
return 'There is exactly one ' + args[0].description;
}
});
// There are more [?] than [?]
StatementList.push({
holes: [PartType.Selector, PartType.Selector],
test: (g: Garden, args: [ProducedSelector, ProducedSelector]) => {
var p1c = 0, p2c = 0;
for (var i = 0; i < Gardens.Size; i++) {
if (args[0].test(g, i)) p1c++;
if (args[1].test(g, i)) p2c++;
}
if(p1c > p2c && p2c > 0) {
return StateTestResult.Pass;
} else if(p1c > p2c) {
return StateTestResult.WeakPass;
} else {
return StateTestResult.Fail;
}
},
describe: args => {
return 'There are more ' + args[0].descriptionPlural + ' than ' + args[1].descriptionPlural;
}
});
}
function randomElementOf<T>(arr: T[]): T {
if (arr.length === 0) {
return undefined;
} else {
return arr[Math.floor(Math.random() * arr.length)];
}
}
function randomWeightedElementOf<T extends { weight: number }>(arr: T[]): T {
var totalWeight = arr.reduce((acc, v) => acc + v.weight, 0);
var rnd = Math.random() * totalWeight;
for (var i = 0; i < arr.length; i++) {
rnd -= arr[i].weight;
if (rnd <= 0) return arr[i];
}
// Got destroyed by floating error, just try again
return randomWeightedElementOf(arr);
}
export function buildRandomNewSelector(maxPrecedence = 1000000): ProducedSelector {
var choices = SelectorTemplates;
let initial = randomWeightedElementOf(choices.filter(p => p.precedence <= maxPrecedence));
// Fill in the holes
if (initial.childTypes) {
var fills = initial.childTypes.map(h => {
if (h === PartType.Selector) {
return buildRandomNewSelector(initial.precedence - 1);
} else {
throw new Error('Only know how to fill Selector holes')
}
});
return buildSelector(initial, fills);
} else {
return buildSelector(initial, []);
}
}
export function makeEmptyGarden(): Garden {
var g = {} as Garden;
g.colors = [];
g.shapes = [];
for (var i = 0; i < Gardens.Size; i++) {
g.colors.push(Gardens.RockColor.Empty);
g.shapes.push(Gardens.RockShape.Empty);
}
return g;
}
export function gardenToString(g: Garden): string {
return g.colors.join('') + g.shapes.join('');
}
export function makeRandomGarden(): Garden {
var g = makeEmptyGarden();
blitRandomGardenPair(g, g);
return g;
}
export function cloneGarden(g: Garden): Garden {
var result: Garden = {
colors: g.colors.slice(0),
shapes: g.shapes.slice(0)
};
return result;
}
export function clearGarden(g: Garden) {
for (var i = 0; i < Gardens.Size; i++) {
g.colors[i] = Gardens.RockColor.Empty;
g.shapes[i] = Gardens.RockShape.Empty;
}
}
export function blitRandomGardenPair(g1: Garden, g2: Garden): void {
let placeCount = 0;
for (var i = 0; i < Gardens.Size; i++) {
if (rnd(7) === 0) {
g1.colors[i] = g2.colors[i] = randomColor();
g1.shapes[i] = g2.shapes[i] = randomShape();
} else {
placeCount++;
g1.colors[i] = g2.colors[i] = Gardens.RockColor.Empty;
g1.shapes[i] = g2.shapes[i] = Gardens.RockShape.Empty;
}
}
if (placeCount === 0) blitRandomGardenPair(g1, g2);
}
export function blitNumberedGarden(g: Garden, stoneCount: number, n: number): void {
clearGarden(g);
let cellNumbers = [0, 1, 2, 3, 4, 5, 6, 7, 8];
for (let i = 0; i < stoneCount; i++) {
let cellNum = getValue(cellNumbers.length);
let cell = cellNumbers.splice(cellNum, 1)[0];
g.colors[cell] = getValue(3) + 1;
g.shapes[cell] = getValue(3) + 1;
}
function getValue(max: number) {
let result = n % max;
n = (n - result) / max;
return result;
}
}
export function mutateGarden(g: Garden): void {
while (true) {
var op = rnd(5);
let x = rnd(Gardens.Size);
let y = rnd(Gardens.Size);
switch (op) {
case 0: // Swap two non-identical cells
if (g.colors[x] !== g.colors[y] || g.shapes[x] !== g.shapes[y]) {
var tmp: any = g.colors[x];
g.colors[x] = g.colors[y];
g.colors[y] = tmp;
tmp = g.shapes[x];
g.shapes[x] = g.shapes[y];
g.shapes[y] = tmp;
return;
}
break;
case 1: // Add a stone
if (g.colors[x] === Gardens.RockColor.Empty) {
g.colors[x] = randomColor();
g.shapes[x] = randomShape();
return;
}
break;
case 2: // Remove a stone
if (g.colors.filter(x => x !== Gardens.RockColor.Empty).length === 1) continue;
if (g.colors[x] !== Gardens.RockColor.Empty) {
g.colors[x] = Gardens.RockColor.Empty;
g.shapes[x] = Gardens.RockShape.Empty;
return;
}
break;
case 3: // Change a color
let c = randomColor();
if (g.colors[x] !== Gardens.RockColor.Empty && g.colors[x] !== c) {
g.colors[x] = c;
return;
}
break;
case 4: // Change a shape
let s = randomShape();
if (g.shapes[x] !== Gardens.RockShape.Empty && g.shapes[x] !== s) {
g.shapes[x] = s;
return;
}
break;
}
}
}
}
class Indexion {
sizes: number[];
constructor(...sizes: number[]) {
this.sizes = sizes;
}
public getValues(index: number): number[] {
let result = new Array<number>(this.sizes.length);
this.fillValues(index, result);
return result;
}
public fillValues(index: number, result: number[]): void {
for (var i = 0; i < this.sizes.length; i++) {
result[i] = index % this.sizes[i];
index -= result[i];
index /= this.sizes[i];
}
}
public valuesToIndex(values: number[]): number {
var result = 0;
var factor = 1;
for (var i = 0; i < this.sizes.length; i++) {
result += values[i] * this.sizes[i] * factor;
factor *= this.sizes[i];
}
return result;
}
public getAdjacentIndices(index: number): number[][] {
var baseline = this.getValues(index);
var results: number[][] = [];
for (var i = 0; i < this.sizes.length; i++) {
if(baseline[i] > 0) {
baseline[i]--;
results.push(baseline.slice());
baseline[i]++;
}
if(baseline[i] < this.sizes[i] - 1) {
baseline[i]++;
results.push(baseline.slice());
baseline[i]--;
}
}
return results;
}
public distance(index1: number, index2: number): number {
let delta = 0;
for (var i = 0; i < this.sizes.length; i++) {
var a = index1 % this.sizes[i];
var b = index2 % this.sizes[i];
delta += Math.abs(b - a);
index1 -= a;
index2 -= b;
index1 /= this.sizes[i];
index2 /= this.sizes[i];
}
return delta;
}
}
function makeNewExample() {
while (true) {
var p1 = Koan.buildSelector(Koan.SelectorTemplates[12], []);
var p2 = Koan.buildSelector(Koan.SelectorTemplates[14], []);
var test = Koan.buildStatement(Koan.StatementList[0], [p1, p2]);
var examples: Garden[] = [];
console.log('Attempt to generate examples for "' + test.description + '"');
var maxGarden = /*(9 * 9) + (9 * 9 * 9 * 8) + */(9 * 9 * 9 * 8 * 9 * 7);
let g = Koan.makeEmptyGarden();
let passCount = 0, failCount = 0;
let resultLookup: boolean[] = [];
let lastResult: boolean = undefined;
for (var i = 0; i < maxGarden; i++) {
Koan.blitNumberedGarden(g, 3, i);
let result = test.test(g);
if(result === Koan.StateTestResult.Pass) {
resultLookup[i] = true;
passCount++;
if (lastResult !== true && examples.length < 10) examples.push(Koan.cloneGarden(g));
lastResult = true;
} else if (result === Koan.StateTestResult.Fail) {
resultLookup[i] = false;
failCount++;
if (lastResult !== false && examples.length < 10) examples.push(Koan.cloneGarden(g));
lastResult = false;
}
if (examples.length === 10) break;
}
console.log('Rule passes ' + passCount + ' and fails ' + failCount);
/*
if (!test.hasPassedAndFailed()) {
console.log('Rule has unreachable, contradictory, or tautological clauses');
continue;
}
if (passCount === 0 || failCount === 0) {
console.log('Rule is always true or always false');
continue;
}
*/
var h = document.createElement('h2');
h.innerText = test.description;
document.body.appendChild(h);
return { test: test, examples: examples };
}
}
let list: Garden[] = [];
let test: Koan.ProducedStatement<any>;
window.onload = function() {
let rule = makeNewExample();
let garden = Koan.makeRandomGarden();
list = rule.examples;
test = rule.test;
function renderList() {
function makeGarden(g: Garden, i: number) {
return <GardenDisplay
garden={g}
key={i + Koan.gardenToString(g)}
test={test}
leftButton='✗'
rightButton='✎'
onLeftButtonClicked={() => {
console.log(list.indexOf(g));
list.splice(list.indexOf(g), 1);
renderList();
}}
onRightButtonClicked={() => {
garden = g;
renderEditor();
}}
/>;
}
let gardenList = <div>{list.map(makeGarden)}</div>;
React.render(gardenList, document.getElementById('results'));
}
let i = 0;
function renderEditor() {
i++;
let editor = <GardenEditor key={i} test={rule.test} garden={garden} onSaveClicked={(garden) => {
list.push(garden);
renderList();
}} />;
React.render(editor, document.getElementById('editor'));
}
renderList();
renderEditor();
}
function classNames(nameMap: any): string {
return Object.keys(nameMap).filter(k => nameMap[k]).join(' ');
}
interface GardenCellProps extends React.Props<{}> {
color: Gardens.RockColor;
shape: Gardens.RockShape;
index: number;
movable?: boolean;
onEdit?(newColor: Gardens.RockColor, newShape: Gardens.RockShape): void;
}
interface GardenCellState {
isDragging?: boolean;
}
class GardenCell extends React.Component<GardenCellProps, GardenCellState> {
state: GardenCellState = {};
ignoreNextEdit = false;
render() {
var classes = ['cell', 'index_' + this.props.index];
if (this.state.isDragging) {
// Render as blank
} else {
classes.push(Gardens.RockColor[this.props.color], Gardens.RockShape[this.props.shape]);
}
if (this.props.movable) classes.push('movable');
let events: React.HTMLAttributes = {
onDragStart: (e) => {
this.ignoreNextEdit = false;
e.dataTransfer.dropEffect = 'copyMove';
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('shape', this.props.shape.toString());
e.dataTransfer.setData('color', this.props.color.toString());
let drag = document.getElementById(getGardenName(this.props.color, this.props.shape));
let xfer: any = (e.nativeEvent as DragEvent).dataTransfer;
xfer.setDragImage(drag, drag.clientWidth * 0.5, drag.clientHeight * 0.5);
this.setState({ isDragging: true });
},
onDragEnter: (e) => {
e.dataTransfer.dropEffect = 'move';
e.preventDefault();
},
onDragOver: (e) => {
e.dataTransfer.dropEffect = 'move';
e.preventDefault();
},
onDragEnd: (e) => {
this.setState({ isDragging: false });
if (!this.ignoreNextEdit) {
this.props.onEdit && this.props.onEdit(undefined, undefined);
}
},
draggable: true
}
let handleDrop = (event: React.DragEvent) => {
if(this.props.onEdit) {
if (this.state.isDragging) {
// Dragged to self, don't do anything
this.ignoreNextEdit = true;
} else {
let shape: Gardens.RockShape = +event.dataTransfer.getData('shape');
let color: Gardens.RockColor = +event.dataTransfer.getData('color');
this.props.onEdit(color, shape);
}
}
}
return <span className={classes.join(' ')} onDrop={handleDrop} {...this.props.movable ? events : {}} />;
}
}
interface GardenDisplayProps extends React.Props<GardenDisplay> {
garden?: Garden;
test?: Koan.ProducedStatement<any>;
leftButton?: string;
rightButton?: string;
onLeftButtonClicked?(): void;
onRightButtonClicked?(): void;
editable?: boolean;
onChanged?(newGarden: Garden): void;
}
interface GardenDisplayState {
garden?: Garden;
}
class GardenDisplay extends React.Component<GardenDisplayProps, GardenDisplayState> {
state = {
garden: Koan.cloneGarden(this.props.garden)
};
leftClicked = () => {
this.props.onLeftButtonClicked && this.props.onLeftButtonClicked();
};
rightClicked = () => {
this.props.onRightButtonClicked && this.props.onRightButtonClicked();
};
render() {
let g = this.state.garden;
let pass = (this.props.test && this.props.test.test(this.state.garden));
let classes = {
garden: true,
unknown: pass === undefined,
pass: pass === Koan.StateTestResult.Pass || pass === Koan.StateTestResult.WeakPass,
fail: pass === Koan.StateTestResult.Fail,
editable: this.props.editable
};
var children = g.colors.map((_, i) => (
<GardenCell
key={i}
color={g.colors[i]}
shape={g.shapes[i]}
index={i}
movable={this.props.editable}
onEdit={(newColor, newShape) => {
if(this.props.editable) {
let newGarden = Koan.cloneGarden(this.state.garden);
newGarden.colors[i] = newColor;
newGarden.shapes[i] = newShape;
this.setState({ garden: newGarden });
this.props.onChanged && this.props.onChanged(newGarden);
}
}}
/>));
return <div className="gardenDisplay">
<div className={classNames(classes)}>{children}</div>
<span className="infoRow">
{this.props.leftButton && <div className="button left" onClick={this.leftClicked}>{this.props.leftButton}</div>}
<div className={"passfail " + (pass ? 'pass' : 'fail')}>{pass ? '✓' : '🚫'}</div>
{this.props.rightButton && <div className="button right" onClick={this.rightClicked}>{this.props.rightButton}</div>}
</span>
</div>;
}
}
interface GardenEditorProps extends React.Props<GardenEditor> {
onSaveClicked?(garden: Garden): void;
test?: Koan.ProducedStatement<any>;
garden?: Garden;
}
interface GardenEditorState {
garden?: Garden;
pass?: boolean;
}
class GardenEditor extends React.Component<GardenEditorProps, {}> {
state = { garden: this.props.garden };
save = () => {
this.props.onSaveClicked && this.props.onSaveClicked(this.state.garden);
};
render() {
return <div className="editor">
<GardenDisplay garden={this.state.garden} test={this.props.test} editable onChanged={g => this.setState({ garden: g }) } />
<StonePalette />
<div className="button save" onClick={this.save}>{'💾'}</div>
</div>;
}
}
class StonePalette extends React.Component<{}, {}> {
render() {
let items: JSX.Element[] = [];
Gardens.RockColors.forEach(color => {
Gardens.RockShapes.forEach(shape => {
let name = getGardenName(color, shape);
let extraProps = { id: name, key: name };
let index = items.length;
items.push(<GardenCell
color={color}
shape={shape}
index={index}
movable
{...extraProps} />)
});
});
return <div className="palette">{items}</div>;
}
}
function getGardenName(color: Gardens.RockColor, shape: Gardens.RockShape) {
return 'draggable.' + Gardens.RockShape[shape] + '.' + Gardens.RockColor[color];
}

View File

@@ -1,27 +0,0 @@
//@jsx: preserve
//@module: amd
//@filename: react.d.ts
declare module JSX {
interface Element { }
interface IntrinsicElements {
}
interface ElementAttributesProperty {
props;
}
}
interface Props {
foo: string;
}
//@filename: file.tsx
export class MyComponent {
render() {
}
props: { foo: string; }
}
<MyComponent foo="bar" />; // ok
<MyComponent foo={0} />; // should be an error

View File

@@ -264,4 +264,11 @@ class TestHeuristcs < Minitest::Test
"XML" => all_fixtures("XML", "*.ts") "XML" => all_fixtures("XML", "*.ts")
}) })
end end
def test_tsx_by_heuristics
assert_heuristics({
"TypeScript" => all_fixtures("TypeScript", "*.tsx"),
"XML" => all_fixtures("XML", "*.tsx")
})
end
end end