├── utils ├── runtime.js └── util.js ├── app.wxss ├── components ├── divider │ ├── index.json │ ├── index.wxml │ ├── index.js │ └── index.wxss ├── grid │ ├── index.json │ ├── index.wxml │ ├── index.wxss │ └── index.js ├── grids │ ├── index.json │ ├── index.wxml │ ├── index.wxss │ └── index.js ├── canvasdrawer │ ├── canvasdrawer.json │ ├── canvasdrawer.wxss │ ├── canvasdrawer.wxml │ └── canvasdrawer.js ├── brushPoint │ ├── index.json │ ├── index.wxml │ ├── index.wxss │ └── index.js ├── colorBox │ ├── index.json │ ├── index.wxml │ ├── index.js │ └── index.wxss ├── customButton │ ├── index.json │ ├── index.wxml │ ├── index.wxss │ └── index.js ├── customToggle │ ├── index.json │ ├── index.wxml │ ├── index.wxss │ └── index.js ├── helpers │ ├── mergeOptionsToData.js │ ├── isEmpty.js │ ├── colors.js │ ├── checkIPhoneX.js │ ├── compareVersion.js │ ├── arrayTreeFilter.js │ ├── classNames.js │ ├── safeAreaBehavior.js │ ├── debounce.js │ ├── gestures.js │ ├── safeSetDataBehavior.js │ ├── eventsMixin.js │ ├── shallowEqual.js │ ├── computedBehavior.js │ ├── createFieldsStore.js │ ├── relationsBehavior.js │ ├── baseComponent.js │ ├── funcBehavior.js │ ├── styleToCssString.js │ └── popupMixin.js └── aiModels │ ├── autoPainter │ └── autoPainter.js │ └── classifier │ └── classifier.js ├── images ├── mini-code.jpg ├── gif │ ├── ai-painter.gif │ └── ai-classifier.gif ├── models │ └── flower.png └── games │ └── common │ ├── idea.png │ ├── think.png │ ├── btn_back.png │ ├── btn_back1.png │ ├── btn_erase.png │ ├── btn_refresh.png │ ├── btn_tranCan.png │ ├── btn_pageview.png │ └── btn_tranCan1.png ├── sitemap.json ├── pages ├── index │ ├── index.wxss │ ├── index.js │ ├── index.json │ └── index.wxml ├── share │ ├── share.wxml │ ├── share.wxss │ ├── share.json │ └── share.js ├── ai-classifier │ ├── index.json │ ├── index.wxml │ ├── index.wxss │ └── index.js └── ai-painter │ ├── index.json │ ├── index.wxml │ ├── index.wxss │ └── index.js ├── .gitignore ├── project.private.config.json ├── app.json ├── package.json ├── project.config.json ├── app.js ├── README.md └── miniprogram_npm ├── fetch-wechat ├── index.js.map └── index.js └── regenerator-runtime ├── index.js.map └── index.js /utils/runtime.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app.wxss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | 3 | -------------------------------------------------------------------------------- /components/divider/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true 3 | } -------------------------------------------------------------------------------- /components/grid/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true 3 | } -------------------------------------------------------------------------------- /components/grids/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true 3 | } -------------------------------------------------------------------------------- /components/canvasdrawer/canvasdrawer.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true 3 | } -------------------------------------------------------------------------------- /components/brushPoint/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component":true, 3 | "usingComponents":{} 4 | } -------------------------------------------------------------------------------- /components/colorBox/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /components/canvasdrawer/canvasdrawer.wxss: -------------------------------------------------------------------------------- 1 | .board { 2 | position: fixed; 3 | top: 2000rpx; 4 | } -------------------------------------------------------------------------------- /components/customButton/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /components/customToggle/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /components/grids/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /images/mini-code.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaocc1106/ai-painter-wx-miniprogram/HEAD/images/mini-code.jpg -------------------------------------------------------------------------------- /images/gif/ai-painter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaocc1106/ai-painter-wx-miniprogram/HEAD/images/gif/ai-painter.gif -------------------------------------------------------------------------------- /images/models/flower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaocc1106/ai-painter-wx-miniprogram/HEAD/images/models/flower.png -------------------------------------------------------------------------------- /images/games/common/idea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaocc1106/ai-painter-wx-miniprogram/HEAD/images/games/common/idea.png -------------------------------------------------------------------------------- /images/gif/ai-classifier.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaocc1106/ai-painter-wx-miniprogram/HEAD/images/gif/ai-classifier.gif -------------------------------------------------------------------------------- /images/games/common/think.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaocc1106/ai-painter-wx-miniprogram/HEAD/images/games/common/think.png -------------------------------------------------------------------------------- /images/games/common/btn_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaocc1106/ai-painter-wx-miniprogram/HEAD/images/games/common/btn_back.png -------------------------------------------------------------------------------- /images/games/common/btn_back1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaocc1106/ai-painter-wx-miniprogram/HEAD/images/games/common/btn_back1.png -------------------------------------------------------------------------------- /images/games/common/btn_erase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaocc1106/ai-painter-wx-miniprogram/HEAD/images/games/common/btn_erase.png -------------------------------------------------------------------------------- /images/games/common/btn_refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaocc1106/ai-painter-wx-miniprogram/HEAD/images/games/common/btn_refresh.png -------------------------------------------------------------------------------- /images/games/common/btn_tranCan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaocc1106/ai-painter-wx-miniprogram/HEAD/images/games/common/btn_tranCan.png -------------------------------------------------------------------------------- /components/canvasdrawer/canvasdrawer.wxml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/games/common/btn_pageview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaocc1106/ai-painter-wx-miniprogram/HEAD/images/games/common/btn_pageview.png -------------------------------------------------------------------------------- /images/games/common/btn_tranCan1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaocc1106/ai-painter-wx-miniprogram/HEAD/images/games/common/btn_tranCan1.png -------------------------------------------------------------------------------- /components/divider/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ text }} 4 | 5 | 6 | -------------------------------------------------------------------------------- /sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "*" 6 | }] 7 | } -------------------------------------------------------------------------------- /pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | page{ 2 | height: 100%; 3 | width:100%; 4 | } 5 | 6 | .container #intro { 7 | margin: 30rpx; 8 | } 9 | 10 | .container #intro text { 11 | font-size: 30rpx; 12 | color: gray; 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows 2 | [Dd]esktop.ini 3 | Thumbs.db 4 | $RECYCLE.BIN/ 5 | 6 | # macOS 7 | .DS_Store 8 | .fseventsd 9 | .Spotlight-V100 10 | .TemporaryItems 11 | .Trashes 12 | 13 | # Node.js 14 | node_modules/ 15 | -------------------------------------------------------------------------------- /pages/index/index.js: -------------------------------------------------------------------------------- 1 | // pages/index/index.js 2 | 3 | Page({ 4 | 5 | jumpTo: function (event) { 6 | let page = event.target.dataset.dest; 7 | wx.navigateTo({ 8 | url: '/pages/' + page + '/index', 9 | }) 10 | }, 11 | }) -------------------------------------------------------------------------------- /components/colorBox/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /components/customButton/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{text}} 6 | -------------------------------------------------------------------------------- /project.private.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", 3 | "projectname": "ai-painter-wx-miniprogram", 4 | "setting": { 5 | "compileHotReLoad": true 6 | } 7 | } -------------------------------------------------------------------------------- /components/customToggle/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{text}} 6 | -------------------------------------------------------------------------------- /pages/share/share.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pages/share/share.wxss: -------------------------------------------------------------------------------- 1 | .share-image { 2 | width: 94%; 3 | height: 1000rpx; 4 | margin: 0 auto; 5 | margin-left: 3%; 6 | margin-top: 20rpx; 7 | border-radius: 10rpx; 8 | box-shadow:0 1px 3px rgba(0,0,0,.1) 9 | } 10 | 11 | button { 12 | width: 44%; 13 | float: left; 14 | margin-left: 4%; 15 | margin-top: 20rpx; 16 | color: #333; 17 | background: #ffcc01; 18 | } -------------------------------------------------------------------------------- /pages/share/share.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarBackgroundColor": "#ffcc01", 3 | "navigationBarTextStyle": "black", 4 | "navigationBarTitleText": "分享保存", 5 | "backgroundColor": "#ffffff", 6 | "backgroundTextStyle": "dark", 7 | "enablePullDownRefresh": false, 8 | "disableScroll": true, 9 | "usingComponents": { 10 | "canvasdrawer": "/components/canvasdrawer/canvasdrawer" 11 | } 12 | } -------------------------------------------------------------------------------- /components/helpers/mergeOptionsToData.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 过滤对象的函数属性 3 | * @param {Object} opts 4 | */ 5 | const mergeOptionsToData = (opts = {}) => { 6 | const options = Object.assign({}, opts) 7 | 8 | for (const key in options) { 9 | if (options.hasOwnProperty(key) && typeof options[key] === 'function') { 10 | delete options[key] 11 | } 12 | } 13 | 14 | return options 15 | } 16 | 17 | export default mergeOptionsToData 18 | -------------------------------------------------------------------------------- /pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "AI 涂鸦", 3 | "navigationBarBackgroundColor": "#ffcc01", 4 | "navigationBarTextStyle": "black", 5 | "backgroundColor": "#ffffff", 6 | "backgroundTextStyle": "dark", 7 | "enablePullDownRefresh": false, 8 | "disableScroll": true, 9 | "usingComponents": { 10 | "my-divider": "/components/divider/index", 11 | "my-grids": "/components/grids/index", 12 | "my-grid": "/components/grid/index" 13 | } 14 | } -------------------------------------------------------------------------------- /components/helpers/isEmpty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if a value is empty. 3 | */ 4 | function isEmpty(value) { 5 | if (Array.isArray(value)) { 6 | return value.length === 0 7 | } else if (typeof value === 'object') { 8 | if (value) { 9 | for (const _ in value) { 10 | return false 11 | } 12 | } 13 | return true 14 | } else { 15 | return !value 16 | } 17 | } 18 | 19 | export default isEmpty -------------------------------------------------------------------------------- /components/brushPoint/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /pages/ai-classifier/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarBackgroundColor": "#ffcc01", 3 | "navigationBarTextStyle": "black", 4 | "navigationBarTitleText": "AI猜图", 5 | "backgroundColor": "#ffffff", 6 | "backgroundTextStyle": "dark", 7 | "enablePullDownRefresh": false, 8 | "disableScroll": true, 9 | "usingComponents": { 10 | "brush-point": "/components/brushPoint/index", 11 | "color-box": "/components/colorBox/index", 12 | "custom-toggle": "/components/customToggle/index", 13 | "custom-button": "/components/customButton/index" 14 | } 15 | } -------------------------------------------------------------------------------- /pages/ai-painter/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarBackgroundColor": "#ffcc01", 3 | "navigationBarTextStyle": "black", 4 | "navigationBarTitleText": "AI涂鸦", 5 | "backgroundColor": "#ffffff", 6 | "backgroundTextStyle": "dark", 7 | "enablePullDownRefresh": false, 8 | "disableScroll": true, 9 | "usingComponents": { 10 | "brush-point": "/components/brushPoint/index", 11 | "color-box": "/components/colorBox/index", 12 | "custom-toggle": "/components/customToggle/index", 13 | "custom-button": "/components/customButton/index" 14 | } 15 | } -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index", 4 | "pages/ai-painter/index", 5 | "pages/ai-classifier/index", 6 | "pages/share/share" 7 | ], 8 | "window": { 9 | "backgroundTextStyle": "light", 10 | "navigationBarBackgroundColor": "#f0efef", 11 | "navigationBarTitleText": "AI涂鸦", 12 | "navigationBarTextStyle": "black" 13 | }, 14 | "sitemapLocation": "sitemap.json", 15 | "plugins": { 16 | "tfjsPlugin": { 17 | "version": "0.1.0", 18 | "provider": "wx6afed118d9e81df9" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 基于quickdraw数据集训练的涂鸦分类以及自动涂鸦小模型,代码仓库:https://github.com/zhaocc1106/ai-painter-wx-miniprogram 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /components/helpers/colors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Don't modify this file! 3 | * Colors generated by themes! 4 | */ 5 | 6 | /* eslint-disable */ 7 | 8 | export const colors = { 9 | 'light': '#ddd', 10 | 'stable': '#b2b2b2', 11 | 'positive': '#387ef5', 12 | 'calm': '#11c1f3', 13 | 'balanced': '#33cd5f', 14 | 'energized': '#ffc900', 15 | 'assertive': '#ef473a', 16 | 'royal': '#886aea', 17 | 'dark': '#444', 18 | } 19 | 20 | export const isPresetColor = (color) => { 21 | if (!color) { 22 | return false 23 | } 24 | return colors[color] ? colors[color] : color 25 | } 26 | 27 | /* eslint-enable */ 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ai-painter", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@tensorflow/tfjs-backend-cpu": "2.0.1", 13 | "@tensorflow/tfjs-backend-webgl": "2.0.1", 14 | "@tensorflow/tfjs-backend-wasm": "2.0.1", 15 | "@tensorflow/tfjs-core": "2.0.1", 16 | "@tensorflow/tfjs-layers": "2.0.1", 17 | "@tensorflow/tfjs-converter": "2.0.1", 18 | "fetch-wechat": "0.0.3", 19 | "regenerator-runtime": "0.13.5" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /components/customToggle/index.wxss: -------------------------------------------------------------------------------- 1 | /* components/customToggle/index.wxss */ 2 | 3 | .toggle-container { 4 | width: 100%; 5 | position: relative; 6 | display: flex; 7 | flex-direction: row; 8 | justify-content: center; 9 | } 10 | 11 | .toggle-container .icon{ 12 | width: 50rpx; 13 | height: 50rpx; 14 | z-index: 3; 15 | } 16 | 17 | .toggle-container .text{ 18 | height: 50rpx; 19 | line-height: 50rpx; 20 | text-align: left; 21 | margin-left: 5rpx; 22 | z-index: 3; 23 | font-size: 30rpx; 24 | } 25 | 26 | .toggle-container .toggle-bg{ 27 | z-index: 1; 28 | position: absolute; 29 | height: 100%; 30 | background: white; 31 | border-radius: 20rpx; 32 | } 33 | -------------------------------------------------------------------------------- /components/helpers/checkIPhoneX.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取系统信息 3 | */ 4 | 5 | let systemInfo = null 6 | 7 | export const getSystemInfo = (isForce) => { 8 | if (!systemInfo || isForce) { 9 | try { 10 | systemInfo = wx.getSystemInfoSync() 11 | } catch(e) { /* Ignore */ } 12 | } 13 | 14 | return systemInfo 15 | } 16 | 17 | // iPhoneX 竖屏安全区域 18 | export const safeAreaInset = { 19 | top: 88, // StatusBar & NavBar 20 | left: 0, 21 | right: 0, 22 | bottom: 34, // Home Indicator 23 | } 24 | 25 | const isIPhoneX = ({ model, platform }) => { 26 | return /iPhone X/.test(model) && platform === 'ios' 27 | } 28 | 29 | export const checkIPhoneX = (isForce) => isIPhoneX(getSystemInfo(isForce)) 30 | -------------------------------------------------------------------------------- /components/grids/index.wxss: -------------------------------------------------------------------------------- 1 | .wux-grids { 2 | position: relative; 3 | box-sizing: border-box; 4 | overflow: hidden; 5 | background-color: #fff 6 | } 7 | .wux-grids--bordered:before { 8 | content: " "; 9 | position: absolute; 10 | left: 0; 11 | top: 0; 12 | right: 0; 13 | height: 1PX; 14 | border-top: 1PX solid #d9d9d9; 15 | color: #d9d9d9; 16 | transform-origin: 0 0; 17 | transform: scaleY(.5) 18 | } 19 | .wux-grids--bordered:after { 20 | content: " "; 21 | position: absolute; 22 | left: 0; 23 | top: 0; 24 | width: 1PX; 25 | bottom: 0; 26 | border-left: 1PX solid #d9d9d9; 27 | color: #d9d9d9; 28 | transform-origin: 0 0; 29 | transform: scaleX(.5) 30 | } -------------------------------------------------------------------------------- /components/helpers/compareVersion.js: -------------------------------------------------------------------------------- 1 | function compareVersion(v1, v2) { 2 | const $v1 = v1.split('.') 3 | const $v2 = v2.split('.') 4 | const len = Math.max($v1.length, $v2.length) 5 | 6 | while ($v1.length < len) { 7 | $v1.push('0') 8 | } 9 | while ($v2.length < len) { 10 | $v2.push('0') 11 | } 12 | 13 | for (let i = 0; i < len; i++) { 14 | const num1 = parseInt($v1[i]) 15 | const num2 = parseInt($v2[i]) 16 | 17 | if (num1 > num2) { 18 | return 1 19 | } else if (num1 < num2) { 20 | return -1 21 | } 22 | } 23 | 24 | return 0 25 | } 26 | 27 | export default compareVersion 28 | -------------------------------------------------------------------------------- /components/grid/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{ label }} 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /components/helpers/arrayTreeFilter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/afc163/array-tree-filter 3 | */ 4 | function arrayTreeFilter(data, filterFn, options) { 5 | options = options || {} 6 | options.childrenKeyName = options.childrenKeyName || 'children' 7 | let children = data || [] 8 | const result = [] 9 | let level = 0 10 | do { 11 | const foundItem = children.filter(function(item) { 12 | return filterFn(item, level) 13 | })[0] 14 | if (!foundItem) { 15 | break 16 | } 17 | result.push(foundItem) 18 | children = foundItem[options.childrenKeyName] || [] 19 | level += 1 20 | } while (children.length > 0) 21 | return result 22 | } 23 | 24 | export default arrayTreeFilter -------------------------------------------------------------------------------- /components/colorBox/index.js: -------------------------------------------------------------------------------- 1 | // components/brushPoint/index.js 2 | Component({ 3 | options: { 4 | multipleSlots: true // 在组件定义时的选项中启用多slot支持 5 | }, 6 | /** 7 | * 组件的属性列表 8 | * 用于组件自定义设置 9 | */ 10 | properties: { 11 | selected: { 12 | type: Boolean, 13 | value: false 14 | }, 15 | }, 16 | 17 | /** 18 | * 私有数据,组件的初始数据 19 | * 可用于模版渲染 20 | */ 21 | data: { 22 | }, 23 | 24 | attached: function () { 25 | this.setData({ 26 | color: this.dataset.color 27 | }); 28 | }, 29 | 30 | 31 | /** 32 | * 组件的方法列表 33 | * 更新属性和数据的方法与更新页面数据的方法类似 34 | */ 35 | methods: { 36 | 37 | /* 38 | * 内部私有方法建议以下划线开头 39 | * triggerEvent 用于触发事件 40 | */ 41 | 42 | 43 | // 选中笔刷 44 | _select(e) { 45 | this.triggerEvent("select"); 46 | } 47 | } 48 | }) -------------------------------------------------------------------------------- /components/customButton/index.wxss: -------------------------------------------------------------------------------- 1 | /* components/customToggle/index.wxss */ 2 | 3 | .btn-container { 4 | width: 100%; 5 | position: relative; 6 | display: flex; 7 | flex-direction: row; 8 | justify-content: center; 9 | } 10 | 11 | .btn-container .icon{ 12 | margin-top: 5rpx; 13 | width: 45rpx; 14 | height: 40rpx; 15 | z-index: 3; 16 | } 17 | 18 | .btn-container .text{ 19 | height: 50rpx; 20 | line-height: 50rpx; 21 | text-align: left; 22 | margin-left: 5rpx; 23 | font-size: 0.75em; 24 | color: gray; 25 | z-index: 3; 26 | } 27 | 28 | .btn-container .btn-bg{ 29 | z-index: 1; 30 | opacity: 0; 31 | position: absolute; 32 | height: 100%; 33 | background: gray; 34 | border-radius: 20rpx; 35 | animation: bgShow 0.2s linear 1; 36 | } 37 | 38 | /* animation */ 39 | @keyframes bgShow{ 40 | from, to{ 41 | opacity: 0 42 | } 43 | 44 | 50%{ 45 | opacity: 0.5 46 | } 47 | } -------------------------------------------------------------------------------- /components/customToggle/index.js: -------------------------------------------------------------------------------- 1 | // components/brushPoint/index.js 2 | Component({ 3 | options: { 4 | multipleSlots: true // 在组件定义时的选项中启用多slot支持 5 | }, 6 | /** 7 | * 组件的属性列表 8 | * 用于组件自定义设置 9 | */ 10 | properties: { 11 | imgUrl: { 12 | type: String, 13 | value: "" 14 | }, 15 | 16 | width:{ 17 | type: String, 18 | value: "10rpx" 19 | }, 20 | 21 | text:{ 22 | type: String, 23 | value: "" 24 | }, 25 | 26 | selected: { 27 | type: Boolean, 28 | value: false 29 | }, 30 | }, 31 | 32 | /** 33 | * 私有数据,组件的初始数据 34 | * 可用于模版渲染 35 | */ 36 | data: { 37 | }, 38 | 39 | 40 | /** 41 | * 组件的方法列表 42 | * 更新属性和数据的方法与更新页面数据的方法类似 43 | */ 44 | methods: { 45 | 46 | /* 47 | * 内部私有方法建议以下划线开头 48 | * triggerEvent 用于触发事件 49 | */ 50 | 51 | _click(){ 52 | this.triggerEvent("clickEvent"); 53 | } 54 | } 55 | }) -------------------------------------------------------------------------------- /components/brushPoint/index.wxss: -------------------------------------------------------------------------------- 1 | /* components/brushPoint/index.wxss */ 2 | 3 | .brush-point-container { 4 | position: relative; 5 | height: 100%; 6 | width: 100%; 7 | display: flex; 8 | flex-direction: row; 9 | align-items: center; 10 | justify-content: center; 11 | } 12 | 13 | .brush-point-front { 14 | z-index: 3; 15 | position: absolute; 16 | left: 32.5; 17 | top: 32.5; 18 | } 19 | 20 | .brush-point-bg{ 21 | z-index: 2; 22 | } 23 | 24 | .click-effect { 25 | background: gray; 26 | opacity: 0; 27 | width: 64rpx; 28 | height: 64rpx; 29 | border-radius: 64rpx; 30 | z-index: 1; 31 | position: absolute; 32 | left: 50%; 33 | margin-left: -32rpx; 34 | animation-delay: 0.1s; 35 | animation: click-effect-show 0.1s linear 1; 36 | } 37 | 38 | 39 | /* ---------------------------------动画-------------------------------------------*/ 40 | @keyframes click-effect-show { 41 | from, to { 42 | opacity: 0; 43 | } 44 | 45 | 50%{ 46 | opacity: 0.5; 47 | } 48 | } -------------------------------------------------------------------------------- /components/colorBox/index.wxss: -------------------------------------------------------------------------------- 1 | /* components/colorBox/index.wxss */ 2 | 3 | /* 显示颜色的盒子 */ 4 | 5 | .color-box { 6 | width: 100%; 7 | height: 60rpx; 8 | margin-top: 27rpx; 9 | } 10 | 11 | .color-box-selected { 12 | width: 100%; 13 | height: 60rpx; 14 | margin-top: 27rpx; 15 | animation: color-box-up 0.1s linear 1; 16 | animation-fill-mode: forwards; 17 | } 18 | 19 | .bottom-line { 20 | width: 0rpx; 21 | height: 5rpx; 22 | z-index: 3; 23 | position: absolute; 24 | top: 87rpx; 25 | animation-delay: 0.05s; 26 | animation: bottom-line-show 0.2s ease-in 1; 27 | animation-fill-mode: forwards; 28 | } 29 | 30 | /* ---------------------------------动画-------------------------------------------*/ 31 | 32 | @keyframes color-box-up { 33 | to { 34 | transform: translateY(-10rpx); 35 | } 36 | } 37 | 38 | @keyframes bottom-line-show { 39 | from { 40 | widh: 0rpx; 41 | transform: translateX(30rpx); 42 | } 43 | 44 | to { 45 | width: 60rpx; 46 | transform: translateX(0rpx); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /utils/util.js: -------------------------------------------------------------------------------- 1 | const formatTime = date => { 2 | const year = date.getFullYear() 3 | const month = date.getMonth() + 1 4 | const day = date.getDate() 5 | const hour = date.getHours() 6 | const minute = date.getMinutes() 7 | const second = date.getSeconds() 8 | 9 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':') 10 | } 11 | 12 | const formatNumber = n => { 13 | n = n.toString() 14 | return n[1] ? n : '0' + n 15 | } 16 | 17 | // 只响应最后一次promise 18 | class promiseContainer { 19 | constructor() { 20 | this.promise = null; 21 | this.then = null; 22 | } 23 | addPromise (promise, then) { 24 | let that = this; 25 | that.promise = promise; 26 | that.then = then; 27 | promise.then((data) => { 28 | if (promise === that.promise) { 29 | that.then(data); 30 | } 31 | else { 32 | console.log("Cancel current promise."); 33 | } 34 | }); 35 | } 36 | } 37 | 38 | module.exports = { 39 | formatTime: formatTime, 40 | promiseContainer: promiseContainer, 41 | } 42 | -------------------------------------------------------------------------------- /components/helpers/classNames.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Copyright (c) 2018 Jed Watson. 3 | Licensed under the MIT License (MIT), see 4 | http://jedwatson.github.io/classnames 5 | */ 6 | /* global define */ 7 | 'use strict'; 8 | 9 | var hasOwn = {}.hasOwnProperty; 10 | 11 | function classNames() { 12 | var classes = []; 13 | 14 | for (var i = 0; i < arguments.length; i++) { 15 | var arg = arguments[i]; 16 | if (!arg) continue; 17 | 18 | var argType = typeof arg; 19 | 20 | if (argType === 'string' || argType === 'number') { 21 | classes.push(arg); 22 | } else if (Array.isArray(arg) && arg.length) { 23 | var inner = classNames.apply(null, arg); 24 | if (inner) { 25 | classes.push(inner); 26 | } 27 | } else if (argType === 'object') { 28 | for (var key in arg) { 29 | if (hasOwn.call(arg, key) && arg[key]) { 30 | classes.push(key); 31 | } 32 | } 33 | } 34 | } 35 | 36 | return classes.join(' '); 37 | } 38 | 39 | export default classNames -------------------------------------------------------------------------------- /components/customButton/index.js: -------------------------------------------------------------------------------- 1 | // components/brushPoint/index.js 2 | Component({ 3 | options: { 4 | multipleSlots: true // 在组件定义时的选项中启用多slot支持 5 | }, 6 | /** 7 | * 组件的属性列表 8 | * 用于组件自定义设置 9 | */ 10 | properties: { 11 | imgUrl: { 12 | type: String, 13 | value: "" 14 | }, 15 | 16 | width: { 17 | type: String, 18 | value: "10rpx" 19 | }, 20 | 21 | text: { 22 | type: String, 23 | value: "" 24 | }, 25 | 26 | selected: { 27 | type: Boolean, 28 | value: false 29 | }, 30 | }, 31 | 32 | /** 33 | * 私有数据,组件的初始数据 34 | * 可用于模版渲染 35 | */ 36 | data: { 37 | clicked: false 38 | }, 39 | 40 | 41 | /** 42 | * 组件的方法列表 43 | * 更新属性和数据的方法与更新页面数据的方法类似 44 | */ 45 | methods: { 46 | 47 | /* 48 | * 内部私有方法建议以下划线开头 49 | * triggerEvent 用于触发事件 50 | */ 51 | 52 | _click() { 53 | this.setData({ 54 | clicked: true 55 | }); 56 | 57 | setTimeout(()=>{ 58 | this.setData({ 59 | clicked: false 60 | }); 61 | }, 300); 62 | 63 | this.triggerEvent("clickEvent"); 64 | } 65 | } 66 | }) -------------------------------------------------------------------------------- /components/divider/index.js: -------------------------------------------------------------------------------- 1 | import baseComponent from '../helpers/baseComponent' 2 | import classNames from '../helpers/classNames' 3 | 4 | baseComponent({ 5 | properties: { 6 | prefixCls: { 7 | type: String, 8 | value: 'wux-divider', 9 | }, 10 | position: { 11 | type: String, 12 | value: 'center', 13 | }, 14 | dashed: { 15 | type: Boolean, 16 | value: false, 17 | }, 18 | text: { 19 | type: String, 20 | value: '', 21 | }, 22 | showText: { 23 | type: Boolean, 24 | value: true, 25 | }, 26 | }, 27 | computed: { 28 | classes: ['prefixCls, dashed, showText, position', function(prefixCls, dashed, showText, position) { 29 | const wrap = classNames(prefixCls, { 30 | [`${prefixCls}--dashed`]: dashed, 31 | [`${prefixCls}--text`]: showText, 32 | [`${prefixCls}--text-${position}`]: showText && position, 33 | }) 34 | const text = `${prefixCls}__text` 35 | 36 | return { 37 | wrap, 38 | text, 39 | } 40 | }], 41 | }, 42 | }) 43 | -------------------------------------------------------------------------------- /components/divider/index.wxss: -------------------------------------------------------------------------------- 1 | .wux-divider { 2 | display: block; 3 | height: 2rpx; 4 | width: 100%; 5 | margin: 30rpx 0; 6 | clear: both; 7 | border-top: 2rpx solid #e8e8e8 8 | } 9 | .wux-divider--text { 10 | display: table; 11 | white-space: nowrap; 12 | text-align: center; 13 | background: 0 0; 14 | font-weight: 500; 15 | color: rgba(0,0,0,.85); 16 | font-size: 32rpx; 17 | border-top: none!important 18 | } 19 | .wux-divider--text:after, 20 | .wux-divider--text:before { 21 | content: ''; 22 | display: table-cell; 23 | position: relative; 24 | top: 50%; 25 | width: 50%; 26 | border-top-width: 2rpx; 27 | border-top-style: solid; 28 | border-top-color: #e8e8e8; 29 | transform: translateY(50%) 30 | } 31 | .wux-divider--dashed { 32 | border-top: 2rpx dashed #e8e8e8 33 | } 34 | .wux-divider--dashed.wux-divider--text:after, 35 | .wux-divider--dashed.wux-divider--text:before { 36 | border-top-style: dashed 37 | } 38 | .wux-divider--text-left:before { 39 | width: 5% 40 | } 41 | .wux-divider--text-left:after { 42 | width: 95% 43 | } 44 | .wux-divider--text-right:before { 45 | width: 95% 46 | } 47 | .wux-divider--text-right:after { 48 | width: 5% 49 | } 50 | .wux-divider__text { 51 | display: inline-block; 52 | padding: 0 30rpx 53 | } -------------------------------------------------------------------------------- /components/helpers/safeAreaBehavior.js: -------------------------------------------------------------------------------- 1 | import { getSystemInfo, checkIPhoneX } from './checkIPhoneX' 2 | 3 | const defaultSafeArea = { 4 | top: false, 5 | bottom: false, 6 | } 7 | 8 | const setSafeArea = (params) => { 9 | if (typeof params === 'boolean') { 10 | return Object.assign({}, defaultSafeArea, { 11 | top: params, 12 | bottom: params, 13 | }) 14 | } else if (params !== null && typeof params === 'object') { 15 | return Object.assign({}, defaultSafeArea) 16 | } else if (typeof params === 'string') { 17 | return Object.assign({}, defaultSafeArea, { 18 | [params]: true, 19 | }) 20 | } 21 | return defaultSafeArea 22 | } 23 | 24 | export default Behavior({ 25 | properties: { 26 | safeArea: { 27 | type: [Boolean, String, Object], 28 | value: false, 29 | }, 30 | }, 31 | observers: { 32 | safeArea(newVal) { 33 | this.setData({ safeAreaConfig: setSafeArea(newVal) }) 34 | }, 35 | }, 36 | definitionFilter(defFields) { 37 | const { statusBarHeight } = getSystemInfo() || {} 38 | const isIPhoneX = checkIPhoneX() 39 | 40 | Object.assign(defFields.data = (defFields.data || {}), { 41 | safeAreaConfig: defaultSafeArea, 42 | statusBarHeight, 43 | isIPhoneX, 44 | }) 45 | }, 46 | }) 47 | -------------------------------------------------------------------------------- /pages/ai-classifier/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{status}} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 笔触大小 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {{classesName[index]}} 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /components/helpers/debounce.js: -------------------------------------------------------------------------------- 1 | export default function debounce(func, wait, immediate) { 2 | let timeout, 3 | args, 4 | context, 5 | timestamp, 6 | result 7 | 8 | function later() { 9 | const last = +(new Date()) - timestamp 10 | if (last < wait && last >= 0) { 11 | timeout = setTimeout(later, wait - last) 12 | } else { 13 | timeout = undefined 14 | if (!immediate) { 15 | result = func.apply(context, args) 16 | if (!timeout) { 17 | context = undefined 18 | args = undefined 19 | } 20 | } 21 | } 22 | } 23 | 24 | function debounced() { 25 | context = this 26 | args = arguments 27 | timestamp = +(new Date()) 28 | 29 | const callNow = immediate && !timeout 30 | if (!timeout) { 31 | timeout = setTimeout(later, wait) 32 | } 33 | 34 | if (callNow) { 35 | result = func.apply(context, args) 36 | context = undefined 37 | args = undefined 38 | } 39 | 40 | return result 41 | } 42 | 43 | function cancel() { 44 | if (timeout !== undefined) { 45 | clearTimeout(timeout) 46 | timeout = undefined 47 | } 48 | 49 | context = undefined 50 | args = undefined 51 | } 52 | 53 | debounced.cancel = cancel 54 | 55 | return debounced 56 | } -------------------------------------------------------------------------------- /project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目配置文件。", 3 | "packOptions": { 4 | "ignore": [ 5 | { 6 | "value": "images/gif/", 7 | "type": "folder" 8 | }, 9 | { 10 | "value": "images/mini-code.jpg", 11 | "type": "file" 12 | } 13 | ], 14 | "include": [] 15 | }, 16 | "setting": { 17 | "urlCheck": false, 18 | "es6": true, 19 | "enhance": true, 20 | "postcss": true, 21 | "preloadBackgroundData": false, 22 | "minified": true, 23 | "newFeature": true, 24 | "coverView": true, 25 | "nodeModules": true, 26 | "autoAudits": false, 27 | "showShadowRootInWxmlPanel": true, 28 | "scopeDataCheck": false, 29 | "uglifyFileName": false, 30 | "checkInvalidKey": true, 31 | "checkSiteMap": true, 32 | "uploadWithSourceMap": true, 33 | "compileHotReLoad": false, 34 | "babelSetting": { 35 | "ignore": [], 36 | "disablePlugins": [], 37 | "outputPath": "" 38 | }, 39 | "useIsolateContext": true, 40 | "useCompilerModule": true, 41 | "userConfirmedUseCompilerModuleSwitch": false, 42 | "condition": false 43 | }, 44 | "compileType": "miniprogram", 45 | "libVersion": "2.11.2", 46 | "appid": "wxce0a464ad38acd34", 47 | "projectname": "ai-painter", 48 | "simulatorType": "wechat", 49 | "simulatorPluginLibVersion": {}, 50 | "cloudfunctionTemplateRoot": "cloudfunctionTemplate/", 51 | "condition": {}, 52 | "editorSetting": { 53 | "tabIndent": "insertSpaces", 54 | "tabSize": 2 55 | } 56 | } -------------------------------------------------------------------------------- /components/helpers/gestures.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取触摸点位置信息 3 | */ 4 | export const getTouchPoints = (nativeEvent, index = 0) => { 5 | const touches = nativeEvent.touches 6 | const changedTouches = nativeEvent.changedTouches 7 | const hasTouches = touches && touches.length > 0 8 | const hasChangedTouches = changedTouches && changedTouches.length > 0 9 | const points = !hasTouches && hasChangedTouches ? changedTouches[index] : hasTouches ? touches[index] : nativeEvent 10 | 11 | return { 12 | x: points.pageX, 13 | y: points.pageY, 14 | } 15 | } 16 | 17 | /** 18 | * 获取触摸点个数 19 | */ 20 | export const getPointsNumber = (e) => e.touches && e.touches.length || e.changedTouches && e.changedTouches.length 21 | 22 | /** 23 | * 判断是否为同一点 24 | */ 25 | export const isEqualPoints = (p1, p2) => p1.x === p2.x && p1.y === p2.y 26 | 27 | /** 28 | * 判断是否为相近的两点 29 | */ 30 | export const isNearbyPoints = (p1, p2, DOUBLE_TAP_RADIUS = 25) => { 31 | const xMove = Math.abs(p1.x - p2.x) 32 | const yMove = Math.abs(p1.y - p2.y) 33 | return xMove < DOUBLE_TAP_RADIUS & yMove < DOUBLE_TAP_RADIUS 34 | } 35 | 36 | /** 37 | * 获取两点之间的距离 38 | */ 39 | export const getPointsDistance = (p1, p2) => { 40 | const xMove = Math.abs(p1.x - p2.x) 41 | const yMove = Math.abs(p1.y - p2.y) 42 | return Math.sqrt(xMove * xMove + yMove * yMove) 43 | } 44 | 45 | /** 46 | * 获取触摸移动方向 47 | */ 48 | export const getSwipeDirection = (x1, x2, y1, y2) => { 49 | return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down') 50 | } 51 | -------------------------------------------------------------------------------- /components/brushPoint/index.js: -------------------------------------------------------------------------------- 1 | // components/brushPoint/index.js 2 | Component({ 3 | options: { 4 | multipleSlots: true // 在组件定义时的选项中启用多slot支持 5 | }, 6 | /** 7 | * 组件的属性列表 8 | * 用于组件自定义设置 9 | */ 10 | properties: { 11 | color: { 12 | type: String, 13 | value: "red" 14 | // observer: function (newVal, oldVal) { } 15 | }, 16 | 17 | width: { 18 | type: String, 19 | value: "60rpx" 20 | }, 21 | 22 | height: { 23 | type: String, 24 | value: "60rpx" 25 | }, 26 | 27 | bgColor: { 28 | type: String, 29 | value: "white" 30 | }, 31 | 32 | radius: { 33 | type: Number, 34 | value: 20 35 | }, 36 | 37 | selected: { 38 | type: Boolean, 39 | value: false 40 | }, 41 | }, 42 | 43 | /** 44 | * 私有数据,组件的初始数据 45 | * 可用于模版渲染 46 | */ 47 | data: { 48 | hideEffect: true 49 | }, 50 | 51 | attached: function () { 52 | this.setData({ 53 | innerRadius: this.properties.radius * 1.7 + 10, 54 | outterRadius: this.properties.radius * 1.7 + 20, 55 | }); 56 | }, 57 | 58 | /** 59 | * 组件的方法列表 60 | * 更新属性和数据的方法与更新页面数据的方法类似 61 | */ 62 | methods: { 63 | 64 | /* 65 | * 内部私有方法建议以下划线开头 66 | * triggerEvent 用于触发事件 67 | */ 68 | 69 | // 选中笔刷 70 | _select(e) { 71 | this.setData({ 72 | hideEffect: false 73 | }); 74 | 75 | setTimeout(() => { 76 | this.setData({ 77 | hideEffect: true 78 | }); 79 | }, 500); 80 | this.triggerEvent("select"); 81 | } 82 | } 83 | }) -------------------------------------------------------------------------------- /pages/share/share.js: -------------------------------------------------------------------------------- 1 | //index.js 2 | 3 | Page({ 4 | data: { 5 | painting: {}, 6 | shareImage: '' 7 | }, 8 | onLoad: function (options) { 9 | wx.showLoading({ 10 | title: '绘制中...', 11 | mask: true 12 | }); 13 | this.eventDraw(options.imgUrl) 14 | }, 15 | eventDraw: function (imgUrl) { 16 | this.setData({ 17 | painting: { 18 | width: 375, 19 | height: 555, 20 | clear: true, 21 | views: [ 22 | { 23 | type: 'image', 24 | url: imgUrl, 25 | top: 136, 26 | left: 42.5, 27 | width: 290, 28 | height: 240 29 | }, 30 | ] 31 | } 32 | }) 33 | }, 34 | eventSave: function () { 35 | wx.saveImageToPhotosAlbum({ 36 | filePath: this.data.shareImage, 37 | success (res) { 38 | wx.showToast({ 39 | title: '保存图片成功', 40 | icon: 'success', 41 | duration: 2000 42 | }) 43 | } 44 | }) 45 | }, 46 | eventGetImage: function (event) { 47 | wx.hideLoading() 48 | const { tempFilePath } = event.detail 49 | this.setData({ 50 | shareImage: tempFilePath 51 | }) 52 | }, 53 | /** 54 | * 用户点击右上角分享 55 | */ 56 | onShareAppMessage: function () { 57 | return { 58 | title: '我创作了一副名画,你也来试一下吧!', 59 | path: `/pages/ai-painter/index`, 60 | imageUrl: this.data.imgUrl, 61 | success: function (res) { 62 | // 转发成功 63 | }, 64 | fail: function (res) { 65 | // 转发失败 66 | } 67 | } 68 | }, 69 | }) 70 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | //app.js 2 | 3 | App({ 4 | onLaunch: function () { 5 | // 展示本地存储能力 6 | var logs = wx.getStorageSync('logs') || [] 7 | logs.unshift(Date.now()) 8 | wx.setStorageSync('logs', logs) 9 | 10 | // 登录 11 | wx.login({ 12 | success: res => { 13 | // 发送 res.code 到后台换取 openId, sessionKey, unionId 14 | } 15 | }) 16 | // 获取用户信息 17 | wx.getSetting({ 18 | success: res => { 19 | if (res.authSetting['scope.userInfo']) { 20 | // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框 21 | wx.getUserInfo({ 22 | success: res => { 23 | // 可以将 res 发送给后台解码出 unionId 24 | this.globalData.userInfo = res.userInfo 25 | 26 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回 27 | // 所以此处加入 callback 以防止这种情况 28 | if (this.userInfoReadyCallback) { 29 | this.userInfoReadyCallback(res) 30 | } 31 | } 32 | }) 33 | } 34 | } 35 | }) 36 | }, 37 | 38 | /** 获取用户信息 */ 39 | getUserInfo: function (cb) { 40 | var that = this 41 | if (this.globalData.userInfo) { 42 | typeof cb == "function" && cb(this.globalData.userInfo) 43 | } else { 44 | //调用登陆接口 45 | wx.login({ 46 | success: function () { 47 | wx.getUserInfo({ 48 | success: function (res) { 49 | that.globalData.userInfo = res.userInfo 50 | typeof cb == "function" && cb(that.globalData.userInfo) 51 | } 52 | }) 53 | } 54 | }) 55 | } 56 | }, 57 | 58 | globalData: { 59 | userInfo: null 60 | } 61 | }) -------------------------------------------------------------------------------- /components/helpers/safeSetDataBehavior.js: -------------------------------------------------------------------------------- 1 | export default Behavior({ 2 | lifetimes: { 3 | created() { 4 | this.nextCallback = null 5 | }, 6 | detached() { 7 | this.cancelNextCallback() 8 | }, 9 | }, 10 | methods: { 11 | /** 12 | * safeSetData 13 | * @param {Object} nextData 数据对象 14 | * @param {Function} callback 回调函数 15 | */ 16 | safeSetData(nextData, callback) { 17 | this.pendingData = Object.assign({}, this.data, nextData) 18 | callback = this.setNextCallback(callback) 19 | 20 | this.setData(nextData, () => { 21 | this.pendingData = null 22 | callback() 23 | }) 24 | }, 25 | /** 26 | * 设置下一回调函数 27 | * @param {Function} callback 回调函数 28 | */ 29 | setNextCallback(callback) { 30 | let active = true 31 | 32 | this.nextCallback = (event) => { 33 | if (active) { 34 | active = false 35 | this.nextCallback = null 36 | 37 | callback.call(this, event) 38 | } 39 | } 40 | 41 | this.nextCallback.cancel = () => { 42 | active = false 43 | } 44 | 45 | return this.nextCallback 46 | }, 47 | /** 48 | * 取消下一回调函数 49 | */ 50 | cancelNextCallback() { 51 | if (this.nextCallback !== null) { 52 | this.nextCallback.cancel() 53 | this.nextCallback = null 54 | } 55 | }, 56 | }, 57 | }) -------------------------------------------------------------------------------- /components/grid/index.wxss: -------------------------------------------------------------------------------- 1 | .wux-grid { 2 | position: relative; 3 | float: left; 4 | box-sizing: border-box 5 | } 6 | .wux-grid--bordered:before { 7 | content: " "; 8 | position: absolute; 9 | right: 0; 10 | top: 0; 11 | width: 1PX; 12 | bottom: 0; 13 | border-right: 1PX solid #d9d9d9; 14 | color: #d9d9d9; 15 | transform-origin: 100% 0; 16 | transform: scaleX(.5) 17 | } 18 | .wux-grid--bordered:after { 19 | content: " "; 20 | position: absolute; 21 | left: 0; 22 | bottom: 0; 23 | right: 0; 24 | height: 1PX; 25 | border-bottom: 1PX solid #d9d9d9; 26 | color: #d9d9d9; 27 | transform-origin: 0 100%; 28 | transform: scaleY(.5) 29 | } 30 | .wux-grid--hover { 31 | background-color: #ececec 32 | } 33 | .wux-grid__thumb { 34 | width: 56rpx; 35 | height: 56rpx; 36 | display: block; 37 | margin: 0 auto 38 | } 39 | .wux-grid__label { 40 | text-align: center; 41 | color: #000; 42 | font-size: 28rpx; 43 | margin-top: 10rpx; 44 | width: auto; 45 | overflow: hidden; 46 | text-overflow: ellipsis; 47 | white-space: nowrap; 48 | word-wrap: normal 49 | } 50 | .wux-grid__inner { 51 | height: 100%; 52 | width: 100%; 53 | text-align: center; 54 | padding: 40rpx 20rpx; 55 | box-sizing: border-box; 56 | display: -ms-flexbox; 57 | display: flex; 58 | -ms-flex-direction: column; 59 | flex-direction: column; 60 | -ms-flex-pack: center; 61 | justify-content: center; 62 | -ms-flex-align: center; 63 | align-items: center 64 | } 65 | .wux-grid--square .wux-grid__content { 66 | position: relative; 67 | display: block; 68 | content: ' '; 69 | padding-bottom: 100% 70 | } 71 | .wux-grid--square .wux-grid__inner { 72 | position: absolute; 73 | top: 50%; 74 | transform: translate3d(0,-50%,0) 75 | } -------------------------------------------------------------------------------- /components/grids/index.js: -------------------------------------------------------------------------------- 1 | import baseComponent from '../helpers/baseComponent' 2 | import classNames from '../helpers/classNames' 3 | 4 | baseComponent({ 5 | relations: { 6 | '../grid/index': { 7 | type: 'child', 8 | observer() { 9 | this.debounce(this.changeCurrent) 10 | }, 11 | }, 12 | }, 13 | properties: { 14 | prefixCls: { 15 | type: String, 16 | value: 'wux-grids', 17 | }, 18 | col: { 19 | type: Number, 20 | value: 3, 21 | observer: 'changeCurrent', 22 | }, 23 | bordered: { 24 | type: Boolean, 25 | value: true, 26 | observer: 'changeCurrent', 27 | }, 28 | square: { 29 | type: Boolean, 30 | value: false, 31 | observer: 'changeCurrent', 32 | }, 33 | }, 34 | computed: { 35 | classes: ['prefixCls, bordered', function(prefixCls, bordered) { 36 | const wrap = classNames(prefixCls, { 37 | [`${prefixCls}--bordered`]: bordered, 38 | }) 39 | 40 | return { 41 | wrap, 42 | } 43 | }], 44 | }, 45 | methods: { 46 | changeCurrent() { 47 | const elements = this.getRelationNodes('../grid/index') 48 | const { col, bordered, square } = this.data 49 | const colNum = parseInt(col) > 0 ? parseInt(col) : 1 50 | const width = `${100 / colNum}%` 51 | 52 | if (elements.length > 0) { 53 | elements.forEach((element, index) => { 54 | element.changeCurrent(width, bordered, square, index) 55 | }) 56 | } 57 | }, 58 | }, 59 | }) 60 | -------------------------------------------------------------------------------- /components/helpers/eventsMixin.js: -------------------------------------------------------------------------------- 1 | const defaultEvents = { 2 | onChange() {}, 3 | } 4 | 5 | export default function eventsMixin(params = { defaultEvents }) { 6 | return Behavior({ 7 | lifetimes: { 8 | created () { 9 | this._oriTriggerEvent = this.triggerEvent 10 | this.triggerEvent = this._triggerEvent 11 | }, 12 | }, 13 | properties: { 14 | events: { 15 | type: Object, 16 | value: defaultEvents, 17 | }, 18 | }, 19 | data: { 20 | inputEvents: defaultEvents, 21 | }, 22 | definitionFilter(defFields) { 23 | // set default data 24 | Object.assign(defFields.data = (defFields.data || {}), { 25 | inputEvents: Object.assign({}, defaultEvents, defFields.inputEvents), 26 | }) 27 | 28 | // set default methods 29 | Object.assign(defFields.methods = (defFields.methods || {}), { 30 | _triggerEvent(name, params, runCallbacks = true, option) { 31 | const { inputEvents } = this.data 32 | const method = `on${name[0].toUpperCase()}${name.slice(1)}` 33 | const func = inputEvents[method] 34 | 35 | if (runCallbacks && typeof func === 'function') { 36 | func.call(this, params) 37 | } 38 | 39 | this._oriTriggerEvent(name, params, option) 40 | }, 41 | }) 42 | 43 | // set default observers 44 | Object.assign(defFields.observers = (defFields.observers || {}), { 45 | events(newVal) { 46 | this.setData({ 47 | inputEvents: Object.assign({}, defaultEvents, this.data.inputEvents, newVal), 48 | }) 49 | }, 50 | }) 51 | }, 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /components/helpers/shallowEqual.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @typechecks 8 | * 9 | */ 10 | 11 | /*eslint-disable no-self-compare */ 12 | 13 | 'use strict'; 14 | 15 | var hasOwnProperty = Object.prototype.hasOwnProperty; 16 | 17 | /** 18 | * inlined Object.is polyfill to avoid requiring consumers ship their own 19 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is 20 | */ 21 | function is(x, y) { 22 | // SameValue algorithm 23 | if (x === y) { 24 | // Steps 1-5, 7-10 25 | // Steps 6.b-6.e: +0 != -0 26 | // Added the nonzero y check to make Flow happy, but it is redundant 27 | return x !== 0 || y !== 0 || 1 / x === 1 / y; 28 | } else { 29 | // Step 6.a: NaN == NaN 30 | return x !== x && y !== y; 31 | } 32 | } 33 | 34 | /** 35 | * Performs equality by iterating through keys on an object and returning false 36 | * when any key has values which are not strictly equal between the arguments. 37 | * Returns true when the values of all keys are strictly equal. 38 | */ 39 | function shallowEqual(objA, objB) { 40 | if (is(objA, objB)) { 41 | return true; 42 | } 43 | 44 | if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { 45 | return false; 46 | } 47 | 48 | var keysA = Object.keys(objA); 49 | var keysB = Object.keys(objB); 50 | 51 | if (keysA.length !== keysB.length) { 52 | return false; 53 | } 54 | 55 | // Test for A's keys different from B. 56 | for (var i = 0; i < keysA.length; i++) { 57 | if (!hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) { 58 | return false; 59 | } 60 | } 61 | 62 | return true; 63 | } 64 | 65 | export default shallowEqual -------------------------------------------------------------------------------- /components/helpers/computedBehavior.js: -------------------------------------------------------------------------------- 1 | import isEmpty from './isEmpty' 2 | import shallowEqual from './shallowEqual' 3 | 4 | const ALL_DATA_KEY = '**' 5 | 6 | const trim = (str = '') => str.replace(/\s/g, '') 7 | 8 | export default Behavior({ 9 | lifetimes: { 10 | attached() { 11 | this.initComputed() 12 | }, 13 | }, 14 | definitionFilter(defFields) { 15 | const { computed = {} } = defFields 16 | const observers = Object.keys(computed).reduce((acc, name) => { 17 | const [field, getter] = Array.isArray(computed[name]) ? computed[name] : [ALL_DATA_KEY, computed[name]] 18 | return { 19 | ...acc, 20 | [field]: function(...args) { 21 | if (typeof getter === 'function') { 22 | const newValue = getter.apply(this, args) 23 | const oldValue = this.data[name] 24 | if (!isEmpty(newValue) && !shallowEqual(newValue, oldValue)) { 25 | this.setData({ [name]: newValue }) 26 | } 27 | } 28 | }, 29 | } 30 | }, {}) 31 | 32 | Object.assign(defFields.observers = (defFields.observers || {}), observers) 33 | Object.assign(defFields.methods = (defFields.methods || {}), { 34 | initComputed: function(data = {}, isForce = false) { 35 | if (!this.runInitComputed || isForce) { 36 | this.runInitComputed = false 37 | const context = this 38 | const result = { ...this.data, ...data } 39 | Object.keys(observers).forEach((key) => { 40 | const values = trim(key).split(',').reduce((acc, name) => ([...acc, result[name]]), []) 41 | observers[key].apply(context, values) 42 | }) 43 | this.runInitComputed = true 44 | } 45 | }, 46 | }) 47 | }, 48 | }) 49 | -------------------------------------------------------------------------------- /components/helpers/createFieldsStore.js: -------------------------------------------------------------------------------- 1 | class FieldsStore { 2 | constructor(fields = {}) { 3 | this.fields = fields 4 | } 5 | 6 | setFields(fields) { 7 | Object.assign(this.fields, fields) 8 | } 9 | 10 | updateFields(fields) { 11 | this.fields = fields 12 | } 13 | 14 | clearField(name) { 15 | delete this.fields[name] 16 | } 17 | 18 | getValueFromFields(name, fields) { 19 | const field = fields[name] 20 | if (field && 'value' in field) { 21 | return field.value 22 | } 23 | return field.initialValue 24 | } 25 | 26 | getAllFieldsName() { 27 | const { fields } = this 28 | return fields ? Object.keys(fields) : [] 29 | } 30 | 31 | getField(name) { 32 | return { 33 | ...this.fields[name], 34 | name, 35 | } 36 | } 37 | 38 | getFieldValuePropValue(fieldOption) { 39 | const { name, valuePropName } = fieldOption 40 | const field = this.getField(name) 41 | const fieldValue = 'value' in field ? field.value : field.initialValue 42 | 43 | return { 44 | [valuePropName]: fieldValue, 45 | } 46 | } 47 | 48 | getFieldValue(name) { 49 | return this.getValueFromFields(name, this.fields) 50 | } 51 | 52 | getFieldsValue(names) { 53 | const fields = names || this.getAllFieldsName() 54 | return fields.reduce((acc, name) => { 55 | acc[name] = this.getFieldValue(name) 56 | return acc 57 | }, {}) 58 | } 59 | 60 | resetFields(ns) { 61 | const { fields } = this 62 | const names = ns || this.getAllFieldsName() 63 | return names.reduce((acc, name) => { 64 | const field = fields[name] 65 | if (field) { 66 | acc[name] = field.initialValue 67 | } 68 | return acc 69 | }, {}) 70 | } 71 | } 72 | 73 | export default function createFieldsStore(fields) { 74 | return new FieldsStore(fields) 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ai-painter-wx-miniprogram 2 | tensorflow跑在微信小程序上,实现AI识别简笔画,AI自动画画等。
3 | mini-code 4 | 5 | 6 | ## quick_draw_classifier模型 7 | #### 说明 8 | 对用户画的简笔画进行分类。 9 | #### 数据集 10 | 使用[quick_draw dataset](https://github.com/googlecreativelab/quickdraw-dataset).
11 | #### 模型训练 12 | https://github.com/zhaocc1106/machine_learn/tree/master/NeuralNetworks-tensorflow/RNN/quick_draw
13 | https://github.com/zhaocc1106/machine_learn/tree/master/NeuralNetworks-tensorflow/RNN/quick_draw/quick_draw_classify
14 | 训练完之后通过如下命令将模型转换为tensorflow.js模型。 15 | ``` 16 | tensorflowjs_converter --input_format keras model.h5 ./ 17 | ``` 18 | 转换后会生成model.json文件以及group bin文件,分别记录网络结构以及参数值。
19 | 实际应用模型时可能需要修改一些地方,比如修改model.json的input layer的batch size为1;修改dropout training参数为false。当然也可以在保存HDF5模型文件前做修改。 20 | #### 演示 21 | ai-classifier 22 | 23 | 24 | ## quick_draw_autopainter模型 25 | #### 说明 26 | 根据用户的起始笔画,来完成后续的简笔画. 27 | #### 数据集 28 | 使用[quick_draw dataset](https://github.com/googlecreativelab/quickdraw-dataset).
29 | #### 模型训练 30 | https://github.com/zhaocc1106/machine_learn/tree/master/NeuralNetworks-tensorflow/RNN/quick_draw
31 | https://github.com/zhaocc1106/machine_learn/tree/master/NeuralNetworks-tensorflow/RNN/quick_draw/auto_draw
32 | 训练完之后通过如下命令将模型转换为tensorflow.js模型。 33 | ``` 34 | tensorflowjs_converter --input_format keras model.h5 ./ 35 | ``` 36 | 转换后会生成model.json文件以及group bin文件,分别记录网络结构以及参数值。
37 | 实际应用模型时可能需要修改一些地方,比如修改model.json的input layer的batch size为1;修改dropout training参数为false。当然也可以在保存HDF5模型文件前做修改。 38 | #### 遗留问题 39 | 对起始笔画要求比较高,可能是如下原因:
40 | 1. 笔画采集和预处理不太满足模型输入
41 | 2. 模型本身问题
42 | 3. 训练数据不够随机性
43 | 44 | 后续再继续优化 45 | #### 演示 46 | ai-painter 47 | 48 | 49 | ## 小程序中部署tensorflow模型 50 | 参考官方说明https://github.com/tensorflow/tfjs-wechat 51 | -------------------------------------------------------------------------------- /components/grid/index.js: -------------------------------------------------------------------------------- 1 | import baseComponent from '../helpers/baseComponent' 2 | import classNames from '../helpers/classNames' 3 | 4 | baseComponent({ 5 | relations: { 6 | '../grids/index': { 7 | type: 'parent', 8 | }, 9 | }, 10 | properties: { 11 | prefixCls: { 12 | type: String, 13 | value: 'wux-grid', 14 | }, 15 | hoverClass: { 16 | type: String, 17 | value: 'default', 18 | }, 19 | thumb: { 20 | type: String, 21 | value: '', 22 | }, 23 | label: { 24 | type: String, 25 | value: '', 26 | }, 27 | }, 28 | data: { 29 | width: '100%', 30 | bordered: true, 31 | square: true, 32 | index: 0, 33 | }, 34 | computed: { 35 | classes: ['prefixCls, hoverClass, bordered, square', function(prefixCls, hoverClass, bordered, square) { 36 | const wrap = classNames(prefixCls, { 37 | [`${prefixCls}--bordered`]: bordered, 38 | [`${prefixCls}--square`]: square, 39 | }) 40 | const content = `${prefixCls}__content` 41 | const inner = `${prefixCls}__inner` 42 | const hd = `${prefixCls}__hd` 43 | const thumb = `${prefixCls}__thumb` 44 | const bd = `${prefixCls}__bd` 45 | const label = `${prefixCls}__label` 46 | const hover = hoverClass && hoverClass !== 'default' ? hoverClass : `${prefixCls}--hover` 47 | 48 | return { 49 | wrap, 50 | content, 51 | inner, 52 | hd, 53 | thumb, 54 | bd, 55 | label, 56 | hover, 57 | } 58 | }], 59 | }, 60 | methods: { 61 | changeCurrent(width, bordered, square, index) { 62 | this.setData({ 63 | width, 64 | bordered, 65 | square, 66 | index, 67 | }) 68 | }, 69 | onTap() { 70 | this.triggerEvent('click', this.data) 71 | }, 72 | }, 73 | }) 74 | -------------------------------------------------------------------------------- /components/helpers/relationsBehavior.js: -------------------------------------------------------------------------------- 1 | import isEmpty from './isEmpty' 2 | import debounce from './debounce' 3 | 4 | /** 5 | * bind func to obj 6 | */ 7 | function bindFunc(obj, method, observer) { 8 | const oldFn = obj[method] 9 | obj[method] = function(target) { 10 | if (observer) { 11 | observer.call(this, target, { 12 | [method]: true, 13 | }) 14 | } 15 | if (oldFn) { 16 | oldFn.call(this, target) 17 | } 18 | } 19 | } 20 | 21 | // default methods 22 | const methods = ['linked', 'linkChanged', 'unlinked'] 23 | 24 | // extra props 25 | const extProps = ['observer'] 26 | 27 | export default Behavior({ 28 | lifetimes: { 29 | created() { 30 | this._debounce = null 31 | }, 32 | detached() { 33 | if (this._debounce && this._debounce.cancel) { 34 | this._debounce.cancel() 35 | } 36 | }, 37 | }, 38 | definitionFilter(defFields) { 39 | const { relations } = defFields 40 | 41 | if (!isEmpty(relations)) { 42 | for (const key in relations) { 43 | const relation = relations[key] 44 | 45 | // bind func 46 | methods.forEach((method) => bindFunc(relation, method, relation.observer)) 47 | 48 | // delete extProps 49 | extProps.forEach((prop) => delete relation[prop]) 50 | } 51 | } 52 | 53 | Object.assign(defFields.methods = (defFields.methods || {}), { 54 | getRelationsName: function(types = ['parent', 'child', 'ancestor', 'descendant']) { 55 | return Object.keys(relations || {}).map((key) => { 56 | if (relations[key] && types.includes(relations[key].type)) { 57 | return key 58 | } 59 | return null 60 | }).filter((v) => !!v) 61 | }, 62 | debounce: function(func, wait = 0, immediate = false) { 63 | return (this._debounce = this._debounce || debounce(func.bind(this), wait, immediate)).call(this) 64 | }, 65 | }) 66 | }, 67 | }) 68 | -------------------------------------------------------------------------------- /pages/ai-painter/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{status}} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 图像大小 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | {{item.value}} 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /components/helpers/baseComponent.js: -------------------------------------------------------------------------------- 1 | import computedBehavior from './computedBehavior' 2 | import relationsBehavior from './relationsBehavior' 3 | import safeAreaBehavior from './safeAreaBehavior' 4 | import safeSetDataBehavior from './safeSetDataBehavior' 5 | import funcBehavior from './funcBehavior' 6 | import compareVersion from './compareVersion' 7 | 8 | const { platform, SDKVersion } = wx.getSystemInfoSync() 9 | const libVersion = '2.6.6' 10 | 11 | // check SDKVersion 12 | if (platform === 'devtools' && compareVersion(SDKVersion, libVersion) < 0) { 13 | if (wx && wx.showModal) { 14 | wx.showModal({ 15 | title: '提示', 16 | content: `当前基础库版本(${SDKVersion})过低,无法使用 Wux Weapp 组件库,请更新基础库版本 >=${libVersion} 后重试。`, 17 | }) 18 | } 19 | } 20 | 21 | const baseComponent = (options = {}) => { 22 | // add default externalClasses 23 | options.externalClasses = [ 24 | 'wux-class', 25 | 'wux-hover-class', 26 | ...(options.externalClasses = options.externalClasses || []), 27 | ] 28 | 29 | // add default behaviors 30 | options.behaviors = [ 31 | relationsBehavior, 32 | safeSetDataBehavior, 33 | ...(options.behaviors = options.behaviors || []), 34 | computedBehavior, // make sure it's triggered 35 | ] 36 | 37 | // use safeArea 38 | if (options.useSafeArea) { 39 | options.behaviors = [...options.behaviors, safeAreaBehavior] 40 | delete options.useSafeArea 41 | } 42 | 43 | // use func 44 | if (options.useFunc) { 45 | options.behaviors = [...options.behaviors, funcBehavior] 46 | delete options.useFunc 47 | } 48 | 49 | // use field 50 | if (options.useField) { 51 | options.behaviors = [...options.behaviors, 'wx://form-field'] 52 | delete options.useField 53 | } 54 | 55 | // use export 56 | if (options.useExport) { 57 | options.behaviors = [...options.behaviors, 'wx://component-export'] 58 | options.methods = { 59 | export () { 60 | return this 61 | }, 62 | ...options.methods, 63 | } 64 | delete options.useExport 65 | } 66 | 67 | // add default options 68 | options.options = { 69 | multipleSlots: true, 70 | addGlobalClass: true, 71 | ...options.options, 72 | } 73 | 74 | return Component(options) 75 | } 76 | 77 | export default baseComponent 78 | -------------------------------------------------------------------------------- /components/helpers/funcBehavior.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 过滤对象的函数属性 3 | * @param {Object} opts 4 | */ 5 | const mergeOptionsToData = (opts = {}) => { 6 | const options = Object.assign({}, opts) 7 | 8 | for (const key in options) { 9 | if (options.hasOwnProperty(key) && typeof options[key] === 'function') { 10 | delete options[key] 11 | } 12 | } 13 | 14 | return options 15 | } 16 | 17 | /** 18 | * Simple bind, faster than native 19 | * 20 | * @param {Function} fn 21 | * @param {Object} ctx 22 | * @return {Function} 23 | */ 24 | const bind = (fn, ctx) => { 25 | return (...args) => { 26 | return args.length ? fn.apply(ctx, args) : fn.call(ctx) 27 | } 28 | } 29 | 30 | /** 31 | * Object assign 32 | */ 33 | const assign = (...args) => Object.assign({}, ...args) 34 | 35 | export default Behavior({ 36 | definitionFilter(defFields) { 37 | defFields.data = mergeOptionsToData(defFields.data) 38 | defFields.data.in = false 39 | defFields.data.visible = false 40 | }, 41 | methods: { 42 | /** 43 | * 过滤对象的函数属性 44 | * @param {Object} opts 45 | */ 46 | $$mergeOptionsToData: mergeOptionsToData, 47 | /** 48 | * 合并参数并绑定方法 49 | * 50 | * @param {Object} opts 参数对象 51 | * @param {Object} fns 方法挂载的属性 52 | */ 53 | $$mergeOptionsAndBindMethods (opts = {}, fns = this.fns) { 54 | const options = Object.assign({}, opts) 55 | 56 | for (const key in options) { 57 | if (options.hasOwnProperty(key) && typeof options[key] === 'function') { 58 | fns[key] = bind(options[key], this) 59 | delete options[key] 60 | } 61 | } 62 | 63 | return options 64 | }, 65 | /** 66 | * Promise setData 67 | * @param {Array} args 参数对象 68 | */ 69 | $$setData (...args) { 70 | const params = assign({}, ...args) 71 | 72 | return new Promise((resolve) => { 73 | this.setData(params, resolve) 74 | }) 75 | }, 76 | /** 77 | * 延迟指定时间执行回调函数 78 | * @param {Function} callback 回调函数 79 | * @param {Number} timeout 延迟时间 80 | */ 81 | $$requestAnimationFrame (callback = () => {}, timeout = 1000 / 60) { 82 | return new Promise((resolve) => setTimeout(resolve, timeout)).then(callback) 83 | }, 84 | }, 85 | /** 86 | * 组件生命周期函数,在组件实例进入页面节点树时执行 87 | */ 88 | created () { 89 | this.fns = {} 90 | }, 91 | /** 92 | * 组件生命周期函数,在组件实例被从页面节点树移除时执行 93 | */ 94 | detached () { 95 | this.fns = {} 96 | }, 97 | }) 98 | -------------------------------------------------------------------------------- /miniprogram_npm/fetch-wechat/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["index.js"],"names":[],"mappings":";;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"index.js","sourcesContent":["\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.TEXT_FILE_EXTS = /\\.(txt|json|html|txt|csv)/;\nfunction parseResponse(url, res) {\n var header = res.header || {};\n header = Object.keys(header).reduce(function (map, key) {\n map[key.toLowerCase()] = header[key];\n return map;\n }, {});\n return {\n ok: ((res.statusCode / 200) | 0) === 1,\n status: res.statusCode,\n statusText: res.statusCode,\n url: url,\n clone: function () { return parseResponse(url, res); },\n text: function () {\n return Promise.resolve(typeof res.data === 'string' ? res.data : JSON.stringify(res.data));\n },\n json: function () {\n if (typeof res.data === 'object')\n return Promise.resolve(res.data);\n var json = {};\n try {\n json = JSON.parse(res.data);\n }\n catch (err) {\n console.error(err);\n }\n return Promise.resolve(json);\n },\n arrayBuffer: function () {\n return Promise.resolve(res.data);\n },\n headers: {\n keys: function () { return Object.keys(header); },\n entries: function () {\n var all = [];\n for (var key in header) {\n if (header.hasOwnProperty(key)) {\n all.push([key, header[key]]);\n }\n }\n return all;\n },\n get: function (n) { return header[n.toLowerCase()]; },\n has: function (n) { return n.toLowerCase() in header; }\n }\n };\n}\nexports.parseResponse = parseResponse;\nfunction fetchFunc() {\n // tslint:disable-next-line:no-any\n return function (url, options) {\n options = options || {};\n var dataType = url.match(exports.TEXT_FILE_EXTS) ? 'text' : 'arraybuffer';\n return new Promise(function (resolve, reject) {\n wx.request({\n url: url,\n method: options.method || 'GET',\n data: options.body,\n header: options.headers,\n dataType: dataType,\n responseType: dataType,\n success: function (resp) { return resolve(parseResponse(url, resp)); },\n fail: function (err) { return reject(err); }\n });\n });\n };\n}\nexports.fetchFunc = fetchFunc;\nfunction setWechatFetch(debug) {\n if (debug === void 0) { debug = false; }\n // tslint:disable-next-line:no-any\n var typedGlobal = global;\n if (typeof typedGlobal.fetch !== 'function') {\n if (debug) {\n console.log('setup global fetch...');\n }\n typedGlobal.fetch = fetchFunc();\n }\n}\nexports.setWechatFetch = setWechatFetch;\n//# sourceMappingURL=index.js.map"]} -------------------------------------------------------------------------------- /pages/ai-classifier/index.wxss: -------------------------------------------------------------------------------- 1 | page{ 2 | height: 100%; 3 | width:100%; 4 | } 5 | 6 | .container { 7 | height: 100%; 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | justify-content: space-between; 12 | box-sizing: border-box; 13 | } 14 | 15 | /* 显示的题目 */ 16 | 17 | .container .question { 18 | width: 100%; 19 | height: 5%; 20 | background: #f0efef; 21 | display: flex; 22 | flex-direction: row; 23 | justify-content: center; 24 | align-items: center; 25 | color: #fb21a1; 26 | box-shadow: 2rpx 5rpx 2rpx silver; 27 | } 28 | 29 | /* 刷新按钮 */ 30 | 31 | .container .question .userinfo-avatar { 32 | height: 50rpx; 33 | width: 50rpx; 34 | border-radius: 50%; 35 | overflow: hidden; 36 | } 37 | 38 | .container .question text { 39 | margin: auto 10rpx auto 20rpx; 40 | font-size: 0.75em; 41 | } 42 | 43 | .container .question .refresh-btn { 44 | width: 50rpx; 45 | height: 50rpx; 46 | transform: scaleX(-1); 47 | } 48 | 49 | /* 中间画板 */ 50 | 51 | .container .palette { 52 | width: 100%; 53 | height: 50%; 54 | display: flex; 55 | justify-content: center; 56 | align-items: center; 57 | box-shadow: 2rpx 5rpx 2rpx silver; 58 | } 59 | 60 | /* 按钮的panel */ 61 | 62 | .container .option-panel { 63 | padding-bottom: 10rpx; 64 | font-size: 1em; 65 | text-align: center; 66 | background: #f0efef; 67 | width: 100%; 68 | height: 45%; 69 | display: flex; 70 | flex-direction: column; 71 | justify-content: space-between; 72 | align-items: center; 73 | } 74 | 75 | /* 面板中的行 */ 76 | 77 | .option-panel .option-row { 78 | width: 100%; 79 | display: flex; 80 | flex-direction: row; 81 | justify-content: flex-start; 82 | align-content: center; 83 | } 84 | 85 | #firstRow{ 86 | height: 10%; 87 | } 88 | #secondRow{ 89 | height: 90%; 90 | } 91 | 92 | 93 | /* 面板中的第一行 */ 94 | 95 | .option-panel #firstRow .width-container { 96 | width: 50%; 97 | padding-top: 10rpx; 98 | display: flex; 99 | flex-direction: row; 100 | justify-content: space-around; 101 | align-items: center; 102 | } 103 | 104 | /* 笔触大小四个字 */ 105 | .option-panel #firstRow .width-container text { 106 | margin-left: 25rpx; 107 | font-size: 0.75em; 108 | color: gray; 109 | } 110 | 111 | .brush-point{ 112 | width: 52rpx; 113 | height: 65rpx; 114 | } 115 | 116 | .option-panel #firstRow .useless { 117 | width: 30%; 118 | } 119 | 120 | /* 清除按钮 */ 121 | .option-panel #firstRow .icon-text { 122 | padding-top: 10rpx; 123 | width: 20%; 124 | display: flex; 125 | align-items: center; 126 | font-size: 0.75em; 127 | color: gray; 128 | } 129 | 130 | /* 第二行 */ 131 | .option-panel #secondRow { 132 | margin-top: 20rpx; 133 | width: 100%; 134 | display: flex; 135 | flex-direction: column; 136 | } 137 | 138 | /* 分类视图,包含名称和概率 */ 139 | .option-panel #secondRow .class-view { 140 | width: 100%; 141 | display: flex; 142 | flex-direction: column; 143 | } 144 | 145 | /* 分类名称 */ 146 | .option-panel #secondRow .class-view .class-name { 147 | width: 100%; 148 | margin-left: 25rpx; 149 | font-size: 0.75em; 150 | color: gray; 151 | padding: 5rpx; 152 | display: flex; 153 | flex-direction: row; 154 | justify-content: flex-start; 155 | } 156 | 157 | /* 分类概率进度条 */ 158 | .option-panel #secondRow .class-view .class-prog { 159 | margin-left: 25rpx; 160 | width: 95%; 161 | } 162 | 163 | /*----------------------------------------------------------------------*/ 164 | 165 | canvas { 166 | background: black; 167 | width: 100%; 168 | height: 100%; 169 | border: 1rpx solid #ebebeb; 170 | border-radius: 3rpx; 171 | } 172 | -------------------------------------------------------------------------------- /components/helpers/styleToCssString.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * CSS properties which accept numbers but are not in units of "px". 5 | */ 6 | var isUnitlessNumber = { 7 | boxFlex: true, 8 | boxFlexGroup: true, 9 | columnCount: true, 10 | flex: true, 11 | flexGrow: true, 12 | flexPositive: true, 13 | flexShrink: true, 14 | flexNegative: true, 15 | fontWeight: true, 16 | lineClamp: true, 17 | lineHeight: true, 18 | opacity: true, 19 | order: true, 20 | orphans: true, 21 | widows: true, 22 | zIndex: true, 23 | zoom: true, 24 | 25 | // SVG-related properties 26 | fillOpacity: true, 27 | strokeDashoffset: true, 28 | strokeOpacity: true, 29 | strokeWidth: true 30 | }; 31 | 32 | /** 33 | * @param {string} prefix vendor-specific prefix, eg: Webkit 34 | * @param {string} key style name, eg: transitionDuration 35 | * @return {string} style name prefixed with `prefix`, properly camelCased, eg: 36 | * WebkitTransitionDuration 37 | */ 38 | function prefixKey(prefix, key) { 39 | return prefix + key.charAt(0).toUpperCase() + key.substring(1); 40 | } 41 | 42 | /** 43 | * Support style names that may come passed in prefixed by adding permutations 44 | * of vendor prefixes. 45 | */ 46 | var prefixes = ['Webkit', 'ms', 'Moz', 'O']; 47 | 48 | // Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an 49 | // infinite loop, because it iterates over the newly added props too. 50 | Object.keys(isUnitlessNumber).forEach(function(prop) { 51 | prefixes.forEach(function(prefix) { 52 | isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop]; 53 | }); 54 | }); 55 | 56 | var msPattern = /^ms-/; 57 | 58 | var _uppercasePattern = /([A-Z])/g; 59 | 60 | /** 61 | * Hyphenates a camelcased string, for example: 62 | * 63 | * > hyphenate('backgroundColor') 64 | * < "background-color" 65 | * 66 | * For CSS style names, use `hyphenateStyleName` instead which works properly 67 | * with all vendor prefixes, including `ms`. 68 | * 69 | * @param {string} string 70 | * @return {string} 71 | */ 72 | function hyphenate(string) { 73 | return string.replace(_uppercasePattern, '-$1').toLowerCase(); 74 | } 75 | 76 | /** 77 | * Hyphenates a camelcased CSS property name, for example: 78 | * 79 | * > hyphenateStyleName('backgroundColor') 80 | * < "background-color" 81 | * > hyphenateStyleName('MozTransition') 82 | * < "-moz-transition" 83 | * > hyphenateStyleName('msTransition') 84 | * < "-ms-transition" 85 | * 86 | * As Modernizr suggests (http://modernizr.com/docs/#prefixed), an `ms` prefix 87 | * is converted to `-ms-`. 88 | * 89 | * @param {string} string 90 | * @return {string} 91 | */ 92 | function hyphenateStyleName(string) { 93 | return hyphenate(string).replace(msPattern, '-ms-'); 94 | } 95 | 96 | var isArray = Array.isArray; 97 | var keys = Object.keys; 98 | 99 | var counter = 1; 100 | // Follows syntax at https://developer.mozilla.org/en-US/docs/Web/CSS/content, 101 | // including multiple space separated values. 102 | var unquotedContentValueRegex = /^(normal|none|(\b(url\([^)]*\)|chapter_counter|attr\([^)]*\)|(no-)?(open|close)-quote|inherit)((\b\s*)|$|\s+))+)$/; 103 | 104 | function buildRule(key, value) { 105 | if (!isUnitlessNumber[key] && typeof value === 'number') { 106 | value = '' + value + 'px'; 107 | } else if (key === 'content' && !unquotedContentValueRegex.test(value)) { 108 | value = "'" + value.replace(/'/g, "\\'") + "'"; 109 | } 110 | 111 | return hyphenateStyleName(key) + ': ' + value + '; '; 112 | } 113 | 114 | function styleToCssString(rules) { 115 | var result = '' 116 | if (typeof rules === 'string') { 117 | return rules 118 | } 119 | if (!rules || keys(rules).length === 0) { 120 | return result; 121 | } 122 | var styleKeys = keys(rules); 123 | for (var j = 0, l = styleKeys.length; j < l; j++) { 124 | var styleKey = styleKeys[j]; 125 | var value = rules[styleKey]; 126 | 127 | if (isArray(value)) { 128 | for (var i = 0, len = value.length; i < len; i++) { 129 | result += buildRule(styleKey, value[i]); 130 | } 131 | } else { 132 | result += buildRule(styleKey, value); 133 | } 134 | } 135 | return result; 136 | } 137 | 138 | export default styleToCssString -------------------------------------------------------------------------------- /miniprogram_npm/fetch-wechat/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (function() { 2 | var __MODS__ = {}; 3 | var __DEFINE__ = function(modId, func, req) { var m = { exports: {}, _tempexports: {} }; __MODS__[modId] = { status: 0, func: func, req: req, m: m }; }; 4 | var __REQUIRE__ = function(modId, source) { if(!__MODS__[modId]) return require(source); if(!__MODS__[modId].status) { var m = __MODS__[modId].m; m._exports = m._tempexports; var desp = Object.getOwnPropertyDescriptor(m, "exports"); if (desp && desp.configurable) Object.defineProperty(m, "exports", { set: function (val) { if(typeof val === "object" && val !== m._exports) { m._exports.__proto__ = val.__proto__; Object.keys(val).forEach(function (k) { m._exports[k] = val[k]; }); } m._tempexports = val }, get: function () { return m._tempexports; } }); __MODS__[modId].status = 1; __MODS__[modId].func(__MODS__[modId].req, m, m.exports); } return __MODS__[modId].m.exports; }; 5 | var __REQUIRE_WILDCARD__ = function(obj) { if(obj && obj.__esModule) { return obj; } else { var newObj = {}; if(obj != null) { for(var k in obj) { if (Object.prototype.hasOwnProperty.call(obj, k)) newObj[k] = obj[k]; } } newObj.default = obj; return newObj; } }; 6 | var __REQUIRE_DEFAULT__ = function(obj) { return obj && obj.__esModule ? obj.default : obj; }; 7 | __DEFINE__(1593391889373, function(require, module, exports) { 8 | 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | exports.TEXT_FILE_EXTS = /\.(txt|json|html|txt|csv)/; 11 | function parseResponse(url, res) { 12 | var header = res.header || {}; 13 | header = Object.keys(header).reduce(function (map, key) { 14 | map[key.toLowerCase()] = header[key]; 15 | return map; 16 | }, {}); 17 | return { 18 | ok: ((res.statusCode / 200) | 0) === 1, 19 | status: res.statusCode, 20 | statusText: res.statusCode, 21 | url: url, 22 | clone: function () { return parseResponse(url, res); }, 23 | text: function () { 24 | return Promise.resolve(typeof res.data === 'string' ? res.data : JSON.stringify(res.data)); 25 | }, 26 | json: function () { 27 | if (typeof res.data === 'object') 28 | return Promise.resolve(res.data); 29 | var json = {}; 30 | try { 31 | json = JSON.parse(res.data); 32 | } 33 | catch (err) { 34 | console.error(err); 35 | } 36 | return Promise.resolve(json); 37 | }, 38 | arrayBuffer: function () { 39 | return Promise.resolve(res.data); 40 | }, 41 | headers: { 42 | keys: function () { return Object.keys(header); }, 43 | entries: function () { 44 | var all = []; 45 | for (var key in header) { 46 | if (header.hasOwnProperty(key)) { 47 | all.push([key, header[key]]); 48 | } 49 | } 50 | return all; 51 | }, 52 | get: function (n) { return header[n.toLowerCase()]; }, 53 | has: function (n) { return n.toLowerCase() in header; } 54 | } 55 | }; 56 | } 57 | exports.parseResponse = parseResponse; 58 | function fetchFunc() { 59 | // tslint:disable-next-line:no-any 60 | return function (url, options) { 61 | options = options || {}; 62 | var dataType = url.match(exports.TEXT_FILE_EXTS) ? 'text' : 'arraybuffer'; 63 | return new Promise(function (resolve, reject) { 64 | wx.request({ 65 | url: url, 66 | method: options.method || 'GET', 67 | data: options.body, 68 | header: options.headers, 69 | dataType: dataType, 70 | responseType: dataType, 71 | success: function (resp) { return resolve(parseResponse(url, resp)); }, 72 | fail: function (err) { return reject(err); } 73 | }); 74 | }); 75 | }; 76 | } 77 | exports.fetchFunc = fetchFunc; 78 | function setWechatFetch(debug) { 79 | if (debug === void 0) { debug = false; } 80 | // tslint:disable-next-line:no-any 81 | var typedGlobal = global; 82 | if (typeof typedGlobal.fetch !== 'function') { 83 | if (debug) { 84 | console.log('setup global fetch...'); 85 | } 86 | typedGlobal.fetch = fetchFunc(); 87 | } 88 | } 89 | exports.setWechatFetch = setWechatFetch; 90 | //# sourceMappingURL=index.js.map 91 | }, function(modId) {var map = {}; return __REQUIRE__(map[modId], modId); }) 92 | return __REQUIRE__(1593391889373); 93 | })() 94 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /components/aiModels/autoPainter/autoPainter.js: -------------------------------------------------------------------------------- 1 | // compoents/aiModels/autoPainter/autoPainter.js 2 | // Auto painter模型推理库 3 | 4 | /* 加载tensorflow库 */ 5 | const tf = require('@tensorflow/tfjs-core') 6 | const tfLayers = require('@tensorflow/tfjs-layers'); 7 | 8 | /* 全局常量 */ 9 | const MAX_LEN = { 10 | // 'flower': 352, 11 | // 'butterfly': 246, 12 | // 'apple': 189, 13 | // 'house': 201, 14 | // 'sun': 291, 15 | // 'tree': 579 16 | 'flower': 100, 17 | 'butterfly': 100, 18 | 'apple': 100, 19 | 'house': 100, 20 | 'sun': 100, 21 | 'tree': 100 22 | } 23 | const MODELS_URL = { 24 | 'flower': 'https://6169-ai-painter-6g4jfmfl19cfbe8f-1302478925.tcb.qcloud.la/auto-painter/flower/model.json', 25 | 'butterfly': 'https://6169-ai-painter-6g4jfmfl19cfbe8f-1302478925.tcb.qcloud.la/auto-painter/butterfly/model.json', 26 | 'apple': 'https://6169-ai-painter-6g4jfmfl19cfbe8f-1302478925.tcb.qcloud.la/auto-painter/apple/model.json', 27 | 'house': 'https://6169-ai-painter-7q1db-1302478925.tcb.qcloud.la/auto-painter/house/model.json', 28 | 'sun': 'https://6169-ai-painter-6g4jfmfl19cfbe8f-1302478925.tcb.qcloud.la/auto-painter/sun/model.json', 29 | 'tree': 'https://6169-ai-painter-7q1db-1302478925.tcb.qcloud.la/auto-painter/tree/model.json' 30 | } 31 | 32 | /* 全局变量 */ 33 | // 所有的模型 34 | let models = { 35 | 'flower': null, 36 | 'butterfly': null, 37 | 'apple': null, 38 | 'sun': null 39 | }; 40 | let curModelType = 'apple'; // 当前模型类型 41 | 42 | /** 43 | * 数组转成tensor 44 | */ 45 | async function tensorPreprocess(inks) { 46 | return tf.tidy(() => { 47 | //convert to a tensor 48 | let tensor = tf.tensor(inks); 49 | return tensor.expandDims(0); 50 | }) 51 | } 52 | 53 | /** 54 | * 加载模型 55 | * @param {string} modelType 模型类型 56 | * @returns true for success, false for failed. 57 | */ 58 | async function loadModels(modelType) { 59 | if (modelType) { 60 | curModelType = modelType; 61 | } 62 | 63 | if (models[curModelType]) { // 模型已加载 64 | return true; 65 | } 66 | 67 | // console.time('LoadModel'); 68 | console.log('Loading models...'); 69 | 70 | /* 加载并预热模型 */ 71 | try { 72 | models[curModelType] = await tfLayers.loadLayersModel(MODELS_URL[curModelType]); 73 | } catch (err) { 74 | console.log(err); 75 | return false; 76 | } 77 | let warmupStroke = [ 78 | [0., 0., 0., 0.] 79 | ]; 80 | let strokeTensor = await tensorPreprocess(warmupStroke); 81 | let pred = await models[curModelType].predict(strokeTensor); 82 | let predArr = await pred.dataSync(); 83 | console.log(predArr); 84 | pred.dispose(); 85 | 86 | // console.timeEnd('LoadModel'); 87 | return true; 88 | } 89 | 90 | /** 91 | * 根据当前起始笔画预测并生成后续笔画 92 | * @param {Array} beginStroke 起始笔画 93 | * @returns 二维数组代表预测的后续笔画 94 | */ 95 | async function generate(beginStroke) { 96 | if (models[curModelType] === null) { 97 | console.log("Model unloaded.!"); 98 | return null; 99 | } 100 | // console.time('Generate'); 101 | 102 | // The initial inks len. 103 | const initialLen = beginStroke.length; 104 | console.log("The initial inks len: " + initialLen); 105 | // Enter the initial inks. 106 | models[curModelType].resetStates(); 107 | let strokeTensor = await tensorPreprocess(beginStroke); 108 | let predTf = await models[curModelType].predict(strokeTensor); 109 | let pred = await predTf.dataSync(); 110 | predTf.dispose(); 111 | // Find the last ink. 112 | const index = (initialLen - 1) * 4; 113 | let pred_ = [pred[index], pred[index + 1], pred[index + 2], pred[index + 3]]; 114 | // Save the new ink. 115 | beginStroke.push(pred_); 116 | // Pred the left inks. 117 | let inp = null; 118 | do { 119 | // Use the last ink as input. 120 | inp = [beginStroke[beginStroke.length - 1]]; 121 | // Enter the initial inks. 122 | let inpTensor = await tensorPreprocess(inp); 123 | predTf = await models[curModelType].predict(inpTensor); 124 | pred = await predTf.dataSync(); 125 | predTf.dispose(); 126 | // Find he last ink. 127 | pred_ = [pred[0], pred[1], pred[2] >= 0.5 ? 1.0 : 0.0, pred[3] >= 0.5 ? 1.0 : 0.0]; 128 | // Save the new ink. 129 | beginStroke.push(pred_); 130 | } while (pred[3] < 0.5 && beginStroke.length <= MAX_LEN[curModelType] - initialLen); 131 | // console.log(beginStroke); 132 | // Pop the initial inks. 133 | let followStroke = beginStroke.splice(initialLen, beginStroke.length - initialLen); 134 | 135 | // console.timeEnd('Generate'); 136 | return followStroke; 137 | } 138 | 139 | let autoPainter = { 140 | loadModels: loadModels, 141 | generate: generate, 142 | }; 143 | 144 | module.exports = autoPainter; -------------------------------------------------------------------------------- /components/aiModels/classifier/classifier.js: -------------------------------------------------------------------------------- 1 | // compoents/aiModels/classifier/classifier.js 2 | // Auto painter模型推理库 3 | 4 | /* 加载tensorflow库 */ 5 | const tf = require('@tensorflow/tfjs-core') 6 | const tfLayers = require('@tensorflow/tfjs-layers'); 7 | var plugin = requirePlugin('tfjsPlugin'); 8 | 9 | /* 常量 */ 10 | const MODEL_URL = 'https://6169-ai-painter-6g4jfmfl19cfbe8f-1302478925.tcb.qcloud.la/quick-draw-classifier/model.json' 11 | const CLASSES_NAME_CLOUD_ID = 'cloud://ai-painter-6g4jfmfl19cfbe8f.6169-ai-painter-6g4jfmfl19cfbe8f-1302478925/quick-draw-classifier/classes_names_zh' 12 | 13 | /* 全局变量 */ 14 | let model = null; // 模型 15 | let classNames = null; // 记录所有的类型名 16 | 17 | /** 18 | * 数组转成tensor 19 | */ 20 | async function tensorPreprocess(inks) { 21 | return tf.tidy(() => { 22 | //convert to a tensor 23 | let tensor = tf.tensor(inks); 24 | return tensor.expandDims(0); 25 | }) 26 | } 27 | 28 | /** 29 | * 加载模型 30 | * @returns true for success, false for failed. 31 | */ 32 | async function loadModels() { 33 | if (model != null) { 34 | return true; 35 | } 36 | 37 | console.log('Loading models...'); 38 | // tf.tensor([1, 2]).print(); 39 | 40 | /* 下载并读取类型名文件 */ 41 | wx.cloud.init(); 42 | wx.cloud.downloadFile({ 43 | fileID: CLASSES_NAME_CLOUD_ID, 44 | success: res => { 45 | // console.log(res.tempFilePath); 46 | wx.getFileSystemManager().readFile({ 47 | filePath: res.tempFilePath, 48 | encoding: 'utf-8', 49 | success(res) { 50 | if (res.data) { 51 | classNames = res.data.split('\r\n'); 52 | console.log(classNames); 53 | } 54 | }, 55 | fail(err) { 56 | console.log('读取classes_name失败:', err.errMsg); 57 | } 58 | }); 59 | }, 60 | fail: err => { 61 | console.log('下载classes_name失败: ' + err.errMsg); 62 | } 63 | }); 64 | 65 | // console.time('LoadModel'); 66 | 67 | /* 加载模型 */ 68 | const fileStorageHandler = plugin.fileStorageIO('classifier', wx.getFileSystemManager()); 69 | 70 | try { 71 | model = await tfLayers.loadLayersModel(fileStorageHandler); // 先尝试本地加载模型文件 72 | } catch (err) { 73 | console.log('Load model from local failed: ' + err); 74 | try { 75 | model = await tfLayers.loadLayersModel(MODEL_URL); // 本地加载失败,从云顿加载模型文件 76 | // model.save(fileStorageHandler); // 模型文件保存到本地 77 | } catch (err) { 78 | console.log(err); 79 | return false; 80 | } 81 | } 82 | 83 | /* 预热模型 */ 84 | let warmupStroke = [ 85 | [0., 0., 0.] 86 | ]; 87 | // await classify(warmupStroke); 88 | let strokeTensor = await tensorPreprocess(warmupStroke); 89 | let pred = await model.predict(strokeTensor); 90 | let predArr = await pred.dataSync(); 91 | pred.dispose(); 92 | model.resetStates(); 93 | 94 | // console.timeEnd('LoadModel'); 95 | return true; 96 | } 97 | 98 | /** 99 | * get indices of the top probs 100 | */ 101 | function findIndicesOfMax(inp, count) { 102 | let outp = []; 103 | for (let i = 0; i < inp.length; i++) { 104 | outp.push(i); // add index to output array 105 | if (outp.length > count) { 106 | outp.sort(function (a, b) { 107 | return inp[b] - inp[a]; 108 | }); // descending sort the output array 109 | outp.pop(); // remove the last index (index of smallest element in output array) 110 | } 111 | } 112 | return outp; 113 | } 114 | 115 | /** 116 | * find the top 5 predictions 117 | */ 118 | function findTopValues(inp, count) { 119 | let outp = []; 120 | let indices = findIndicesOfMax(inp, count); 121 | // show 5 greatest scores 122 | for (let i = 0; i < indices.length; i++) { 123 | outp[i] = inp[indices[i]]; 124 | } 125 | return outp; 126 | } 127 | 128 | /** 129 | * get the the class names 130 | */ 131 | function getClassNames(indices) { 132 | let outp = []; 133 | for (let i = 0; i < indices.length; i++) { 134 | outp[i] = classNames[indices[i]]; 135 | } 136 | return outp; 137 | } 138 | 139 | /** 140 | * 对笔画进行分类 141 | * @param {Array} predictStroke 笔画 142 | * @param {function} succCb 成功的回调函数,参数obj包含两个属性,probs代表所有类型概率,names代表所有类型名 143 | * @param {function} failCb 失败的回调函数,参数obj包含一个属性,errInfo代表错误描述 144 | */ 145 | async function classify(predictStroke, succCb, failCb) { 146 | if (model === null) { 147 | console.log('Model unloaded!'); 148 | if (failCb) { 149 | failCb('Model unloaded!'); 150 | } 151 | } 152 | 153 | model.resetStates(); 154 | // Predict the class name index. 155 | let strokeTensor = await tensorPreprocess(predictStroke); 156 | let pred = await model.predict(strokeTensor); 157 | await pred.data().then(predArr => { 158 | pred.dispose(); 159 | // find the top 5 predictions 160 | const indices = findIndicesOfMax(predArr, 5); 161 | // console.log('The predict indices:'); 162 | // console.log(indices); 163 | const probs = findTopValues(predArr, 5); 164 | // console.log('The predict probabilities:'); 165 | // console.log(probs); 166 | const names = getClassNames(indices); 167 | // console.log('The predict names:'); 168 | // console.log(names); 169 | if (succCb) { 170 | succCb({ 171 | probs: probs, 172 | names: names 173 | }); 174 | } 175 | }); 176 | } 177 | 178 | /** 179 | * 重置classifier,主要是重置RNN状态。 180 | */ 181 | async function resetClassifier() { 182 | if (model !== undefined) { 183 | model.resetStates(); 184 | } 185 | } 186 | 187 | let classifier = { 188 | loadModels: loadModels, 189 | classify: classify, 190 | resetClassifier: resetClassifier, 191 | }; 192 | 193 | module.exports = classifier; -------------------------------------------------------------------------------- /pages/ai-painter/index.wxss: -------------------------------------------------------------------------------- 1 | page{ 2 | height: 100%; 3 | width:100%; 4 | } 5 | 6 | .container { 7 | height: 100%; 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | justify-content: space-between; 12 | box-sizing: border-box; 13 | } 14 | 15 | /* 显示的题目 */ 16 | 17 | .container .question { 18 | width: 100%; 19 | height: 5%; 20 | background: #f0efef; 21 | display: flex; 22 | flex-direction: row; 23 | justify-content: center; 24 | align-items: center; 25 | color: #fb21a1; 26 | box-shadow: 2rpx 5rpx 2rpx silver; 27 | } 28 | 29 | /* 刷新按钮 */ 30 | 31 | .container .question .userinfo-avatar { 32 | height: 50rpx; 33 | width: 50rpx; 34 | border-radius: 50%; 35 | overflow: hidden; 36 | } 37 | 38 | .container .question text { 39 | margin: auto 10rpx auto 20rpx; 40 | font-size: 0.75em; 41 | } 42 | 43 | .container .question .refresh-btn { 44 | width: 50rpx; 45 | height: 50rpx; 46 | transform: scaleX(-1); 47 | } 48 | 49 | /* 中间画板 */ 50 | 51 | .container .palette { 52 | width: 100%; 53 | height: 60%; 54 | display: flex; 55 | justify-content: center; 56 | align-items: center; 57 | box-shadow: 2rpx 5rpx 2rpx silver; 58 | } 59 | 60 | /* 按钮的panel */ 61 | 62 | .container .option-panel { 63 | padding-bottom: 10rpx; 64 | font-size: 1em; 65 | text-align: center; 66 | background: #f0efef; 67 | width: 100%; 68 | height: 35%; 69 | display: flex; 70 | flex-direction: column; 71 | justify-content: space-between; 72 | align-items: center; 73 | } 74 | 75 | /* 面板中的行 */ 76 | 77 | .option-panel .option-row { 78 | width: 100%; 79 | display: flex; 80 | flex-direction: row; 81 | justify-content: flex-start; 82 | align-content: center; 83 | } 84 | 85 | #firstRow{ 86 | height: 20%; 87 | } 88 | 89 | #secondRow{ 90 | height: 25%; 91 | } 92 | 93 | #thirdRow{ 94 | height: 25%; 95 | } 96 | 97 | #forthRow{ 98 | height: 20%; 99 | } 100 | /* 面板中的第一行 */ 101 | 102 | .option-panel #firstRow .width-container { 103 | width: 50%; 104 | padding-top: 10rpx; 105 | display: flex; 106 | flex-direction: row; 107 | justify-content: space-around; 108 | align-items: center; 109 | } 110 | 111 | /* 笔触大小四个字 */ 112 | .option-panel #firstRow .width-container text { 113 | margin-left: 25rpx; 114 | font-size: 0.75em; 115 | color: gray; 116 | } 117 | 118 | .brush-point{ 119 | width: 52rpx; 120 | height: 65rpx; 121 | } 122 | 123 | .option-panel #firstRow .useless { 124 | width: 30%; 125 | } 126 | 127 | /* 清除按钮 */ 128 | .option-panel #firstRow .icon-text { 129 | padding-top: 10rpx; 130 | width: 20%; 131 | display: flex; 132 | align-items: center; 133 | font-size: 0.75em; 134 | color: gray; 135 | } 136 | 137 | /* option-panel的第二行 */ 138 | 139 | .option-panel #secondRow { 140 | display: flex; 141 | justify-content: space-around; 142 | flex-direction: row; 143 | align-items: center; 144 | } 145 | 146 | /*选颜色*/ 147 | 148 | /* 左边的箭头 */ 149 | 150 | .option-panel #secondRow .color-slecotr-left { 151 | width: 0; 152 | height: 0; 153 | margin-left: 3%; 154 | border-top: 25rpx solid transparent; 155 | border-right: 30rpx solid #c9c8c8; 156 | border-bottom: 25rpx solid transparent; 157 | } 158 | 159 | /* 右边的箭头 */ 160 | 161 | .option-panel #secondRow .color-slecotr-right { 162 | width: 0; 163 | height: 0; 164 | margin-right: 3%; 165 | border-top: 25rpx solid transparent; 166 | border-left: 30rpx solid #c9c8c8; 167 | border-bottom: 25rpx solid transparent; 168 | } 169 | 170 | /* 装颜色盒子的srcoll-view */ 171 | 172 | .option-panel #secondRow scroll-view { 173 | width: 80%; 174 | display: flex; 175 | flex-direction: row; 176 | white-space: nowrap; 177 | align-content: flex-end; 178 | } 179 | 180 | .option-panel #secondRow scroll-view .color-box { 181 | display: inline-block; 182 | width: 60rpx; 183 | height: 100%; 184 | margin: 0 10rpx 0 10rpx; 185 | } 186 | 187 | /* option-panel的第三行 */ 188 | 189 | .option-panel #thirdRow { 190 | display: flex; 191 | justify-content: space-around; 192 | flex-direction: row; 193 | align-items: center; 194 | } 195 | 196 | /*选颜色*/ 197 | 198 | /* 左边的箭头 */ 199 | 200 | .option-panel #thirdRow .color-slecotr-left { 201 | width: 0; 202 | height: 0; 203 | margin-left: 3%; 204 | border-top: 25rpx solid transparent; 205 | border-right: 30rpx solid #c9c8c8; 206 | border-bottom: 25rpx solid transparent; 207 | } 208 | 209 | /* 右边的箭头 */ 210 | 211 | .option-panel #thirdRow .color-slecotr-right { 212 | width: 0; 213 | height: 0; 214 | margin-right: 3%; 215 | border-top: 25rpx solid transparent; 216 | border-left: 30rpx solid #c9c8c8; 217 | border-bottom: 25rpx solid transparent; 218 | } 219 | 220 | /* 装颜色盒子的srcoll-view */ 221 | 222 | .option-panel #thirdRow scroll-view { 223 | width: 80%; 224 | display: flex; 225 | flex-direction: row; 226 | white-space: nowrap; 227 | align-content: flex-end; 228 | } 229 | 230 | .option-panel #thirdRow scroll-view .radio-group { 231 | display: flex; 232 | flex-direction: row; 233 | width: 60rpx; 234 | height: 100%; 235 | margin: 0 10rpx 0 10rpx; 236 | } 237 | 238 | .option-panel #thirdRow scroll-view .radio { 239 | width: 50rpx; 240 | height: 100%; 241 | margin-left: 35rpx; 242 | margin-right: 45rpx; 243 | } 244 | 245 | .option-panel #thirdRow scroll-view .radio .text { 246 | font-size: 28rpx; 247 | } 248 | 249 | /* 第四行 */ 250 | .option-panel #forthRow .share-btn{ 251 | background: #ffcc01; 252 | } 253 | 254 | /* 生成佳作的按钮 */ 255 | 256 | .option-panel .share-btn { 257 | width: 90%; 258 | height: 80rpx; 259 | line-height: 80rpx; 260 | font-size: 34rpx; 261 | color: #000; 262 | } 263 | 264 | /*----------------------------------------------------------------------*/ 265 | 266 | canvas { 267 | background: black; 268 | width: 100%; 269 | height: 100%; 270 | border: 1rpx solid #ebebeb; 271 | border-radius: 3rpx; 272 | } 273 | -------------------------------------------------------------------------------- /components/canvasdrawer/canvasdrawer.js: -------------------------------------------------------------------------------- 1 | /* global Component wx */ 2 | 3 | Component({ 4 | properties: { 5 | painting: { 6 | type: Object, 7 | value: {view: []}, 8 | observer (newVal, oldVal) { 9 | if (!this.data.isPainting) { 10 | if (newVal.width && newVal.height) { 11 | this.setData({ 12 | isPainting: true 13 | }) 14 | this.readyPigment() 15 | } 16 | } 17 | } 18 | } 19 | }, 20 | data: { 21 | width: 100, 22 | height: 100, 23 | 24 | index: 0, 25 | imageList: [], 26 | tempFileList: [], 27 | 28 | isPainting: false 29 | }, 30 | ctx: null, 31 | cache: {}, 32 | ready () { 33 | wx.removeStorageSync('canvasdrawer_pic_cache') 34 | this.cache = wx.getStorageSync('canvasdrawer_pic_cache') || {} 35 | this.ctx = wx.createCanvasContext('canvasdrawer', this) 36 | }, 37 | methods: { 38 | readyPigment () { 39 | const { width, height, views } = this.data.painting 40 | this.setData({ 41 | width, 42 | height 43 | }) 44 | 45 | const inter = setInterval(() => { 46 | if (this.ctx) { 47 | clearInterval(inter) 48 | this.ctx.clearActions() 49 | this.drawRect({ 50 | background: 'white', 51 | top: 0, 52 | left: 0, 53 | width, 54 | height 55 | }) 56 | this.getImageList(views) 57 | this.downLoadImages(0) 58 | } 59 | }, 100) 60 | }, 61 | getImageList (views) { 62 | const imageList = [] 63 | for (let i = 0; i < views.length; i++) { 64 | if (views[i].type === 'image') { 65 | imageList.push(views[i].url) 66 | } 67 | } 68 | this.setData({ 69 | imageList 70 | }) 71 | }, 72 | downLoadImages (index) { 73 | const { imageList, tempFileList } = this.data 74 | if (index < imageList.length) { 75 | // console.log(imageList[index]) 76 | this.getImageInfo(imageList[index]).then(file => { 77 | tempFileList.push(file) 78 | this.setData({ 79 | tempFileList 80 | }) 81 | this.downLoadImages(index + 1) 82 | }) 83 | } else { 84 | this.startPainting() 85 | } 86 | }, 87 | startPainting () { 88 | const { tempFileList, painting: { views } } = this.data 89 | for (let i = 0, imageIndex = 0; i < views.length; i++) { 90 | if (views[i].type === 'image') { 91 | this.drawImage({ 92 | ...views[i], 93 | url: tempFileList[imageIndex] 94 | }) 95 | imageIndex++ 96 | } else if (views[i].type === 'text') { 97 | if (!this.ctx.measureText) { 98 | wx.showModal({ 99 | title: '提示', 100 | content: '当前微信版本过低,无法使用 measureText 功能,请升级到最新微信版本后重试。' 101 | }) 102 | } else { 103 | this.drawText(views[i]) 104 | } 105 | } else if (views[i].type === 'rect') { 106 | this.drawRect(views[i]) 107 | } 108 | } 109 | this.ctx.draw(true, () => { 110 | wx.setStorageSync('canvasdrawer_pic_cache', this.cache) 111 | this.saveImageToLocal() 112 | }) 113 | }, 114 | drawImage (params) { 115 | // console.log(params) 116 | const { url, top = 0, left = 0, width = 0, height = 0 } = params 117 | this.ctx.drawImage(url, left, top, width, height) 118 | }, 119 | drawText (params) { 120 | const { 121 | MaxLineNumber = 2, 122 | breakWord = false, 123 | color = 'black', 124 | content = '', 125 | fontSize = 16, 126 | top = 0, 127 | left = 0, 128 | lineHeight = 20, 129 | textAlign = 'left', 130 | width, 131 | bolder = false, 132 | textDecoration = 'none' 133 | } = params 134 | 135 | this.ctx.setTextBaseline('top') 136 | this.ctx.setTextAlign(textAlign) 137 | this.ctx.setFillStyle(color) 138 | this.ctx.setFontSize(fontSize) 139 | 140 | if (!breakWord) { 141 | this.ctx.fillText(content, left, top) 142 | this.drawTextLine(left, top, textDecoration, color, fontSize, content) 143 | } else { 144 | let fillText = '' 145 | let fillTop = top 146 | let lineNum = 1 147 | for (let i = 0; i < content.length; i++) { 148 | fillText += [content[i]] 149 | if (this.ctx.measureText(fillText).width > width) { 150 | if (lineNum === MaxLineNumber) { 151 | if (i !== content.length) { 152 | fillText = fillText.substring(0, fillText.length - 1) + '...' 153 | this.ctx.fillText(fillText, left, fillTop) 154 | this.drawTextLine(left, fillTop, textDecoration, color, fontSize, fillText) 155 | fillText = '' 156 | break 157 | } 158 | } 159 | this.ctx.fillText(fillText, left, fillTop) 160 | this.drawTextLine(left, fillTop, textDecoration, color, fontSize, fillText) 161 | fillText = '' 162 | fillTop += lineHeight 163 | lineNum ++ 164 | } 165 | } 166 | this.ctx.fillText(fillText, left, fillTop) 167 | this.drawTextLine(left, fillTop, textDecoration, color, fontSize, fillText) 168 | } 169 | 170 | if (bolder) { 171 | this.drawText({ 172 | ...params, 173 | left: left + 0.3, 174 | top: top + 0.3, 175 | bolder: false, 176 | textDecoration: 'none' 177 | }) 178 | } 179 | }, 180 | drawTextLine (left, top, textDecoration, color, fontSize, content) { 181 | if (textDecoration === 'underline') { 182 | this.drawRect({ 183 | background: color, 184 | top: top + fontSize * 1.2, 185 | left: left - 1, 186 | width: this.ctx.measureText(content).width + 2, 187 | height: 1 188 | }) 189 | } else if (textDecoration === 'line-through') { 190 | this.drawRect({ 191 | background: color, 192 | top: top + fontSize * 0.6, 193 | left: left - 1, 194 | width: this.ctx.measureText(content).width + 2, 195 | height: 1 196 | }) 197 | } 198 | }, 199 | drawRect (params) { 200 | // console.log(params) 201 | const { background, top = 0, left = 0, width = 0, height = 0 } = params 202 | this.ctx.setFillStyle(background) 203 | this.ctx.fillRect(left, top, width, height) 204 | }, 205 | getImageInfo (url) { 206 | return new Promise((resolve, reject) => { 207 | /* 获得要在画布上绘制的图片 */ 208 | if (this.cache[url]) { 209 | resolve(this.cache[url]) 210 | } else { 211 | const objExp = new RegExp(/^http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/) 212 | if (objExp.test(url)) { 213 | wx.getImageInfo({ 214 | src: url, 215 | complete: res => { 216 | if (res.errMsg === 'getImageInfo:ok') { 217 | this.cache[url] = res.path 218 | resolve(res.path) 219 | } else { 220 | reject(new Error('getImageInfo fail')) 221 | } 222 | } 223 | }) 224 | } else { 225 | this.cache[url] = url 226 | resolve(url) 227 | } 228 | } 229 | }) 230 | }, 231 | saveImageToLocal () { 232 | const { width, height } = this.data 233 | wx.canvasToTempFilePath({ 234 | x: 0, 235 | y: 0, 236 | width, 237 | height, 238 | canvasId: 'canvasdrawer', 239 | success: res => { 240 | if (res.errMsg === 'canvasToTempFilePath:ok') { 241 | this.setData({ 242 | isPainting: false, 243 | imageList: [], 244 | tempFileList: [] 245 | }) 246 | this.triggerEvent('getImage', {tempFilePath: res.tempFilePath}) 247 | } 248 | } 249 | }, this) 250 | } 251 | } 252 | }) 253 | -------------------------------------------------------------------------------- /components/helpers/popupMixin.js: -------------------------------------------------------------------------------- 1 | import classNames from './classNames' 2 | import eventsMixin from './eventsMixin' 3 | 4 | const DEFAULT_TRIGGER = 'onClick' 5 | const CELL_NAME = '../cell/index' 6 | const FIELD_NAME = '../field/index' 7 | 8 | const defaultToolbar = { 9 | title: '请选择', 10 | cancelText: '取消', 11 | confirmText: '确定', 12 | } 13 | 14 | const defaultEvents = { 15 | onChange() {}, 16 | onConfirm() {}, 17 | onCancel() {}, 18 | onVisibleChange() {}, 19 | onValueChange() {}, 20 | } 21 | 22 | const defaultPlatformProps = { 23 | labelPropName: 'label', 24 | format(values, props) { 25 | return Array.isArray(values.displayValue) ? values.displayValue.join(',') : values.displayValue 26 | }, 27 | } 28 | 29 | export default function popupMixin(selector = '#wux-picker', platformProps = defaultPlatformProps) { 30 | return Behavior({ 31 | behaviors: [eventsMixin({ defaultEvents })], 32 | properties: { 33 | toolbar: { 34 | type: Object, 35 | value: defaultToolbar, 36 | }, 37 | trigger: { 38 | type: String, 39 | value: DEFAULT_TRIGGER, 40 | }, 41 | defaultVisible: { 42 | type: Boolean, 43 | value: false, 44 | }, 45 | visible: { 46 | type: Boolean, 47 | value: false, 48 | }, 49 | controlled: { 50 | type: Boolean, 51 | value: false, 52 | }, 53 | disabled: { 54 | type: Boolean, 55 | value: false, 56 | }, 57 | }, 58 | data: { 59 | mounted: false, 60 | popupVisible: false, 61 | inputValue: [], 62 | }, 63 | methods: { 64 | /** 65 | * 设置组件显示或隐藏 66 | */ 67 | setVisibleState(popupVisible, callback = () => {}) { 68 | if (this.data.popupVisible !== popupVisible) { 69 | const params = { 70 | mounted: true, 71 | inputValue: this.data.value, // forceUpdate 72 | popupVisible, 73 | } 74 | this.setData(popupVisible ? params : { popupVisible }, () => { 75 | // collect field component & forceUpdate 76 | if (popupVisible && this.hasFieldDecorator) { 77 | const field = this.getFieldElem() 78 | if (field) { 79 | field.changeValue(field.data.value) 80 | } 81 | } 82 | callback() 83 | }) 84 | } 85 | }, 86 | /** 87 | * 触发 visibleChange 事件 88 | */ 89 | fireVisibleChange(popupVisible) { 90 | if (this.data.popupVisible !== popupVisible) { 91 | if (!this.data.controlled) { 92 | this.setVisibleState(popupVisible) 93 | } 94 | this.setScrollValue(undefined) 95 | this.triggerEvent('visibleChange', { visible: popupVisible }) 96 | } 97 | }, 98 | /** 99 | * 打开 100 | */ 101 | open() { 102 | this.fireVisibleChange(true) 103 | }, 104 | /** 105 | * 关闭 106 | */ 107 | close(callback) { 108 | if (typeof callback === 'function') { 109 | const values = this.getPickerValue(this.scrollValue || this.data.inputValue) 110 | callback.call(this, this.formatPickerValue(values)) 111 | } 112 | this.fireVisibleChange(false) 113 | }, 114 | /** 115 | * 组件关闭时重置其内部数据 116 | */ 117 | onClosed() { 118 | this.picker = null 119 | this.setData({ mounted: false, inputValue: null }) 120 | }, 121 | /** 122 | * 点击确定按钮时的回调函数 123 | */ 124 | onConfirm() { 125 | this.close((values) => { 126 | this.triggerEvent('change', values) // collect field component 127 | this.triggerEvent('confirm', values) 128 | }) 129 | }, 130 | /** 131 | * 点击取消按钮时的回调函数 132 | */ 133 | onCancel() { 134 | this.close((values) => this.triggerEvent('cancel', values)) 135 | }, 136 | /** 137 | * 每列数据选择变化后的回调函数 138 | */ 139 | onValueChange(e) { 140 | if (!this.data.mounted) return 141 | const { value } = e.detail 142 | if (this.data.cascade) { 143 | this.setCasecadeScrollValue(value) 144 | } else { 145 | this.setScrollValue(value) 146 | } 147 | 148 | this.updated(value, true) 149 | this.triggerEvent('valueChange', this.formatPickerValue(e.detail)) 150 | }, 151 | /** 152 | * 获取当前 picker 的值 153 | */ 154 | getPickerValue(value = this.data.inputValue) { 155 | this.picker = this.picker || this.selectComponent(selector) 156 | return this.picker && this.picker.getValue(value) 157 | }, 158 | /** 159 | * 格式化 picker 返回值 160 | */ 161 | formatPickerValue(values) { 162 | return { 163 | ...values, 164 | [platformProps.labelPropName]: platformProps.format(values, this.data), 165 | } 166 | }, 167 | /** 168 | * 获取 field 父元素 169 | */ 170 | getFieldElem() { 171 | return this.field = (this.field || this.getRelationNodes(FIELD_NAME)[0]) 172 | }, 173 | /** 174 | * 设置子元素 props 175 | */ 176 | setChildProps() { 177 | if (this.data.disabled) return 178 | const elements = this.getRelationNodes(CELL_NAME) 179 | const { trigger = DEFAULT_TRIGGER } = this.data 180 | if (elements.length > 0) { 181 | elements.forEach((inputElem) => { 182 | const { inputEvents } = inputElem.data 183 | const oriInputEvents = inputElem.data.oriInputEvents || { ...inputEvents } 184 | inputEvents[trigger] = (...args) => { 185 | if (oriInputEvents && oriInputEvents[trigger]) { 186 | oriInputEvents[trigger](...args) 187 | } 188 | this.onTriggerClick() 189 | } 190 | inputElem.setData({ oriInputEvents, inputEvents }) 191 | }) 192 | } 193 | }, 194 | /** 195 | * 触发事件 196 | */ 197 | onTriggerClick() { 198 | this.fireVisibleChange(!this.data.popupVisible) 199 | }, 200 | /** 201 | * 阻止移动触摸 202 | */ 203 | noop() {}, 204 | /** 205 | * 更新值 206 | */ 207 | updated(inputValue, isForce) { 208 | if (!this.hasFieldDecorator || isForce) { 209 | if (this.data.inputValue !== inputValue) { 210 | this.setData({ inputValue }) 211 | } 212 | } 213 | }, 214 | /** 215 | * 记录每列数据的变化值 216 | */ 217 | setScrollValue(value) { 218 | this.scrollValue = value 219 | }, 220 | /** 221 | * 记录每列数据的变化值 - 针对于级联 222 | */ 223 | setCasecadeScrollValue(value) { 224 | if (value && this.scrollValue) { 225 | const length = this.scrollValue.length 226 | if (length === value.length && this.scrollValue[length - 1] === value[length - 1]) { 227 | return 228 | } 229 | } 230 | this.setScrollValue(value) 231 | }, 232 | }, 233 | lifetimes: { 234 | ready() { 235 | const { defaultVisible, visible, controlled, value } = this.data 236 | const popupVisible = controlled ? visible : defaultVisible 237 | 238 | this.mounted = true 239 | this.scrollValue = undefined 240 | this.setVisibleState(popupVisible) 241 | this.setChildProps() 242 | }, 243 | detached() { 244 | this.mounted = false 245 | }, 246 | }, 247 | definitionFilter(defFields) { 248 | // set default child 249 | Object.assign(defFields.relations = (defFields.relations || {}), { 250 | [CELL_NAME]: { 251 | type: 'child', 252 | observer() { 253 | this.setChildProps() 254 | }, 255 | }, 256 | [FIELD_NAME]: { 257 | type: 'ancestor', 258 | }, 259 | }) 260 | 261 | // set default classes 262 | Object.assign(defFields.computed = (defFields.computed || {}), { 263 | classes: ['prefixCls', function(prefixCls) { 264 | const wrap = classNames(prefixCls) 265 | const toolbar = `${prefixCls}__toolbar` 266 | const inner = `${prefixCls}__inner` 267 | const cancel = classNames(`${prefixCls}__button`, { 268 | [`${prefixCls}__button--cancel`]: true 269 | }) 270 | const confirm = classNames(`${prefixCls}__button`, { 271 | [`${prefixCls}__button--confirm`]: true 272 | }) 273 | const hover = `${prefixCls}__button--hover` 274 | const title = `${prefixCls}__title` 275 | 276 | return { 277 | wrap, 278 | toolbar, 279 | inner, 280 | cancel, 281 | confirm, 282 | hover, 283 | title, 284 | } 285 | }], 286 | }) 287 | 288 | // set default observers 289 | Object.assign(defFields.observers = (defFields.observers || {}), { 290 | visible(popupVisible) { 291 | if (this.data.controlled) { 292 | this.setVisibleState(popupVisible) 293 | } 294 | }, 295 | value(value) { 296 | this.updated(value) 297 | }, 298 | }) 299 | }, 300 | }) 301 | } 302 | -------------------------------------------------------------------------------- /pages/ai-classifier/index.js: -------------------------------------------------------------------------------- 1 | // pages/ai-classifier/index.js 2 | 3 | const app = getApp(); 4 | 5 | const classifier = require('../../components/aiModels/classifier/classifier.js'); 6 | const fetchWechat = require('fetch-wechat'); 7 | const tf = require('@tensorflow/tfjs-core'); 8 | const cpu = require('@tensorflow/tfjs-backend-cpu'); 9 | const webgl = require('@tensorflow/tfjs-backend-webgl'); 10 | const plugin = requirePlugin('tfjsPlugin'); 11 | const util = require('../../utils/util.js') 12 | 13 | const DRAW_SIZE = [255, 255]; 14 | const DRAW_PREC = 0.03; 15 | 16 | var drawInfos = []; 17 | var startX = 0; 18 | var startY = 0; 19 | var bgColor = "white"; 20 | var begin = false; 21 | var curDrawArr = []; 22 | var lastCoord = null; // 记录画笔上一个坐标 23 | var predictStroke = []; // 用于预测的笔画偏移量数组 24 | var drawing = false; // 正在画 25 | var classifing = false; // 正在猜 26 | const promiseObj = new util.promiseContainer(); // 只响应最后一次promise 27 | 28 | Page({ 29 | /** 30 | * 页面的初始数据 31 | */ 32 | data: { 33 | levelId: 0, 34 | status: "正在努力加载模型ᕙ༼ ͝°益° ༽ᕗ", 35 | hidden: true, // 是否隐藏生成海报的canvas 36 | bgColor: bgColor, 37 | currentColor: 'black', 38 | avatarUrl: "", 39 | curWidthIndex: 0, 40 | lineWidthArr: [3, 5, 10, 20], 41 | avaliableColors: ["black", "red", "blue", "gray", "#ff4081", 42 | "#009681", "#259b24", "green", "#0000CD", "#1E90FF", "#ADD8E6", "#FAEBD7", "#FFF0F5", // orange 43 | '#FFEBA3', '#FFDE7A', '#FFCE52', '#FFBB29', '#FFA500', '#D98600', '#B36800', '#8C4D00', '#663500', 44 | // red 45 | '#FFAFA3', '#FF887A', '#FF5D52', '#FF3029', '#FF0000', '#D90007', '#B3000C', '#8C000E', '#66000E', 46 | // green 47 | '#7BB372', '#58A650', '#389931', '#1A8C16', '#008000', '#005903', '#003303', 48 | // yellow 49 | '#FFF27A', '#FFF352', '#FFF829', '#FFFF00', '#D2D900', '#A7B300', '#7E8C00', '#586600', 50 | // cyan 51 | '#A3FFF3', '#7AFFF2', '#52FFF3', '#29FFF8', '#00FFFF', '#00D2D9', '#00A7B3', '#007E8C', '#005866', 52 | // blue 53 | '#A3AFFF', '#7A88FF', '#525DFF', '#2930FF', '#0000FF', '#0700D9', '#0C00B3', '#0E008C', '#0E0066', 54 | // Violet 55 | '#FAB1F7', '#EE82EE', '#C463C7', '#9B48A1', '#73317A', '#4D2154', 56 | "black" 57 | ], 58 | classesName: ['class1', 'class2', 'class3', 'class4', 'class5'], 59 | classesProg: [0, 0, 0, 0, 0] 60 | }, 61 | 62 | /** 63 | * 生命周期函数--监听页面加载 64 | */ 65 | onLoad: function (options) { 66 | wx.showShareMenu({ 67 | withShareTicket: true 68 | }); 69 | 70 | let systemInfo = wx.getSystemInfoSync(); 71 | console.log(systemInfo.platform); 72 | if (systemInfo.platform == 'android') { 73 | plugin.configPlugin({ 74 | // polyfill fetch function 75 | fetchFunc: fetchWechat.fetchFunc(), 76 | // inject tfjs runtime 77 | tf, 78 | cpu, 79 | canvas: wx.createOffscreenCanvas() 80 | }); 81 | // setWasmPath('https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-wasm@2.0.0/wasm-out/tfjs-backend-wasm.wasm', true); 82 | // tf.setBackend('wasm').then(() => console.log('set wasm backend')); 83 | } else { 84 | plugin.configPlugin({ 85 | // polyfill fetch function 86 | fetchFunc: fetchWechat.fetchFunc(), 87 | // inject tfjs runtime 88 | tf, 89 | // inject backend 90 | webgl, 91 | // provide webgl canvas 92 | canvas: wx.createOffscreenCanvas() 93 | }); 94 | } 95 | }, 96 | 97 | /** 98 | * 生命周期函数--监听页面初次渲染完成 99 | */ 100 | onReady: function () { 101 | this.context = wx.createCanvasContext("firstCanvas"); 102 | this.init(); 103 | this.fillBackground(this.context); 104 | this.draw(); 105 | 106 | predictStroke = []; 107 | wx.showLoading({ 108 | title: '加载模型...', 109 | mask: true, 110 | }); 111 | classifier.loadModels().then((res) => { 112 | wx.hideLoading(); 113 | if (res) { 114 | this.setData({ 115 | status: '你来作画,我来猜!(⊙ᗜ⊙)' 116 | }); 117 | this.setCurrentColor(this.data.avaliableColors[Math.floor((Math.random() * this.data.avaliableColors.length))]); 118 | wx.showToast({ 119 | title: '加载完成', 120 | }) 121 | } else { 122 | this.setData({ 123 | status: '模型加载超时(´□`川)' 124 | }); 125 | wx.showModal({ 126 | title: '加载失败', 127 | content: '刷新一下?', 128 | success(res) { 129 | if (res.confirm) { 130 | wx.reLaunch({ 131 | url: '/pages/ai-classifier/index', 132 | }) 133 | } 134 | } 135 | }); 136 | } 137 | }); 138 | }, 139 | 140 | /** 141 | * 生命周期函数--监听页面显示 142 | */ 143 | onShow: function () {}, 144 | 145 | /** 146 | * 生命周期函数--监听页面隐藏 147 | */ 148 | onHide: function () { 149 | 150 | }, 151 | 152 | /** 153 | * 生命周期函数--监听页面卸载 154 | */ 155 | onUnload: function () { 156 | 157 | }, 158 | 159 | /** 160 | * 页面相关事件处理函数--监听用户下拉动作 161 | */ 162 | onPullDownRefresh: function () { 163 | 164 | }, 165 | 166 | /** 167 | * 页面上拉触底事件的处理函数 168 | */ 169 | onReachBottom: function () { 170 | 171 | }, 172 | 173 | /** 174 | * 用户点击右上角分享 175 | */ 176 | onShareAppMessage: function (res) { 177 | 178 | }, 179 | 180 | /*--------------------- UI事件 --------------------------------------------------- */ 181 | // 绘制开始 手指开始按到屏幕上 182 | touchStart: function (e) { 183 | if (classifing) { 184 | return; 185 | } 186 | drawing = true; 187 | 188 | this.lineBegin(e.touches[0].x, e.touches[0].y); 189 | this.recordPredictStroke(e.touches[0].x, e.touches[0].y); 190 | this.draw(true); 191 | curDrawArr.push({ 192 | x: e.touches[0].x, 193 | y: e.touches[0].y 194 | }); 195 | }, 196 | 197 | // 绘制中 手指在屏幕上移动 198 | touchMove: function (e) { 199 | if (classifing) { 200 | return; 201 | } 202 | drawing = true; 203 | 204 | if (begin) { 205 | this.lineAddPoint(e.touches[0].x, e.touches[0].y); 206 | this.recordPredictStroke(e.touches[0].x, e.touches[0].y); 207 | this.draw(true); 208 | curDrawArr.push({ 209 | x: e.touches[0].x, 210 | y: e.touches[0].y 211 | }); 212 | } 213 | }, 214 | 215 | // 绘制结束 手指抬起 216 | touchEnd: function () { 217 | drawing = false; 218 | if (classifing) { 219 | return; 220 | } 221 | 222 | drawInfos.push({ 223 | drawArr: curDrawArr, 224 | color: this.data.currentColor, 225 | lineWidth: this.data.lineWidthArr[this.data.curWidthIndex], 226 | }); 227 | // console.log(curDrawArr); 228 | curDrawArr = []; 229 | this.lineEnd(); 230 | predictStroke[predictStroke.length - 1][2] = 1.0; 231 | 232 | this.setData({ 233 | status: Math.random() > 0.5 ? '正在思索中[(--)]zzz' : '绞尽脑汁中[(--)]zzz', 234 | }); 235 | 236 | promiseObj.addPromise(new Promise((resolve) => setTimeout(resolve, 800)), () => { 237 | if (drawing) { 238 | console.log('Drawing, do not predict.'); 239 | return true; 240 | } 241 | classifing = true; 242 | wx.showLoading({ 243 | title: Math.random() > 0.5 ? '冥思中' : '思考中', 244 | mask: true 245 | }) 246 | classifier.classify(predictStroke, (res) => { 247 | // console.log(res); 248 | wx.hideLoading(); 249 | let probs = res.probs; 250 | for (let i = 0; i < probs.length; i++) { 251 | probs[i] = Number(probs[i] * 100).toFixed(1); 252 | } 253 | 254 | classifing = false; 255 | this.setData({ 256 | status: '我猜出来了!(⊙ᗜ⊙)' 257 | }); 258 | this.setData({ 259 | classesName: res.names, 260 | classesProg: probs 261 | }); 262 | // predictStroke = []; 263 | }, res => { 264 | wx.hideLoading(); 265 | classifing = false; 266 | this.setData({ 267 | status: '我猜不出来(╥╯^╰╥)', 268 | }); 269 | }); 270 | }); 271 | 272 | this.setCurrentColor(this.data.avaliableColors[Math.floor((Math.random() * this.data.avaliableColors.length))]); 273 | }, 274 | 275 | 276 | // 点击设置线条宽度 277 | clickChangeWidth: function (e) { 278 | let index = e.currentTarget.dataset.index; 279 | this.setLineWidthByIndex(index); 280 | }, 281 | 282 | // 点击设置线条颜色 283 | clickChangeColor: function (e) { 284 | let color = e.currentTarget.dataset.color; 285 | this.setCurrentColor(color); 286 | }, 287 | 288 | // 点击切换到擦除 289 | clickErase: function () { 290 | this.setCurrentColor(bgColor); 291 | }, 292 | 293 | // 点击清空canvas 294 | clickClearAll: function () { 295 | this.fillBackground(this.context); 296 | this.draw(); 297 | drawInfos = []; 298 | lastCoord = null; 299 | predictStroke = []; 300 | this.setData({ 301 | classesName: ['class1', 'class2', 'class3', 'class4', 'class5'], 302 | classesProg: [0, 0, 0, 0, 0] 303 | }); 304 | classifier.resetClassifier(); 305 | this.init(); 306 | }, 307 | 308 | // 点击撤销上一步 309 | clickFallback: function () { 310 | if (drawInfos.length >= 1) { 311 | drawInfos.pop(); 312 | } 313 | this.reDraw(); 314 | }, 315 | 316 | // 点击重绘canvas 317 | clickReDraw: function () { 318 | this.reDraw(); 319 | }, 320 | 321 | // 保存canvas图像到本地缓存 322 | clickStore: function () { 323 | let that = this; 324 | this.store("firstCanvas", res => { 325 | wx.saveImageToPhotosAlbum({ 326 | filePath: res, 327 | }) 328 | }); 329 | }, 330 | 331 | // 点击分享 332 | // 现在的功能是拼接一个分享出去的图像,然后保存到本地相册 333 | clickShare: function () { 334 | let that = this; 335 | // 在用户看不到的地方显示隐藏的canvas 336 | this.setData({ 337 | hidden: false 338 | }); 339 | // 截图的时候屏蔽用户操作 340 | wx.showLoading({ 341 | title: '请稍等', 342 | mask: true 343 | }) 344 | // 300毫秒后恢复正常 345 | setTimeout(() => { 346 | wx.hideLoading(); 347 | that.setData({ 348 | hidden: true 349 | }); 350 | }, 300); 351 | 352 | // 截图用户绘制的canvaas 353 | this.store("firstCanvas", filePath => { 354 | // 生成海报并分享 355 | 356 | that.sharePost(filePath); 357 | 358 | }); 359 | }, 360 | 361 | /*---------------------end of 界面绑定的函数------------------------------------------ */ 362 | 363 | // 初始化 364 | init: function () { 365 | this.context.setLineCap('round') // 让线条圆润 366 | this.context.strokeStyle = this.data.currentColor; 367 | this.context.setLineWidth(this.data.lineWidthArr[this.data.curWidthIndex]); 368 | this.setData({ 369 | currentColor: this.data.currentColor, 370 | curWidthIndex: this.data.curWidthIndex 371 | }); 372 | }, 373 | 374 | // 设置线条颜色 375 | setCurrentColor: function (color) { 376 | this.data.currentColor = color; 377 | this.context.strokeStyle = color; 378 | this.setData({ 379 | currentColor: color 380 | }); 381 | }, 382 | 383 | // 设置线条宽度 384 | setLineWidthByIndex: function (index) { 385 | let width = this.data.lineWidthArr[index]; 386 | this.context.setLineWidth(width); 387 | this.setData({ 388 | curWidthIndex: index 389 | }); 390 | }, 391 | 392 | // 开始绘制线条 393 | lineBegin: function (x, y) { 394 | begin = true; 395 | this.context.beginPath() 396 | startX = x; 397 | startY = y; 398 | this.context.moveTo(startX, startY) 399 | this.lineAddPoint(x, y); 400 | }, 401 | 402 | // 绘制线条中间添加点 403 | lineAddPoint: function (x, y) { 404 | this.context.moveTo(startX, startY) 405 | this.context.lineTo(x, y) 406 | this.context.stroke(); 407 | startX = x; 408 | startY = y; 409 | }, 410 | 411 | // 绘制线条结束 412 | lineEnd: function () { 413 | this.context.closePath(); 414 | begin = false; 415 | }, 416 | 417 | // 根据保存的绘制信息重新绘制 418 | reDraw: function () { 419 | this.init(); 420 | this.fillBackground(this.context); 421 | // this.draw(false); 422 | for (var i = 0; i < drawInfos.length; i++) { 423 | this.context.strokeStyle = drawInfos[i].color; 424 | this.context.setLineWidth(drawInfos[i].lineWidth); 425 | let drawArr = drawInfos[i].drawArr; 426 | this.lineBegin(drawArr[0].x, drawArr[0].y) 427 | for (var j = 1; j < drawArr.length; j++) { 428 | this.lineAddPoint(drawArr[j].x, drawArr[j].y); 429 | // this.draw(true); 430 | } 431 | 432 | this.lineEnd(); 433 | } 434 | 435 | this.draw(); 436 | }, 437 | 438 | // 将canvas导出为临时图像文件 439 | // canvasId: 要导出的canvas的id 440 | // cb: 回调函数 441 | store: function (canvasId, cb) { 442 | wx.canvasToTempFilePath({ 443 | destWidth: 400, 444 | destHeight: 300, 445 | canvasId: canvasId, 446 | success: function (res) { 447 | typeof (cb) == 'function' && cb(res.tempFilePath); 448 | }, 449 | fail: function (res) { 450 | console.log("store fail"); 451 | console.log(res); 452 | } 453 | }) 454 | }, 455 | 456 | // 绘制canvas 457 | // isReverse: 是否保留之前的像素 458 | draw: function (isReverse = false, cb) { 459 | this.context.draw(isReverse, () => { 460 | if (cb && typeof (cb) == "function") { 461 | cb(); 462 | } 463 | }); 464 | }, 465 | 466 | 467 | // canvas上下文设置背景为白色 468 | fillBackground: function (context) { 469 | context.setFillStyle(bgColor); 470 | context.fillRect(0, 0, 500, 500); //TODO context的宽和高待定 471 | context.fill(); 472 | }, 473 | 474 | 475 | // 预览 476 | pageView: function () { 477 | wx.canvasToTempFilePath({ 478 | canvasId: 'firstCanvas', 479 | success: function (res) { 480 | wx.previewImage({ 481 | current: res.tempFilePath, 482 | urls: [res.tempFilePath], 483 | success: function (res) { 484 | 485 | }, 486 | fail: function (res) { 487 | 488 | }, 489 | }); 490 | }, 491 | fail: function (res) { 492 | wx.showToast({ 493 | title: '保存失败', 494 | icon: 'none' 495 | }) 496 | } 497 | }, this) 498 | }, 499 | 500 | // 分享海报 501 | sharePost: function (filePath) { 502 | let url = `/pages/share/share?imgUrl=${filePath}&levelId=${this.data.levelId}`; 503 | wx.navigateTo({ 504 | url: url 505 | }) 506 | }, 507 | 508 | // 记录笔画偏移量数组,用于预测分类 509 | recordPredictStroke: function (x, y) { 510 | var posX = x; 511 | var posY = y; 512 | if (lastCoord !== null) { 513 | // Calc the delta. 514 | let xDelta = posX - lastCoord[0]; 515 | let yDelta = lastCoord[1] - posY; // Reverse the y coordinate. 516 | // Normalization. 517 | xDelta = Number((xDelta / DRAW_SIZE[0]).toFixed(10)); 518 | yDelta = Number((yDelta / DRAW_SIZE[1]).toFixed(10)); 519 | // xDelta = xDelta / DRAW_SIZE[0]; 520 | // yDelta = yDelta / DRAW_SIZE[1]; 521 | if (predictStroke.length > 0) { 522 | if (xDelta === 0.0 && predictStroke[predictStroke.length - 1][0] === 0.0) { 523 | // Merge if only move in y axis. 524 | predictStroke[predictStroke.length - 1][1] += yDelta; 525 | lastCoord = [posX, posY]; 526 | return; 527 | } 528 | if (yDelta === 0.0 && predictStroke[predictStroke.length - 1][1] === 0.0) { 529 | // Merge if only move in x axis. 530 | predictStroke[predictStroke.length - 1][0] += xDelta; 531 | lastCoord = [posX, posY]; 532 | return; 533 | } 534 | } 535 | // Ignore < DRAW_PREC. 536 | if (Math.abs(xDelta) >= DRAW_PREC || Math.abs(yDelta) >= DRAW_PREC) { 537 | predictStroke.push([xDelta, yDelta, 0.0]); 538 | lastCoord = [posX, posY]; 539 | } 540 | // console.log(predictStroke) 541 | } else { 542 | lastCoord = [posX, posY]; 543 | } 544 | } 545 | }) -------------------------------------------------------------------------------- /pages/ai-painter/index.js: -------------------------------------------------------------------------------- 1 | // pages/ai-painter/index.js 2 | 3 | const app = getApp(); 4 | 5 | const autoPainter = require('../../components/aiModels/autoPainter/autoPainter.js'); 6 | const fetchWechat = require('fetch-wechat'); 7 | const tf = require('@tensorflow/tfjs-core'); 8 | const cpu = require('@tensorflow/tfjs-backend-cpu'); 9 | const webgl = require('@tensorflow/tfjs-backend-webgl'); 10 | const plugin = requirePlugin('tfjsPlugin'); 11 | 12 | // 不同图像尺寸大小 13 | const DRAW_SIZE = [ 14 | [61.25, 61.25], 15 | [122.5, 122.5], 16 | [183.75, 183.75], 17 | [255, 255] 18 | ]; 19 | const DRAW_PREC = 0.02; 20 | 21 | var drawInfos = [] 22 | var startX = 0 23 | var startY = 0 24 | var bgColor = "white" 25 | var begin = false 26 | var curDrawArr = [] 27 | var lastCoord = null; // 记录画笔上一个坐标 28 | let beginPosition = null; // 记录后续笔画的其实坐标 29 | var predictStroke = []; // 用于预测的笔画偏移量数组 30 | var predicting = false; // 正在预测 31 | 32 | Page({ 33 | /** 34 | * 页面的初始数据 35 | */ 36 | data: { 37 | levelId: 0, 38 | status: "正在努力加载模型ᕙ༼ ͝°益° ༽ᕗ", 39 | hidden: true, // 是否隐藏生成海报的canvas 40 | bgColor: bgColor, 41 | currentColor: 'black', 42 | avatarUrl: "", 43 | curSizeIndex: 1, 44 | imgSizeArr: [1, 5, 10, 20], 45 | curWidthIndex: 0, 46 | lineWidthArr: [3, 5, 10, 20], 47 | avaliableColors: ["black", "red", "blue", "gray", "#ff4081", 48 | "#009681", "#259b24", "green", "#0000CD", "#1E90FF", "#ADD8E6", "#FAEBD7", "#FFF0F5", // orange 49 | '#FFEBA3', '#FFDE7A', '#FFCE52', '#FFBB29', '#FFA500', '#D98600', '#B36800', '#8C4D00', '#663500', 50 | // red 51 | '#FFAFA3', '#FF887A', '#FF5D52', '#FF3029', '#FF0000', '#D90007', '#B3000C', '#8C000E', '#66000E', 52 | // green 53 | '#7BB372', '#58A650', '#389931', '#1A8C16', '#008000', '#005903', '#003303', 54 | // yellow 55 | '#FFF27A', '#FFF352', '#FFF829', '#FFFF00', '#D2D900', '#A7B300', '#7E8C00', '#586600', 56 | // cyan 57 | '#A3FFF3', '#7AFFF2', '#52FFF3', '#29FFF8', '#00FFFF', '#00D2D9', '#00A7B3', '#007E8C', '#005866', 58 | // blue 59 | '#A3AFFF', '#7A88FF', '#525DFF', '#2930FF', '#0000FF', '#0700D9', '#0C00B3', '#0E008C', '#0E0066', 60 | // Violet 61 | '#FAB1F7', '#EE82EE', '#C463C7', '#9B48A1', '#73317A', '#4D2154', 62 | "black" 63 | ], 64 | avaliableModels: [{ 65 | name: 'apple', 66 | value: '苹果', 67 | checked: true 68 | }, 69 | { 70 | name: 'flower', 71 | value: '花朵', 72 | checked: false 73 | }, 74 | { 75 | name: 'butterfly', 76 | value: '蝴蝶', 77 | checked: false 78 | }, 79 | { 80 | name: 'sun', 81 | value: '太阳', 82 | checked: false 83 | }, 84 | ], 85 | }, 86 | 87 | /** 88 | * 生命周期函数--监听页面加载 89 | */ 90 | onLoad: function (options) { 91 | wx.showShareMenu({ 92 | withShareTicket: true 93 | }); 94 | 95 | plugin.configPlugin({ 96 | // polyfill fetch function 97 | fetchFunc: fetchWechat.fetchFunc(), 98 | // inject tfjs runtime 99 | tf, 100 | // inject backend 101 | webgl, 102 | // provide webgl canvas 103 | canvas: wx.createOffscreenCanvas() 104 | }); 105 | }, 106 | 107 | /** 108 | * 生命周期函数--监听页面初次渲染完成 109 | */ 110 | onReady: function () { 111 | this.context = wx.createCanvasContext("firstCanvas"); 112 | this.init(); 113 | this.fillBackground(this.context); 114 | this.draw(); 115 | 116 | predictStroke = []; 117 | wx.showLoading({ 118 | title: '加载模型...', 119 | mask: true, 120 | }); 121 | autoPainter.loadModels('apple').then((res) => { 122 | wx.hideLoading(); 123 | if (res) { 124 | this.setData({ 125 | status: '给我一个起始笔画吧,' + (Math.random() > 0.5 ? '不要太长哦!(⊙ᗜ⊙)' : '短一些会更好!(⊙ᗜ⊙)') 126 | }); 127 | this.setCurrentColor(this.data.avaliableColors[Math.floor((Math.random() * this.data.avaliableColors.length))]); 128 | wx.showToast({ 129 | title: '加载完成', 130 | }) 131 | } else { 132 | this.setData({ 133 | status: '模型加载超时(´□`川)' 134 | }); 135 | wx.showModal({ 136 | title: '加载失败', 137 | content: '选择其他模型试试吧', 138 | showCancel: false, 139 | }); 140 | } 141 | }); 142 | }, 143 | 144 | /** 145 | * 生命周期函数--监听页面显示 146 | */ 147 | onShow: function () {}, 148 | 149 | /** 150 | * 生命周期函数--监听页面隐藏 151 | */ 152 | onHide: function () { 153 | 154 | }, 155 | 156 | /** 157 | * 生命周期函数--监听页面卸载 158 | */ 159 | onUnload: function () { 160 | 161 | }, 162 | 163 | /** 164 | * 页面相关事件处理函数--监听用户下拉动作 165 | */ 166 | onPullDownRefresh: function () { 167 | 168 | }, 169 | 170 | /** 171 | * 页面上拉触底事件的处理函数 172 | */ 173 | onReachBottom: function () { 174 | 175 | }, 176 | 177 | /** 178 | * 用户点击右上角分享 179 | */ 180 | onShareAppMessage: function (res) { 181 | 182 | }, 183 | 184 | /*--------------------- UI事件 --------------------------------------------------- */ 185 | // 绘制开始 手指开始按到屏幕上 186 | touchStart: function (e) { 187 | if (predicting) { 188 | return; 189 | } 190 | 191 | this.lineBegin(e.touches[0].x, e.touches[0].y); 192 | this.recordPredictStroke(e.touches[0].x, e.touches[0].y); 193 | this.draw(true); 194 | curDrawArr.push({ 195 | x: e.touches[0].x, 196 | y: e.touches[0].y 197 | }); 198 | }, 199 | 200 | // 绘制中 手指在屏幕上移动 201 | touchMove: function (e) { 202 | if (predicting) { 203 | return; 204 | } 205 | 206 | if (begin) { 207 | this.lineAddPoint(e.touches[0].x, e.touches[0].y); 208 | this.recordPredictStroke(e.touches[0].x, e.touches[0].y); 209 | this.draw(true); 210 | curDrawArr.push({ 211 | x: e.touches[0].x, 212 | y: e.touches[0].y 213 | }); 214 | } 215 | }, 216 | 217 | // 绘制结束 手指抬起 218 | touchEnd: function () { 219 | if (predicting) { 220 | return; 221 | } 222 | 223 | drawInfos.push({ 224 | drawArr: curDrawArr, 225 | color: this.data.currentColor, 226 | lineWidth: this.data.lineWidthArr[this.data.curWidthIndex], 227 | }); 228 | 229 | // 后续笔画的起始坐标 230 | beginPosition = [curDrawArr[curDrawArr.length - 1]['x'], curDrawArr[curDrawArr.length - 1]['y']]; 231 | 232 | curDrawArr = []; 233 | this.lineEnd(); 234 | 235 | this.setData({ 236 | status: '想象中[(--)]zzz' 237 | }); 238 | 239 | if (predictStroke.length <= 1) { 240 | this.setData({ 241 | status: '笔画太少了(╥╯^╰╥)' 242 | }); 243 | return; 244 | } 245 | 246 | wx.showLoading({ 247 | title: Math.random() > 0.5 ? '想象中' : '冥思中', 248 | mask: true 249 | }) 250 | predicting = true; // 开始预测与绘制 251 | autoPainter.generate(predictStroke).then(followStroke => { 252 | wx.hideLoading(); 253 | if (followStroke) { 254 | console.log('The followStroke length: ' + followStroke.length); 255 | this.setData({ 256 | status: '绘制中(∪。∪)。。。zzz' 257 | }); 258 | this.drawFollowStroke(followStroke, beginPosition); // 绘制后续笔画 259 | predictStroke = []; 260 | lastCoord = null; 261 | } else { 262 | wx.hideLoading(); 263 | this.setData({ 264 | status: '我没想出来(╥╯^╰╥)' 265 | }); 266 | predicting = false; 267 | } 268 | }); 269 | }, 270 | 271 | // 点击图像大小 272 | clickChangeSize: function (e) { 273 | let index = e.currentTarget.dataset.index; 274 | this.setData({ 275 | curSizeIndex: index 276 | }); 277 | }, 278 | 279 | // 点击设置线条宽度 280 | clickChangeWidth: function (e) { 281 | let index = e.currentTarget.dataset.index; 282 | this.setLineWidthByIndex(index); 283 | }, 284 | 285 | // 点击设置线条颜色 286 | clickChangeColor: function (e) { 287 | let color = e.currentTarget.dataset.color; 288 | this.setCurrentColor(color); 289 | }, 290 | 291 | // 点击切换到擦除 292 | clickErase: function () { 293 | this.setCurrentColor(bgColor); 294 | }, 295 | 296 | // 点击清空canvas 297 | clickClearAll: function () { 298 | this.fillBackground(this.context); 299 | this.draw(); 300 | drawInfos = []; 301 | this.init(); 302 | predicting = false; 303 | }, 304 | 305 | // 点击撤销上一步 306 | clickFallback: function () { 307 | if (drawInfos.length >= 1) { 308 | drawInfos.pop(); 309 | } 310 | this.reDraw(); 311 | }, 312 | 313 | // 点击重绘canvas 314 | clickReDraw: function () { 315 | this.reDraw(); 316 | }, 317 | 318 | // 保存canvas图像到本地缓存 319 | clickStore: function () { 320 | let that = this; 321 | this.store("firstCanvas", res => { 322 | wx.saveImageToPhotosAlbum({ 323 | filePath: res, 324 | }) 325 | }); 326 | }, 327 | 328 | // 点击分享 329 | // 现在的功能是拼接一个分享出去的图像,然后保存到本地相册 330 | clickShare: function () { 331 | let that = this; 332 | // 在用户看不到的地方显示隐藏的canvas 333 | this.setData({ 334 | hidden: false 335 | }); 336 | // 截图的时候屏蔽用户操作 337 | wx.showLoading({ 338 | title: '请稍等', 339 | mask: true 340 | }) 341 | // 300毫秒后恢复正常 342 | setTimeout(() => { 343 | wx.hideLoading(); 344 | that.setData({ 345 | hidden: true 346 | }); 347 | }, 300); 348 | 349 | // 截图用户绘制的canvaas 350 | this.store("firstCanvas", filePath => { 351 | // 生成海报并分享 352 | 353 | that.sharePost(filePath); 354 | 355 | }); 356 | }, 357 | 358 | modelChange: async function (e) { 359 | this.setData({ 360 | status: '正在努力加载模型ᕙ༼ ͝°益° ༽ᕗ' 361 | }); 362 | 363 | console.log(e.detail.value); 364 | wx.showLoading({ 365 | title: '加载模型...', 366 | mask: true, 367 | }); 368 | await autoPainter.loadModels(e.detail.value).then((res) => { 369 | wx.hideLoading(); 370 | if (res) { 371 | this.setData({ 372 | status: '给我一个起始笔画吧,' + (Math.random() > 0.5 ? '不要太长哦!(⊙ᗜ⊙)' : '短一些会更好!(⊙ᗜ⊙)') 373 | }); 374 | this.setCurrentColor(this.data.avaliableColors[Math.floor((Math.random() * this.data.avaliableColors.length))]); 375 | wx.showToast({ 376 | title: '加载完成', 377 | }); 378 | } else { 379 | this.setData({ 380 | status: '模型加载超时(´□`川)' 381 | }); 382 | wx.showModal({ 383 | title: '加载失败', 384 | content: '选择其他模型试试吧', 385 | showCancel: false, 386 | }); 387 | } 388 | }); 389 | }, 390 | 391 | /*---------------------end of 界面绑定的函数------------------------------------------ */ 392 | 393 | // 初始化 394 | init: function () { 395 | this.context.setLineCap('round') // 让线条圆润 396 | this.context.strokeStyle = this.data.currentColor; 397 | this.context.setLineWidth(this.data.lineWidthArr[this.data.curWidthIndex]); 398 | this.setData({ 399 | currentColor: this.data.currentColor, 400 | curWidthIndex: this.data.curWidthIndex 401 | }); 402 | }, 403 | 404 | // 设置线条颜色 405 | setCurrentColor: function (color) { 406 | this.data.currentColor = color; 407 | this.context.strokeStyle = color; 408 | this.setData({ 409 | currentColor: color 410 | }); 411 | }, 412 | 413 | // 设置线条宽度 414 | setLineWidthByIndex: function (index) { 415 | let width = this.data.lineWidthArr[index]; 416 | this.context.setLineWidth(width); 417 | this.setData({ 418 | curWidthIndex: index 419 | }); 420 | }, 421 | 422 | // 开始绘制线条 423 | lineBegin: function (x, y) { 424 | begin = true; 425 | this.context.beginPath() 426 | startX = x; 427 | startY = y; 428 | this.context.moveTo(startX, startY) 429 | this.lineAddPoint(x, y); 430 | }, 431 | 432 | // 绘制线条中间添加点 433 | lineAddPoint: function (x, y) { 434 | this.context.moveTo(startX, startY) 435 | this.context.lineTo(x, y) 436 | this.context.stroke(); 437 | startX = x; 438 | startY = y; 439 | }, 440 | 441 | // 绘制线条结束 442 | lineEnd: function () { 443 | this.context.closePath(); 444 | begin = false; 445 | }, 446 | 447 | // 根据保存的绘制信息重新绘制 448 | reDraw: function () { 449 | this.init(); 450 | this.fillBackground(this.context); 451 | // this.draw(false); 452 | for (var i = 0; i < drawInfos.length; i++) { 453 | this.context.strokeStyle = drawInfos[i].color; 454 | this.context.setLineWidth(drawInfos[i].lineWidth); 455 | let drawArr = drawInfos[i].drawArr; 456 | this.lineBegin(drawArr[0].x, drawArr[0].y) 457 | for (var j = 1; j < drawArr.length; j++) { 458 | this.lineAddPoint(drawArr[j].x, drawArr[j].y); 459 | // this.draw(true); 460 | } 461 | 462 | this.lineEnd(); 463 | } 464 | 465 | this.draw(); 466 | }, 467 | 468 | // 将canvas导出为临时图像文件 469 | // canvasId: 要导出的canvas的id 470 | // cb: 回调函数 471 | store: function (canvasId, cb) { 472 | wx.canvasToTempFilePath({ 473 | destWidth: 400, 474 | destHeight: 300, 475 | canvasId: canvasId, 476 | success: function (res) { 477 | typeof (cb) == 'function' && cb(res.tempFilePath); 478 | }, 479 | fail: function (res) { 480 | console.log("store fail"); 481 | console.log(res); 482 | } 483 | }) 484 | }, 485 | 486 | // 绘制canvas 487 | // isReverse: 是否保留之前的像素 488 | draw: function (isReverse = false, cb) { 489 | this.context.draw(isReverse, () => { 490 | if (cb && typeof (cb) == "function") { 491 | cb(); 492 | } 493 | }); 494 | }, 495 | 496 | 497 | // canvas上下文设置背景为白色 498 | fillBackground: function (context) { 499 | context.setFillStyle(bgColor); 500 | context.fillRect(0, 0, 500, 500); //TODO context的宽和高待定 501 | context.fill(); 502 | }, 503 | 504 | 505 | // 预览 506 | pageView: function () { 507 | wx.canvasToTempFilePath({ 508 | canvasId: 'firstCanvas', 509 | success: function (res) { 510 | wx.previewImage({ 511 | current: res.tempFilePath, 512 | urls: [res.tempFilePath], 513 | success: function (res) { 514 | 515 | }, 516 | fail: function (res) { 517 | 518 | }, 519 | }); 520 | }, 521 | fail: function (res) { 522 | wx.showToast({ 523 | title: '保存失败', 524 | icon: 'none' 525 | }) 526 | } 527 | }, this) 528 | }, 529 | 530 | // 分享海报 531 | sharePost: function (filePath) { 532 | let url = `/pages/share/share?imgUrl=${filePath}&levelId=${this.data.levelId}`; 533 | wx.navigateTo({ 534 | url: url 535 | }) 536 | }, 537 | 538 | // 记录笔画偏移量数组,用于预测后续笔画 539 | recordPredictStroke: function (x, y) { 540 | var posX = x; 541 | var posY = y; 542 | if (lastCoord !== null) { 543 | // Calc the delta. 544 | let xDelta = posX - lastCoord[0]; 545 | let yDelta = lastCoord[1] - posY; // Reverse the y coordinate. 546 | // Normalization. 547 | xDelta = xDelta / DRAW_SIZE[this.data.curSizeIndex][0]; 548 | yDelta = yDelta / DRAW_SIZE[this.data.curSizeIndex][1]; 549 | if (predictStroke.length > 0) { 550 | if (xDelta === 0.0 && predictStroke[predictStroke.length - 1][0] === 0.0) { 551 | // Merge if only move in y axis. 552 | predictStroke[predictStroke.length - 1][1] += yDelta; 553 | lastCoord = [posX, posY]; 554 | return; 555 | } 556 | if (yDelta === 0.0 && predictStroke[predictStroke.length - 1][1] === 0.0) { 557 | // Merge if only move in x axis. 558 | predictStroke[predictStroke.length - 1][0] += xDelta; 559 | lastCoord = [posX, posY]; 560 | return; 561 | } 562 | } 563 | // Ignore < DRAW_PREC. 564 | if (Math.abs(xDelta) >= DRAW_PREC || Math.abs(yDelta) >= DRAW_PREC) { 565 | predictStroke.push([xDelta, yDelta, 0.0, 0.0]); 566 | lastCoord = [posX, posY]; 567 | } 568 | // console.log(predictStroke) 569 | } else { 570 | lastCoord = [posX, posY]; 571 | } 572 | }, 573 | 574 | // 绘制预测的后续笔画 575 | drawFollowStroke: async function (inks, beginPos) { 576 | console.log('drawFollowStroke'); 577 | // 1, generate coords by deltas. 578 | let inkCoords = [ 579 | [beginPos[0] / DRAW_SIZE[this.data.curSizeIndex][0], beginPos[1] / DRAW_SIZE[this.data.curSizeIndex][1], 0, 0] 580 | ]; 581 | for (let ink in inks) { 582 | let posX = inkCoords[ink][0] + inks[ink][0]; 583 | let posY = inkCoords[ink][1] - inks[ink][1]; 584 | let endFlag = inks[ink][2]; 585 | let completeFlag = inks[ink][3]; 586 | inkCoords.push([posX, posY, endFlag, completeFlag]); 587 | } 588 | // 2, zoom in to DRAW_SIZE scale. 589 | for (let ink in inkCoords) { 590 | inkCoords[ink][0] *= DRAW_SIZE[this.data.curSizeIndex][0]; 591 | inkCoords[ink][1] *= DRAW_SIZE[this.data.curSizeIndex][1]; 592 | } 593 | 594 | // console.log(inkCoords); 595 | // 3. Draw every stroke. 596 | let stroke = []; 597 | for (let ink in inkCoords) { 598 | // Check if is complete ink. 599 | if (inkCoords[ink][3] > 0.5) { 600 | await this.drawStroke(stroke); 601 | stroke = []; 602 | predicting = false; // 预测与绘制完毕 603 | this.setData({ 604 | status: '我画完了(⊙ᗜ⊙)' + (Math.random() > 0.5 ? '不满意的话尝试调整或缩短一下起始笔画吧' : '') 605 | }); 606 | return; 607 | } 608 | // Check if is stroke end ink. 609 | if (inkCoords[ink][2] > 0.5) { 610 | // It's the stroke end ink, draw current stroke. 611 | stroke.push([inkCoords[ink][0], inkCoords[ink][1]]); 612 | await this.drawStroke(stroke); 613 | stroke = []; 614 | } else { 615 | // It's one point in stroke, add into current stroke. 616 | stroke.push([inkCoords[ink][0], inkCoords[ink][1]]); 617 | } 618 | } 619 | if (stroke.length !== 0) { 620 | // There has been left inks. 621 | await this.drawStroke(stroke); 622 | } 623 | predicting = false; // 预测与绘制完毕 624 | this.setData({ 625 | status: '我画完了(⊙ᗜ⊙),不满意的话尝试调整或缩短一下起始笔画吧' 626 | }); 627 | }, 628 | 629 | // 绘制一笔 630 | drawStroke: async function (stroke) { 631 | // 睡眠 632 | let sleep = function (time) { 633 | let systemInfo = wx.getSystemInfoSync(); 634 | if (systemInfo.platform == 'android') { 635 | // android sleep will very slow. 636 | return; 637 | } 638 | return new Promise((resolve) => setTimeout(resolve, time)); 639 | } 640 | // 欧氏距离 641 | let eucDistance = function (x0, x1, y0, y1) { 642 | return ((x1 - x0) ** 2 + (y1 - y0) ** 2) ** 0.5; 643 | } 644 | 645 | // 随机颜色 646 | this.setCurrentColor(this.data.avaliableColors[Math.floor((Math.random() * this.data.avaliableColors.length))]); 647 | 648 | if (stroke.length === 1) { 649 | console.log("Only have one ink."); 650 | this.lineAddPoint(stroke[0][0], stroke[0][1]); 651 | this.draw(true); 652 | await sleep(10); 653 | return; 654 | } 655 | 656 | for (let ink in stroke) { 657 | if (ink == 0) { 658 | continue; 659 | } 660 | this.lineAddPoint(stroke[ink][0], stroke[ink][1]); 661 | this.draw(true); 662 | 663 | // If euclidean distance of two ink < 3, add path later and don't delay. 664 | // console.log(eucDistance(stroke[ink - 1][0], stroke[ink][0], stroke[ink - 1][1], stroke[ink][1])); 665 | if (eucDistance(stroke[ink - 1][0], stroke[ink][0], stroke[ink - 1][1], stroke[ink][1]) < 3) { 666 | console.log("If euclidean distance of two ink < 3, add path later and don't delay."); 667 | } else { 668 | await sleep(10); 669 | } 670 | } 671 | } 672 | }) -------------------------------------------------------------------------------- /miniprogram_npm/regenerator-runtime/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["runtime.js"],"names":[],"mappings":";;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"index.js","sourcesContent":["/**\n * Copyright (c) 2014-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nvar runtime = (function (exports) {\n \n\n var Op = Object.prototype;\n var hasOwn = Op.hasOwnProperty;\n var undefined; // More compressible than void 0.\n var $Symbol = typeof Symbol === \"function\" ? Symbol : {};\n var iteratorSymbol = $Symbol.iterator || \"@@iterator\";\n var asyncIteratorSymbol = $Symbol.asyncIterator || \"@@asyncIterator\";\n var toStringTagSymbol = $Symbol.toStringTag || \"@@toStringTag\";\n\n function wrap(innerFn, outerFn, self, tryLocsList) {\n // If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator.\n var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator;\n var generator = Object.create(protoGenerator.prototype);\n var context = new Context(tryLocsList || []);\n\n // The ._invoke method unifies the implementations of the .next,\n // .throw, and .return methods.\n generator._invoke = makeInvokeMethod(innerFn, self, context);\n\n return generator;\n }\n exports.wrap = wrap;\n\n // Try/catch helper to minimize deoptimizations. Returns a completion\n // record like context.tryEntries[i].completion. This interface could\n // have been (and was previously) designed to take a closure to be\n // invoked without arguments, but in all the cases we care about we\n // already have an existing method we want to call, so there's no need\n // to create a new function object. We can even get away with assuming\n // the method takes exactly one argument, since that happens to be true\n // in every case, so we don't have to touch the arguments object. The\n // only additional allocation required is the completion record, which\n // has a stable shape and so hopefully should be cheap to allocate.\n function tryCatch(fn, obj, arg) {\n try {\n return { type: \"normal\", arg: fn.call(obj, arg) };\n } catch (err) {\n return { type: \"throw\", arg: err };\n }\n }\n\n var GenStateSuspendedStart = \"suspendedStart\";\n var GenStateSuspendedYield = \"suspendedYield\";\n var GenStateExecuting = \"executing\";\n var GenStateCompleted = \"completed\";\n\n // Returning this object from the innerFn has the same effect as\n // breaking out of the dispatch switch statement.\n var ContinueSentinel = {};\n\n // Dummy constructor functions that we use as the .constructor and\n // .constructor.prototype properties for functions that return Generator\n // objects. For full spec compliance, you may wish to configure your\n // minifier not to mangle the names of these two functions.\n function Generator() {}\n function GeneratorFunction() {}\n function GeneratorFunctionPrototype() {}\n\n // This is a polyfill for %IteratorPrototype% for environments that\n // don't natively support it.\n var IteratorPrototype = {};\n IteratorPrototype[iteratorSymbol] = function () {\n return this;\n };\n\n var getProto = Object.getPrototypeOf;\n var NativeIteratorPrototype = getProto && getProto(getProto(values([])));\n if (NativeIteratorPrototype &&\n NativeIteratorPrototype !== Op &&\n hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) {\n // This environment has a native %IteratorPrototype%; use it instead\n // of the polyfill.\n IteratorPrototype = NativeIteratorPrototype;\n }\n\n var Gp = GeneratorFunctionPrototype.prototype =\n Generator.prototype = Object.create(IteratorPrototype);\n GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype;\n GeneratorFunctionPrototype.constructor = GeneratorFunction;\n GeneratorFunctionPrototype[toStringTagSymbol] =\n GeneratorFunction.displayName = \"GeneratorFunction\";\n\n // Helper for defining the .next, .throw, and .return methods of the\n // Iterator interface in terms of a single ._invoke method.\n function defineIteratorMethods(prototype) {\n [\"next\", \"throw\", \"return\"].forEach(function(method) {\n prototype[method] = function(arg) {\n return this._invoke(method, arg);\n };\n });\n }\n\n exports.isGeneratorFunction = function(genFun) {\n var ctor = typeof genFun === \"function\" && genFun.constructor;\n return ctor\n ? ctor === GeneratorFunction ||\n // For the native GeneratorFunction constructor, the best we can\n // do is to check its .name property.\n (ctor.displayName || ctor.name) === \"GeneratorFunction\"\n : false;\n };\n\n exports.mark = function(genFun) {\n if (Object.setPrototypeOf) {\n Object.setPrototypeOf(genFun, GeneratorFunctionPrototype);\n } else {\n genFun.__proto__ = GeneratorFunctionPrototype;\n if (!(toStringTagSymbol in genFun)) {\n genFun[toStringTagSymbol] = \"GeneratorFunction\";\n }\n }\n genFun.prototype = Object.create(Gp);\n return genFun;\n };\n\n // Within the body of any async function, `await x` is transformed to\n // `yield regeneratorRuntime.awrap(x)`, so that the runtime can test\n // `hasOwn.call(value, \"__await\")` to determine if the yielded value is\n // meant to be awaited.\n exports.awrap = function(arg) {\n return { __await: arg };\n };\n\n function AsyncIterator(generator, PromiseImpl) {\n function invoke(method, arg, resolve, reject) {\n var record = tryCatch(generator[method], generator, arg);\n if (record.type === \"throw\") {\n reject(record.arg);\n } else {\n var result = record.arg;\n var value = result.value;\n if (value &&\n typeof value === \"object\" &&\n hasOwn.call(value, \"__await\")) {\n return PromiseImpl.resolve(value.__await).then(function(value) {\n invoke(\"next\", value, resolve, reject);\n }, function(err) {\n invoke(\"throw\", err, resolve, reject);\n });\n }\n\n return PromiseImpl.resolve(value).then(function(unwrapped) {\n // When a yielded Promise is resolved, its final value becomes\n // the .value of the Promise<{value,done}> result for the\n // current iteration.\n result.value = unwrapped;\n resolve(result);\n }, function(error) {\n // If a rejected Promise was yielded, throw the rejection back\n // into the async generator function so it can be handled there.\n return invoke(\"throw\", error, resolve, reject);\n });\n }\n }\n\n var previousPromise;\n\n function enqueue(method, arg) {\n function callInvokeWithMethodAndArg() {\n return new PromiseImpl(function(resolve, reject) {\n invoke(method, arg, resolve, reject);\n });\n }\n\n return previousPromise =\n // If enqueue has been called before, then we want to wait until\n // all previous Promises have been resolved before calling invoke,\n // so that results are always delivered in the correct order. If\n // enqueue has not been called before, then it is important to\n // call invoke immediately, without waiting on a callback to fire,\n // so that the async generator function has the opportunity to do\n // any necessary setup in a predictable way. This predictability\n // is why the Promise constructor synchronously invokes its\n // executor callback, and why async functions synchronously\n // execute code before the first await. Since we implement simple\n // async functions in terms of async generators, it is especially\n // important to get this right, even though it requires care.\n previousPromise ? previousPromise.then(\n callInvokeWithMethodAndArg,\n // Avoid propagating failures to Promises returned by later\n // invocations of the iterator.\n callInvokeWithMethodAndArg\n ) : callInvokeWithMethodAndArg();\n }\n\n // Define the unified helper method that is used to implement .next,\n // .throw, and .return (see defineIteratorMethods).\n this._invoke = enqueue;\n }\n\n defineIteratorMethods(AsyncIterator.prototype);\n AsyncIterator.prototype[asyncIteratorSymbol] = function () {\n return this;\n };\n exports.AsyncIterator = AsyncIterator;\n\n // Note that simple async functions are implemented on top of\n // AsyncIterator objects; they just return a Promise for the value of\n // the final result produced by the iterator.\n exports.async = function(innerFn, outerFn, self, tryLocsList, PromiseImpl) {\n if (PromiseImpl === void 0) PromiseImpl = Promise;\n\n var iter = new AsyncIterator(\n wrap(innerFn, outerFn, self, tryLocsList),\n PromiseImpl\n );\n\n return exports.isGeneratorFunction(outerFn)\n ? iter // If outerFn is a generator, return the full iterator.\n : iter.next().then(function(result) {\n return result.done ? result.value : iter.next();\n });\n };\n\n function makeInvokeMethod(innerFn, self, context) {\n var state = GenStateSuspendedStart;\n\n return function invoke(method, arg) {\n if (state === GenStateExecuting) {\n throw new Error(\"Generator is already running\");\n }\n\n if (state === GenStateCompleted) {\n if (method === \"throw\") {\n throw arg;\n }\n\n // Be forgiving, per 25.3.3.3.3 of the spec:\n // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume\n return doneResult();\n }\n\n context.method = method;\n context.arg = arg;\n\n while (true) {\n var delegate = context.delegate;\n if (delegate) {\n var delegateResult = maybeInvokeDelegate(delegate, context);\n if (delegateResult) {\n if (delegateResult === ContinueSentinel) continue;\n return delegateResult;\n }\n }\n\n if (context.method === \"next\") {\n // Setting context._sent for legacy support of Babel's\n // function.sent implementation.\n context.sent = context._sent = context.arg;\n\n } else if (context.method === \"throw\") {\n if (state === GenStateSuspendedStart) {\n state = GenStateCompleted;\n throw context.arg;\n }\n\n context.dispatchException(context.arg);\n\n } else if (context.method === \"return\") {\n context.abrupt(\"return\", context.arg);\n }\n\n state = GenStateExecuting;\n\n var record = tryCatch(innerFn, self, context);\n if (record.type === \"normal\") {\n // If an exception is thrown from innerFn, we leave state ===\n // GenStateExecuting and loop back for another invocation.\n state = context.done\n ? GenStateCompleted\n : GenStateSuspendedYield;\n\n if (record.arg === ContinueSentinel) {\n continue;\n }\n\n return {\n value: record.arg,\n done: context.done\n };\n\n } else if (record.type === \"throw\") {\n state = GenStateCompleted;\n // Dispatch the exception by looping back around to the\n // context.dispatchException(context.arg) call above.\n context.method = \"throw\";\n context.arg = record.arg;\n }\n }\n };\n }\n\n // Call delegate.iterator[context.method](context.arg) and handle the\n // result, either by returning a { value, done } result from the\n // delegate iterator, or by modifying context.method and context.arg,\n // setting context.delegate to null, and returning the ContinueSentinel.\n function maybeInvokeDelegate(delegate, context) {\n var method = delegate.iterator[context.method];\n if (method === undefined) {\n // A .throw or .return when the delegate iterator has no .throw\n // method always terminates the yield* loop.\n context.delegate = null;\n\n if (context.method === \"throw\") {\n // Note: [\"return\"] must be used for ES3 parsing compatibility.\n if (delegate.iterator[\"return\"]) {\n // If the delegate iterator has a return method, give it a\n // chance to clean up.\n context.method = \"return\";\n context.arg = undefined;\n maybeInvokeDelegate(delegate, context);\n\n if (context.method === \"throw\") {\n // If maybeInvokeDelegate(context) changed context.method from\n // \"return\" to \"throw\", let that override the TypeError below.\n return ContinueSentinel;\n }\n }\n\n context.method = \"throw\";\n context.arg = new TypeError(\n \"The iterator does not provide a 'throw' method\");\n }\n\n return ContinueSentinel;\n }\n\n var record = tryCatch(method, delegate.iterator, context.arg);\n\n if (record.type === \"throw\") {\n context.method = \"throw\";\n context.arg = record.arg;\n context.delegate = null;\n return ContinueSentinel;\n }\n\n var info = record.arg;\n\n if (! info) {\n context.method = \"throw\";\n context.arg = new TypeError(\"iterator result is not an object\");\n context.delegate = null;\n return ContinueSentinel;\n }\n\n if (info.done) {\n // Assign the result of the finished delegate to the temporary\n // variable specified by delegate.resultName (see delegateYield).\n context[delegate.resultName] = info.value;\n\n // Resume execution at the desired location (see delegateYield).\n context.next = delegate.nextLoc;\n\n // If context.method was \"throw\" but the delegate handled the\n // exception, let the outer generator proceed normally. If\n // context.method was \"next\", forget context.arg since it has been\n // \"consumed\" by the delegate iterator. If context.method was\n // \"return\", allow the original .return call to continue in the\n // outer generator.\n if (context.method !== \"return\") {\n context.method = \"next\";\n context.arg = undefined;\n }\n\n } else {\n // Re-yield the result returned by the delegate method.\n return info;\n }\n\n // The delegate iterator is finished, so forget it and continue with\n // the outer generator.\n context.delegate = null;\n return ContinueSentinel;\n }\n\n // Define Generator.prototype.{next,throw,return} in terms of the\n // unified ._invoke helper method.\n defineIteratorMethods(Gp);\n\n Gp[toStringTagSymbol] = \"Generator\";\n\n // A Generator should always return itself as the iterator object when the\n // @@iterator function is called on it. Some browsers' implementations of the\n // iterator prototype chain incorrectly implement this, causing the Generator\n // object to not be returned from this call. This ensures that doesn't happen.\n // See https://github.com/facebook/regenerator/issues/274 for more details.\n Gp[iteratorSymbol] = function() {\n return this;\n };\n\n Gp.toString = function() {\n return \"[object Generator]\";\n };\n\n function pushTryEntry(locs) {\n var entry = { tryLoc: locs[0] };\n\n if (1 in locs) {\n entry.catchLoc = locs[1];\n }\n\n if (2 in locs) {\n entry.finallyLoc = locs[2];\n entry.afterLoc = locs[3];\n }\n\n this.tryEntries.push(entry);\n }\n\n function resetTryEntry(entry) {\n var record = entry.completion || {};\n record.type = \"normal\";\n delete record.arg;\n entry.completion = record;\n }\n\n function Context(tryLocsList) {\n // The root entry object (effectively a try statement without a catch\n // or a finally block) gives us a place to store values thrown from\n // locations where there is no enclosing try statement.\n this.tryEntries = [{ tryLoc: \"root\" }];\n tryLocsList.forEach(pushTryEntry, this);\n this.reset(true);\n }\n\n exports.keys = function(object) {\n var keys = [];\n for (var key in object) {\n keys.push(key);\n }\n keys.reverse();\n\n // Rather than returning an object with a next method, we keep\n // things simple and return the next function itself.\n return function next() {\n while (keys.length) {\n var key = keys.pop();\n if (key in object) {\n next.value = key;\n next.done = false;\n return next;\n }\n }\n\n // To avoid creating an additional object, we just hang the .value\n // and .done properties off the next function object itself. This\n // also ensures that the minifier will not anonymize the function.\n next.done = true;\n return next;\n };\n };\n\n function values(iterable) {\n if (iterable) {\n var iteratorMethod = iterable[iteratorSymbol];\n if (iteratorMethod) {\n return iteratorMethod.call(iterable);\n }\n\n if (typeof iterable.next === \"function\") {\n return iterable;\n }\n\n if (!isNaN(iterable.length)) {\n var i = -1, next = function next() {\n while (++i < iterable.length) {\n if (hasOwn.call(iterable, i)) {\n next.value = iterable[i];\n next.done = false;\n return next;\n }\n }\n\n next.value = undefined;\n next.done = true;\n\n return next;\n };\n\n return next.next = next;\n }\n }\n\n // Return an iterator with no values.\n return { next: doneResult };\n }\n exports.values = values;\n\n function doneResult() {\n return { value: undefined, done: true };\n }\n\n Context.prototype = {\n constructor: Context,\n\n reset: function(skipTempReset) {\n this.prev = 0;\n this.next = 0;\n // Resetting context._sent for legacy support of Babel's\n // function.sent implementation.\n this.sent = this._sent = undefined;\n this.done = false;\n this.delegate = null;\n\n this.method = \"next\";\n this.arg = undefined;\n\n this.tryEntries.forEach(resetTryEntry);\n\n if (!skipTempReset) {\n for (var name in this) {\n // Not sure about the optimal order of these conditions:\n if (name.charAt(0) === \"t\" &&\n hasOwn.call(this, name) &&\n !isNaN(+name.slice(1))) {\n this[name] = undefined;\n }\n }\n }\n },\n\n stop: function() {\n this.done = true;\n\n var rootEntry = this.tryEntries[0];\n var rootRecord = rootEntry.completion;\n if (rootRecord.type === \"throw\") {\n throw rootRecord.arg;\n }\n\n return this.rval;\n },\n\n dispatchException: function(exception) {\n if (this.done) {\n throw exception;\n }\n\n var context = this;\n function handle(loc, caught) {\n record.type = \"throw\";\n record.arg = exception;\n context.next = loc;\n\n if (caught) {\n // If the dispatched exception was caught by a catch block,\n // then let that catch block handle the exception normally.\n context.method = \"next\";\n context.arg = undefined;\n }\n\n return !! caught;\n }\n\n for (var i = this.tryEntries.length - 1; i >= 0; --i) {\n var entry = this.tryEntries[i];\n var record = entry.completion;\n\n if (entry.tryLoc === \"root\") {\n // Exception thrown outside of any try block that could handle\n // it, so set the completion value of the entire function to\n // throw the exception.\n return handle(\"end\");\n }\n\n if (entry.tryLoc <= this.prev) {\n var hasCatch = hasOwn.call(entry, \"catchLoc\");\n var hasFinally = hasOwn.call(entry, \"finallyLoc\");\n\n if (hasCatch && hasFinally) {\n if (this.prev < entry.catchLoc) {\n return handle(entry.catchLoc, true);\n } else if (this.prev < entry.finallyLoc) {\n return handle(entry.finallyLoc);\n }\n\n } else if (hasCatch) {\n if (this.prev < entry.catchLoc) {\n return handle(entry.catchLoc, true);\n }\n\n } else if (hasFinally) {\n if (this.prev < entry.finallyLoc) {\n return handle(entry.finallyLoc);\n }\n\n } else {\n throw new Error(\"try statement without catch or finally\");\n }\n }\n }\n },\n\n abrupt: function(type, arg) {\n for (var i = this.tryEntries.length - 1; i >= 0; --i) {\n var entry = this.tryEntries[i];\n if (entry.tryLoc <= this.prev &&\n hasOwn.call(entry, \"finallyLoc\") &&\n this.prev < entry.finallyLoc) {\n var finallyEntry = entry;\n break;\n }\n }\n\n if (finallyEntry &&\n (type === \"break\" ||\n type === \"continue\") &&\n finallyEntry.tryLoc <= arg &&\n arg <= finallyEntry.finallyLoc) {\n // Ignore the finally entry if control is not jumping to a\n // location outside the try/catch block.\n finallyEntry = null;\n }\n\n var record = finallyEntry ? finallyEntry.completion : {};\n record.type = type;\n record.arg = arg;\n\n if (finallyEntry) {\n this.method = \"next\";\n this.next = finallyEntry.finallyLoc;\n return ContinueSentinel;\n }\n\n return this.complete(record);\n },\n\n complete: function(record, afterLoc) {\n if (record.type === \"throw\") {\n throw record.arg;\n }\n\n if (record.type === \"break\" ||\n record.type === \"continue\") {\n this.next = record.arg;\n } else if (record.type === \"return\") {\n this.rval = this.arg = record.arg;\n this.method = \"return\";\n this.next = \"end\";\n } else if (record.type === \"normal\" && afterLoc) {\n this.next = afterLoc;\n }\n\n return ContinueSentinel;\n },\n\n finish: function(finallyLoc) {\n for (var i = this.tryEntries.length - 1; i >= 0; --i) {\n var entry = this.tryEntries[i];\n if (entry.finallyLoc === finallyLoc) {\n this.complete(entry.completion, entry.afterLoc);\n resetTryEntry(entry);\n return ContinueSentinel;\n }\n }\n },\n\n \"catch\": function(tryLoc) {\n for (var i = this.tryEntries.length - 1; i >= 0; --i) {\n var entry = this.tryEntries[i];\n if (entry.tryLoc === tryLoc) {\n var record = entry.completion;\n if (record.type === \"throw\") {\n var thrown = record.arg;\n resetTryEntry(entry);\n }\n return thrown;\n }\n }\n\n // The context.catch method must only be called with a location\n // argument that corresponds to a known catch block.\n throw new Error(\"illegal catch attempt\");\n },\n\n delegateYield: function(iterable, resultName, nextLoc) {\n this.delegate = {\n iterator: values(iterable),\n resultName: resultName,\n nextLoc: nextLoc\n };\n\n if (this.method === \"next\") {\n // Deliberately forget the last sent value so that we don't\n // accidentally pass it on to the delegate.\n this.arg = undefined;\n }\n\n return ContinueSentinel;\n }\n };\n\n // Regardless of whether this script is executing as a CommonJS module\n // or not, return the runtime object so that we can declare the variable\n // regeneratorRuntime in the outer scope, which allows this module to be\n // injected easily by `bin/regenerator --include-runtime script.js`.\n return exports;\n\n}(\n // If this script is executing as a CommonJS module, use module.exports\n // as the regeneratorRuntime namespace. Otherwise create a new empty\n // object. Either way, the resulting object will be used to initialize\n // the regeneratorRuntime variable at the top of this file.\n typeof module === \"object\" ? module.exports : {}\n));\n\ntry {\n regeneratorRuntime = runtime;\n} catch (accidentalStrictMode) {\n // This module should not be running in strict mode, so the above\n // assignment should always work unless something is misconfigured. Just\n // in case runtime.js accidentally runs in strict mode, we can escape\n // strict mode using a global Function call. This could conceivably fail\n // if a Content Security Policy forbids using Function, but in that case\n // the proper solution is to fix the accidental strict mode problem. If\n // you've misconfigured your bundler to force strict mode and applied a\n // CSP to forbid Function, and you're not willing to fix either of those\n // problems, please detail your unique predicament in a GitHub issue.\n Function(\"r\", \"regeneratorRuntime = r\")(runtime);\n}\n"]} -------------------------------------------------------------------------------- /miniprogram_npm/regenerator-runtime/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (function() { 2 | var __MODS__ = {}; 3 | var __DEFINE__ = function(modId, func, req) { var m = { exports: {}, _tempexports: {} }; __MODS__[modId] = { status: 0, func: func, req: req, m: m }; }; 4 | var __REQUIRE__ = function(modId, source) { if(!__MODS__[modId]) return require(source); if(!__MODS__[modId].status) { var m = __MODS__[modId].m; m._exports = m._tempexports; var desp = Object.getOwnPropertyDescriptor(m, "exports"); if (desp && desp.configurable) Object.defineProperty(m, "exports", { set: function (val) { if(typeof val === "object" && val !== m._exports) { m._exports.__proto__ = val.__proto__; Object.keys(val).forEach(function (k) { m._exports[k] = val[k]; }); } m._tempexports = val }, get: function () { return m._tempexports; } }); __MODS__[modId].status = 1; __MODS__[modId].func(__MODS__[modId].req, m, m.exports); } return __MODS__[modId].m.exports; }; 5 | var __REQUIRE_WILDCARD__ = function(obj) { if(obj && obj.__esModule) { return obj; } else { var newObj = {}; if(obj != null) { for(var k in obj) { if (Object.prototype.hasOwnProperty.call(obj, k)) newObj[k] = obj[k]; } } newObj.default = obj; return newObj; } }; 6 | var __REQUIRE_DEFAULT__ = function(obj) { return obj && obj.__esModule ? obj.default : obj; }; 7 | __DEFINE__(1593391889375, function(require, module, exports) { 8 | /** 9 | * Copyright (c) 2014-present, Facebook, Inc. 10 | * 11 | * This source code is licensed under the MIT license found in the 12 | * LICENSE file in the root directory of this source tree. 13 | */ 14 | 15 | var runtime = (function (exports) { 16 | 17 | 18 | var Op = Object.prototype; 19 | var hasOwn = Op.hasOwnProperty; 20 | var undefined; // More compressible than void 0. 21 | var $Symbol = typeof Symbol === "function" ? Symbol : {}; 22 | var iteratorSymbol = $Symbol.iterator || "@@iterator"; 23 | var asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator"; 24 | var toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag"; 25 | 26 | function wrap(innerFn, outerFn, self, tryLocsList) { 27 | // If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator. 28 | var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator; 29 | var generator = Object.create(protoGenerator.prototype); 30 | var context = new Context(tryLocsList || []); 31 | 32 | // The ._invoke method unifies the implementations of the .next, 33 | // .throw, and .return methods. 34 | generator._invoke = makeInvokeMethod(innerFn, self, context); 35 | 36 | return generator; 37 | } 38 | exports.wrap = wrap; 39 | 40 | // Try/catch helper to minimize deoptimizations. Returns a completion 41 | // record like context.tryEntries[i].completion. This interface could 42 | // have been (and was previously) designed to take a closure to be 43 | // invoked without arguments, but in all the cases we care about we 44 | // already have an existing method we want to call, so there's no need 45 | // to create a new function object. We can even get away with assuming 46 | // the method takes exactly one argument, since that happens to be true 47 | // in every case, so we don't have to touch the arguments object. The 48 | // only additional allocation required is the completion record, which 49 | // has a stable shape and so hopefully should be cheap to allocate. 50 | function tryCatch(fn, obj, arg) { 51 | try { 52 | return { type: "normal", arg: fn.call(obj, arg) }; 53 | } catch (err) { 54 | return { type: "throw", arg: err }; 55 | } 56 | } 57 | 58 | var GenStateSuspendedStart = "suspendedStart"; 59 | var GenStateSuspendedYield = "suspendedYield"; 60 | var GenStateExecuting = "executing"; 61 | var GenStateCompleted = "completed"; 62 | 63 | // Returning this object from the innerFn has the same effect as 64 | // breaking out of the dispatch switch statement. 65 | var ContinueSentinel = {}; 66 | 67 | // Dummy constructor functions that we use as the .constructor and 68 | // .constructor.prototype properties for functions that return Generator 69 | // objects. For full spec compliance, you may wish to configure your 70 | // minifier not to mangle the names of these two functions. 71 | function Generator() {} 72 | function GeneratorFunction() {} 73 | function GeneratorFunctionPrototype() {} 74 | 75 | // This is a polyfill for %IteratorPrototype% for environments that 76 | // don't natively support it. 77 | var IteratorPrototype = {}; 78 | IteratorPrototype[iteratorSymbol] = function () { 79 | return this; 80 | }; 81 | 82 | var getProto = Object.getPrototypeOf; 83 | var NativeIteratorPrototype = getProto && getProto(getProto(values([]))); 84 | if (NativeIteratorPrototype && 85 | NativeIteratorPrototype !== Op && 86 | hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) { 87 | // This environment has a native %IteratorPrototype%; use it instead 88 | // of the polyfill. 89 | IteratorPrototype = NativeIteratorPrototype; 90 | } 91 | 92 | var Gp = GeneratorFunctionPrototype.prototype = 93 | Generator.prototype = Object.create(IteratorPrototype); 94 | GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype; 95 | GeneratorFunctionPrototype.constructor = GeneratorFunction; 96 | GeneratorFunctionPrototype[toStringTagSymbol] = 97 | GeneratorFunction.displayName = "GeneratorFunction"; 98 | 99 | // Helper for defining the .next, .throw, and .return methods of the 100 | // Iterator interface in terms of a single ._invoke method. 101 | function defineIteratorMethods(prototype) { 102 | ["next", "throw", "return"].forEach(function(method) { 103 | prototype[method] = function(arg) { 104 | return this._invoke(method, arg); 105 | }; 106 | }); 107 | } 108 | 109 | exports.isGeneratorFunction = function(genFun) { 110 | var ctor = typeof genFun === "function" && genFun.constructor; 111 | return ctor 112 | ? ctor === GeneratorFunction || 113 | // For the native GeneratorFunction constructor, the best we can 114 | // do is to check its .name property. 115 | (ctor.displayName || ctor.name) === "GeneratorFunction" 116 | : false; 117 | }; 118 | 119 | exports.mark = function(genFun) { 120 | if (Object.setPrototypeOf) { 121 | Object.setPrototypeOf(genFun, GeneratorFunctionPrototype); 122 | } else { 123 | genFun.__proto__ = GeneratorFunctionPrototype; 124 | if (!(toStringTagSymbol in genFun)) { 125 | genFun[toStringTagSymbol] = "GeneratorFunction"; 126 | } 127 | } 128 | genFun.prototype = Object.create(Gp); 129 | return genFun; 130 | }; 131 | 132 | // Within the body of any async function, `await x` is transformed to 133 | // `yield regeneratorRuntime.awrap(x)`, so that the runtime can test 134 | // `hasOwn.call(value, "__await")` to determine if the yielded value is 135 | // meant to be awaited. 136 | exports.awrap = function(arg) { 137 | return { __await: arg }; 138 | }; 139 | 140 | function AsyncIterator(generator, PromiseImpl) { 141 | function invoke(method, arg, resolve, reject) { 142 | var record = tryCatch(generator[method], generator, arg); 143 | if (record.type === "throw") { 144 | reject(record.arg); 145 | } else { 146 | var result = record.arg; 147 | var value = result.value; 148 | if (value && 149 | typeof value === "object" && 150 | hasOwn.call(value, "__await")) { 151 | return PromiseImpl.resolve(value.__await).then(function(value) { 152 | invoke("next", value, resolve, reject); 153 | }, function(err) { 154 | invoke("throw", err, resolve, reject); 155 | }); 156 | } 157 | 158 | return PromiseImpl.resolve(value).then(function(unwrapped) { 159 | // When a yielded Promise is resolved, its final value becomes 160 | // the .value of the Promise<{value,done}> result for the 161 | // current iteration. 162 | result.value = unwrapped; 163 | resolve(result); 164 | }, function(error) { 165 | // If a rejected Promise was yielded, throw the rejection back 166 | // into the async generator function so it can be handled there. 167 | return invoke("throw", error, resolve, reject); 168 | }); 169 | } 170 | } 171 | 172 | var previousPromise; 173 | 174 | function enqueue(method, arg) { 175 | function callInvokeWithMethodAndArg() { 176 | return new PromiseImpl(function(resolve, reject) { 177 | invoke(method, arg, resolve, reject); 178 | }); 179 | } 180 | 181 | return previousPromise = 182 | // If enqueue has been called before, then we want to wait until 183 | // all previous Promises have been resolved before calling invoke, 184 | // so that results are always delivered in the correct order. If 185 | // enqueue has not been called before, then it is important to 186 | // call invoke immediately, without waiting on a callback to fire, 187 | // so that the async generator function has the opportunity to do 188 | // any necessary setup in a predictable way. This predictability 189 | // is why the Promise constructor synchronously invokes its 190 | // executor callback, and why async functions synchronously 191 | // execute code before the first await. Since we implement simple 192 | // async functions in terms of async generators, it is especially 193 | // important to get this right, even though it requires care. 194 | previousPromise ? previousPromise.then( 195 | callInvokeWithMethodAndArg, 196 | // Avoid propagating failures to Promises returned by later 197 | // invocations of the iterator. 198 | callInvokeWithMethodAndArg 199 | ) : callInvokeWithMethodAndArg(); 200 | } 201 | 202 | // Define the unified helper method that is used to implement .next, 203 | // .throw, and .return (see defineIteratorMethods). 204 | this._invoke = enqueue; 205 | } 206 | 207 | defineIteratorMethods(AsyncIterator.prototype); 208 | AsyncIterator.prototype[asyncIteratorSymbol] = function () { 209 | return this; 210 | }; 211 | exports.AsyncIterator = AsyncIterator; 212 | 213 | // Note that simple async functions are implemented on top of 214 | // AsyncIterator objects; they just return a Promise for the value of 215 | // the final result produced by the iterator. 216 | exports.async = function(innerFn, outerFn, self, tryLocsList, PromiseImpl) { 217 | if (PromiseImpl === void 0) PromiseImpl = Promise; 218 | 219 | var iter = new AsyncIterator( 220 | wrap(innerFn, outerFn, self, tryLocsList), 221 | PromiseImpl 222 | ); 223 | 224 | return exports.isGeneratorFunction(outerFn) 225 | ? iter // If outerFn is a generator, return the full iterator. 226 | : iter.next().then(function(result) { 227 | return result.done ? result.value : iter.next(); 228 | }); 229 | }; 230 | 231 | function makeInvokeMethod(innerFn, self, context) { 232 | var state = GenStateSuspendedStart; 233 | 234 | return function invoke(method, arg) { 235 | if (state === GenStateExecuting) { 236 | throw new Error("Generator is already running"); 237 | } 238 | 239 | if (state === GenStateCompleted) { 240 | if (method === "throw") { 241 | throw arg; 242 | } 243 | 244 | // Be forgiving, per 25.3.3.3.3 of the spec: 245 | // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume 246 | return doneResult(); 247 | } 248 | 249 | context.method = method; 250 | context.arg = arg; 251 | 252 | while (true) { 253 | var delegate = context.delegate; 254 | if (delegate) { 255 | var delegateResult = maybeInvokeDelegate(delegate, context); 256 | if (delegateResult) { 257 | if (delegateResult === ContinueSentinel) continue; 258 | return delegateResult; 259 | } 260 | } 261 | 262 | if (context.method === "next") { 263 | // Setting context._sent for legacy support of Babel's 264 | // function.sent implementation. 265 | context.sent = context._sent = context.arg; 266 | 267 | } else if (context.method === "throw") { 268 | if (state === GenStateSuspendedStart) { 269 | state = GenStateCompleted; 270 | throw context.arg; 271 | } 272 | 273 | context.dispatchException(context.arg); 274 | 275 | } else if (context.method === "return") { 276 | context.abrupt("return", context.arg); 277 | } 278 | 279 | state = GenStateExecuting; 280 | 281 | var record = tryCatch(innerFn, self, context); 282 | if (record.type === "normal") { 283 | // If an exception is thrown from innerFn, we leave state === 284 | // GenStateExecuting and loop back for another invocation. 285 | state = context.done 286 | ? GenStateCompleted 287 | : GenStateSuspendedYield; 288 | 289 | if (record.arg === ContinueSentinel) { 290 | continue; 291 | } 292 | 293 | return { 294 | value: record.arg, 295 | done: context.done 296 | }; 297 | 298 | } else if (record.type === "throw") { 299 | state = GenStateCompleted; 300 | // Dispatch the exception by looping back around to the 301 | // context.dispatchException(context.arg) call above. 302 | context.method = "throw"; 303 | context.arg = record.arg; 304 | } 305 | } 306 | }; 307 | } 308 | 309 | // Call delegate.iterator[context.method](context.arg) and handle the 310 | // result, either by returning a { value, done } result from the 311 | // delegate iterator, or by modifying context.method and context.arg, 312 | // setting context.delegate to null, and returning the ContinueSentinel. 313 | function maybeInvokeDelegate(delegate, context) { 314 | var method = delegate.iterator[context.method]; 315 | if (method === undefined) { 316 | // A .throw or .return when the delegate iterator has no .throw 317 | // method always terminates the yield* loop. 318 | context.delegate = null; 319 | 320 | if (context.method === "throw") { 321 | // Note: ["return"] must be used for ES3 parsing compatibility. 322 | if (delegate.iterator["return"]) { 323 | // If the delegate iterator has a return method, give it a 324 | // chance to clean up. 325 | context.method = "return"; 326 | context.arg = undefined; 327 | maybeInvokeDelegate(delegate, context); 328 | 329 | if (context.method === "throw") { 330 | // If maybeInvokeDelegate(context) changed context.method from 331 | // "return" to "throw", let that override the TypeError below. 332 | return ContinueSentinel; 333 | } 334 | } 335 | 336 | context.method = "throw"; 337 | context.arg = new TypeError( 338 | "The iterator does not provide a 'throw' method"); 339 | } 340 | 341 | return ContinueSentinel; 342 | } 343 | 344 | var record = tryCatch(method, delegate.iterator, context.arg); 345 | 346 | if (record.type === "throw") { 347 | context.method = "throw"; 348 | context.arg = record.arg; 349 | context.delegate = null; 350 | return ContinueSentinel; 351 | } 352 | 353 | var info = record.arg; 354 | 355 | if (! info) { 356 | context.method = "throw"; 357 | context.arg = new TypeError("iterator result is not an object"); 358 | context.delegate = null; 359 | return ContinueSentinel; 360 | } 361 | 362 | if (info.done) { 363 | // Assign the result of the finished delegate to the temporary 364 | // variable specified by delegate.resultName (see delegateYield). 365 | context[delegate.resultName] = info.value; 366 | 367 | // Resume execution at the desired location (see delegateYield). 368 | context.next = delegate.nextLoc; 369 | 370 | // If context.method was "throw" but the delegate handled the 371 | // exception, let the outer generator proceed normally. If 372 | // context.method was "next", forget context.arg since it has been 373 | // "consumed" by the delegate iterator. If context.method was 374 | // "return", allow the original .return call to continue in the 375 | // outer generator. 376 | if (context.method !== "return") { 377 | context.method = "next"; 378 | context.arg = undefined; 379 | } 380 | 381 | } else { 382 | // Re-yield the result returned by the delegate method. 383 | return info; 384 | } 385 | 386 | // The delegate iterator is finished, so forget it and continue with 387 | // the outer generator. 388 | context.delegate = null; 389 | return ContinueSentinel; 390 | } 391 | 392 | // Define Generator.prototype.{next,throw,return} in terms of the 393 | // unified ._invoke helper method. 394 | defineIteratorMethods(Gp); 395 | 396 | Gp[toStringTagSymbol] = "Generator"; 397 | 398 | // A Generator should always return itself as the iterator object when the 399 | // @@iterator function is called on it. Some browsers' implementations of the 400 | // iterator prototype chain incorrectly implement this, causing the Generator 401 | // object to not be returned from this call. This ensures that doesn't happen. 402 | // See https://github.com/facebook/regenerator/issues/274 for more details. 403 | Gp[iteratorSymbol] = function() { 404 | return this; 405 | }; 406 | 407 | Gp.toString = function() { 408 | return "[object Generator]"; 409 | }; 410 | 411 | function pushTryEntry(locs) { 412 | var entry = { tryLoc: locs[0] }; 413 | 414 | if (1 in locs) { 415 | entry.catchLoc = locs[1]; 416 | } 417 | 418 | if (2 in locs) { 419 | entry.finallyLoc = locs[2]; 420 | entry.afterLoc = locs[3]; 421 | } 422 | 423 | this.tryEntries.push(entry); 424 | } 425 | 426 | function resetTryEntry(entry) { 427 | var record = entry.completion || {}; 428 | record.type = "normal"; 429 | delete record.arg; 430 | entry.completion = record; 431 | } 432 | 433 | function Context(tryLocsList) { 434 | // The root entry object (effectively a try statement without a catch 435 | // or a finally block) gives us a place to store values thrown from 436 | // locations where there is no enclosing try statement. 437 | this.tryEntries = [{ tryLoc: "root" }]; 438 | tryLocsList.forEach(pushTryEntry, this); 439 | this.reset(true); 440 | } 441 | 442 | exports.keys = function(object) { 443 | var keys = []; 444 | for (var key in object) { 445 | keys.push(key); 446 | } 447 | keys.reverse(); 448 | 449 | // Rather than returning an object with a next method, we keep 450 | // things simple and return the next function itself. 451 | return function next() { 452 | while (keys.length) { 453 | var key = keys.pop(); 454 | if (key in object) { 455 | next.value = key; 456 | next.done = false; 457 | return next; 458 | } 459 | } 460 | 461 | // To avoid creating an additional object, we just hang the .value 462 | // and .done properties off the next function object itself. This 463 | // also ensures that the minifier will not anonymize the function. 464 | next.done = true; 465 | return next; 466 | }; 467 | }; 468 | 469 | function values(iterable) { 470 | if (iterable) { 471 | var iteratorMethod = iterable[iteratorSymbol]; 472 | if (iteratorMethod) { 473 | return iteratorMethod.call(iterable); 474 | } 475 | 476 | if (typeof iterable.next === "function") { 477 | return iterable; 478 | } 479 | 480 | if (!isNaN(iterable.length)) { 481 | var i = -1, next = function next() { 482 | while (++i < iterable.length) { 483 | if (hasOwn.call(iterable, i)) { 484 | next.value = iterable[i]; 485 | next.done = false; 486 | return next; 487 | } 488 | } 489 | 490 | next.value = undefined; 491 | next.done = true; 492 | 493 | return next; 494 | }; 495 | 496 | return next.next = next; 497 | } 498 | } 499 | 500 | // Return an iterator with no values. 501 | return { next: doneResult }; 502 | } 503 | exports.values = values; 504 | 505 | function doneResult() { 506 | return { value: undefined, done: true }; 507 | } 508 | 509 | Context.prototype = { 510 | constructor: Context, 511 | 512 | reset: function(skipTempReset) { 513 | this.prev = 0; 514 | this.next = 0; 515 | // Resetting context._sent for legacy support of Babel's 516 | // function.sent implementation. 517 | this.sent = this._sent = undefined; 518 | this.done = false; 519 | this.delegate = null; 520 | 521 | this.method = "next"; 522 | this.arg = undefined; 523 | 524 | this.tryEntries.forEach(resetTryEntry); 525 | 526 | if (!skipTempReset) { 527 | for (var name in this) { 528 | // Not sure about the optimal order of these conditions: 529 | if (name.charAt(0) === "t" && 530 | hasOwn.call(this, name) && 531 | !isNaN(+name.slice(1))) { 532 | this[name] = undefined; 533 | } 534 | } 535 | } 536 | }, 537 | 538 | stop: function() { 539 | this.done = true; 540 | 541 | var rootEntry = this.tryEntries[0]; 542 | var rootRecord = rootEntry.completion; 543 | if (rootRecord.type === "throw") { 544 | throw rootRecord.arg; 545 | } 546 | 547 | return this.rval; 548 | }, 549 | 550 | dispatchException: function(exception) { 551 | if (this.done) { 552 | throw exception; 553 | } 554 | 555 | var context = this; 556 | function handle(loc, caught) { 557 | record.type = "throw"; 558 | record.arg = exception; 559 | context.next = loc; 560 | 561 | if (caught) { 562 | // If the dispatched exception was caught by a catch block, 563 | // then let that catch block handle the exception normally. 564 | context.method = "next"; 565 | context.arg = undefined; 566 | } 567 | 568 | return !! caught; 569 | } 570 | 571 | for (var i = this.tryEntries.length - 1; i >= 0; --i) { 572 | var entry = this.tryEntries[i]; 573 | var record = entry.completion; 574 | 575 | if (entry.tryLoc === "root") { 576 | // Exception thrown outside of any try block that could handle 577 | // it, so set the completion value of the entire function to 578 | // throw the exception. 579 | return handle("end"); 580 | } 581 | 582 | if (entry.tryLoc <= this.prev) { 583 | var hasCatch = hasOwn.call(entry, "catchLoc"); 584 | var hasFinally = hasOwn.call(entry, "finallyLoc"); 585 | 586 | if (hasCatch && hasFinally) { 587 | if (this.prev < entry.catchLoc) { 588 | return handle(entry.catchLoc, true); 589 | } else if (this.prev < entry.finallyLoc) { 590 | return handle(entry.finallyLoc); 591 | } 592 | 593 | } else if (hasCatch) { 594 | if (this.prev < entry.catchLoc) { 595 | return handle(entry.catchLoc, true); 596 | } 597 | 598 | } else if (hasFinally) { 599 | if (this.prev < entry.finallyLoc) { 600 | return handle(entry.finallyLoc); 601 | } 602 | 603 | } else { 604 | throw new Error("try statement without catch or finally"); 605 | } 606 | } 607 | } 608 | }, 609 | 610 | abrupt: function(type, arg) { 611 | for (var i = this.tryEntries.length - 1; i >= 0; --i) { 612 | var entry = this.tryEntries[i]; 613 | if (entry.tryLoc <= this.prev && 614 | hasOwn.call(entry, "finallyLoc") && 615 | this.prev < entry.finallyLoc) { 616 | var finallyEntry = entry; 617 | break; 618 | } 619 | } 620 | 621 | if (finallyEntry && 622 | (type === "break" || 623 | type === "continue") && 624 | finallyEntry.tryLoc <= arg && 625 | arg <= finallyEntry.finallyLoc) { 626 | // Ignore the finally entry if control is not jumping to a 627 | // location outside the try/catch block. 628 | finallyEntry = null; 629 | } 630 | 631 | var record = finallyEntry ? finallyEntry.completion : {}; 632 | record.type = type; 633 | record.arg = arg; 634 | 635 | if (finallyEntry) { 636 | this.method = "next"; 637 | this.next = finallyEntry.finallyLoc; 638 | return ContinueSentinel; 639 | } 640 | 641 | return this.complete(record); 642 | }, 643 | 644 | complete: function(record, afterLoc) { 645 | if (record.type === "throw") { 646 | throw record.arg; 647 | } 648 | 649 | if (record.type === "break" || 650 | record.type === "continue") { 651 | this.next = record.arg; 652 | } else if (record.type === "return") { 653 | this.rval = this.arg = record.arg; 654 | this.method = "return"; 655 | this.next = "end"; 656 | } else if (record.type === "normal" && afterLoc) { 657 | this.next = afterLoc; 658 | } 659 | 660 | return ContinueSentinel; 661 | }, 662 | 663 | finish: function(finallyLoc) { 664 | for (var i = this.tryEntries.length - 1; i >= 0; --i) { 665 | var entry = this.tryEntries[i]; 666 | if (entry.finallyLoc === finallyLoc) { 667 | this.complete(entry.completion, entry.afterLoc); 668 | resetTryEntry(entry); 669 | return ContinueSentinel; 670 | } 671 | } 672 | }, 673 | 674 | "catch": function(tryLoc) { 675 | for (var i = this.tryEntries.length - 1; i >= 0; --i) { 676 | var entry = this.tryEntries[i]; 677 | if (entry.tryLoc === tryLoc) { 678 | var record = entry.completion; 679 | if (record.type === "throw") { 680 | var thrown = record.arg; 681 | resetTryEntry(entry); 682 | } 683 | return thrown; 684 | } 685 | } 686 | 687 | // The context.catch method must only be called with a location 688 | // argument that corresponds to a known catch block. 689 | throw new Error("illegal catch attempt"); 690 | }, 691 | 692 | delegateYield: function(iterable, resultName, nextLoc) { 693 | this.delegate = { 694 | iterator: values(iterable), 695 | resultName: resultName, 696 | nextLoc: nextLoc 697 | }; 698 | 699 | if (this.method === "next") { 700 | // Deliberately forget the last sent value so that we don't 701 | // accidentally pass it on to the delegate. 702 | this.arg = undefined; 703 | } 704 | 705 | return ContinueSentinel; 706 | } 707 | }; 708 | 709 | // Regardless of whether this script is executing as a CommonJS module 710 | // or not, return the runtime object so that we can declare the variable 711 | // regeneratorRuntime in the outer scope, which allows this module to be 712 | // injected easily by `bin/regenerator --include-runtime script.js`. 713 | return exports; 714 | 715 | }( 716 | // If this script is executing as a CommonJS module, use module.exports 717 | // as the regeneratorRuntime namespace. Otherwise create a new empty 718 | // object. Either way, the resulting object will be used to initialize 719 | // the regeneratorRuntime variable at the top of this file. 720 | typeof module === "object" ? module.exports : {} 721 | )); 722 | 723 | try { 724 | regeneratorRuntime = runtime; 725 | } catch (accidentalStrictMode) { 726 | // This module should not be running in strict mode, so the above 727 | // assignment should always work unless something is misconfigured. Just 728 | // in case runtime.js accidentally runs in strict mode, we can escape 729 | // strict mode using a global Function call. This could conceivably fail 730 | // if a Content Security Policy forbids using Function, but in that case 731 | // the proper solution is to fix the accidental strict mode problem. If 732 | // you've misconfigured your bundler to force strict mode and applied a 733 | // CSP to forbid Function, and you're not willing to fix either of those 734 | // problems, please detail your unique predicament in a GitHub issue. 735 | Function("r", "regeneratorRuntime = r")(runtime); 736 | } 737 | 738 | }, function(modId) {var map = {}; return __REQUIRE__(map[modId], modId); }) 739 | return __REQUIRE__(1593391889375); 740 | })() 741 | //# sourceMappingURL=index.js.map --------------------------------------------------------------------------------