├── .browserslistrc ├── babel.config.js ├── work ├── sketch.png ├── mindnode.png ├── artworks │ ├── 00.png │ ├── 01.png │ ├── 03.png │ ├── 04.png │ └── 05.png ├── pixel-paint.pxcp ├── pixel-paint.sketch ├── pixel-paint.mindnode │ ├── contents.xml │ ├── viewState.plist │ ├── QuickLook │ │ └── Preview.jpg │ └── style.mindnodestyle │ │ ├── metadata.plist │ │ └── contents.xml └── convert.js ├── public ├── favicon.ico └── index.html ├── src ├── assets │ ├── logo.png │ ├── BPdotsSquareBold.otf │ └── icon-set.js ├── views │ ├── About.vue │ ├── Home.vue │ ├── artwork.vue │ └── edit.vue ├── App.vue ├── store │ ├── getter.js │ ├── actions.js │ ├── index.js │ ├── state.js │ └── mutations.js ├── components │ ├── common │ │ ├── alert │ │ │ ├── alert.js │ │ │ ├── instance.js │ │ │ └── alert.vue │ │ ├── prompt.vue │ │ └── confirm.vue │ ├── edit │ │ ├── cursor.vue │ │ ├── grid-highlight.vue │ │ ├── grid.vue │ │ ├── rename.vue │ │ ├── push.vue │ │ ├── brush-list.vue │ │ ├── touch.vue │ │ ├── header-bar.vue │ │ ├── sidebar.vue │ │ └── draw.vue │ ├── artwork │ │ ├── add-new.vue │ │ ├── canvas-item.vue │ │ └── new-canvas.vue │ └── HelloWorld.vue ├── svg │ ├── clear.svg │ ├── share.svg │ ├── export.svg │ ├── rename.svg │ ├── pencil-add.svg │ ├── undo.svg │ ├── brush.svg │ ├── redo.svg │ ├── move.svg │ ├── push.svg │ ├── palette.svg │ ├── cursor.svg │ ├── fill.svg │ ├── grid.svg │ └── buttonDraw.svg ├── js │ ├── uuid.js │ ├── cache.js │ ├── storage.js │ └── jsonc.js ├── main.js ├── style │ ├── global.scss │ └── variable.scss └── router.js ├── .editorconfig ├── .gitignore ├── .eslintrc.js ├── postcss.config.js ├── vue.config.js ├── .travis.yml ├── README.md ├── package.json └── LICENSE /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /work/sketch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/journey-ad/pixel-paint/HEAD/work/sketch.png -------------------------------------------------------------------------------- /work/mindnode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/journey-ad/pixel-paint/HEAD/work/mindnode.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/journey-ad/pixel-paint/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/journey-ad/pixel-paint/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /work/artworks/00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/journey-ad/pixel-paint/HEAD/work/artworks/00.png -------------------------------------------------------------------------------- /work/artworks/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/journey-ad/pixel-paint/HEAD/work/artworks/01.png -------------------------------------------------------------------------------- /work/artworks/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/journey-ad/pixel-paint/HEAD/work/artworks/03.png -------------------------------------------------------------------------------- /work/artworks/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/journey-ad/pixel-paint/HEAD/work/artworks/04.png -------------------------------------------------------------------------------- /work/artworks/05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/journey-ad/pixel-paint/HEAD/work/artworks/05.png -------------------------------------------------------------------------------- /work/pixel-paint.pxcp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/journey-ad/pixel-paint/HEAD/work/pixel-paint.pxcp -------------------------------------------------------------------------------- /work/pixel-paint.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/journey-ad/pixel-paint/HEAD/work/pixel-paint.sketch -------------------------------------------------------------------------------- /src/assets/BPdotsSquareBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/journey-ad/pixel-paint/HEAD/src/assets/BPdotsSquareBold.otf -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /work/pixel-paint.mindnode/contents.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/journey-ad/pixel-paint/HEAD/work/pixel-paint.mindnode/contents.xml -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /work/pixel-paint.mindnode/viewState.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/journey-ad/pixel-paint/HEAD/work/pixel-paint.mindnode/viewState.plist -------------------------------------------------------------------------------- /work/pixel-paint.mindnode/QuickLook/Preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/journey-ad/pixel-paint/HEAD/work/pixel-paint.mindnode/QuickLook/Preview.jpg -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /src/store/getter.js: -------------------------------------------------------------------------------- 1 | export default { 2 | viewportSize (state) { 3 | return Math.min(Math.max(16, state.viewportSize), state.artwork.size) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/store/actions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | setCurrentOffset ({ commit }, offset) { 3 | commit('setCurrentOffset', offset) 4 | }, 5 | psuhHistory ({ commit }, action) { 6 | commit('pushHistory', action) 7 | }, 8 | psuhTempHistory ({ commit }, action) { 9 | commit('pushTempHistory', action) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | temp 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import state from './state' 2 | import mutations from './mutations' 3 | import actions from './actions' 4 | import getters from './getter' 5 | import Vue from 'vue' 6 | import Vuex from 'vuex' 7 | 8 | Vue.use(Vuex) 9 | 10 | export default new Vuex.Store({ 11 | state, 12 | mutations, 13 | actions, 14 | getters 15 | }) 16 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | '@vue/standard' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/common/alert/alert.js: -------------------------------------------------------------------------------- 1 | import Instance from './instance.js' 2 | 3 | let alertInstance 4 | 5 | function getAlertInstance () { 6 | alertInstance = alertInstance || Instance.newInstance() 7 | return alertInstance 8 | } 9 | 10 | function alert ({ title = 'Alert', content = '' }) { 11 | let instance = getAlertInstance() 12 | instance.show({ 13 | title, 14 | content 15 | }) 16 | } 17 | export default alert 18 | -------------------------------------------------------------------------------- /src/svg/clear.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/svg/share.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/svg/export.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/svg/rename.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /work/pixel-paint.mindnode/style.mindnodestyle/metadata.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | author 6 | MindNode 7 | id 8 | 514DD5D6-7C79-481B-8CB1-A08FA9F5D3DF 9 | title 10 | Tropical 11 | version 12 | 3 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/js/uuid.js: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript#answer-8809472 2 | export default () => { 3 | let d = Date.now() 4 | if (typeof performance !== 'undefined' && typeof performance.now === 'function') { 5 | d += performance.now() 6 | } 7 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 8 | let r = (d + Math.random() * 16) % 16 | 0 9 | d = Math.floor(d / 16) 10 | return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16) 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /src/store/state.js: -------------------------------------------------------------------------------- 1 | export default { 2 | artwork: { 3 | id: '', 4 | title: '', 5 | size: -1, 6 | created: -1, 7 | updated: -1, 8 | brush: { 9 | title: '', 10 | colors: [] 11 | }, 12 | currentBrushColor: '', 13 | canvasData: [], 14 | thumb: '' 15 | }, 16 | canvas: null, 17 | isPushing: false, 18 | isGridShow: true, 19 | viewportSize: -1, 20 | viewportOffset: { x: 0, y: 0 }, 21 | currentOffset: { x: 0, y: 0 }, 22 | undoHistory: [], 23 | redoHistory: [] 24 | } 25 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {}, 4 | 'postcss-px-to-viewport': { 5 | unitToConvert: 'px', 6 | viewportWidth: 375, 7 | unitPrecision: 5, 8 | propList: ['*'], 9 | viewportUnit: 'vw', 10 | fontViewportUnit: 'vw', 11 | selectorBlackList: [], 12 | minPixelValue: 1, 13 | mediaQuery: false, 14 | replace: true, 15 | exclude: [], 16 | landscape: false, 17 | landscapeUnit: 'vw', 18 | landscapeWidth: 568 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/svg/pencil-add.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store/index' 5 | import Icon from 'vue-svg-icon/Icon.vue' 6 | import Alert from './components/common/alert/alert' 7 | import Vue2TouchEvents from 'vue2-touch-events' 8 | import 'normalize.css' 9 | import './style/global.scss' 10 | 11 | Vue.config.productionTip = false 12 | 13 | Vue.component('icon', Icon) 14 | 15 | Vue.prototype.$Alert = Alert 16 | 17 | Vue.use(Vue2TouchEvents) 18 | 19 | new Vue({ 20 | router, 21 | store, 22 | render: h => h(App) 23 | }).$mount('#app') 24 | -------------------------------------------------------------------------------- /src/style/global.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "pixel"; 3 | src: url("../assets/BPdotsSquareBold.otf"); 4 | } 5 | 6 | html, 7 | body { 8 | font-family: 'pixel', 'PingFang SC', 'STHeitiSC-Light', 'Helvetica-Light', arial, sans-serif, 'Droid Sans Fallback'; 9 | letter-spacing: -0.02em; 10 | line-height: 1; 11 | user-select: none; 12 | -webkit-tap-highlight-color: transparent; 13 | background: $background-color; 14 | overscroll-behavior: none; // 禁止 Chrome 63+ 的下拉刷新行为 15 | image-rendering: pixelated; // 使用符合像素风格的临近算法渲染页面 16 | } 17 | 18 | *, 19 | ::after, 20 | ::before { 21 | box-sizing: border-box; 22 | } -------------------------------------------------------------------------------- /src/components/common/alert/instance.js: -------------------------------------------------------------------------------- 1 | import Alert from './alert.vue' 2 | import Vue from 'vue' 3 | 4 | Alert.newInstance = properties => { 5 | const props = properties || {} 6 | const Instance = new Vue({ 7 | data: props, 8 | render (h) { 9 | return h(Alert, { 10 | props: props 11 | }) 12 | } 13 | }) 14 | 15 | const component = Instance.$mount() 16 | document.body.appendChild(component.$el) 17 | const alert = Instance.$children[0] 18 | 19 | return { 20 | show (options) { 21 | alert.show(options) 22 | }, 23 | ok () { 24 | alert.ok() 25 | } 26 | } 27 | } 28 | 29 | export default Alert 30 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12 | pixel-paint 13 | 14 | 15 | 16 | 20 |
21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/components/edit/cursor.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 18 | 31 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | let webpack = require('webpack') 2 | let LodashModuleReplacementPlugin = require('lodash-webpack-plugin') 3 | 4 | module.exports = { 5 | publicPath: process.env.NODE_ENV === 'production' 6 | ? './.' 7 | : '/', 8 | css: { 9 | loaderOptions: { 10 | sass: { 11 | // @/ is an alias to src/ 12 | // so this assumes you have a file named `src/variables.scss` 13 | data: '@import "@/style/variable.scss";' 14 | } 15 | } 16 | }, 17 | chainWebpack: config => { 18 | config.plugin('ignore') 19 | .use(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)) 20 | config.plugin('loadshReplace') 21 | .use(new LodashModuleReplacementPlugin()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/svg/undo.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/svg/brush.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svg/redo.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "11" 4 | 5 | install: 6 | - npm install 7 | 8 | script: 9 | - npm run build 10 | 11 | after_success: 12 | - cd ./dist 13 | - git init 14 | - git config --global user.name "${U_NAME}" 15 | - git config --global user.email "${U_EMAIL}" 16 | - git add . 17 | - git commit -m "Automatically publish from travis-ci" 18 | - git push --quiet --force "https://${GH_TOKEN}@${GH_REF}" master:${P_BRANCH} 19 | 20 | branches: 21 | only: 22 | - master 23 | 24 | notifications: 25 | email: 26 | - ad@imjad.cn 27 | on_failure: always 28 | 29 | # Note: you should set Environment Variables here or 'Settings' on travis-ci.org 30 | env: 31 | global: 32 | - GH_REF: github.com/journey-ad/pixel-paint.git 33 | -------------------------------------------------------------------------------- /src/svg/move.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icon-set.js: -------------------------------------------------------------------------------- 1 | import Brush from './icons/brush.svg' 2 | import ButtonDraw from './icons/buttonDraw.svg' 3 | import Clear from './icons/clear.svg' 4 | import Export from './icons/export.svg' 5 | import Fill from './icons/fill.svg' 6 | import Grid from './icons/grid.svg' 7 | import Move from './icons/move.svg' 8 | import Palette from './icons/palette.svg' 9 | import PencilAdd from './icons/pencil-add.svg' 10 | import Push from './icons/push.svg' 11 | import Redo from './icons/redo.svg' 12 | import Rename from './icons/rename.svg' 13 | import Share from './icons/share.svg' 14 | import Undo from './icons/undo.svg' 15 | 16 | export { 17 | Brush, 18 | ButtonDraw, 19 | Clear, 20 | Export, 21 | Fill, 22 | Grid, 23 | Move, 24 | Palette, 25 | PencilAdd, 26 | Push, 27 | Redo, 28 | Rename, 29 | Share, 30 | Undo 31 | } 32 | -------------------------------------------------------------------------------- /src/svg/push.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/artwork/add-new.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 18 | 40 | -------------------------------------------------------------------------------- /src/svg/palette.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/style/variable.scss: -------------------------------------------------------------------------------- 1 | $theme-color: #d46791; 2 | $theme-color-active: #6793d4; 3 | $theme-color-disabled: #9e9e9e; 4 | $item-color:#e9e9e9; 5 | $background-color: #f7f7f7; 6 | $background-color-overlay: #0009; 7 | $background-color-list: #fafafa; 8 | $background-color-sidebar: #333333; 9 | $background-color-warn: #f0ad4e; 10 | $background-color-danger: #bb2124; 11 | $border-color: #f2f2f2; 12 | $border-color-edit:#e0e0e0; 13 | $border-color-push: #000; 14 | $border-color-sidebar:#959595; 15 | $white-color: #ffffff; 16 | $black-color: #4a4a4a; 17 | $text-color:#ffffff; 18 | $text-title-color:#4a4a4a; 19 | $text-meta-color:#9b9b9b; 20 | $gray-color:#9b9b9b; 21 | $push-color:#4f4759; 22 | 23 | $box-shadow: 0px 2px 16px rgba(0, 0, 0, 0.12); 24 | 25 | $font-size-small-s: 10px; 26 | $font-size-small: 12px; 27 | $font-size-small-x: 14px; 28 | $font-size-medium: 16px; 29 | $font-size-medium-x: 18px; 30 | $font-size-large: 20px; 31 | $font-size-large-x: 22px; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 像素绘板 2 | 3 | [![Build Status](https://www.travis-ci.org/journey-ad/pixel-paint.svg?branch=master)](https://www.travis-ci.org/journey-ad/pixel-paint) 4 | 5 | ## 开坑中 6 | 7 | *功能和界面参考[dotpict](https://play.google.com/store/apps/details?id=net.dotpicko.dotpict)* 8 | 9 | [DEMO](https://journey-ad.github.io/pixel-paint) 10 | 11 | ![qrcode](https://api.imjad.cn/qrcode/?text=http%3A%2F%2Fjourney-ad.github.io%2Fpixel-paint&size=200&level=H) 12 | 13 | ### 应用原型图 14 | ![原型图](work/sketch.png) 15 | 16 | 17 | ### 思维导图 18 | ![思维导图](work/mindnode.png) 19 | 20 | ## Project setup 21 | ``` 22 | yarn install 23 | ``` 24 | 25 | ### Compiles and hot-reloads for development 26 | ``` 27 | yarn run serve 28 | ``` 29 | 30 | ### Compiles and minifies for production 31 | ``` 32 | yarn run build 33 | ``` 34 | 35 | ### Run your tests 36 | ``` 37 | yarn run test 38 | ``` 39 | 40 | ### Lints and fixes files 41 | ``` 42 | yarn run lint 43 | ``` 44 | 45 | ### Customize configuration 46 | See [Configuration Reference](https://cli.vuejs.org/config/). 47 | -------------------------------------------------------------------------------- /src/svg/cursor.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | const Artwork = (resolve) => { 5 | import(/* webpackChunkName: "artwork" */ './views/artwork.vue').then((module) => { 6 | resolve(module) 7 | }) 8 | } 9 | 10 | const Edit = (resolve) => { 11 | import(/* webpackChunkName: "edit" */ './views/edit.vue').then((module) => { 12 | resolve(module) 13 | }) 14 | } 15 | 16 | Vue.use(Router) 17 | 18 | export default new Router({ 19 | routes: [ 20 | { 21 | path: '/', 22 | redirect: '/artwork' 23 | }, 24 | { 25 | path: '/artwork', 26 | name: 'artwork', 27 | component: Artwork 28 | }, 29 | { 30 | path: '/edit', 31 | name: 'edit', 32 | component: Edit 33 | }, 34 | { 35 | path: '/about', 36 | name: 'about', 37 | // route level code-splitting 38 | // this generates a separate chunk (about.[hash].js) for this route 39 | // which is lazy-loaded when the route is visited. 40 | component: () => import(/* webpackChunkName: "about" */ './views/About.vue') 41 | } 42 | ] 43 | }) 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pixel-paint", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "core-js": "^2.6.5", 12 | "lodash": "^4.17.11", 13 | "moment": "^2.24.0", 14 | "normalize.css": "^8.0.1", 15 | "vue": "^2.6.10", 16 | "vue-router": "^3.0.3", 17 | "vue2-touch-events": "^2.0.0", 18 | "vuex": "^3.0.1" 19 | }, 20 | "devDependencies": { 21 | "@vue/cli-plugin-babel": "^3.8.0", 22 | "@vue/cli-plugin-eslint": "^3.8.0", 23 | "@vue/cli-service": "^3.8.0", 24 | "@vue/eslint-config-standard": "^4.0.0", 25 | "babel-eslint": "^10.0.1", 26 | "eslint": "^5.16.0", 27 | "eslint-plugin-vue": "^5.0.0", 28 | "get-pixels": "^3.3.2", 29 | "lodash-webpack-plugin": "^0.11.5", 30 | "postcss-px-to-viewport": "^1.1.0", 31 | "sass": "^1.18.0", 32 | "sass-loader": "^7.1.0", 33 | "sharp": "^0.22.1", 34 | "vue-svg-icon": "^1.2.9", 35 | "vue-template-compiler": "^2.6.10" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jad 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/svg/fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/svg/grid.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/js/cache.js: -------------------------------------------------------------------------------- 1 | import { LocalStorage } from './storage' 2 | 3 | const ARTWORK_KEY = '__PIXEL_PAINT_ARTWORK__' 4 | 5 | const insertArray = (arr, val, compare, max) => { 6 | const index = arr.findIndex(compare) 7 | 8 | if (index >= 0) { 9 | arr[index] = Object.assign({}, arr[index], val) 10 | } else { 11 | arr.unshift(val) 12 | } 13 | 14 | if (max && arr.length > max) { 15 | arr.pop() 16 | } 17 | } 18 | 19 | const deleteFromArray = (arr, compare) => { 20 | const index = arr.findIndex(compare) 21 | 22 | if (index > -1) { 23 | arr.splice(index, 1) 24 | } 25 | } 26 | 27 | export const loadArtwork = () => { 28 | return LocalStorage.get(ARTWORK_KEY) 29 | } 30 | 31 | export const saveArtwork = artwork => { 32 | let artworks = LocalStorage.get(ARTWORK_KEY, []) 33 | 34 | insertArray(artworks, artwork, item => { 35 | return artwork.id === item.id 36 | }) 37 | 38 | return LocalStorage.set(ARTWORK_KEY, artworks) 39 | } 40 | 41 | export const deleteArtwork = id => { 42 | let artworks = LocalStorage.get(ARTWORK_KEY, []) 43 | 44 | deleteFromArray(artworks, item => { 45 | return item.id === id 46 | }) 47 | 48 | return LocalStorage.set(ARTWORK_KEY, artworks) 49 | } 50 | -------------------------------------------------------------------------------- /src/js/storage.js: -------------------------------------------------------------------------------- 1 | import { JSONC } from './jsonc' 2 | 3 | class Storage { 4 | get (key, def) { 5 | let result = this.drive.getItem(key) 6 | if (result) { 7 | return deserialize(result) 8 | } else { 9 | return def 10 | } 11 | } 12 | 13 | set (key, val) { 14 | if (val === undefined) { 15 | return this.remove(key) 16 | } 17 | this.drive.setItem(key, serialize(val)) 18 | return val 19 | } 20 | 21 | has (key) { 22 | return this.get(key) !== undefined 23 | } 24 | 25 | remove (key) { 26 | this.drive.removeItem(key) 27 | } 28 | 29 | clear () { 30 | this.drive.clear() 31 | } 32 | } 33 | 34 | class Local extends Storage { 35 | constructor () { 36 | super() 37 | this.drive = window.localStorage 38 | } 39 | } 40 | 41 | class Session extends Storage { 42 | constructor () { 43 | super() 44 | this.drive = window.sessionStorage 45 | } 46 | } 47 | 48 | export const LocalStorage = new Local() 49 | export const SessionStorage = new Session() 50 | 51 | function serialize (val) { 52 | return JSONC.pack(val) 53 | } 54 | 55 | function deserialize (val) { 56 | if (typeof val !== 'string') { 57 | return undefined 58 | } 59 | try { 60 | return JSONC.unpack(val) 61 | } catch (e) { 62 | return val || undefined 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/edit/grid-highlight.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | Ï 47 | 48 | 57 | -------------------------------------------------------------------------------- /src/components/edit/grid.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 51 | 52 | 59 | -------------------------------------------------------------------------------- /src/store/mutations.js: -------------------------------------------------------------------------------- 1 | export default { 2 | setCanvas (state, canvas) { 3 | state.canvas = canvas 4 | }, 5 | setArtworkInfo (state, info) { 6 | state.artwork = info ? Object.assign({}, state.artwork, info) : {} 7 | }, 8 | updateThumb (state, thumb) { 9 | state.artwork.thumb = thumb 10 | }, 11 | setPushing (state, flag) { 12 | state.isPushing = flag 13 | }, 14 | setViewportSize (state, size) { 15 | state.viewportSize = Math.min(Math.max(16, size), state.artwork.size) 16 | }, 17 | setGridShow (state, flag) { 18 | state.isGridShow = flag 19 | }, 20 | setViewportOffset (state, offset) { 21 | state.viewportOffset = Object.assign({}, state.viewportOffset, offset) 22 | }, 23 | setCurrentOffset (state, offset) { 24 | state.currentOffset = Object.assign({}, state.currentOffset, offset) 25 | }, 26 | UndoHistoryHandle (state, { action, data }) { 27 | if (typeof state.undoHistory === 'undefined') state.undoHistory = [] 28 | switch (action) { 29 | case 'push': 30 | state.undoHistory.push(data) 31 | break 32 | case 'pop': 33 | state.undoHistory.pop() 34 | break 35 | case 'clear': 36 | state.undoHistory = [] 37 | } 38 | }, 39 | RedoHistoryHandle (state, { action, data }) { 40 | if (typeof state.redoHistory === 'undefined') state.redoHistory = [] 41 | switch (action) { 42 | case 'push': 43 | state.redoHistory.push(data) 44 | break 45 | case 'pop': 46 | state.redoHistory.pop() 47 | break 48 | case 'clear': 49 | state.redoHistory = [] 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/components/edit/rename.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 34 | 35 | 74 | -------------------------------------------------------------------------------- /src/svg/buttonDraw.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | buttonDraw 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /work/convert.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const _ = require('lodash') 3 | const argv = require('optimist').argv 4 | const sharp = require('sharp') 5 | const getPixels = require('get-pixels') 6 | 7 | if (!argv.p || !argv.s) return 8 | 9 | const uuid = () => { 10 | let d = Date.now() 11 | if (typeof performance !== 'undefined' && typeof performance.now === 'function') { 12 | d += performance.now() 13 | } 14 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 15 | let r = (d + Math.random() * 16) % 16 | 0 16 | d = Math.floor(d / 16) 17 | return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16) 18 | }) 19 | } 20 | 21 | sharp(argv.p) 22 | .extract({ 23 | left: 0, 24 | top: 169, 25 | width: 1080, 26 | height: 1080 27 | }) 28 | .resize(argv.s, argv.s, { 29 | kernel: 'nearest' 30 | }) 31 | .toBuffer((_err, data, info) => { 32 | console.log(info) 33 | getPixels(data, 'image/png', function (err, pixels) { 34 | if (err) { 35 | console.log(err) 36 | return 37 | } 38 | 39 | let canvasData = [] 40 | 41 | for (let y = 0; y < pixels.shape[1]; y++) { 42 | for (let x = 0; x < pixels.shape[0]; x++) { 43 | const r = pixels.get(x, y, 0).toString(16) 44 | const g = pixels.get(x, y, 1).toString(16) 45 | const b = pixels.get(x, y, 2).toString(16) 46 | const rgb = `#${r}${g}${b}` 47 | canvasData.push(rgb) 48 | } 49 | } 50 | 51 | let result = { 52 | id: uuid(), 53 | title: '', 54 | author: '', 55 | size: argv.size, 56 | created: Date.now(), 57 | updated: Date.now(), 58 | canvasData: _.chunk(canvasData, argv.s), 59 | brush: { 60 | title: 'Brush', 61 | colors: [...new Set(canvasData)] 62 | } 63 | } 64 | 65 | // console.log(result) 66 | fs.writeFileSync(`./artwork_${argv.s}.json`, JSON.stringify(result), 'utf-8') 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /src/components/common/prompt.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 41 | 42 | 87 | -------------------------------------------------------------------------------- /src/components/common/confirm.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 32 | 33 | 85 | -------------------------------------------------------------------------------- /src/components/common/alert/alert.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 37 | 38 | 89 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 41 | 42 | 43 | 59 | -------------------------------------------------------------------------------- /src/components/edit/push.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 56 | 57 | 100 | -------------------------------------------------------------------------------- /src/components/edit/brush-list.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 78 | 79 | 107 | -------------------------------------------------------------------------------- /src/components/edit/touch.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 97 | 98 | 105 | -------------------------------------------------------------------------------- /src/components/edit/header-bar.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 75 | 76 | 116 | -------------------------------------------------------------------------------- /src/views/artwork.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 121 | 122 | 128 | -------------------------------------------------------------------------------- /src/views/edit.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 143 | 144 | 150 | -------------------------------------------------------------------------------- /src/components/artwork/canvas-item.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 97 | 98 | 173 | -------------------------------------------------------------------------------- /src/components/edit/sidebar.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 148 | 149 | 228 | -------------------------------------------------------------------------------- /work/pixel-paint.mindnode/style.mindnodestyle/contents.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | allowNodeStrokeColorVariation 6 | 7 | backgroundColor 8 | {0.933333, 0.933333, 0.952941, 1.000000} 9 | baseSubnode 10 | 11 | shapeStyle 12 | 13 | borderStrokeStyle 14 | 15 | color 16 | {1.000000, 0.588235, 0.352941, 1.000000} 17 | dash 18 | 0 19 | width 20 | 6 21 | 22 | fillColor 23 | {1.000000, 1.000000, 1.000000, 1.000000} 24 | shapeType 25 | 0 26 | 27 | strokeStyle 28 | 29 | color 30 | {1.000000, 0.588235, 0.352941, 1.000000} 31 | dash 32 | 0 33 | width 34 | 6 35 | 36 | titleStyle 37 | 38 | alignment 39 | 0 40 | bold 41 | 42 | color 43 | {0.428090, 0.428100, 0.428094, 1.000000} 44 | fontName 45 | Helvetica 46 | fontSize 47 | 20 48 | italic 49 | 50 | strikethrough 51 | 52 | underline 53 | 54 | 55 | 56 | crossConnections 57 | 58 | 59 | arrowStyle 60 | 61 | endArrow 62 | 1 63 | startArrow 64 | 0 65 | 66 | pathStyle 67 | 68 | strokeStyle 69 | 70 | color 71 | {0.366349, 0.366358, 0.366353, 1.000000} 72 | dash 73 | 1 74 | width 75 | 1 76 | 77 | 78 | titleStyle 79 | 80 | alignment 81 | 1 82 | bold 83 | 84 | color 85 | {1.000000, 1.000000, 1.000000, 1.000000} 86 | fontName 87 | HelveticaNeue 88 | fontSize 89 | 14 90 | italic 91 | 92 | strikethrough 93 | 94 | underline 95 | 96 | 97 | 98 | 99 | mainNodes 100 | 101 | 102 | shapeStyle 103 | 104 | borderStrokeStyle 105 | 106 | color 107 | {1.000000, 1.000000, 1.000000, 1.000000} 108 | dash 109 | 0 110 | width 111 | 1 112 | 113 | fillColor 114 | {1.000000, 1.000000, 1.000000, 1.000000} 115 | shapeType 116 | 2 117 | 118 | strokeStyle 119 | 120 | color 121 | {0.294118, 0.294118, 0.294118, 1.000000} 122 | dash 123 | 0 124 | width 125 | 4 126 | 127 | titleStyle 128 | 129 | alignment 130 | 1 131 | bold 132 | 133 | color 134 | {0.292470, 0.292477, 0.292473, 1.000000} 135 | fontName 136 | Helvetica 137 | fontSize 138 | 24 139 | italic 140 | 141 | 142 | 143 | 144 | subnodeColors 145 | 146 | 147 | shapeStyle 148 | 149 | borderStrokeStyle 150 | 151 | color 152 | {1.000000, 0.588235, 0.352941, 1.000000} 153 | 154 | fillColor 155 | {1.000000, 1.000000, 1.000000, 1.000000} 156 | 157 | strokeStyle 158 | 159 | color 160 | {1.000000, 0.588235, 0.352941, 1.000000} 161 | 162 | titleStyle 163 | 164 | color 165 | {0.428090, 0.428100, 0.428094, 1.000000} 166 | 167 | 168 | 169 | shapeStyle 170 | 171 | borderStrokeStyle 172 | 173 | color 174 | {0.450980, 0.784314, 1.000000, 1.000000} 175 | 176 | fillColor 177 | {1.000000, 1.000000, 1.000000, 1.000000} 178 | 179 | strokeStyle 180 | 181 | color 182 | {0.450980, 0.784314, 1.000000, 1.000000} 183 | 184 | titleStyle 185 | 186 | color 187 | {0.428090, 0.428100, 0.428094, 1.000000} 188 | 189 | 190 | 191 | shapeStyle 192 | 193 | borderStrokeStyle 194 | 195 | color 196 | {0.686275, 0.313725, 0.784314, 1.000000} 197 | 198 | fillColor 199 | {1.000000, 1.000000, 1.000000, 1.000000} 200 | 201 | strokeStyle 202 | 203 | color 204 | {0.686275, 0.313725, 0.784314, 1.000000} 205 | 206 | titleStyle 207 | 208 | color 209 | {0.428090, 0.428100, 0.428094, 1.000000} 210 | 211 | 212 | 213 | shapeStyle 214 | 215 | borderStrokeStyle 216 | 217 | color 218 | {1.000000, 0.803922, 0.235294, 1.000000} 219 | 220 | fillColor 221 | {1.000000, 1.000000, 1.000000, 1.000000} 222 | 223 | strokeStyle 224 | 225 | color 226 | {1.000000, 0.803922, 0.235294, 1.000000} 227 | 228 | titleStyle 229 | 230 | color 231 | {0.428090, 0.428100, 0.428094, 1.000000} 232 | 233 | 234 | 235 | shapeStyle 236 | 237 | borderStrokeStyle 238 | 239 | color 240 | {1.000000, 0.372549, 0.411765, 1.000000} 241 | 242 | fillColor 243 | {1.000000, 1.000000, 1.000000, 1.000000} 244 | 245 | strokeStyle 246 | 247 | color 248 | {1.000000, 0.372549, 0.411765, 1.000000} 249 | 250 | titleStyle 251 | 252 | color 253 | {0.428090, 0.428100, 0.428094, 1.000000} 254 | 255 | 256 | 257 | shapeStyle 258 | 259 | borderStrokeStyle 260 | 261 | color 262 | {0.392157, 0.784314, 0.803922, 1.000000} 263 | 264 | fillColor 265 | {1.000000, 1.000000, 1.000000, 1.000000} 266 | 267 | strokeStyle 268 | 269 | color 270 | {0.392157, 0.784314, 0.803922, 1.000000} 271 | 272 | titleStyle 273 | 274 | color 275 | {0.428090, 0.428100, 0.428094, 1.000000} 276 | 277 | 278 | 279 | subnodeLevels 280 | 281 | 282 | 283 | -------------------------------------------------------------------------------- /src/components/edit/draw.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 266 | 267 | 288 | -------------------------------------------------------------------------------- /src/components/artwork/new-canvas.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 249 | 250 | 379 | -------------------------------------------------------------------------------- /src/js/jsonc.js: -------------------------------------------------------------------------------- 1 | // Copyright © 2013 Pieroxy 2 | // This work is free. You can redistribute it and/or modify it 3 | // under the terms of the WTFPL, Version 2 4 | // For more information see LICENSE.txt or http://www.wtfpl.net/ 5 | // 6 | // LZ-based compression algorithm, version 1.0.2-rc1 7 | var LZString = { 8 | 9 | writeBit: function (value, data) { 10 | data.val = (data.val << 1) | value 11 | if (data.position == 15) { 12 | data.position = 0 13 | data.string += String.fromCharCode(data.val) 14 | data.val = 0 15 | } else { 16 | data.position++ 17 | } 18 | }, 19 | 20 | writeBits: function (numBits, value, data) { 21 | if (typeof (value) === 'string') { value = value.charCodeAt(0) } 22 | for (var i = 0; i < numBits; i++) { 23 | this.writeBit(value & 1, data) 24 | value = value >> 1 25 | } 26 | }, 27 | 28 | produceW: function (context) { 29 | if (Object.prototype.hasOwnProperty.call(context.dictionaryToCreate, context.w)) { 30 | if (context.w.charCodeAt(0) < 256) { 31 | this.writeBits(context.numBits, 0, context.data) 32 | this.writeBits(8, context.w, context.data) 33 | } else { 34 | this.writeBits(context.numBits, 1, context.data) 35 | this.writeBits(16, context.w, context.data) 36 | } 37 | this.decrementEnlargeIn(context) 38 | delete context.dictionaryToCreate[context.w] 39 | } else { 40 | this.writeBits(context.numBits, context.dictionary[context.w], context.data) 41 | } 42 | this.decrementEnlargeIn(context) 43 | }, 44 | 45 | decrementEnlargeIn: function (context) { 46 | context.enlargeIn-- 47 | if (context.enlargeIn == 0) { 48 | context.enlargeIn = Math.pow(2, context.numBits) 49 | context.numBits++ 50 | } 51 | }, 52 | 53 | compress: function (uncompressed) { 54 | var context = { 55 | dictionary: {}, 56 | dictionaryToCreate: {}, 57 | c: '', 58 | wc: '', 59 | w: '', 60 | enlargeIn: 2, // Compensate for the first entry which should not count 61 | dictSize: 3, 62 | numBits: 2, 63 | result: '', 64 | data: { string: '', val: 0, position: 0 } 65 | }; var i 66 | 67 | for (i = 0; i < uncompressed.length; i += 1) { 68 | context.c = uncompressed.charAt(i) 69 | if (!Object.prototype.hasOwnProperty.call(context.dictionary, context.c)) { 70 | context.dictionary[context.c] = context.dictSize++ 71 | context.dictionaryToCreate[context.c] = true 72 | } 73 | 74 | context.wc = context.w + context.c 75 | if (Object.prototype.hasOwnProperty.call(context.dictionary, context.wc)) { 76 | context.w = context.wc 77 | } else { 78 | this.produceW(context) 79 | // Add wc to the dictionary. 80 | context.dictionary[context.wc] = context.dictSize++ 81 | context.w = String(context.c) 82 | } 83 | } 84 | 85 | // Output the code for w. 86 | if (context.w !== '') { 87 | this.produceW(context) 88 | } 89 | 90 | // Mark the end of the stream 91 | this.writeBits(context.numBits, 2, context.data) 92 | 93 | // Flush the last char 94 | while (context.data.val > 0) this.writeBit(0, context.data) 95 | return context.data.string 96 | }, 97 | 98 | readBit: function (data) { 99 | var res = data.val & data.position 100 | data.position >>= 1 101 | if (data.position == 0) { 102 | data.position = 32768 103 | data.val = data.string.charCodeAt(data.index++) 104 | } 105 | // data.val = (data.val << 1); 106 | return res > 0 ? 1 : 0 107 | }, 108 | 109 | readBits: function (numBits, data) { 110 | var res = 0 111 | var maxpower = Math.pow(2, numBits) 112 | var power = 1 113 | while (power != maxpower) { 114 | res |= this.readBit(data) * power 115 | power <<= 1 116 | } 117 | return res 118 | }, 119 | 120 | decompress: function (compressed) { 121 | var dictionary = {} 122 | var next 123 | var enlargeIn = 4 124 | var dictSize = 4 125 | var numBits = 3 126 | var entry = ''; 127 | var result = ''; 128 | var i 129 | var w 130 | var c 131 | var errorCount = 0 132 | var literal 133 | var data = { string: compressed, val: compressed.charCodeAt(0), position: 32768, index: 1 } 134 | 135 | for (i = 0; i < 3; i += 1) { 136 | dictionary[i] = i 137 | } 138 | 139 | next = this.readBits(2, data) 140 | switch (next) { 141 | case 0: 142 | c = String.fromCharCode(this.readBits(8, data)) 143 | break 144 | case 1: 145 | c = String.fromCharCode(this.readBits(16, data)) 146 | break 147 | case 2: 148 | return '' 149 | } 150 | dictionary[3] = c 151 | w = result = c 152 | while (true) { 153 | c = this.readBits(numBits, data) 154 | 155 | switch (c) { 156 | case 0: 157 | if (errorCount++ > 10000) return 'Error' 158 | c = String.fromCharCode(this.readBits(8, data)) 159 | dictionary[dictSize++] = c 160 | c = dictSize - 1 161 | enlargeIn-- 162 | break 163 | case 1: 164 | c = String.fromCharCode(this.readBits(16, data)) 165 | dictionary[dictSize++] = c 166 | c = dictSize - 1 167 | enlargeIn-- 168 | break 169 | case 2: 170 | return result 171 | } 172 | 173 | if (enlargeIn == 0) { 174 | enlargeIn = Math.pow(2, numBits) 175 | numBits++ 176 | } 177 | 178 | if (dictionary[c]) { 179 | entry = dictionary[c] 180 | } else { 181 | if (c === dictSize) { 182 | entry = w + w.charAt(0) 183 | } else { 184 | return null 185 | } 186 | } 187 | result += entry 188 | 189 | // Add w+entry[0] to the dictionary. 190 | dictionary[dictSize++] = w + entry.charAt(0) 191 | enlargeIn-- 192 | 193 | w = entry 194 | 195 | if (enlargeIn == 0) { 196 | enlargeIn = Math.pow(2, numBits) 197 | numBits++ 198 | } 199 | } 200 | return result 201 | } 202 | }/*global LZString */ 203 | export const JSONC = (function () { 204 | var root 205 | var JSONC = {} 206 | var isNodeEnvironment 207 | var _nCode = -1 208 | var toString = {}.toString 209 | 210 | /** 211 | * set the correct root depending from the environment. 212 | * @type {Object} 213 | * @private 214 | */ 215 | root = this 216 | /** 217 | * Check if JSONC is loaded in Node.js environment 218 | * @type {Boolean} 219 | * @private 220 | */ 221 | isNodeEnvironment = typeof exports === 'object' && typeof module === 'object' && typeof module.exports === 'object' && typeof require === 'function' 222 | /** 223 | * Checks if the value exist in the array. 224 | * @param arr 225 | * @param v 226 | * @returns {boolean} 227 | */ 228 | function contains (arr, v) { 229 | var nIndex 230 | var nLen = arr.length 231 | for (nIndex = 0; nIndex < nLen; nIndex++) { 232 | if (arr[nIndex][1] === v) { 233 | return true 234 | } 235 | } 236 | return false 237 | } 238 | /** 239 | * Removes duplicated values in an array 240 | * @param oldArray 241 | * @returns {Array} 242 | */ 243 | function unique (oldArray) { 244 | var nIndex 245 | var nLen = oldArray.length 246 | var aArr = [] 247 | for (nIndex = 0; nIndex < nLen; nIndex++) { 248 | if (!contains(aArr, oldArray[nIndex][1])) { 249 | aArr.push(oldArray[nIndex]) 250 | } 251 | } 252 | return aArr 253 | } 254 | /** 255 | * Escapes a RegExp 256 | * @param text 257 | * @returns {*} 258 | */ 259 | function escapeRegExp (text) { 260 | return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') 261 | } 262 | /** 263 | * Returns if the obj is an object or not. 264 | * @param obj 265 | * @returns {boolean} 266 | * @private 267 | */ 268 | function _isObject (obj) { 269 | return toString.call(obj) === '[object Object]' 270 | } 271 | /** 272 | * Returns if the obj is an array or not 273 | * @param obj 274 | * @returns {boolean} 275 | * @private 276 | */ 277 | function _isArray (obj) { 278 | return toString.call(obj) === '[object Array]' 279 | } 280 | /** 281 | * Converts a bidimensional array to object 282 | * @param aArr 283 | * @returns {{}} 284 | * @private 285 | */ 286 | function _biDimensionalArrayToObject (aArr) { 287 | var obj = {} 288 | var nIndex 289 | var nLen = aArr.length 290 | var oItem 291 | for (nIndex = 0; nIndex < nLen; nIndex++) { 292 | oItem = aArr[nIndex] 293 | obj[oItem[0]] = oItem[1] 294 | } 295 | return obj 296 | } 297 | 298 | /** 299 | * Convert a number to their ascii code/s. 300 | * @param index 301 | * @param totalChar 302 | * @param offset 303 | * @returns {Array} 304 | * @private 305 | */ 306 | function _numberToKey (index, totalChar, offset) { 307 | var aArr = [] 308 | var currentChar = index 309 | totalChar = totalChar || 26 310 | offset = offset || 65 311 | while (currentChar >= totalChar) { 312 | aArr.push((currentChar % totalChar) + offset) 313 | currentChar = Math.floor(currentChar / totalChar - 1) 314 | } 315 | aArr.push(currentChar + offset) 316 | return aArr.reverse() 317 | } 318 | 319 | /** 320 | * Returns the string using an array of ASCII values 321 | * @param aKeys 322 | * @returns {string} 323 | * @private 324 | */ 325 | function _getSpecialKey (aKeys) { 326 | return String.fromCharCode.apply(String, aKeys) 327 | } 328 | 329 | /** 330 | * Traverse all the objects looking for keys and set an array with the new keys 331 | * @param json 332 | * @param aKeys 333 | * @returns {*} 334 | * @private 335 | */ 336 | function _getKeys (json, aKeys) { 337 | var aKey, 338 | sKey, 339 | oItem 340 | 341 | for (sKey in json) { 342 | if (json.hasOwnProperty(sKey)) { 343 | oItem = json[sKey] 344 | if (_isObject(oItem) || _isArray(oItem)) { 345 | aKeys = aKeys.concat(unique(_getKeys(oItem, aKeys))) 346 | } 347 | if (isNaN(Number(sKey))) { 348 | if (!contains(aKeys, sKey)) { 349 | _nCode += 1 350 | aKey = [] 351 | aKey.push(_getSpecialKey(_numberToKey(_nCode)), sKey) 352 | aKeys.push(aKey) 353 | } 354 | } 355 | } 356 | } 357 | return aKeys 358 | } 359 | 360 | /** 361 | * Method to compress array objects 362 | * @private 363 | * @param json 364 | * @param aKeys 365 | */ 366 | function _compressArray (json, aKeys) { 367 | var nIndex, 368 | nLenKeys 369 | 370 | for (nIndex = 0, nLenKeys = json.length; nIndex < nLenKeys; nIndex++) { 371 | json[nIndex] = JSONC.compress(json[nIndex], aKeys) 372 | } 373 | } 374 | 375 | /** 376 | * Method to compress anything but array 377 | * @private 378 | * @param json 379 | * @param aKeys 380 | * @returns {*} 381 | */ 382 | function _compressOther (json, aKeys) { 383 | var oKeys, 384 | aKey, 385 | str, 386 | nLenKeys, 387 | nIndex, 388 | obj 389 | aKeys = _getKeys(json, aKeys) 390 | aKeys = unique(aKeys) 391 | oKeys = _biDimensionalArrayToObject(aKeys) 392 | 393 | str = JSON.stringify(json) 394 | nLenKeys = aKeys.length 395 | 396 | for (nIndex = 0; nIndex < nLenKeys; nIndex++) { 397 | aKey = aKeys[nIndex] 398 | str = str.replace(new RegExp(escapeRegExp('"' + aKey[1] + '"'), 'g'), '"' + aKey[0] + '"') 399 | } 400 | obj = JSON.parse(str) 401 | obj._ = oKeys 402 | return obj 403 | } 404 | 405 | /** 406 | * Method to decompress array objects 407 | * @private 408 | * @param json 409 | */ 410 | function _decompressArray (json) { 411 | var nIndex, nLenKeys 412 | 413 | for (nIndex = 0, nLenKeys = json.length; nIndex < nLenKeys; nIndex++) { 414 | json[nIndex] = JSONC.decompress(json[nIndex]) 415 | } 416 | } 417 | 418 | /** 419 | * Method to decompress anything but array 420 | * @private 421 | * @param jsonCopy 422 | * @returns {*} 423 | */ 424 | function _decompressOther (jsonCopy) { 425 | var oKeys, str, sKey 426 | 427 | oKeys = JSON.parse(JSON.stringify(jsonCopy._)) 428 | delete jsonCopy._ 429 | str = JSON.stringify(jsonCopy) 430 | for (sKey in oKeys) { 431 | if (oKeys.hasOwnProperty(sKey)) { 432 | str = str.replace(new RegExp('"' + sKey + '"', 'g'), '"' + oKeys[sKey] + '"') 433 | } 434 | } 435 | return str 436 | } 437 | 438 | /** 439 | * Compress a RAW JSON 440 | * @param json 441 | * @param optKeys 442 | * @returns {*} 443 | */ 444 | JSONC.compress = function (json, optKeys) { 445 | if (!optKeys) { 446 | _nCode = -1 447 | } 448 | var aKeys = optKeys || [] 449 | var obj 450 | 451 | if (_isArray(json)) { 452 | _compressArray(json, aKeys) 453 | obj = json 454 | } else { 455 | obj = _compressOther(json, aKeys) 456 | } 457 | return obj 458 | } 459 | /** 460 | * Use LZString to get the compressed string. 461 | * @param json 462 | * @param bCompress 463 | * @returns {String} 464 | */ 465 | JSONC.pack = function (json, bCompress) { 466 | var str = JSON.stringify((bCompress ? JSONC.compress(json) : json)) 467 | return LZString.compress(str) 468 | } 469 | /** 470 | * Decompress a compressed JSON 471 | * @param json 472 | * @returns {*} 473 | */ 474 | JSONC.decompress = function (json) { 475 | var str 476 | var jsonCopy = JSON.parse(JSON.stringify(json)) 477 | if (_isArray(jsonCopy)) { 478 | _decompressArray(jsonCopy) 479 | } else { 480 | str = _decompressOther(jsonCopy) 481 | } 482 | return str ? JSON.parse(str) : jsonCopy 483 | } 484 | /** 485 | * Returns the JSON object from the LZW string 486 | * @param lzw 487 | * @param bDecompress 488 | * @returns {Object} 489 | */ 490 | JSONC.unpack = function (lzw, bDecompress) { 491 | var str = LZString.decompress(lzw) 492 | var json = JSON.parse(str) 493 | return bDecompress ? JSONC.decompress(json) : json 494 | } 495 | /* 496 | * Expose Hydra to be used in node.js, as AMD module or as global 497 | */ 498 | return JSONC 499 | }.call(this)) 500 | --------------------------------------------------------------------------------