mirror of
https://github.com/KevinMidboe/linguist.git
synced 2025-10-29 17:50:22 +00:00
Disambiguate TypeScript with tsx extension. (#3464)
Using the technique as discussed in #2761.
This commit is contained in:
committed by
Colin Seymour
parent
b66fcb2529
commit
f1be771611
@@ -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
|
||||||
|
|||||||
384
samples/TypeScript/import.tsx
Normal file
384
samples/TypeScript/import.tsx
Normal 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 & 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))
|
||||||
366
samples/TypeScript/react-native.tsx
Normal file
366
samples/TypeScript/react-native.tsx
Normal 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     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
|
||||||
240
samples/TypeScript/require.tsx
Normal file
240
samples/TypeScript/require.tsx
Normal 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);
|
||||||
863
samples/TypeScript/triple-slash-reference.tsx
Normal file
863
samples/TypeScript/triple-slash-reference.tsx
Normal 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user