├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .prettierrc.json ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── app-controller.js ├── app-test.js ├── app.js ├── components │ ├── grid │ │ ├── grid-controller.js │ │ ├── grid-test.js │ │ ├── grid.html │ │ └── grid.js │ ├── header │ │ ├── header-controller.js │ │ ├── header-test.js │ │ ├── header.html │ │ └── header.js │ ├── keyboard │ │ ├── keyboard-controller.js │ │ ├── keyboard-test.js │ │ ├── keyboard.html │ │ └── keyboard.js │ ├── phrase │ │ ├── phrase-controller.js │ │ ├── phrase-test.js │ │ ├── phrase.html │ │ └── phrase.js │ ├── recent │ │ ├── recent-controller.js │ │ ├── recent-test.js │ │ ├── recent.html │ │ └── recent.js │ └── symbol │ │ ├── symbol-controller.js │ │ ├── symbol-test.js │ │ ├── symbol.html │ │ └── symbol.js ├── css │ ├── font.css │ ├── icon.css │ ├── norm.css │ ├── sortable.css │ └── style.css ├── data │ ├── en.json │ ├── test-en.json │ └── tr.json ├── img │ ├── conj-en-noun │ │ ├── from.svg │ │ ├── in.svg │ │ ├── my.svg │ │ ├── simple.svg │ │ ├── that.svg │ │ ├── the.svg │ │ ├── this.svg │ │ ├── to.svg │ │ └── your.svg │ ├── conj-en │ │ ├── ableMode.svg │ │ ├── certFutuTence.svg │ │ ├── imperMode.svg │ │ ├── ogrGecZam.svg │ │ ├── possibMode.svg │ │ ├── presContTence.svg │ │ ├── simFutuTence.svg │ │ ├── simPastTence.svg │ │ ├── simPresTence.svg │ │ └── subjunMode.svg │ ├── conj-tr-noun │ │ ├── benim.svg │ │ ├── bu.svg │ │ ├── de.svg │ │ ├── den.svg │ │ ├── e.svg │ │ ├── i.svg │ │ ├── senin.svg │ │ ├── simple.svg │ │ ├── su.svg │ │ ├── şu.svg │ │ ├── şu.svg │ │ └── şşşşu.svg │ └── conj-tr │ │ ├── dilKip.svg │ │ ├── emrKip.svg │ │ ├── gelZam.svg │ │ ├── genZam.svg │ │ ├── gerKip.svg │ │ ├── gorGecZam.svg │ │ ├── istKip.svg │ │ ├── ogrGecZam.svg │ │ └── simZam.svg ├── index.html ├── js │ ├── constants.js │ ├── init.js │ ├── utils-test.js │ └── utils.js ├── mocks.js ├── otsimo.json ├── services │ ├── conjunction.js │ ├── event.js │ ├── global.js │ ├── localstorage-test.js │ ├── localstorage.js │ ├── otsimo-handler.js │ └── tts.js ├── symbols │ ├── aac-test-en │ │ ├── data.json │ │ ├── images │ │ │ ├── i.svg │ │ │ ├── it.svg │ │ │ ├── they.svg │ │ │ ├── we.svg │ │ │ └── you.svg │ │ ├── main.js │ │ └── metadata.json │ ├── back.svg │ ├── next.svg │ └── prev.svg └── woff │ ├── font-latin-ext.woff │ ├── font.woff │ └── icon.woff ├── ci ├── Dockerfile └── start.sh ├── e2e-tests ├── protractor.conf.js └── scenarios.js ├── generator.js ├── i18n ├── en │ ├── ss1_en_1024x768.png │ ├── ss2_en_1024x768.png │ └── ss3_en_1024x768.png ├── icon.png ├── logo.png └── tr │ ├── ss1_tr_1024x768.png │ ├── ss2_tr_1024x768.png │ └── ss3_tr_1024x768.png ├── jsconfig.json ├── karma.conf.js ├── karma.shim.js ├── package.json ├── tasks ├── update-symbols.js └── version.js ├── webpack.config.js ├── webpack.dev.config.js ├── webpack.prod.config.js └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | app/**/*-test.js 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "globals": { 7 | "otsimo": true, 8 | "angular": true, 9 | "responsiveVoice": true 10 | }, 11 | "extends": "airbnb", 12 | "rules": { 13 | "func-names": "off", 14 | "no-console": "off", 15 | "class-methods-use-this": "off" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | dist 27 | spec 28 | .vscode 29 | app/symbols/aac-en 30 | app/symbols/aac-tr 31 | out 32 | public 33 | 34 | # Dependency directories 35 | node_modules 36 | jspm_packages 37 | 38 | # Optional npm cache directory 39 | .npm 40 | 41 | # Optional REPL history 42 | .node_repl_history 43 | typings 44 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all" 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6.1' 4 | before_install: 5 | - export CHROME_BIN=chromium-browser 6 | - export DISPLAY=:99.0 7 | - sh -e /etc/init.d/xvfb start 8 | install: 9 | - npm install eslint 10 | - npm install 11 | script: 12 | - npm run test-travis 13 | notifications: 14 | email: false 15 | slack: 16 | secure: CXpzdWAJUREDEfTSmtCE3sq0eVvJc4Q0Ci57yu6670HYi/Nowx9CXQlv7u8TwUakL22NygGB8POGHtCJZ1CBDV44T1T81xHnsJYBe2AkbML9KhTZwP4v4STtgz7wLc9KJ0LtMD/F3EpgfWW4xj7eth8mQWj8V4hMxLTQArhvMH1LtdEF5o102ZtFEfNKlged/jSXl/NOL47ONl/GM13ZE9riocpaAKztlG/ZxAgAd5gGk08Gv7GkYug3NWi2nSPDrYqnOkORBCjr3sXzj2djDCYoKjdBIXe9gLnX3iCC+QnN0NbxHcz2jHsyKSohCWrbjzHgujrNoY7l/HyJNUI2edgIZSzmST1zuV6r/jxu88pictjBHcy7pjYgyS+ob+V2AnEr8/n6mHiqQL3dFIkmdO4UDq+oK1NJkJC54xp8HhVtMafuuDh4BsY6pBtYiuD65xHwmvJSm35KDY8fYFEZHq/zepio+cLKfpOUrHYfzXzEkTFD5d4zBMKyvzQRwQdVuLoE7GUNYW0tkPgJ3onA0aQI/Ih2c11hh/kajjZQeQKSVJevBSmn8IIIBSSD5VRHyLbf9/Qp6OtFs5RNKHae6dl0K2iKCzHIrfAp1vo4eJwkJWVvwrfSDGjU3E53IhbOLagaKbNj/iHNQR/J/4YkOsDdyabZwK4tHn4+KUbdr1w= 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Otsimo AAC PECS App 2 | 3 | An app that helps kids with autism to compose phrases. 4 | 5 | Otsimo AAC is an application that ables kids with autism who can not speak to compose phrases by tapping on the words. Otsimo AAC uses picture exchange communication system. 6 | 7 | This project was tested with the latest version of nodeJS and npm, please make sure you have atleast node.js 5+ and NPM 3+ installed. 8 | 9 | ## Usage & Develop 10 | 11 | - Clone or fork this repository 12 | - run `npm install` to install dependencies 13 | - run `npm run start` to fire up dev server 14 | - open browser to [`http://localhost:3001`](http://localhost:3001) 15 | 16 | ## Build 17 | 18 | to create a ready production distribution package of the project please run: 19 | 20 | ``` 21 | npm run build 22 | ``` 23 | 24 | after running build the generated files will be available at `/dist` 25 | 26 | ## Testing 27 | 28 | This seed is has protractor and karma for end to end testing and unit testing respectively. 29 | 30 | ### Unit Testing 31 | 32 | make sure your tests are named with a `-test.js` suffix then. to run karma simply run: 33 | 34 | ``` 35 | npm test 36 | ``` 37 | 38 | ### End to end Testing 39 | 40 | to start protractor tests please run: 41 | 42 | ``` 43 | npm run protractor 44 | ``` 45 | 46 | ### Update Symbols 47 | 48 | to update the symbols: 49 | 50 | ``` 51 | npm run update-symbols 52 | ``` 53 | -------------------------------------------------------------------------------- /app/app-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | import {module, inject} from 'mocks'; 3 | import ngTouch from 'angular-touch'; 4 | import FastClick from 'fastclick'; 5 | import AppController from './app-controller'; 6 | import app from './app'; 7 | //import * as CONSTANT from './js/constants'; 8 | import Global from 'services/global'; 9 | import EventManager from 'services/event'; 10 | import TTSManager from 'services/tts'; 11 | import LSManager from 'services/localstorage'; 12 | //import MOCKED_CONSTANT from './js/constants'; 13 | 14 | 15 | Array.prototype.unique = function() { 16 | var u = {}, 17 | a = []; 18 | for (var i = 0, l = this.length; i < l; ++i) { 19 | if (u.hasOwnProperty(this[i])) { 20 | continue; 21 | } 22 | a.push(this[i]); 23 | u[this[i]] = 1; 24 | } 25 | return a; 26 | } 27 | 28 | function randomString(len, charSet) { 29 | charSet = charSet || 'abcdefghijklmnopqrstuvwxyz'; 30 | let randomString = ''; 31 | for (let i = 0; i < len; i++) { 32 | let randomPoz = Math.floor(Math.random() * charSet.length); 33 | randomString += charSet.substring(randomPoz, randomPoz + 1); 34 | } 35 | return randomString; 36 | } 37 | 38 | function generateSymbol(length, normal, derivable, group) { 39 | let classes = []; 40 | if (normal) { 41 | classes.push("main"); 42 | } 43 | if (derivable) { 44 | classes.push("derivable"); 45 | } 46 | if (group) { 47 | classes.push("group"); 48 | } 49 | let i = 0; 50 | let symbArray = []; 51 | while (i < length) { 52 | let randString = randomString(7); 53 | let wordObj = { 54 | title: randString, 55 | slug: randString, 56 | type: 'noun', 57 | parent: 'main' 58 | }; 59 | wordObj.class = classes[Math.floor(Math.random() * (classes.length - 1)) + 1]; 60 | symbArray.push(wordObj); 61 | i++; 62 | } 63 | return symbArray; 64 | } 65 | 66 | Array.prototype.contains = function(obj) { 67 | return this.indexOf(obj) > -1; 68 | }; 69 | 70 | String.prototype.contains = function(it) { 71 | return this.indexOf(it) != -1; 72 | }; 73 | 74 | describe('aacApp.main module', () => { 75 | beforeEach(module("aacApp")); 76 | 77 | let $controller, 78 | $timeout; 79 | let appCtrl; 80 | let g, 81 | event, 82 | tts, 83 | ls, 84 | ots; 85 | beforeEach(inject((_$controller_, _$timeout_) => { 86 | // The injector unwraps the underscores (_) from around the parameter names when matching 87 | $controller = _$controller_; 88 | $timeout = _$timeout_; 89 | })); 90 | 91 | beforeEach(() => { 92 | g = new Global(); 93 | g.pushToCurrentPhrase = function(obj) { 94 | keyboardCtrl.$scope.global.currentPhrase.push(obj); 95 | } 96 | g.extendedArray = []; 97 | g.mainArray = []; 98 | event = { 99 | appPhrase: function() {}, 100 | appWord: function() {} 101 | }; 102 | tts = { 103 | speak: function() {} 104 | }; 105 | ls = new LSManager(); 106 | g.changeTab = function() {} 107 | //$controller(dependentController, {$scope:{},$global:g}); 108 | ots = { 109 | init: function() { 110 | return {run: function() {}, onResolutionChanged: function() {}, kv: {}, child: {}} 111 | } 112 | }; 113 | }); 114 | 115 | describe('app controller', () => { 116 | it('should be defined', () => { 117 | appCtrl = $controller(AppController, { 118 | $scope: {}, 119 | $global: g, 120 | $timeout: $timeout, 121 | EventManager: event, 122 | TTSManager: tts, 123 | OtsimoHandler: ots 124 | }); 125 | expect(appCtrl).toBeDefined(); 126 | }); 127 | }); 128 | 129 | describe('setUIText', () => { 130 | it('should set texts and variables from otsimo to scope', () => { 131 | appCtrl = $controller(AppController, { 132 | $scope: {}, 133 | $global: g, 134 | $timeout: $timeout, 135 | EventManager: event, 136 | TTSManager: tts, 137 | OtsimoHandler: ots 138 | }); 139 | appCtrl.otsimo.kv.pageText1 = "pagetextmock"; 140 | appCtrl.setUIText(); 141 | expect(appCtrl.$scope.pageText1).toBe("pagetextmock"); 142 | }); 143 | }); 144 | 145 | describe('language', () => { 146 | it('should be set!', () => { 147 | appCtrl = $controller(AppController, { 148 | $scope: {}, 149 | $global: g, 150 | $timeout: $timeout, 151 | EventManager: event, 152 | TTSManager: tts, 153 | OtsimoHandler: ots 154 | }); 155 | appCtrl.otsimo.child.language = "tr"; 156 | appCtrl.setSettings(); 157 | expect(appCtrl.$scope.global.language).toBe("tr"); 158 | appCtrl.otsimo.child.language = "en"; 159 | appCtrl.setSettings(); 160 | expect(appCtrl.$scope.global.language).toBe("en"); 161 | }); 162 | }); 163 | 164 | describe('map', () => { 165 | it('should be expected', () => { 166 | appCtrl = $controller(AppController, { 167 | $scope: {}, 168 | $global: g, 169 | $timeout: $timeout, 170 | EventManager: event, 171 | TTSManager: tts, 172 | OtsimoHandler: ots 173 | }); 174 | appCtrl.otsimo.child.language = "tr"; 175 | 176 | appCtrl.$scope.global.extendedArray = []; 177 | appCtrl.$scope.global.extendedTitleArray = []; 178 | appCtrl.$scope.global.extendedSlugArray = []; 179 | 180 | appCtrl.$scope.global.mainArray = [ 181 | { 182 | title: "aasd", 183 | slug: "aasd", 184 | synonym: ["sdsdsd"] 185 | }, { 186 | title: "asd qwe", 187 | slug: "asd-qwe", 188 | synonym: ["qwe123"] 189 | } 190 | ]; 191 | appCtrl.initExtendedSymbols(); 192 | expect(appCtrl.$scope.global.extendedArray).toBeDefined(); 193 | expect(appCtrl.$scope.global.extendedTitleArray.length).toBe(4); 194 | expect(appCtrl.$scope.global.extendedSlugArray.length).toBe(4); 195 | 196 | appCtrl.$scope.global.extendedArray = []; 197 | appCtrl.$scope.global.extendedTitleArray = []; 198 | appCtrl.$scope.global.extendedSlugArray = []; 199 | 200 | appCtrl.$scope.global.mainArray = [ 201 | { 202 | title: "aasd", 203 | slug: "aasd", 204 | synonym: ["sdsdsd"], 205 | type: "noun" 206 | }, { 207 | title: "asd qwe", 208 | slug: "asd-qwe", 209 | synonym: ["qwe123"], 210 | type: "noun" 211 | } 212 | ]; 213 | appCtrl.initExtendedSymbols(); 214 | expect(appCtrl.$scope.global.extendedArray).toBeDefined(); 215 | expect(appCtrl.$scope.global.extendedTitleArray.length).toBe(22); 216 | expect(appCtrl.$scope.global.extendedSlugArray.length).toBe(22); 217 | 218 | appCtrl.$scope.global.extendedArray = []; 219 | appCtrl.$scope.global.extendedTitleArray = []; 220 | appCtrl.$scope.global.extendedSlugArray = []; 221 | 222 | appCtrl.$scope.global.mainArray = [ 223 | { 224 | title: "aasd", 225 | slug: "aasd", 226 | synonym: [], 227 | type: "verb" 228 | }, { 229 | title: "asd qwe", 230 | slug: "asd-qwe", 231 | synonym: [], 232 | type: "verb" 233 | } 234 | ]; 235 | appCtrl.initExtendedSymbols(); 236 | expect(appCtrl.$scope.global.extendedArray.length).toBe(128); 237 | expect(appCtrl.$scope.global.extendedTitleArray.length).toBe(128); 238 | expect(appCtrl.$scope.global.extendedSlugArray.length).toBe(128); 239 | }); 240 | }); 241 | 242 | }); 243 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular' 2 | import ngsortable from 'ng-sortable' 3 | import otsimo from 'otsimo'; 4 | import headerModule from 'components/header/header' 5 | import phraseModule from 'components/phrase/phrase' 6 | import gridModule from 'components/grid/grid' 7 | 8 | import AppController from './app-controller' 9 | import Global from 'services/global' 10 | import EventManager from 'services/event' 11 | import TTSManager from 'services/tts' 12 | import LSManager from 'services/localstorage' 13 | import ConjunctionManager from 'services/conjunction' 14 | import OtsimoHandler from 'services/otsimo-handler'; 15 | 16 | let aacApp = angular.module('aacApp', [headerModule.name, phraseModule.name, gridModule.name, 'ngTouch', 'as.sortable']); 17 | 18 | aacApp.factory('$global', () => new Global()); 19 | aacApp.factory('EventManager', () => new EventManager()); 20 | aacApp.factory('TTSManager', () => new TTSManager()); 21 | aacApp.factory('LSManager', () => new LSManager()); 22 | aacApp.factory('ConjunctionManager', () => new ConjunctionManager()); 23 | aacApp.factory('OtsimoHandler', () => new OtsimoHandler()); 24 | aacApp.controller('aac-controller', AppController); 25 | 26 | // Unhandled Rejection -> false 27 | // Check this on other releases ^1.6.0 28 | aacApp.config([ 29 | '$qProvider', 30 | function ($qProvider) { 31 | $qProvider.errorOnUnhandledRejections(false); 32 | } 33 | ]); 34 | // Bootstrap the angular when document is ready. 35 | 36 | otsimo.run(() => { 37 | angular.element(document).ready(() => { 38 | angular.bootstrap(document.body, [aacApp.name]); 39 | }); 40 | }); 41 | 42 | otsimo.init({}); 43 | -------------------------------------------------------------------------------- /app/components/grid/grid-controller.js: -------------------------------------------------------------------------------- 1 | import * as CONSTANT from '../../js/constants'; 2 | 3 | /** 4 | * 5 | * @class GridController 6 | */ 7 | export default class GridController { 8 | /** 9 | * Creates an instance of GridController. 10 | * 11 | * @param {any} $scope 12 | * @param {any} $global 13 | * @param {any} $timeout 14 | * @param {TTSManager} TTSManager 15 | * @param {ConjunctionManager} ConjunctionManager 16 | * 17 | */ 18 | constructor($scope, $global, $timeout, TTSManager, ConjunctionManager) { 19 | this.$scope = $scope; 20 | this.$scope.global = $global; 21 | this.global = $global; 22 | this.$timeout = $timeout; 23 | this.tts = TTSManager; 24 | this.conj = ConjunctionManager; 25 | 26 | // Initilize variables for controller. 27 | this.$scope.pageNo = 0; 28 | this.$scope.maxPageNo = 0; 29 | this.prevPageNo = 0; 30 | this.prevGroupStack = []; 31 | this.prevDerivableStack = []; 32 | 33 | // Call controllerInit 34 | this.controllerInit(); 35 | } 36 | 37 | /** 38 | * Create or facilitate new functions for $scope or $global service. 39 | */ 40 | controllerInit() { 41 | this.global.go2FirstPage = this.go2FirstPage.bind(this); 42 | this.global.getPage = this.getPage.bind(this); 43 | this.global.changeTab = this.changeTab.bind(this); 44 | this.global.pushToCurrentPhrase = this.pushToCurrentPhrase.bind(this); 45 | } 46 | 47 | /** 48 | * Handles logical actions about tab change. 49 | * 50 | * @param {string} tabExp - the ne tab 51 | */ 52 | changeTab(tabExp) { 53 | let mArray = this.global.mainArray; 54 | switch (tabExp) { 55 | case CONSTANT.TAB_MAIN: 56 | this.$scope.mainDataUnpaged = mArray.filter((f) => f.parent === CONSTANT.CLASS_MAIN); 57 | break; 58 | case CONSTANT.TAB_DERIVABLE: 59 | this.prevPageNo = this.$scope.pageNo; 60 | this.pushNavigationHistory(CONSTANT.TAB_DERIVABLE); 61 | this.$scope.pageNo = 0; 62 | this.$scope.mainDataUnpaged = mArray.filter(f => f.parent === this.global.currentDerivable); 63 | break; 64 | case CONSTANT.TAB_GROUP: 65 | this.prevPageNo = this.$scope.pageNo; 66 | this.pushNavigationHistory(CONSTANT.TAB_GROUP); 67 | this.$scope.pageNo = 0; 68 | this.$scope.mainDataUnpaged = mArray.filter(f => f.parent === this.global.currentGroup); 69 | break; 70 | } 71 | this.updateGridSlicing(); 72 | } 73 | /** 74 | * Stores the history of navigation (navigation history used for 'goback' navigation) 75 | * 76 | * @param {string} tab for history pushing 77 | */ 78 | pushNavigationHistory(tab) { 79 | switch (tab) { 80 | case CONSTANT.TAB_DERIVABLE: 81 | let currentD = this.global.currentDerivable; 82 | if (this.prevDerivableStack[this.prevDerivableStack.length - 1] !== currentD) { 83 | this.prevDerivableStack.push(currentD); 84 | } 85 | break; 86 | case CONSTANT.TAB_GROUP: 87 | let currentG = this.global.currentGroup; 88 | if (this.prevGroupStack[this.prevGroupStack.length - 1] !== currentG) { 89 | this.prevGroupStack.push(currentG); 90 | } 91 | break; 92 | } 93 | } 94 | 95 | /** 96 | * Handles paging of the grid. Slices the currently showed array into grid size. 97 | * 98 | * @param {number} symbolQuantity 99 | */ 100 | sliceArray(symbolQuantity) { 101 | if (this.global.isHome === 1 && this.$scope.pageNo !== 0) { 102 | this.$scope.mainData = this.$scope.mainDataUnpaged.slice(parseInt(this.$scope.pageNo * symbolQuantity + 1), parseInt((this.$scope.pageNo + 1) * symbolQuantity + 1)).map(this.mapStyle); 103 | } else { 104 | this.$scope.mainData = this.$scope.mainDataUnpaged.slice(this.$scope.pageNo * symbolQuantity, (this.$scope.pageNo + 1) * symbolQuantity).map(this.mapStyle); 105 | } 106 | this.$scope.maxPageNo = this.returnMaxPage(); 107 | } 108 | 109 | /** 110 | * Gives appropriate classNames with respect to the class of the symbol 111 | * in the currently showing symbol array. 112 | * 113 | * @param {Object} symb - the symbol object 114 | * @returns {Object} the symbol object 115 | */ 116 | mapStyle(symb) { 117 | if (symb.class === CONSTANT.CLASS_GROUP) { 118 | symb.style = 'gridGroup'; 119 | } else { 120 | symb.style = 'gridType-' + symb.type; 121 | } 122 | return symb; 123 | } 124 | 125 | /** 126 | * Calculates the max page quantity. 127 | * in the currently showing symbol array. 128 | * @returns {number} max page 129 | */ 130 | returnMaxPage() { 131 | return Math.ceil(this.$scope.mainDataUnpaged.length / this.global.gridQuantity + 0.0001) - 1; 132 | } 133 | 134 | /** 135 | * Navigation function to go to prior page or view. 136 | * Go back symbol only shows up when there is a prior view. 137 | */ 138 | goBack() { 139 | this.prevGroupStack.pop(); 140 | this.prevDerivableStack.pop(); 141 | this.$scope.pageNo = this.prevPageNo; 142 | let prevGroup = this.prevGroupStack[this.prevGroupStack.length - 1]; 143 | let prevDerivable = this.prevDerivableStack[this.prevDerivableStack.length - 1]; 144 | 145 | if (this.prevGroup) { 146 | this.global.currentGroup = this.prevGroup; 147 | this.global.changeCurrentTab(CONSTANT.TAB_GROUP); 148 | } else if (this.prevDerivable) { 149 | this.global.currentDerivable = this.prevDerivable; 150 | this.global.changeCurrentTab(CONSTANT.TAB_DERIVABLE); 151 | } else { 152 | this.global.currentGroup = ''; 153 | this.global.currentDerivable = ''; 154 | this.global.changeCurrentTab(CONSTANT.TAB_MAIN); 155 | } 156 | 157 | this.updateGridSlicing(); 158 | } 159 | 160 | /** 161 | * Navigation function to go to next page 162 | * In current symbol array. 163 | */ 164 | goNextMain() { 165 | this.$scope.pageNo++; 166 | this.updateGridSlicing(); 167 | } 168 | /** 169 | * Navigation function to go to previous page 170 | * In current symbol array. 171 | */ 172 | goPrevMain() { 173 | if (this.$scope.pageNo !== 0) { 174 | this.$scope.pageNo--; 175 | } 176 | this.updateGridSlicing(); 177 | } 178 | /** 179 | * Updates sliceAmount and calls sliceArray function 180 | * Also calls animateSlicing function. 181 | */ 182 | updateGridSlicing() { 183 | let sliceAmount; 184 | if (this.global.isHome === 1 && this.$scope.pageNo === 0) { 185 | sliceAmount = this.global.gridQuantity - 1; 186 | } else { 187 | sliceAmount = this.global.gridQuantity - 2; 188 | } 189 | this.sliceArray(sliceAmount); 190 | this.animateSlicing(); 191 | return sliceAmount; 192 | } 193 | 194 | /** 195 | * Navigates user to the first page 196 | * In the currently showing symbol array. 197 | * @returns {number} current pageNo for test 198 | */ 199 | go2FirstPage() { 200 | let pageNo = this.$scope.pageNo = 0; 201 | this.updateGridSlicing(); 202 | return pageNo; 203 | } 204 | 205 | /** 206 | * currently showing page's number. 207 | * @returns {number} the page number 208 | */ 209 | getPage() { 210 | return this.$scope.pageNo; 211 | } 212 | 213 | /** 214 | * Adds animation to slicing (page or view changes) 215 | * By adding class to the carrier DOM element 216 | * Actual animations are handles in CSS. 217 | */ 218 | animateSlicing() { 219 | let elemGridHolder = document.getElementById('gridHolder'); 220 | if (elemGridHolder) { 221 | document.getElementById('gridHolder').className = 'gridHolder gridSlicingAnim'; 222 | this.$timeout(() => { 223 | elemGridHolder.className = 'gridHolder gridNoAnim'; 224 | }, 200); 225 | } 226 | } 227 | 228 | pushToCurrentPhrase(wordObj2Push, speak) { 229 | let wo2p = JSON.parse(JSON.stringify(wordObj2Push)); 230 | let cp = this.$scope.global.currentPhrase; 231 | 232 | // Update word with custom languagePack function. 233 | wo2p = this.conj.beforePushing(wo2p, cp); 234 | 235 | this.$scope.global.currentPhrase.push(wo2p); 236 | if (speak == true) { 237 | this.tts.speak(wo2p.title); 238 | } 239 | } 240 | 241 | /** 242 | * Closes cover on clicking on it! 243 | */ 244 | clickCover() { 245 | let cover = document.getElementById("symbolCover"); 246 | if (cover) { 247 | cover.style.display = "none"; 248 | } 249 | this.$scope.global.isDraggable = true; 250 | } 251 | } 252 | // Service Dependency Injection 253 | GridController.$inject = ['$scope', '$global', '$timeout', 'TTSManager', 'ConjunctionManager']; 254 | -------------------------------------------------------------------------------- /app/components/grid/grid.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 | 8 |
{{previousText}}
9 |
10 | 11 |
12 | 13 |
{{backText}}
14 |
15 | 16 |
17 | 18 |
19 | 20 |
{{nextText}}
21 |
22 |
23 |
24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /app/components/grid/grid.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular' 2 | 3 | import recentModule from 'components/recent/recent' 4 | import symbolModule from 'components/symbol/symbol' 5 | import keyboardModule from 'components/keyboard/keyboard' 6 | import GridController from './grid-controller' 7 | 8 | let gridModule = angular.module('aacApp.grid', [recentModule.name, symbolModule.name, keyboardModule.name]); 9 | 10 | gridModule.directive('grid', function() { 11 | return {templateUrl: 'components/grid/grid.html'}; 12 | }); 13 | gridModule.controller('grid-controller', GridController); 14 | 15 | export default gridModule; 16 | -------------------------------------------------------------------------------- /app/components/header/header-controller.js: -------------------------------------------------------------------------------- 1 | import {capitalize} from '../../js/utils' 2 | import * as CONSTANT from '../../js/constants' 3 | import otsimo from 'otsimo'; 4 | 5 | /** 6 | * 7 | * 8 | * @export 9 | * @class HeaderController 10 | */ 11 | export default class HeaderController { 12 | /** 13 | * Creates an instance of HeaderController. 14 | * 15 | * @param {any} $scope 16 | * @param {any} $global 17 | * @param {any} $timeout 18 | * 19 | * @memberOf HeaderController 20 | */ 21 | constructor($scope, $global, $timeout) { 22 | console.log("HEADER MOUNTED"); 23 | this.$scope = $scope; 24 | this.global = $global; 25 | this.$timeout = $timeout; 26 | // Initilize variables for controller. 27 | 28 | // Call controllerInit 29 | this.controllerInit(); 30 | } 31 | /** 32 | * Create or facilitate new functions for $scope or $global service. 33 | */ 34 | controllerInit() { 35 | this.global.changeCurrentTab = this.changeCurrentTab.bind(this); 36 | } 37 | 38 | /** 39 | * This function handles the text and variable changes 40 | * on the view when the tab is changed. 41 | * Also calles changeTab function. 42 | * 43 | * @param {string} tabExp - new tab name 44 | */ 45 | changeCurrentTab(tabExp) { 46 | if (tabExp === CONSTANT.TAB_MAIN) { 47 | this.global.currentPage = this.$scope.pageText1; 48 | this.global.isHome = 1; 49 | } else if (tabExp === CONSTANT.TAB_GROUP) { 50 | this.global.currentPage = this.$scope.pageText2 + capitalize(this.global.currentGroup); 51 | this.global.isHome = 0; 52 | } else if (tabExp === CONSTANT.TAB_DERIVABLE) { 53 | this.global.currentPage = this.$scope.pageText3 + capitalize(this.global.currentDerivable); 54 | this.global.isHome = 0; 55 | } else if (tabExp === CONSTANT.TAB_RECENT) { 56 | this.global.currentPage = this.$scope.pageText4; 57 | this.global.isHome = 0; 58 | } else if (tabExp === CONSTANT.TAB_KEYBOARD) { 59 | this.global.currentPage = this.$scope.pageText5; 60 | this.global.isHome = 0; 61 | } 62 | 63 | if (tabExp === CONSTANT.TAB_RECENT || tabExp === CONSTANT.TAB_KEYBOARD) { 64 | this.global.currentTab = tabExp; 65 | } else { 66 | this.global.currentTab = CONSTANT.TAB_MAIN; 67 | this.global.changeTab(tabExp); 68 | } 69 | } 70 | 71 | /** 72 | * Opens recent tab. 73 | */ 74 | openRecent() { 75 | this.changeCurrentTab(CONSTANT.TAB_RECENT); 76 | // Animate Recent Icon 77 | this.animIconTouch('rIcon'); 78 | } 79 | /** 80 | * Initilize Going the entrance screen when; 81 | * Current tab is not main or, 82 | * The page is not 0 or, 83 | * there is a currentGroup or currentDerivable 84 | */ 85 | goHome() { 86 | let gCarrier = document.getElementById("generalCarrier"); 87 | if (gCarrier) { 88 | gCarrier.style.height = "100%"; 89 | } 90 | if (this.global.currentTab !== CONSTANT.TAB_MAIN || this.global.getPage() !== 0 || this.global.currentGroup || this.global.currentDerivable) { 91 | // Animate Home Icon 92 | this.animIconTouch('hIcon'); 93 | 94 | this.global.changeCurrentTab(CONSTANT.TAB_MAIN); 95 | this.global.currentGroup = ''; 96 | this.global.currentDerivable = ''; 97 | this.global.go2FirstPage(); 98 | this.updateGridQuantity(); 99 | } 100 | } 101 | /** 102 | * Opens the grid page 103 | */ 104 | openGrid() { 105 | let gCarrier = document.getElementById("generalCarrier"); 106 | if (gCarrier) { 107 | gCarrier.style.height = "100%"; 108 | } 109 | if (this.global.currentTab !== CONSTANT.TAB_MAIN) { 110 | // Animate Grid Icon 111 | this.animIconTouch('gIcon'); 112 | this.global.changeCurrentTab(CONSTANT.TAB_MAIN); 113 | } 114 | } 115 | /** 116 | * Opens the keyboard page 117 | */ 118 | openKeyboard() { 119 | let gCarrier = document.getElementById("generalCarrier"); 120 | if (gCarrier) { 121 | gCarrier.style.height = "2000px"; 122 | } 123 | if (this.global.currentTab !== CONSTANT.TAB_KEYBOARD) { 124 | // Animate Grid Icon 125 | this.animIconTouch('kIcon'); 126 | this.global.changeCurrentTab(CONSTANT.TAB_KEYBOARD); 127 | } 128 | } 129 | /** 130 | * Quits the application and turns back to Otsimo app hub if the isHome is true. 131 | * Else, get the app to home (goHome) 132 | */ 133 | quitGame() { 134 | // Special animation for Back icon. 135 | let backIconElem = document.getElementById('bIcon'); 136 | if (backIconElem) { 137 | backIconElem.className = 'backIcon backIconHovered'; 138 | this.$timeout(() => backIconElem.className = 'backIcon', 300); 139 | } 140 | if (this.global.isHome === 1) { 141 | otsimo.quitgame(); 142 | } else { 143 | this.goHome(); 144 | } 145 | } 146 | /** 147 | * Does animation for icon's DOM that is given. 148 | * 149 | * @param {string} iconId - the icon id 150 | */ 151 | animIconTouch(iconId) { 152 | let IconElem = document.getElementById(iconId); 153 | if (IconElem) { 154 | IconElem.className = 'material-icons iconOpacity'; 155 | this.$timeout(() => IconElem.className = 'material-icons', 300); 156 | } 157 | } 158 | 159 | /** 160 | * Updates the quantity of the symbols in the grid 161 | * with respect to the current page/view. 162 | */ 163 | updateGridQuantity() { 164 | let x = this.global.gridSize[0]; 165 | let y = this.global.gridSize[1]; 166 | let gridQuantity; 167 | if (this.global.currentTab !== CONSTANT.TAB_MAIN) { 168 | gridQuantity = (x * y) - 1; 169 | } else { 170 | if (this.$scope.pageNo === 0) { 171 | gridQuantity = x * y; 172 | } else { 173 | gridQuantity = (x * y) - 1; 174 | } 175 | } 176 | this.global.gridQuantity = gridQuantity; 177 | } 178 | 179 | } 180 | 181 | // Service Dependency Injection 182 | HeaderController.$inject = ['$scope', '$global', '$timeout']; 183 | -------------------------------------------------------------------------------- /app/components/header/header-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | import { module, inject } from 'mocks' 3 | import HeaderController from './header-controller' 4 | import GridController from '../grid/grid-controller' 5 | import header from './header' 6 | import Global from '../../services/global' 7 | import LSManager from '../../services/localstorage' 8 | 9 | describe('aacApp.header module', () => { 10 | beforeEach(module(header.name)); 11 | 12 | let $controller; 13 | let headerCtrl; 14 | let g, ls, tts, cm; 15 | beforeEach(inject((_$controller_) => { 16 | // The injector unwraps the underscores (_) from around the parameter names when matching 17 | $controller = _$controller_; 18 | })); 19 | 20 | beforeEach(() => { 21 | g = new Global(); 22 | g.changeTab = function() {} 23 | ls = new LSManager(); 24 | g.pushToCurrentPhrase = function(obj) { 25 | headerCtrl.$scope.global.currentPhrase.push(obj); 26 | } 27 | tts = { speak: function() {} }; 28 | cm = { 29 | englishConjunctor: function() {}, 30 | turkishConjunctor: function() {}, 31 | addPossessiveEn: function() {}, 32 | addPossessiveTr: function() {} 33 | }; 34 | headerCtrl = $controller(HeaderController, { $scope: {}, $global: g }); 35 | $controller(GridController, { $scope: {}, $global: g, LSManager: ls, TTSManager: tts, ConjunctionManager: cm }); 36 | }); 37 | 38 | describe('header controller', () => { 39 | it('should be defined', () => { 40 | expect(headerCtrl) 41 | .toBeDefined(); 42 | }); 43 | }); 44 | 45 | describe('currentTab', () => { 46 | it('should be equal to recent after calling openRecent function', () => { 47 | headerCtrl.openRecent(); 48 | expect(headerCtrl.$scope.global.currentTab) 49 | .toBe('recent'); 50 | expect(headerCtrl.$scope.global.isHome) 51 | .toBe(0); 52 | }); 53 | 54 | it('should be equal to main after calling openGrid function', () => { 55 | headerCtrl.openGrid(); 56 | expect(headerCtrl.$scope.global.currentTab) 57 | .toBe('main'); 58 | }); 59 | 60 | it('should be equal to main after calling goHome function', () => { 61 | headerCtrl.goHome(); 62 | expect(headerCtrl.$scope.global.currentTab) 63 | .toBe('main'); 64 | expect(headerCtrl.$scope.global.isHome) 65 | .toBe(1); 66 | }); 67 | 68 | }); 69 | 70 | describe('pageNo', () => { 71 | it('should be equal to 0 after calling goHome function', () => { 72 | headerCtrl.goHome(); 73 | expect(headerCtrl.$scope.global.go2FirstPage()) 74 | .toBe(0); 75 | }); 76 | }); 77 | 78 | describe('currentGroup', () => { 79 | it('should be empty string after calling goHome f', () => { 80 | headerCtrl.goHome(); 81 | expect(headerCtrl.$scope.global.currentGroup) 82 | .toBe(''); 83 | }); 84 | }); 85 | 86 | describe('currentDerivable', () => { 87 | it('should be empty string after calling goHome f', () => { 88 | headerCtrl.goHome(); 89 | expect(headerCtrl.$scope.global.currentDerivable) 90 | .toBe(''); 91 | }); 92 | }); 93 | 94 | }); 95 | -------------------------------------------------------------------------------- /app/components/header/header.html: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /app/components/header/header.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import HeaderController from './header-controller'; 3 | 4 | let headerModule = angular.module('aacApp.header', []); 5 | 6 | headerModule.directive('header', function () { 7 | return { 8 | templateUrl: 'components/header/header.html' 9 | }; 10 | }); 11 | 12 | headerModule.controller('header-controller', HeaderController); 13 | 14 | export default headerModule; 15 | -------------------------------------------------------------------------------- /app/components/keyboard/keyboard-controller.js: -------------------------------------------------------------------------------- 1 | import { returnTime, updateCurrentPhraseScroll } from "js/utils"; 2 | 3 | /** 4 | * KeyboardController 5 | * @export 6 | * @class KeyboardController 7 | */ 8 | export default class KeyboardController { 9 | /** 10 | * Creates an instance of KeyboardController. 11 | * 12 | * @param {any} $scope 13 | * @param {any} $global 14 | * @param {any} $timeout 15 | * @param {TTSManager} TTSManager 16 | * 17 | * @memberOf RecentController 18 | */ 19 | constructor($scope, $global, $timeout, EventManager, TTSManager) { 20 | this.$scope = $scope; 21 | this.$scope.global = $global; 22 | this.$timeout = $timeout; 23 | this.tts = TTSManager; 24 | this.event = EventManager; 25 | this.fastTypeTimer = null; 26 | this.suggestions = false; 27 | // Initilize variables for controller. 28 | 29 | // Call controllerInit 30 | this.controllerInit(); 31 | } 32 | 33 | /** 34 | * Create or facilitate new functions for $scope or $global service. 35 | */ 36 | controllerInit() { 37 | this.phraseIndex = 0; 38 | this.suggestionList = []; 39 | } 40 | 41 | /** 42 | * Shows the keyboard 43 | */ 44 | showKeyboard() { 45 | let typeInput = document.getElementById("typeInput"); 46 | if (typeInput) { 47 | typeInput.focus(); 48 | } 49 | } 50 | /** 51 | * Removes readonly from typeInput 52 | * Triggered when type input is 53 | * Walkaround for ios autocorrect 54 | */ 55 | showKeyboardFocused(e) { 56 | let inpCarrier = document.querySelector(".inputCarrier"); 57 | let typeInput = document.getElementById("typeInput"); 58 | if (typeInput) { 59 | e.preventDefault(); 60 | e.stopPropagation(); 61 | let headerHeight = document.getElementById("header").clientHeight; 62 | let phHeight = document.querySelector(".phraseHolder").clientHeight; 63 | 64 | document.addEventListener("touchmove", function(ev) { 65 | ev.preventDefault(); 66 | }); 67 | 68 | this.$timeout(() => { 69 | e.preventDefault(); 70 | e.stopPropagation(); 71 | // inpCarrier.style.top = parseInt(window.innerHeight - (headerHeight + phHeight + 70)) + 'px'; 72 | window.scrollTo(0, 0); 73 | console.log("virtual keyboard opened!"); 74 | this.showHideSuggestions(true); 75 | }, 500); 76 | } 77 | } 78 | 79 | /** 80 | * Hides the keyboard 81 | */ 82 | hideKeyboard() { 83 | let inpCarrier = document.querySelector(".inputCarrier"); 84 | let typeInput = document.getElementById("typeInput"); 85 | if (typeInput) { 86 | document.getElementById("generalCarrier").style.height = "100%"; 87 | inpCarrier.style.top = "0px"; 88 | this.showHideSuggestions(false); 89 | } 90 | } 91 | 92 | /** 93 | * Shows/Hides the suggestionList 94 | */ 95 | showHideSuggestions(status) { 96 | let suggestionList = document.getElementById("suggestionList"); 97 | if (suggestionList) { 98 | suggestionList.style.opacity = status ? "1" : "0"; 99 | } 100 | } 101 | 102 | /** 103 | * Submits (handles) the current typeInput string content. 104 | */ 105 | submitCurrentInput() { 106 | let typeInput = document.getElementById("typeInput"); 107 | if (typeInput.value) { 108 | let inputWord = {}; 109 | inputWord.title = typeInput.value.toLocaleLowerCase(); 110 | let getFromMap = this.$scope.global.extendedSlugMap[inputWord.title]; 111 | if (getFromMap) { 112 | inputWord.slug = getFromMap; 113 | } else { 114 | inputWord.slug = inputWord.title; 115 | } 116 | let checkExist = this.checkWordInDB(inputWord.slug); 117 | inputWord.slugExist = !checkExist; 118 | 119 | if (inputWord.title.contains(" ") && !checkExist) { 120 | this.recognizeWord(inputWord.title); 121 | } else { 122 | inputWord.phraseIndex = this.phraseIndex++; 123 | this.$scope.global.pushToCurrentPhrase(inputWord); 124 | } 125 | updateCurrentPhraseScroll(); 126 | this.tts.speak(inputWord.title); 127 | let gs = this.$scope.global.gridSize; 128 | this.event.appWord(inputWord.title, gs[0], gs[1], true); 129 | typeInput.value = ""; 130 | } 131 | } 132 | 133 | /** 134 | * Handles the submission of the input via "enter" from the device 135 | */ 136 | enterSubmit($event) { 137 | if ($event.keyCode === 13) { 138 | this.submitCurrentInput(); 139 | this.suggestionList = []; 140 | } else { 141 | let typeInput = document.getElementById("typeInput"); 142 | if (typeInput) { 143 | // If there is a timeout, clear it. 144 | if (this.fastTypeTimer) { 145 | this.$timeout.cancel(this.fastTypeTimer); 146 | } 147 | this.fastTypeTimer = this.$timeout(() => { 148 | if (typeInput.value.length > 1) { 149 | this.suggestWordsByInput(typeInput.value); 150 | } else { 151 | this.suggestionList = []; 152 | this.suggestions = false; 153 | } 154 | }, 300); 155 | } 156 | } 157 | } 158 | 159 | /** 160 | * Handles, creates the data to set suggestions by, 161 | * current keyboard input. 162 | */ 163 | suggestWordsByInput(searchLetter) { 164 | this.suggestions = true; 165 | searchLetter = searchLetter.toLocaleLowerCase(); 166 | //searchLetter = searchLetter.replace(" ", "-"); 167 | // Might need replace all? 168 | this.suggestionList = this.$scope.global.extendedTitleArray.filter(word => { 169 | if (word) { 170 | return word.substring(0, searchLetter.length) == searchLetter; 171 | } 172 | }); 173 | if (this.suggestionList.length > 0) { 174 | this.suggestionList = this.suggestionList.filter( 175 | (it, i, ar) => ar.indexOf(it) === i, 176 | ); 177 | this.suggestionList.sort(this.sortByLength); 178 | } 179 | } 180 | 181 | /** 182 | * Handles actions when a suggestion symbol is clicked 183 | */ 184 | clickSuggestion(wordSlug) { 185 | let wordObj2Push = {}; 186 | wordObj2Push.title = wordSlug.replace("-", " "); 187 | wordObj2Push.slug = this.$scope.global.extendedSlugMap[wordSlug]; 188 | if (this.isVerb(wordSlug)) { 189 | wordObj2Push.type = "verb"; 190 | } 191 | wordObj2Push.phraseIndex = this.phraseIndex++; 192 | this.$scope.global.pushToCurrentPhrase(wordObj2Push); 193 | this.tts.speak(wordObj2Push.title); 194 | let typeInput = document.getElementById("typeInput"); 195 | if (typeInput) { 196 | document.getElementById("typeInput").value = ""; 197 | } 198 | this.suggestionList = []; 199 | this.suggestions = false; 200 | } 201 | 202 | /** 203 | * Recognizes the string given by the current word package before pushing. 204 | * Then pushes the pieces indivicually. 205 | */ 206 | recognizeWord(word) { 207 | // this function can be better algorithmicly! 208 | let splittedUnrecognizedWord = word.split(" "); 209 | splittedUnrecognizedWord.forEach(wordPiece => { 210 | if (wordPiece) { 211 | let wordObj2Push = {}; 212 | wordObj2Push.title = wordPiece; 213 | let getFromMap = this.$scope.global.extendedSlugMap[wordPiece]; 214 | if (getFromMap) { 215 | wordObj2Push.slug = getFromMap; 216 | } else { 217 | wordObj2Push.slug = wordPiece; 218 | } 219 | wordObj2Push.slugExist = !this.checkWordInDB(wordObj2Push.slug); 220 | wordObj2Push.phraseIndex = this.phraseIndex++; 221 | this.$scope.global.pushToCurrentPhrase(wordObj2Push); 222 | } 223 | }); 224 | } 225 | 226 | /** 227 | * Checks if the given word in the mainSlugArray 228 | */ 229 | checkWordInDB(word) { 230 | return this.$scope.global.extendedSlugArray.contains(word); 231 | } 232 | 233 | /** 234 | * Checks if the given word is a verb. 235 | */ 236 | isVerb(word) { 237 | let words = this.$scope.global.mainArray.filter(w => { 238 | return w.slug == word; 239 | }); 240 | if (words.length > 0) { 241 | if (words[0].type == "verb") { 242 | return true; 243 | } else { 244 | return false; 245 | } 246 | } else { 247 | return false; 248 | } 249 | } 250 | 251 | /** 252 | * Turn suggested word into its slug 253 | */ 254 | slugSuggest(suggestWord) { 255 | return this.$scope.global.extendedSlugMap[suggestWord]; 256 | } 257 | 258 | /** 259 | * Comparission function for .sort() prototype. 260 | */ 261 | sortByLength(a, b) { 262 | if (a.length > b.length) { 263 | return 1; 264 | } 265 | if (a.length < b.length) { 266 | return -1; 267 | } 268 | return 0; 269 | } 270 | } 271 | 272 | // Service Dependency Injection 273 | KeyboardController.$inject = [ 274 | "$scope", 275 | "$global", 276 | "$timeout", 277 | "EventManager", 278 | "TTSManager", 279 | ]; 280 | -------------------------------------------------------------------------------- /app/components/keyboard/keyboard.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
{{suggestionWord}}
6 |
7 |
8 | 9 |
10 | 12 | check_circle 13 |
14 |
15 | -------------------------------------------------------------------------------- /app/components/keyboard/keyboard.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import KeyboardController from './keyboard-controller' 3 | 4 | let keyboardModule = angular.module('aacApp.keyboard', []); 5 | 6 | keyboardModule.directive('keyboard', function () { 7 | return { 8 | templateUrl: 'components/keyboard/keyboard.html' 9 | }; 10 | }); 11 | 12 | keyboardModule.controller('keyboard-controller', KeyboardController); 13 | 14 | export default keyboardModule; 15 | -------------------------------------------------------------------------------- /app/components/phrase/phrase-controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * PhraseController 3 | * @export 4 | * @class PhraseController 5 | */ 6 | export default class PhraseController { 7 | /** 8 | * Creates an instance of PhraseController. 9 | * 10 | * @param {any} $scope 11 | * @param {any} $global 12 | * @param {any} $timeout 13 | * @param {EventManager} EventManager 14 | * @param {TTSManager} TTSManager 15 | * @param {LSManager} LSManager 16 | */ 17 | constructor($scope, $global, $timeout, EventManager, TTSManager, LSManager) { 18 | this.$scope = $scope; 19 | this.$scope.global = $global; 20 | this.$timeout = $timeout; 21 | this.events = EventManager; 22 | this.tts = TTSManager; 23 | this.ls = LSManager; 24 | // Initilize variables for controller. 25 | this.bstouchTimer = null; 26 | this.phraseTouchTimer = null; 27 | this.beforeTouch = 0; 28 | this.willActivateDraggable = true; 29 | this.activatedFirstTime = false; 30 | this.dragListener = { 31 | allowDuplicates: true, 32 | } 33 | 34 | // Call controllerInit 35 | this.controllerInit(); 36 | } 37 | /** 38 | * Create or facilitate new functions for $scope or $global service. 39 | */ 40 | controllerInit() {} 41 | 42 | /** 43 | * Remove Last Word from the current phrase. 44 | */ 45 | removeLastWord() { 46 | this.$scope.global.currentPhrase.pop(); 47 | } 48 | /** 49 | * Submit - Save to History - Play the current phrase. 50 | */ 51 | submitPhrase() { 52 | if (this.$scope.global.currentPhrase.length > 0) { 53 | let i = 0; 54 | let currentPhraseString = ''; 55 | this.$scope.currentPhraseTransition = 'cpTransition'; 56 | this.ls.addPhrase2History(this.$scope.global.currentPhrase); 57 | 58 | this.$timeout(() => this.$scope.currentPhraseTransition = '', 300); 59 | while (i < this.$scope.global.currentPhrase.length) { 60 | currentPhraseString = currentPhraseString + this.$scope.global.currentPhrase[i].title + ' '; 61 | i++; 62 | } 63 | //Send phrase as a string to otsimo.tts with spaces between words. 64 | this.tts.speak(currentPhraseString); 65 | this.events.appPhrase(currentPhraseString); 66 | } 67 | } 68 | 69 | phraseTouchStart(e, cp) { 70 | let cPhrase = (!cp) ? document.getElementById("cPhrase") : cp; 71 | if (cPhrase) { 72 | this.beforeTouch = cPhrase.scrollLeft; 73 | } 74 | 75 | this.phraseTouchTimer = this.$timeout(() => { 76 | if (Math.abs(this.beforeTouch - cPhrase.scrollLeft) > 20) { 77 | this.willActivateDraggable = false; 78 | } else { 79 | this.activatedFirstTime = true; 80 | this.activateDraggable(); 81 | } 82 | }, 1000); 83 | } 84 | 85 | phraseTouchEnd(e) { 86 | if (this.phraseTouchTimer) { 87 | this.$timeout.cancel(this.phraseTouchTimer); 88 | 89 | let status = this.phraseTouchTimer.$$state.status; 90 | if (status == 1 && this.willActivateDraggable) { 91 | this.activateDraggable(); 92 | this.willActivateDraggable = false; 93 | } else if (status == 2) { 94 | if (this.$scope.global.isDraggable) { 95 | let cPhrase = document.getElementById("cPhrase"); 96 | if (this.beforeTouch == cPhrase.scrollLeft) { 97 | this.submitPhrase(); 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | activateDraggable() { 105 | this.$scope.global.isDraggable = false; 106 | let cover = document.getElementById("symbolCover"); 107 | if (cover) { 108 | cover.style.display = "block"; 109 | } 110 | } 111 | 112 | 113 | /** 114 | * TouchStart function for backspace button. 115 | * Check backspace hold action in 500 m-secs. 116 | */ 117 | bsTouchStart() { 118 | let bsElem = document.getElementById('bs'); 119 | if (bsElem) { 120 | bsElem.style.color = this.$scope.removeHoldColor; 121 | } 122 | this.bstouchTimer = this.$timeout(() => { 123 | this.$scope.global.currentPhrase.splice(0, this.$scope.global.currentPhrase.length); 124 | }, 500); 125 | } 126 | 127 | /** 128 | * TouchEnd function for backspace button. 129 | * Clear the timeout for removing all words from current phrase. 130 | */ 131 | bsTouchEnd() { 132 | let bsElem = document.getElementById('bs'); 133 | if (bsElem) { 134 | bsElem.style.color = this.$scope.removeNormalColor; 135 | } 136 | this.$timeout.cancel(this.bstouchTimer); 137 | } 138 | } 139 | 140 | // Service Dependency Injection 141 | PhraseController.$inject = ['$scope', '$global', '$timeout', 'EventManager', 'TTSManager', 'LSManager']; 142 | -------------------------------------------------------------------------------- /app/components/phrase/phrase.html: -------------------------------------------------------------------------------- 1 |
2 |
5 |
6 | 7 |
9 |
10 | 11 |
{{word.title}}
12 |
13 | 14 |
{{word.title}}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
23 | backspace 24 |
25 |
-------------------------------------------------------------------------------- /app/components/phrase/phrase.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import PhraseController from './phrase-controller' 3 | 4 | let phraseModule = angular.module('aacApp.phrase', []); 5 | 6 | phraseModule.directive('phrase', function () { 7 | return { 8 | templateUrl: 'components/phrase/phrase.html' 9 | }; 10 | }); 11 | 12 | phraseModule.controller('phrase-controller', PhraseController); 13 | 14 | export default phraseModule; 15 | -------------------------------------------------------------------------------- /app/components/recent/recent-controller.js: -------------------------------------------------------------------------------- 1 | import { returnTime } from 'js/utils'; 2 | 3 | /** 4 | * RecentController 5 | * @export 6 | * @class RecentController 7 | */ 8 | export default class RecentController { 9 | /** 10 | * Creates an instance of RecentController. 11 | * 12 | * @param {any} $scope 13 | * @param {any} $global 14 | * @param {any} EventManager 15 | * 16 | * @memberOf RecentController 17 | */ 18 | constructor($scope, $global, EventManager, LSManager) { 19 | this.$scope = $scope; 20 | this.$scope.global = $global; 21 | this.events = EventManager; 22 | this.ls = LSManager; 23 | 24 | // Initilize variables for controller. 25 | this.bstouchTimer; 26 | 27 | // Call controllerInit 28 | this.controllerInit(); 29 | } 30 | 31 | /** 32 | * Create or facilitate new functions for $scope or $global service. 33 | */ 34 | controllerInit() { 35 | this.$scope.global.recentPhrases = this.ls.getHistoryAsArray(); 36 | // Open 'last 30 min' History interval then controller initilized. 37 | this.changeInterval(1); 38 | } 39 | 40 | /** 41 | * Loads a prior phrase from history to the current phrase. 42 | * If there is; Adds the old phrase to end of the current phrase. 43 | * @param {number} index - the index 44 | */ 45 | loadRecentPhrase(index) { 46 | let phraseHistory = this.$scope.recentPhrasesList; 47 | let phrase2Add = phraseHistory[phraseHistory.length - (index + 1)].phrase; 48 | this.$scope.global.currentPhrase = this.$scope.global.currentPhrase.concat(phrase2Add); 49 | } 50 | 51 | /** 52 | * Changes the showing history interval to stated interval. 53 | * @param {number} val The value which can be 1,2,3,4 54 | */ 55 | changeInterval(val) { 56 | // 1 -> last 30 min; 57 | // 2 -> Today 58 | // 3 -> Yesterday 59 | // 4 -> Last Week 60 | let timeH; 61 | let timeC = returnTime(); 62 | let timeL; 63 | const halfHour = 1000 * 60 * 30; 64 | const oneDay = halfHour * 2 * 24; 65 | const oneWeek = oneDay * 7; 66 | if (val === 1) { 67 | timeH = timeC; 68 | timeL = timeC - halfHour; 69 | } else if (val === 2) { 70 | timeH = timeC - halfHour; 71 | timeL = timeC - oneDay; 72 | } else if (val === 3) { 73 | timeH = timeC - oneDay; 74 | timeL = timeC - oneDay * 2; 75 | } else if (val === 4) { 76 | timeH = timeC - oneDay * 2; 77 | timeL = timeC - oneWeek; 78 | } 79 | if (this.$scope.global.recentPhrases) { 80 | this.$scope.recentPhrasesList = this.$scope.global.recentPhrases 81 | .filter((phraseRecent) => phraseRecent.time < timeH && phraseRecent.time > timeL); 82 | } 83 | this.events.appInterval(val); 84 | } 85 | } 86 | 87 | // Service Dependency Injection 88 | RecentController.$inject = ['$scope', '$global', 'EventManager', 'LSManager']; 89 | -------------------------------------------------------------------------------- /app/components/recent/recent-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | import { module, inject } from 'mocks'; 3 | import RecentController from './recent-controller'; 4 | import recentModule from './recent'; 5 | import * as CONSTANT from '../../js/constants'; 6 | import Global from '../../services/global'; 7 | import LSManager from '../../services/localstorage'; 8 | 9 | function randomString(len, charSet) { 10 | charSet = charSet || 'abcdefghijklmnopqrstuvwxyz'; 11 | let randomString = ''; 12 | for (let i = 0; i < len; i++) { 13 | let randomPoz = Math.floor(Math.random() * charSet.length); 14 | randomString += charSet.substring(randomPoz, randomPoz + 1); 15 | } 16 | return randomString; 17 | } 18 | 19 | function generateSymbol(length, normal, derivable, group) { 20 | let classes = []; 21 | if (normal) { classes.push(CONSTANT.CLASS_MAIN); } 22 | if (derivable) { classes.push(CONSTANT.CLASS_DERIVABLE); } 23 | if (group) { classes.push(CONSTANT.CLASS_GROUP); } 24 | let i = 0; 25 | let symbArray = []; 26 | while (i < length) { 27 | let randString = randomString(7); 28 | let wordObj = { 29 | title: randString, 30 | slug: randString, 31 | type: 'noun', 32 | parent: 'main' 33 | }; 34 | wordObj.class = classes[Math.floor(Math.random() * (classes.length - 1)) + 1]; 35 | symbArray.push(wordObj); 36 | i++; 37 | } 38 | return symbArray; 39 | } 40 | 41 | function generatePhraseHistory(length) { 42 | let symbPhraseArray = []; 43 | let i = 0; 44 | while (i < length) { 45 | let randString = randomString(7); 46 | let phraseObj = { 47 | phrase: generateSymbol(3, 1, 1, 0), 48 | time: 1473336150685 49 | }; 50 | symbPhraseArray.push(phraseObj); 51 | i++; 52 | } 53 | return symbPhraseArray; 54 | } 55 | 56 | describe('aacApp.phrase module', () => { 57 | beforeEach(module(recentModule.name)); 58 | 59 | let $controller; 60 | let recentCtrl; 61 | let g, event, ls; 62 | beforeEach(inject((_$controller_) => { 63 | // The injector unwraps the underscores (_) from around the parameter names when matching 64 | $controller = _$controller_; 65 | })); 66 | 67 | beforeEach(() => { 68 | g = new Global(); 69 | ls = new LSManager(); 70 | event = { appInterval: function () {} }; 71 | g.changeTab = function () {} 72 | //$controller(dependentController, {$scope:{},$global:g}); 73 | }); 74 | 75 | describe('recent controller', () => { 76 | it('should be defined', () => { 77 | recentCtrl = $controller(RecentController, { $scope: {}, $global: g, EventManager: event, LSManager: ls }); 78 | expect(recentCtrl) 79 | .toBeDefined(); 80 | }); 81 | }); 82 | 83 | describe('loadRecentPhrase', () => { 84 | it('should pushes the the prior phrase with given index', () => { 85 | g.currentPhrase = generateSymbol(2, 1, 1, 0); 86 | g.recentPhrases = generatePhraseHistory(5); 87 | recentCtrl = $controller(RecentController, { $scope: {}, $global: g, EventManager: event, LSManager: ls }); 88 | recentCtrl.$scope.recentPhrasesList = generatePhraseHistory(10); 89 | let index = 0; 90 | while (index < recentCtrl.$scope.recentPhrasesList.length) { 91 | recentCtrl.$scope.global.currentPhrase = generateSymbol(2, 1, 1, 1); 92 | recentCtrl.loadRecentPhrase(index); 93 | let cp = recentCtrl.$scope.global.currentPhrase; 94 | let addition2CurrentPhrase = cp.slice(2, cp.length); 95 | let rp = recentCtrl.$scope.recentPhrasesList; 96 | let loadedPhrase = rp[rp.length - (index + 1)].phrase; 97 | expect(JSON.stringify(addition2CurrentPhrase)) 98 | .toBe(JSON.stringify(loadedPhrase)); 99 | index++; 100 | } 101 | }); 102 | }); 103 | 104 | 105 | describe('recentPhrases', () => { 106 | it('should have at least 1 symbol element inside', () => { 107 | let i = 0; 108 | recentCtrl.$scope.global.currentPhrase = generateSymbol(2, 1, 1, 1); 109 | recentCtrl.$scope.global.recentPhrases.forEach(() => { 110 | expect(recentCtrl.$scope.global.recentPhrases[i].phrase.length) 111 | .toBeGreaterThan(0); 112 | i++; 113 | }); 114 | }); 115 | }); 116 | 117 | describe('changeInterval', () => { 118 | it('should filter recentPhrases to the given time interval', () => { 119 | g.currentPhrase = generateSymbol(2, 1, 1, 0); 120 | g.recentPhrases = generatePhraseHistory(5); 121 | recentCtrl = $controller(RecentController, { $scope: {}, $global: g, EventManager: event, LSManager: ls }); 122 | recentCtrl.$scope.recentPhrasesList = generatePhraseHistory(10); 123 | recentCtrl.changeInterval(1); 124 | expect(recentCtrl.$scope.recentPhrasesList.length) 125 | .toMatch(/\d{1,}/); 126 | recentCtrl.changeInterval(2); 127 | expect(recentCtrl.$scope.recentPhrasesList.length) 128 | .toMatch(/\d{1,}/); 129 | recentCtrl.changeInterval(3); 130 | expect(recentCtrl.$scope.recentPhrasesList.length) 131 | .toMatch(/\d{1,}/); 132 | recentCtrl.changeInterval(4); 133 | expect(recentCtrl.$scope.recentPhrasesList.length) 134 | .toMatch(/\d{1,}/); 135 | }); 136 | }); 137 | 138 | 139 | 140 | }); 141 | -------------------------------------------------------------------------------- /app/components/recent/recent.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 9 |
10 |
11 | 12 |
13 |
14 |
15 |
16 | 17 | 18 |
{{wordRecent.title}}
19 |
20 | 21 |
{{wordRecent.title}}
22 |
23 |
24 |
25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /app/components/recent/recent.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import RecentController from './recent-controller' 3 | 4 | let recentModule = angular.module('aacApp.recent', []); 5 | 6 | recentModule.directive('recent', function () { 7 | return { 8 | templateUrl: 'components/recent/recent.html' 9 | }; 10 | }); 11 | 12 | recentModule.controller('recent-controller', RecentController); 13 | 14 | export default recentModule; 15 | -------------------------------------------------------------------------------- /app/components/symbol/symbol-controller.js: -------------------------------------------------------------------------------- 1 | import { updateCurrentPhraseScroll } from "../../js/utils"; 2 | import * as CONSTANT from "../../js/constants"; 3 | 4 | /** 5 | * The SymbolController 6 | * @export 7 | * @class SymbolController 8 | */ 9 | export default class SymbolController { 10 | /** 11 | * Creates an instance of SymbolController. 12 | * 13 | * @param {any} $scope 14 | * @param {any} $http 15 | * @param {any} $timeout 16 | * @param {any} $global 17 | * @param {EventManager} EventManager 18 | * 19 | * @memberOf SymbolController 20 | */ 21 | constructor( 22 | $scope, 23 | $http, 24 | $timeout, 25 | $global, 26 | EventManager, 27 | ConjunctionManager, 28 | ) { 29 | this.$scope = $scope; 30 | this.$scope.global = $global; 31 | this.$timeout = $timeout; 32 | this.events = EventManager; 33 | this.conj = ConjunctionManager; 34 | // Initilize variables for controller. 35 | this.wordTouchTimer = null; 36 | this.currentlyHolding = null; 37 | } 38 | 39 | /** 40 | * Called when user starts touch action. 41 | * Handles hold actions if user holds on symbol 300ms 42 | * @param {Object} wordObj 43 | * @param {number} index 44 | */ 45 | wordTouchStart(wordObj, index) { 46 | this.currentlyHolding = wordObj.title; 47 | this.wordTouchTimer = this.$timeout(() => { 48 | if (wordObj.class === CONSTANT.CLASS_DERIVABLE) { 49 | this.$scope.global.currentDerivable = wordObj.slug; 50 | this.events.appDerive(wordObj.slug); 51 | this.$scope.global.changeCurrentTab(CONSTANT.TAB_DERIVABLE); 52 | } else if (wordObj.class === CONSTANT.CLASS_GROUP) { 53 | this.$scope.global.currentGroup = wordObj.title.replaceAll(" ", "-"); 54 | this.$scope.global.changeCurrentTab(CONSTANT.TAB_GROUP); 55 | /* 56 | Category animation killing in touchEnd: 57 | */ 58 | } else if (wordObj.type === "verb") { 59 | this.openVerbConjunction(wordObj); 60 | } else if (wordObj.type === "noun") { 61 | this.openNounConjunction(wordObj); 62 | } 63 | this.clickAnimStop(); 64 | }, 300); 65 | this.clickAnimStart(index); 66 | } 67 | 68 | openVerbConjunction(wordObj) { 69 | this.conjArr = []; 70 | this.conj.conjtype.forEach(c => { 71 | let conjable = { 72 | title: this.conj.conjVerb(wordObj.title, c), 73 | tence: c, 74 | }; 75 | this.conjArr.push(conjable); 76 | }); 77 | document.getElementById("verbConj").style.display = "block"; 78 | } 79 | 80 | openNounConjunction(wordObj) { 81 | this.nounConjArr = []; 82 | this.conj.nounCondition.forEach(c => { 83 | let conjable = { 84 | title: this.conj.conjNoun(wordObj.title, c), 85 | condition: c, 86 | }; 87 | this.nounConjArr.push(conjable); 88 | }); 89 | document.getElementById("nounConj").style.display = "block"; 90 | } 91 | 92 | clickConjuncted(wordTitle, tence) { 93 | let wordObj = {}; 94 | wordObj.title = wordTitle; 95 | wordObj.slug = this.$scope.global.extendedSlugMap[wordTitle]; 96 | wordObj.type = "verb"; 97 | wordObj.tence = tence; 98 | this.$scope.global.pushToCurrentPhrase(wordObj, true); 99 | updateCurrentPhraseScroll(); 100 | } 101 | 102 | clickNounConjuncted(nounTitle, condition) { 103 | let wordObj = {}; 104 | wordObj.title = nounTitle; 105 | wordObj.slug = this.$scope.global.extendedSlugMap[nounTitle]; 106 | wordObj.type = "noun"; 107 | wordObj.condition = condition; 108 | this.$scope.global.pushToCurrentPhrase(wordObj, true); 109 | updateCurrentPhraseScroll(); 110 | } 111 | 112 | closeConjMenu() { 113 | let verbMenu = document.getElementById("verbConj"); 114 | if (verbMenu) { 115 | verbMenu.style.display = "none"; 116 | } 117 | } 118 | 119 | closeNounConjMenu() { 120 | let nounMenu = document.getElementById("nounConj"); 121 | if (nounMenu) { 122 | nounMenu.style.display = "none"; 123 | } 124 | } 125 | 126 | /** 127 | * Called when user ends touch event. 128 | * Cancels the onhold actions when touch canceled before 300ms 129 | * @param {Object} wordObj 130 | * @param {number} index 131 | */ 132 | wordTouchEnd(wordObj, index) { 133 | this.$timeout.cancel(this.wordTouchTimer); 134 | if ( 135 | this.currentlyHolding === wordObj.title && 136 | wordObj.class !== CONSTANT.CLASS_GROUP 137 | ) { 138 | if (wordObj.type == "verb" || wordObj.type == "noun") { 139 | if (this.wordTouchTimer.$$state.status == 2) { 140 | this.clickWord(wordObj); 141 | } 142 | } else { 143 | this.clickWord(wordObj); 144 | } 145 | } 146 | if (wordObj.class === "group") { 147 | this.$scope.global.currentGroup = wordObj.title.replaceAll(" ", "-"); 148 | this.$scope.global.changeCurrentTab(CONSTANT.TAB_GROUP); 149 | } 150 | this.clickAnimEnd(index); 151 | } 152 | 153 | /** 154 | * Starts click animation by adding className to the symbol with given index 155 | * @param {number} index 156 | */ 157 | clickAnimStart(index) { 158 | let elem = document.getElementById("item-" + index); 159 | if (elem) { 160 | elem.className = elem.className + " gridItemClick"; 161 | } 162 | this.$scope.currentAnimIndex = index; 163 | } 164 | 165 | /** 166 | * Reverses click animation by removing className to the symbol with given index 167 | * @param {number} index 168 | */ 169 | clickAnimEnd(index) { 170 | let elem = document.getElementById("item-" + index); 171 | if (elem) { 172 | elem.className = elem.className.replace(" gridItemClick", ""); 173 | } 174 | } 175 | 176 | /** 177 | * Cancels the animation for all symbol grids. 178 | * Kills transition by adding a className (transitionKill) 179 | */ 180 | clickAnimStop() { 181 | let elem = document.getElementById("item-" + this.$scope.currentAnimIndex); 182 | if (elem) { 183 | elem.className = 184 | elem.className.replace(" gridItemClick", "") + " transitionKill"; 185 | this.$timeout( 186 | () => (elem.className = elem.className.replace(" transitionKill", "")), 187 | 101, 188 | ); 189 | } 190 | } 191 | 192 | /** 193 | * Pushes the symbol object to current phrase 194 | * Sends word's string to device's TTS Service. 195 | * @param {Object} wordObj 196 | */ 197 | clickWord(wordObj) { 198 | this.$scope.global.pushToCurrentPhrase(wordObj, true); 199 | updateCurrentPhraseScroll(); 200 | this.events.appWord( 201 | wordObj.title, 202 | this.$scope.global.gridSize[0], 203 | this.$scope.global.gridSize[1], 204 | ); 205 | } 206 | } 207 | 208 | // Service Dependency Injection 209 | SymbolController.$inject = [ 210 | "$scope", 211 | "$http", 212 | "$timeout", 213 | "$global", 214 | "EventManager", 215 | "ConjunctionManager", 216 | ]; 217 | -------------------------------------------------------------------------------- /app/components/symbol/symbol-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | import { module, inject } from 'mocks'; 3 | import SymbolController from './symbol-controller'; 4 | import GridController from '../grid/grid-controller'; 5 | import HeaderController from '../header/header-controller'; 6 | import * as CONSTANT from '../../js/constants'; 7 | import symbolModule from './symbol'; 8 | import Global from '../../services/global'; 9 | 10 | function randomString(len, charSet) { 11 | charSet = charSet || 'abcdefghijklmnopqrstuvwxyz'; 12 | let randomString = ''; 13 | for (let i = 0; i < len; i++) { 14 | let randomPoz = Math.floor(Math.random() * charSet.length); 15 | randomString += charSet.substring(randomPoz, randomPoz + 1); 16 | } 17 | return randomString; 18 | } 19 | 20 | function generateSymbol(length, normal, derivable, group) { 21 | let classes = []; 22 | if (normal) { classes.push('normal'); } 23 | if (derivable) { classes.push('derivable'); } 24 | if (group) { classes.push('group'); } 25 | let i = 0; 26 | let symbArray = []; 27 | while (i < length) { 28 | let randString = randomString(7); 29 | let wordObj = { 30 | title: randString, 31 | slug: randString, 32 | type: 'noun', 33 | parent: 'main' 34 | }; 35 | wordObj.class = classes[Math.floor(Math.random() * (classes.length - 1)) + 1]; 36 | symbArray.push(wordObj); 37 | i++; 38 | } 39 | return symbArray; 40 | } 41 | 42 | 43 | describe('aacApp.symbol module', () => { 44 | beforeEach(module(symbolModule.name)); 45 | 46 | let $controller, $timeout; 47 | let symbolCtrl; 48 | let g, tts, cm; 49 | beforeEach(inject((_$controller_, _$timeout_) => { 50 | // The injector unwraps the underscores (_) from around the parameter names when matching 51 | $controller = _$controller_; 52 | $timeout = _$timeout_; 53 | })); 54 | 55 | beforeEach(() => { 56 | g = new Global(); 57 | g.changeTab = function() {} 58 | g.currentPhrase = generateSymbol(2, 1, 1, 0); 59 | tts = { speak: function() {} }; 60 | event = { 61 | appDerive: function() {}, 62 | appWord: function() {} 63 | }; 64 | g.pushToCurrentPhrase = function(obj) { 65 | symbolCtrl.$scope.global.currentPhrase.push(obj); 66 | } 67 | cm = { 68 | englishConjunctor: function() {}, 69 | turkishConjunctor: function() {}, 70 | addPossessiveEn: function() {}, 71 | addPossessiveTr: function() {} 72 | }; 73 | symbolCtrl = $controller(SymbolController, { $scope: {}, $global: g, $timeout: $timeout, TTSManager: tts, EventManager: event, ConjunctionManager: cm }); 74 | $controller(HeaderController, { $scope: {}, $global: g }); 75 | }); 76 | 77 | describe('symbol controller', () => { 78 | it('should be defined', () => { 79 | expect(symbolCtrl) 80 | .toBeDefined(); 81 | }); 82 | }); 83 | 84 | describe('clickAnimStart', () => { 85 | it('should change the clickAnimIndex', () => { 86 | symbolCtrl.clickAnimStart(5); 87 | expect(symbolCtrl.$scope.currentAnimIndex) 88 | .toBe(5); 89 | }); 90 | }); 91 | 92 | describe('clickWord', () => { 93 | it('should add the given symbol object to currentPhrase', () => { 94 | let word2add = { 95 | title: 'newWord', 96 | slug: 'newWord' 97 | }; 98 | symbolCtrl.clickWord(word2add); 99 | let cp = symbolCtrl.$scope.global.currentPhrase; 100 | expect(JSON.stringify(cp[cp.length - 1])) 101 | .toBe(JSON.stringify(word2add)); 102 | }); 103 | }); 104 | 105 | describe('wordTouchStart', () => { 106 | let dummyWord1 = { 107 | title: 'dummyWord1', 108 | slug: 'dummyWord1', 109 | type: 'naun', 110 | class: 'derive', 111 | parent: 'main', 112 | style: 'gridType-noun' 113 | }; 114 | let dummyWord2 = { 115 | title: 'dummyWord2', 116 | slug: 'dummyWord2', 117 | type: 'naun', 118 | class: 'group', 119 | parent: 'main', 120 | style: 'gridType-group' 121 | }; 122 | it('should start the when timeout flushed get in derivable page if given symbol is derivable', () => { 123 | symbolCtrl.wordTouchStart(dummyWord1, 0); 124 | $timeout.flush(); 125 | expect(symbolCtrl.$scope.global.currentTab) 126 | .toBe(CONSTANT.TAB_MAIN); 127 | expect(symbolCtrl.$scope.currentAnimIndex) 128 | .toBe(0); 129 | 130 | symbolCtrl.wordTouchStart(dummyWord2, 3); 131 | $timeout.flush(); 132 | expect(symbolCtrl.$scope.global.currentTab) 133 | .toBe(CONSTANT.TAB_MAIN); 134 | expect(symbolCtrl.$scope.currentAnimIndex) 135 | .toBe(3); 136 | 137 | }); 138 | 139 | it('should change currentDerivable to its title if given symbol is derivable', () => { 140 | symbolCtrl.wordTouchStart(dummyWord1, 8); 141 | $timeout.flush(); 142 | expect(symbolCtrl.$scope.global.currentDerivable) 143 | .toBe('dummyWord1'); 144 | expect(symbolCtrl.$scope.currentAnimIndex) 145 | .toBe(8); 146 | }); 147 | 148 | it('should change currentGroup to its title if given symbol is group', () => { 149 | symbolCtrl.wordTouchStart(dummyWord2, 4); 150 | $timeout.flush(); 151 | expect(symbolCtrl.$scope.global.currentGroup) 152 | .toBe('dummyWord2'); 153 | expect(symbolCtrl.$scope.currentAnimIndex) 154 | .toBe(4); 155 | }); 156 | }); 157 | 158 | 159 | 160 | describe('wordTouchStart', () => { 161 | let dummyWord1 = { 162 | title: 'dummyWord1', 163 | slug: 'dummyWord1', 164 | type: 'naun', 165 | class: 'derive', 166 | parent: 'main', 167 | style: 'gridType-noun' 168 | }; 169 | let dummyWord2 = { 170 | title: 'dummyWord2', 171 | slug: 'dummyWord2', 172 | type: 'naun', 173 | class: 'group', 174 | parent: 'main', 175 | style: 'gridType-group' 176 | }; 177 | it('should cancel the wordTouchTimer timeout ', () => { 178 | symbolCtrl.wordTouchStart(dummyWord1, 4); 179 | expect(symbolCtrl.wordTouchTimer.$$state.status) 180 | .toBe(0); 181 | symbolCtrl.wordTouchEnd(dummyWord1, 4); 182 | // Flush timeout if there is any 183 | expect(symbolCtrl.wordTouchTimer.$$state.value) 184 | .toBe('canceled'); 185 | 186 | }); 187 | it('should add the wordObject to the current phrase if obj is not group', () => { 188 | //Dummuword1 is a derivable class word. 189 | symbolCtrl.wordTouchStart(dummyWord1, 4); 190 | symbolCtrl.wordTouchEnd(dummyWord1, 4); 191 | let cp = symbolCtrl.$scope.global.currentPhrase; 192 | expect(JSON.stringify(cp[cp.length - 1])).toBe(JSON.stringify(dummyWord1)); 193 | }); 194 | it('should NOT add the wordObject to the current phrase if obj is group', () => { 195 | //Dummuword2 is a group class word. 196 | symbolCtrl.wordTouchStart(dummyWord2, 4); 197 | symbolCtrl.wordTouchEnd(dummyWord2, 4); 198 | let cp = symbolCtrl.$scope.global.currentPhrase; 199 | expect(cp.length).toBe(2); 200 | }); 201 | }); 202 | 203 | }); 204 | -------------------------------------------------------------------------------- /app/components/symbol/symbol.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | {{conj.title}}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
17 | {{conj.title}}
18 |
19 |
20 |
21 | 22 |
23 | 24 |
{{main.title}}
25 |
{{main.title}}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | -------------------------------------------------------------------------------- /app/components/symbol/symbol.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import SymbolController from './symbol-controller'; 3 | 4 | let symbolModule = angular.module('aacApp.symbol', []); 5 | 6 | symbolModule.directive('symbol', function () { 7 | return { 8 | templateUrl: 'components/symbol/symbol.html' 9 | }; 10 | }); 11 | 12 | symbolModule.controller('symbol-controller', SymbolController); 13 | 14 | export default symbolModule; 15 | -------------------------------------------------------------------------------- /app/css/font.css: -------------------------------------------------------------------------------- 1 | /* 2 | - font.css 3 | Used to initilize the font used in app. 4 | Contains latin and latin-extended version of the titillium web open source font. 5 | */ 6 | 7 | /* latin-ext */ 8 | @font-face { 9 | font-family: 'Titillium Web'; 10 | font-style: normal; 11 | font-weight: 400; 12 | src: local('Titillium Web'), local('TitilliumWeb-Regular'), url(../woff/font-latin-ext.woff) format('woff'); 13 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 14 | } 15 | /* latin */ 16 | @font-face { 17 | font-family: 'Titillium Web'; 18 | font-style: normal; 19 | font-weight: 400; 20 | src: local('Titillium Web'), local('TitilliumWeb-Regular'), url(../woff/font.woff) format('woff'); 21 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 22 | } 23 | -------------------------------------------------------------------------------- /app/css/icon.css: -------------------------------------------------------------------------------- 1 | /* 2 | - icon.css 3 | Used to initilizce the Material-icons icon font. 4 | Apache License Version 2.0. 5 | */ 6 | 7 | @font-face { 8 | font-family: 'Material Icons'; 9 | font-style: normal; 10 | font-weight: 400; 11 | src: local('Material Icons'), local('MaterialIcons-Regular'), url(../woff/icon.woff) format('woff'); 12 | } 13 | .material-icons { 14 | font-family: 'Material Icons'; 15 | font-weight: normal; 16 | font-style: normal; 17 | font-size: 24px; 18 | line-height: 1; 19 | letter-spacing: normal; 20 | text-transform: none; 21 | display: inline-block; 22 | white-space: nowrap; 23 | word-wrap: normal; 24 | direction: ltr; 25 | text-rendering: optimizeLegibility; 26 | -webkit-font-smoothing: antialiased; 27 | } -------------------------------------------------------------------------------- /app/css/norm.css: -------------------------------------------------------------------------------- 1 | /* 2 | - norm.css 3 | Used to reset any default behaviour of the environment or browser 4 | that the app is working on. 5 | */ 6 | 7 | article, aside, details, 8 | figcaption, figure, 9 | footer, header, 10 | main, menu, nav, 11 | section, summary{display:block;} 12 | audio, canvas, progress, video{display:inline-block;vertical-align:baseline;} 13 | audio:not([controls]){display:none;height:0;} 14 | [hidden], template{display:none;} 15 | a{background-color:transparent;} 16 | a:active, a:hover{outline:0;} 17 | abbr[title]{border-bottom:1px dotted;} 18 | b, strong{font-weight:bold;} 19 | dfn{font-style:italic;} 20 | h1{font-size:2em;margin:0.67em 0;} 21 | mark{background:#ff0;color:#000;} 22 | small{font-size:80%;} 23 | sub, sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;} 24 | sup{top:-0.5em;} 25 | sub{bottom:-0.25em;} 26 | img{border:0;} 27 | svg:not(:root){overflow:hidden;} 28 | figure{margin:1em 40px;} 29 | hr{box-sizing:content-box;height:0;} 30 | pre{overflow:auto;} 31 | code, kbd, pre, samp{font-family:monospace, monospace;font-size:1em;} 32 | button, input, optgroup, select, textarea{color:inherit;font:inherit;margin:0;} 33 | button{overflow:visible;} 34 | button, select{text-transform:none;} 35 | button, html input[type="button"], 36 | input[type="reset"], input[type="submit"]{-webkit-appearance:button;cursor:pointer;} 37 | button[disabled], html input[disabled]{cursor:default;} 38 | button::-moz-focus-inner, input::-moz-focus-inner{border:0;padding:0;} 39 | input{line-height:normal;} 40 | input[type="checkbox"], 41 | input[type="radio"]{box-sizing:border-box;padding:0;} 42 | input[type="number"]::-webkit-inner-spin-button, 43 | input[type="number"]::-webkit-outer-spin-button{height:auto;} 44 | input[type="search"]{-webkit-appearance:textfield;} 45 | input[type="search"]::-webkit-search-cancel-button, 46 | input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;} 47 | fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em;} 48 | legend{border:0;padding:0;} 49 | textarea{overflow:auto;} 50 | optgroup{font-weight:bold;} 51 | table{border-collapse:collapse;border-spacing:0;} 52 | td, th{padding:0;} 53 | h1, h2, h3, 54 | h4, h5, h6{padding-top:15px;color:#2E2E2E;line-height:1.15em;margin:0 0 0.4em 0;text-rendering:geometricPrecision;} 55 | h1{font-size:2.1rem;letter-spacing:-2px;text-indent:-3px;} 56 | h2{font-size:1.75rem;letter-spacing:-1px;} 57 | h3{font-size:1.5rem;} 58 | h4{font-size:1.3rem;} 59 | h5{font-size:1.2rem;} 60 | h6{font-size:1.1rem;} 61 | a{color:#4A4A4A;transition:color 0.3s ease;text-decoration:none;} 62 | a:hover{color:#111;} 63 | .clear{clear:both;} 64 | -------------------------------------------------------------------------------- /app/css/sortable.css: -------------------------------------------------------------------------------- 1 | /* ************************************** */ 2 | /* Mandatory CSS required for ng-sortable */ 3 | /* ************************************** */ 4 | 5 | .as-sortable-item, .as-sortable-placeholder { 6 | display: block; 7 | } 8 | 9 | @keyframes mymove { 10 | 0% { transform: rotateZ(-3deg); } 11 | 50% { transform: rotateZ(3deg); } 12 | 100% { transform: rotateZ(-3deg); } 13 | } 14 | 15 | .as-sortable-item { 16 | -ms-touch-action: inital; 17 | touch-action: inital; 18 | /* to disable context menu on iOS devices */ 19 | -webkit-touch-callout: none; 20 | } 21 | 22 | .as-sortable-item-handle { 23 | cursor: move; 24 | cursor: -webkit-grab; 25 | cursor: -moz-grab; 26 | } 27 | 28 | .as-sortable-placeholder { 29 | } 30 | 31 | .as-sortable-drag { 32 | position: absolute; 33 | pointer-events: none; 34 | z-index: 9999; 35 | } 36 | 37 | .as-sortable-hidden { 38 | display: none !important; 39 | } 40 | 41 | .as-sortable-un-selectable { 42 | -webkit-touch-callout: none; 43 | -webkit-user-select: none; 44 | -khtml-user-select: none; 45 | -moz-user-select: none; 46 | -ms-user-select: none; 47 | user-select: none; 48 | } 49 | 50 | .as-sortable-item, .as-sortable-placeholder { 51 | float: left; 52 | } 53 | 54 | .as-sortable-item { 55 | transform: rotateZ(3deg) scale(1); 56 | animation: mymove 300ms infinite; 57 | transition:300ms all; 58 | } 59 | .draggable-true .as-sortable-item { 60 | transform: none !important; 61 | animation: none !important; 62 | } 63 | 64 | .as-sortable-item-handle { 65 | } 66 | 67 | .as-sortable-placeholder { 68 | background-color: #eaeaea; 69 | } 70 | 71 | .as-sortable-drag { 72 | opacity: .8; 73 | } 74 | 75 | .as-sortable-dragging .as-sortable-item{ 76 | transform: scale(1.2) !important; 77 | animation: none !important; 78 | } 79 | 80 | .as-sortable-hidden { 81 | } 82 | -------------------------------------------------------------------------------- /app/data/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "settings/gridsize/title": "Picture Grid Size", 3 | "settings/gridsize/description": "The grid size of the picture panel", 4 | "settings/gridsize/keys/grid-3x2/title": "3x2 Size", 5 | "settings/gridsize/keys/grid-3x2/description": "3x2 Size", 6 | "settings/gridsize/keys/grid-3x3/title": "3x3 Size", 7 | "settings/gridsize/keys/grid-3x3/description": "3x3 Size", 8 | "settings/gridsize/keys/grid-4x3/title": "4x3 Size", 9 | "settings/gridsize/keys/grid-4x3/description": "4x3 Size", 10 | "settings/gridsize/keys/grid-5x3/title": "5x3 Size", 11 | "settings/gridsize/keys/grid-5x3/description": "5x3 Size", 12 | "settings/gridsize/keys/grid-5x4/title": "5x4 Size", 13 | "settings/gridsize/keys/grid-5x4/description": "5x4 Size", 14 | "settings/gridsize/keys/grid-6x4/title": "6x4 Size", 15 | "settings/gridsize/keys/grid-6x4/description": "6x4 Size", 16 | "settings/gridsize/keys/grid-6x5/title": "6x5 Size", 17 | "settings/gridsize/keys/grid-6x5/description": "6x5 Size", 18 | "settings/gridsize/keys/grid-8x5/title": "8x5 Size", 19 | "settings/gridsize/keys/grid-8x5/description": "8x5 Size", 20 | "settings/gridsize/keys/grid-10x7/title": "10x7 Size", 21 | "settings/gridsize/keys/grid-10x7/description": "10x7 Size", 22 | "settings/gridsize/keys/grid-11x9/title": "11x9 Size", 23 | "settings/gridsize/keys/grid-11x9/description": "11x9 Size", 24 | "aac_consent": "Send the written words and show analytics.", 25 | "loadingText": "Loading...", 26 | "generalFont": "18px", 27 | "pageText1": "Main Picture Cards", 28 | "pageText2": "Group Cards Of ", 29 | "pageText3": "Derivables Of ", 30 | "pageText4": "Recent Phrases", 31 | "pageText5": "Type With Keyboard", 32 | "startTyping": "Start Typing...", 33 | "timeIntervalText1": "Last 30 Min", 34 | "timeIntervalText2": "Today", 35 | "timeIntervalText3": "Yesterday", 36 | "timeIntervalText4": "Last Week", 37 | "previousText": "Previous", 38 | "nextText": "Next", 39 | "backText": "Go Back", 40 | "completeChangesText": "Complete Changes", 41 | "headerColor": "#3a3e4a", 42 | "removeHoldColor": "#ff0000", 43 | "removeNormalColor": "#444444", 44 | "symbolPack": "symbols/aac-en" 45 | } 46 | -------------------------------------------------------------------------------- /app/data/test-en.json: -------------------------------------------------------------------------------- 1 | { 2 | "settings/gridsize/title": "Picture Grid Size", 3 | "settings/gridsize/description": "The grid size of the picture panel", 4 | "settings/gridsize/keys/grid-3x2/title": "3x2 Size", 5 | "settings/gridsize/keys/grid-3x2/description": "3x2 Size", 6 | "settings/gridsize/keys/grid-3x3/title": "3x3 Size", 7 | "settings/gridsize/keys/grid-3x3/description": "3x3 Size", 8 | "settings/gridsize/keys/grid-4x3/title": "4x3 Size", 9 | "settings/gridsize/keys/grid-4x3/description": "4x3 Size", 10 | "settings/gridsize/keys/grid-5x3/title": "5x3 Size", 11 | "settings/gridsize/keys/grid-5x3/description": "5x3 Size", 12 | "settings/gridsize/keys/grid-5x4/title": "5x4 Size", 13 | "settings/gridsize/keys/grid-5x4/description": "5x4 Size", 14 | "settings/gridsize/keys/grid-6x4/title": "6x4 Size", 15 | "settings/gridsize/keys/grid-6x4/description": "6x4 Size", 16 | "settings/gridsize/keys/grid-6x5/title": "6x5 Size", 17 | "settings/gridsize/keys/grid-6x5/description": "6x5 Size", 18 | "settings/gridsize/keys/grid-8x5/title": "8x5 Size", 19 | "settings/gridsize/keys/grid-8x5/description": "8x5 Size", 20 | "settings/gridsize/keys/grid-10x7/title": "10x7 Size", 21 | "settings/gridsize/keys/grid-10x7/description": "10x7 Size", 22 | "settings/gridsize/keys/grid-11x9/title": "11x9 Size", 23 | "settings/gridsize/keys/grid-11x9/description": "11x9 Size", 24 | "loadingText": "Loading...", 25 | "generalFont": "18px", 26 | "pageText1": "Main Picture Cards", 27 | "pageText2": "Group Cards Of ", 28 | "pageText3": "Derivables Of ", 29 | "pageText4": "Recent Phrases", 30 | "pageText5": "Type With Keyboard", 31 | "startTyping": "Start Typing...", 32 | "timeIntervalText1": "Last 30 Min", 33 | "timeIntervalText2": "Today", 34 | "timeIntervalText3": "Yesterday", 35 | "timeIntervalText4": "Last Week", 36 | "previousText": "Previous", 37 | "nextText": "Next", 38 | "backText": "Go Back", 39 | "completeChangesText": "Complete Changes", 40 | "headerColor": "#3a3e4a", 41 | "removeHoldColor": "#ff0000", 42 | "removeNormalColor": "#444444", 43 | "symbolPack": "symbols/aac-test-en" 44 | } 45 | -------------------------------------------------------------------------------- /app/data/tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "settings/gridsize/title": "Resim Izgarası Boyutu", 3 | "settings/gridsize/description": "Resim panelinin ızgara boyutu", 4 | "settings/gridsize/keys/grid-3x2/title": "3x2 Boyutu", 5 | "settings/gridsize/keys/grid-3x2/description": "3x2 Boyutu", 6 | "settings/gridsize/keys/grid-3x3/title": "3x3 Boyutu", 7 | "settings/gridsize/keys/grid-3x3/description": "3x3 Boyutu", 8 | "settings/gridsize/keys/grid-4x3/title": "4x3 Boyutu", 9 | "settings/gridsize/keys/grid-4x3/description": "4x3 Boyutu", 10 | "settings/gridsize/keys/grid-5x3/title": "5x3 Boyutu", 11 | "settings/gridsize/keys/grid-5x3/description": "5x3 Boyutu", 12 | "settings/gridsize/keys/grid-5x4/title": "5x4 Boyutu", 13 | "settings/gridsize/keys/grid-5x4/description": "5x4 Boyutu", 14 | "settings/gridsize/keys/grid-6x4/title": "6x4 Boyutu", 15 | "settings/gridsize/keys/grid-6x4/description": "6x4 Boyutu", 16 | "settings/gridsize/keys/grid-6x5/title": "6x5 Boyutu", 17 | "settings/gridsize/keys/grid-6x5/description": "6x5 Boyutu", 18 | "settings/gridsize/keys/grid-8x5/title": "8x5 Boyutu", 19 | "settings/gridsize/keys/grid-8x5/description": "8x5 Boyutu", 20 | "settings/gridsize/keys/grid-10x7/title": "10x7 Boyutu", 21 | "settings/gridsize/keys/grid-10x7/description": "10x7 Boyutu", 22 | "settings/gridsize/keys/grid-11x9/title": "11x9 Boyutu", 23 | "settings/gridsize/keys/grid-11x9/description": "11x9 Boyutu", 24 | "aac_consent": "Yazılan kelimeleri gönder", 25 | "loadingText": "Yükleniyor...", 26 | "generalFont": "18px", 27 | "pageText1": "Tüm Resim Kartları", 28 | "pageText2": "Resim Kartı Grubu: ", 29 | "pageText3": "Türetilmiş Kartlar: ", 30 | "pageText4": "Son Cümleler", 31 | "pageText5": "Klavye ile Yazmak", 32 | "startTyping": "Yazmaya Başla...", 33 | "timeIntervalText1": "Son 30 Dk", 34 | "timeIntervalText2": "Bugün", 35 | "timeIntervalText3": "Dün", 36 | "timeIntervalText4": "Son Hafta", 37 | "previousText": "Önceki", 38 | "nextText": "Sonraki", 39 | "backText": "Geri Dön", 40 | "completeChangesText": "Değişiklikleri Kaydet", 41 | "headerColor": "#3a3e4a", 42 | "removeHoldColor": "#ff0000", 43 | "removeNormalColor": "#444444", 44 | "symbolPack": "symbols/aac-tr" 45 | } 46 | -------------------------------------------------------------------------------- /app/img/conj-en-noun/from.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 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 | -------------------------------------------------------------------------------- /app/img/conj-en-noun/in.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/img/conj-en-noun/my.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /app/img/conj-en-noun/simple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 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 | -------------------------------------------------------------------------------- /app/img/conj-en-noun/that.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 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 | 43 | 44 | -------------------------------------------------------------------------------- /app/img/conj-en-noun/the.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 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 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/img/conj-en-noun/this.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 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 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/img/conj-en-noun/to.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/img/conj-en-noun/your.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /app/img/conj-en/ableMode.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | emrKip 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Can 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/img/conj-en/certFutuTence.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | simFutuTence 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/img/conj-en/imperMode.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | emrKip 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Must 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/img/conj-en/ogrGecZam.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | conj 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/img/conj-en/possibMode.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | emrKip 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Might 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/img/conj-en/presContTence.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | conj 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/img/conj-en/simFutuTence.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | conj 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/img/conj-en/simPastTence.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | conj 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/img/conj-en/simPresTence.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 13 | 15 | 16 | 17 | 18 | 19 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/img/conj-en/subjunMode.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | subjunMode 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Should 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/img/conj-tr-noun/benim.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /app/img/conj-tr-noun/bu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 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 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/img/conj-tr-noun/de.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/img/conj-tr-noun/den.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 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 | -------------------------------------------------------------------------------- /app/img/conj-tr-noun/e.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/img/conj-tr-noun/i.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 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 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/img/conj-tr-noun/senin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /app/img/conj-tr-noun/simple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 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 | -------------------------------------------------------------------------------- /app/img/conj-tr-noun/su.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 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 | 43 | 44 | -------------------------------------------------------------------------------- /app/img/conj-tr-noun/şu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 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 | 43 | 44 | -------------------------------------------------------------------------------- /app/img/conj-tr-noun/şu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 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 | 43 | 44 | -------------------------------------------------------------------------------- /app/img/conj-tr-noun/şşşşu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 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 | 43 | 44 | -------------------------------------------------------------------------------- /app/img/conj-tr/dilKip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | Dilek 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/img/conj-tr/emrKip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | Emir 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/img/conj-tr/gelZam.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | conj 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/img/conj-tr/genZam.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 13 | 15 | 16 | 17 | 18 | 19 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/img/conj-tr/gerKip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | Gereklilik 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/img/conj-tr/gorGecZam.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | conj 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/img/conj-tr/istKip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | İstek 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/img/conj-tr/ogrGecZam.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | conj 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/img/conj-tr/simZam.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | conj 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SPEC App UI 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 76 | 77 | 78 | 79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | 92 | 93 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /app/js/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | - constants.js 3 | Holds and exports the constant variables for the whole app to use. 4 | */ 5 | 6 | export const CLASS_MAIN = 'main'; 7 | export const CLASS_DERIVABLE = 'derive'; 8 | export const CLASS_GROUP = 'group'; 9 | 10 | export const TAB_MAIN = 'main'; 11 | export const TAB_DERIVABLE = 'derivable'; 12 | export const TAB_GROUP = 'group'; 13 | export const TAB_RECENT = 'recent'; 14 | export const TAB_KEYBOARD = 'keyboard'; 15 | 16 | export const PORTRAIT = 'portrait'; 17 | export const PORTRAIT_PRIMARY = 'portrait-primary'; 18 | export const UPSIDE_DOWN = 'upside-down'; 19 | export const LANDSCAPE_PRIMARY = 'landscape-primary'; 20 | export const LANDSCAPE_LEFT = 'landscape-left'; 21 | export const LANDSCAPE_RIGHT = 'landspace-right'; 22 | -------------------------------------------------------------------------------- /app/js/init.js: -------------------------------------------------------------------------------- 1 | if ('addEventListener' in document) { 2 | document.addEventListener('DOMContentLoaded', function() { 3 | FastClick.attach(document.body); 4 | }, false); 5 | } 6 | 7 | if (!localStorage.phraseHistory) { 8 | console.log('LS: not yet initilized, firstTime load.'); 9 | localStorage.phraseHistory = '[]'; 10 | } 11 | 12 | Array.prototype.contains = function(obj) { 13 | return this.indexOf(obj) > -1; 14 | }; 15 | 16 | String.prototype.contains = function(it) { 17 | return this.indexOf(it) != -1; 18 | }; 19 | 20 | String.prototype.replaceAll = function(search, replace) { 21 | if (replace === undefined) { 22 | return this.toString(); 23 | } 24 | 25 | return this.replace(new RegExp('[' + search + ']', 'g'), replace); 26 | }; 27 | -------------------------------------------------------------------------------- /app/js/utils-test.js: -------------------------------------------------------------------------------- 1 | import * as utils from './utils' 2 | 3 | describe('returnTime', () => { 4 | it('should return an number', () => { 5 | expect(utils.returnTime()) 6 | .toBeGreaterThan(0); 7 | expect(utils.returnTime()) 8 | .toMatch(/\d{1,}/); 9 | }); 10 | }); 11 | 12 | describe('capitalize', () => { 13 | it('first letter of the returning string should be capital', () => { 14 | let text = [{ text: 'aqwesafsdg', up: 'A' }, { text: 'ıaşsdiwe', up: 'I' }, { text: '4sdf4sdvs', up: '4' }, { text: 'ğsifşd', up: 'Ğ' }]; 15 | text.forEach((t) => { 16 | expect(utils.capitalize(t.text)[0]) 17 | .toBe(t.up); 18 | }); 19 | }); 20 | }); 21 | 22 | describe('deviceType', () => { 23 | it('should return the tablet', () => { 24 | let type = utils.deviceType(1000, 600); 25 | expect(type) 26 | .toBe('tablet'); 27 | }); 28 | it('should return the phone', () => { 29 | let type = utils.deviceType(320, 700); 30 | expect(type) 31 | .toBe('phone'); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /app/js/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | -- utils.js 3 | This file contains generic functions for other javascript files to use. 4 | Like returnTime(), capitalize() etc. 5 | 6 | */ 7 | 8 | /** 9 | * Get unix time 10 | * @returns {number} 11 | */ 12 | export function returnTime() { 13 | let d = new Date(); 14 | return d.getTime(); 15 | } 16 | 17 | /** 18 | * Updates the current phrase's scroll amount 19 | * called after a new word pushed to current phrase 20 | */ 21 | export function updateCurrentPhraseScroll() { 22 | let element = document.getElementById("cPhrase"); 23 | if (element) { 24 | setTimeout(() => { 25 | element.scrollLeft = element.scrollWidth 26 | }, 1); 27 | } 28 | } 29 | 30 | /** 31 | * capitalize first letter 32 | * @param {string} str 33 | * @returns {string} 34 | */ 35 | export function capitalize(str) { 36 | return str.charAt(0).toLocaleUpperCase() + str.slice(1); 37 | } 38 | 39 | /** 40 | * Find the device the types 41 | * 42 | * @param {number} w Width 43 | * @param {number} h Height 44 | * @returns {string} the device types which can be 'phone' or 'tablet' 45 | */ 46 | export function deviceType(w, h) { 47 | let type; 48 | let total; 49 | if (w && h) { 50 | total = w + h; 51 | } else { 52 | total = window.innerWidth + window.innerHeight; 53 | } 54 | 55 | if (total < 1500) { 56 | type = "phone"; 57 | } else { 58 | type = "tablet"; 59 | } 60 | return type; 61 | } 62 | -------------------------------------------------------------------------------- /app/mocks.js: -------------------------------------------------------------------------------- 1 | if (!window.angular.mock) { 2 | require('angular-mocks') 3 | } 4 | module.exports = window.angular.mock 5 | -------------------------------------------------------------------------------- /app/otsimo.json: -------------------------------------------------------------------------------- 1 | { 2 | "unique_name": "aac", 3 | "version": "2.4.33", 4 | "main": "index.html", 5 | "licence": "GPL-v3", 6 | "languages": ["en", "tr"], 7 | "supported_orientations": ["all"], 8 | "default_language": "en", 9 | "developer_name": "Otsimo", 10 | "homepage": "https://github.com/otsimo/aac", 11 | "repository": "https://github.com/otsimo/aac.git", 12 | "authors": [ 13 | { 14 | "name": "Burak Tokak", 15 | "email": "burak@otsimo.com" 16 | } 17 | ], 18 | "exclude": [], 19 | "settings": "settings.json", 20 | "kv_path": "data", 21 | "capabilities": ["sandbox", "tts"], 22 | "labels": { 23 | "categories": "communication", 24 | "color": "#2ca8c2", 25 | "consent": "aac_consent", 26 | "consent_default": "yes", 27 | "no-game-jump": "yes" 28 | }, 29 | "device_filter": { 30 | "edition": { 31 | "exclude": ["kid"] 32 | } 33 | }, 34 | "options": { 35 | "gridsize": { 36 | "id": "gridsize", 37 | "type": "string", 38 | "default": "grid-6x4", 39 | "enum": [ 40 | "grid-3x2", 41 | "grid-3x3", 42 | "grid-4x3", 43 | "grid-5x3", 44 | "grid-5x4", 45 | "grid-6x4", 46 | "grid-6x5", 47 | "grid-8x5", 48 | "grid-10x7", 49 | "grid-11x9" 50 | ] 51 | } 52 | }, 53 | "metadata": [ 54 | { 55 | "language": "en", 56 | "visible_name": "Otsimo AAC", 57 | "summary": "Augmentative and alternative way of communication!", 58 | "description": "Otsimo AAC helps children with speech disorders to communicate with significant others by tapping on word cards.", 59 | "keywords": ["aac", "pecs", "phrase", "talking", "communication"], 60 | "logo": "i18n/logo.png", 61 | "icon": "i18n/icon.png", 62 | "images": [ 63 | "i18n/en/ss1_en_1024x768.png", 64 | "i18n/en/ss2_en_1024x768.png", 65 | "i18n/en/ss3_en_1024x768.png" 66 | ], 67 | "assets": [], 68 | "annotations": { 69 | "knowledge": "Children gain ability of self-expression by using AAC symbols. By this way they learn to communicate effectively.", 70 | "thinking-skills": "Children can develop language skills with the help of verbal labels of objects and actions.", 71 | "physical-skills": "Children can control their finger movements by pointing and tapping.", 72 | "settings/gridsize/title": "Picture Grid Size", 73 | "settings/gridsize/description": "The grid size of the picture panel", 74 | "settings/gridsize/keys/grid-3x2/title": "3x2 Size", 75 | "settings/gridsize/keys/grid-3x2/description": "3x2 Size", 76 | "settings/gridsize/keys/grid-3x3/title": "3x3 Size", 77 | "settings/gridsize/keys/grid-3x3/description": "3x3 Size", 78 | "settings/gridsize/keys/grid-4x3/title": "4x3 Size", 79 | "settings/gridsize/keys/grid-4x3/description": "4x3 Size", 80 | "settings/gridsize/keys/grid-5x3/title": "5x3 Size", 81 | "settings/gridsize/keys/grid-5x3/description": "5x3 Size", 82 | "settings/gridsize/keys/grid-5x4/title": "5x4 Size", 83 | "settings/gridsize/keys/grid-5x4/description": "5x4 Size", 84 | "settings/gridsize/keys/grid-6x4/title": "6x4 Size", 85 | "settings/gridsize/keys/grid-6x4/description": "6x4 Size", 86 | "settings/gridsize/keys/grid-6x5/title": "6x5 Size", 87 | "settings/gridsize/keys/grid-6x5/description": "6x5 Size", 88 | "settings/gridsize/keys/grid-8x5/title": "8x5 Size", 89 | "settings/gridsize/keys/grid-8x5/description": "8x5 Size", 90 | "settings/gridsize/keys/grid-10x7/title": "10x7 Size", 91 | "settings/gridsize/keys/grid-10x7/description": "10x7 Size", 92 | "settings/gridsize/keys/grid-11x9/title": "11x9 Size", 93 | "settings/gridsize/keys/grid-11x9/description": "11x9 Size" 94 | } 95 | }, 96 | { 97 | "language": "tr", 98 | "visible_name": "Otsimo AAC", 99 | "summary": "İletişimin alternatif ve yardımcı yolu!", 100 | "description": "Otsimo AAC konuşma bozukluğu olan çocukların çevresindekilerle iletişim kurmasını sağlamak amacıyla geliştirilen bir uygulamadır. Çocuklar sembollerin üzerine tıklayarak söylemek istediklerini ifade edebilirler.", 101 | "keywords": ["aac", "pecs", "cümle", "konuşma", "iletişim"], 102 | "logo": "i18n/logo.png", 103 | "icon": "i18n/icon.png", 104 | "images": [ 105 | "i18n/tr/ss1_tr_1024x768.png", 106 | "i18n/tr/ss2_tr_1024x768.png", 107 | "i18n/tr/ss3_tr_1024x768.png" 108 | ], 109 | "assets": [], 110 | "annotations": { 111 | "knowledge": "Konuşma güçlüğü çeken çocuklar AAC sembollerini kullanarak kendilerini kolayca ifade edebilirler. Bu şekilde aileleriyle etkili bir iletişim kurabilirler.", 112 | "thinking-skills": "Çocuklar sesli semboller yardımıyla objeleri ve faaliyetleri öğrenebilir.", 113 | "physical-skills": "Çocuklar parmak ile işaret etmeyi öğrenirken tıklama hareketleriyle parmak hareketlerini kontrol edebilir.", 114 | "settings/gridsize/title": "Resim Izgarası Boyutu", 115 | "settings/gridsize/description": "Resim panelinin ızgara boyutu", 116 | "settings/gridsize/keys/grid-3x2/title": "3x2 Boyutu", 117 | "settings/gridsize/keys/grid-3x2/description": "3x2 Boyutu", 118 | "settings/gridsize/keys/grid-3x3/title": "3x3 Boyutu", 119 | "settings/gridsize/keys/grid-3x3/description": "3x3 Boyutu", 120 | "settings/gridsize/keys/grid-4x3/title": "4x3 Boyutu", 121 | "settings/gridsize/keys/grid-4x3/description": "4x3 Boyutu", 122 | "settings/gridsize/keys/grid-5x3/title": "5x3 Boyutu", 123 | "settings/gridsize/keys/grid-5x3/description": "5x3 Boyutu", 124 | "settings/gridsize/keys/grid-5x4/title": "5x4 Boyutu", 125 | "settings/gridsize/keys/grid-5x4/description": "5x4 Boyutu", 126 | "settings/gridsize/keys/grid-6x4/title": "6x4 Boyutu", 127 | "settings/gridsize/keys/grid-6x4/description": "6x4 Boyutu", 128 | "settings/gridsize/keys/grid-6x5/title": "6x5 Boyutu", 129 | "settings/gridsize/keys/grid-6x5/description": "6x5 Boyutu", 130 | "settings/gridsize/keys/grid-8x5/title": "8x5 Boyutu", 131 | "settings/gridsize/keys/grid-8x5/description": "8x5 Boyutu", 132 | "settings/gridsize/keys/grid-10x7/title": "10x7 Boyutu", 133 | "settings/gridsize/keys/grid-10x7/description": "10x7 Boyutu", 134 | "settings/gridsize/keys/grid-11x9/title": "11x9 Boyutu", 135 | "settings/gridsize/keys/grid-11x9/description": "11x9 Boyutu" 136 | } 137 | } 138 | ] 139 | } 140 | -------------------------------------------------------------------------------- /app/services/conjunction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ConjunctionManager handles all events 3 | * @export 4 | * @class ConjunctionManager 5 | */ 6 | 7 | export default class ConjunctionManager { 8 | constructor() { 9 | const symbolPackPath = otsimo.kv.symbolPack; 10 | const so = symbolPackPath.replace('symbols/', ''); 11 | const pluginModule = require(`./../symbols/${so}/main.js`); 12 | this.pm = new pluginModule.default(); 13 | this.name = this.pm.name; 14 | this.conjtype = this.pm.conjtype; 15 | this.defaultConjunctor = this.pm.defaultConjunctor; 16 | this.poss = this.pm.poss; 17 | this.nounCondition = this.pm.nounCondition; 18 | } 19 | conjVerb(verb, tence, poss) { 20 | return this.pm.conjVerb(verb, tence, poss); 21 | } 22 | conjNoun(noun, type) { 23 | return this.pm.conjNoun(noun, type); 24 | } 25 | addPoss(verb, poss, tence) { 26 | return this.pm.addPoss(verb, poss, tence); 27 | } 28 | beforePushing(wordObj, currentPhrase) { 29 | return this.pm.beforePushing(wordObj, currentPhrase); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/services/event.js: -------------------------------------------------------------------------------- 1 | /** 2 | * EventManager handles all events 3 | * @export 4 | * @class EventManager 5 | */ 6 | export default class EventManager { 7 | /** 8 | * Custom Event: Sends submitted current phrase as string with white spaces (word1 word2 word3 ...). 9 | * @param {string} currentPhrase 10 | */ 11 | appPhrase(currentPhrase) { 12 | otsimo.customevent('app:phrase', {phrase: currentPhrase}); 13 | } 14 | /** 15 | * Custom Event: Sends the time interval changes in the recent tab 16 | * @param {number} timeInterval 17 | */ 18 | appInterval(timeInterval) { 19 | otsimo.customevent('app:time_interval', {recent_time_interval: timeInterval}); 20 | } 21 | /** 22 | * Custom Event: Sends the derivable word when user holds on one. 23 | * @param {string} derived 24 | */ 25 | appDerive(derived) { 26 | otsimo.customevent('app:derive', {derivative: derived}); 27 | } 28 | /** 29 | * Custom Event: Sends a word that added to current pharase list 30 | * @param {string} word the word 31 | * @param {number} x grid height 32 | * @param {number} y grid width 33 | */ 34 | appWord(word, x, y, isKeyboard) { 35 | if (isKeyboard && word.includes(" ")) { 36 | //Submitted string is a keyboard input. 37 | word.split(" ").forEach((w) => { 38 | this.appWord(w, x, y, true); 39 | }); 40 | } else { 41 | otsimo.customevent('app:word', { 42 | word: word, 43 | grid_x: x, 44 | grid_y: y, 45 | grid_xy: x + 'x' + y, 46 | is_keyboard: isKeyboard 47 | ? true 48 | : false 49 | }); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/services/global.js: -------------------------------------------------------------------------------- 1 | /* 2 | - global.js 3 | A Service for the whole app to share global variables and functions. 4 | */ 5 | 6 | export default class Global { 7 | constructor() { 8 | console.log("GLOBAL INITED"); 9 | this.currentPhrase = []; 10 | this.mainArray = []; 11 | this.extendedArray = []; 12 | this.extendedTitleArray = []; 13 | this.extendedSlugArray = []; 14 | this.extendedSlugMap = []; 15 | this.phraseIndex = 1; 16 | this.isHome = 1; 17 | this.currentTab = ''; 18 | this.currentPage = ''; 19 | this.currentDerivable = ''; 20 | this.currentGroup = ''; 21 | this.gridSize = [0, 0]; 22 | this.gridSizeStatic = [0, 0]; 23 | this.gridQuantity = 0; 24 | this.isDraggable = true; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/services/localstorage-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | import { module, inject } from 'mocks' 3 | import LSManager from './localstorage' 4 | 5 | let localstorage = new LSManager(); 6 | 7 | describe('getHistoryAsArray', () => { 8 | let word1 = {}; 9 | word1.title = 'sample1'; 10 | word1.slug = 'sample1'; 11 | word1.type = 'naun'; 12 | word1.class = 'derive'; 13 | word1.parent = 'main'; 14 | word1.style = 'gridType-noun'; 15 | let word2 = {}; 16 | word2.title = 'sample2'; 17 | word2.slug = 'sample2'; 18 | word2.type = 'naun'; 19 | word2.class = 'word'; 20 | word2.parent = 'main'; 21 | word2.style = 'gridType-noun'; 22 | let samplePhrase = [word1, word2]; 23 | beforeEach(() => { 24 | localStorage.phraseHistory = '[{"phrase":[{"title":"değil","slug":"değil","type":"noun","class":"word","parent":"main","style":"gridType-noun"},{"title":"kaplumbağa","slug":"kaplumbağa","type":"noun","class":"word","parent":"hayvanlar","style":"gridType-noun"},{"title":"aslan","slug":"aslan","type":"noun","class":"word","parent":"hayvanlar","style":"gridType-noun"},{"title":"koyun","slug":"koyun","type":"noun","class":"word","parent":"hayvanlar","style":"gridType-noun"},{"title":"kelebek","slug":"kelebek","type":"noun","class":"word","parent":"hayvanlar","style":"gridType-noun"}],"time":1473232105695},{"phrase":[{"title":"değil","slug":"değil","type":"noun","class":"word","parent":"main","style":"gridType-noun"},{"title":"babaannem","slug":"babaannem","type":"noun","class":"word","parent":"insanlar","style":"gridType-noun"}],"time":1473232819800},{"phrase":[{"title":"kar","slug":"kar","type":"noun","class":"word","parent":"doğa","style":"gridType-noun"},{"title":"deniz","slug":"deniz","type":"noun","class":"word","parent":"doğa","style":"gridType-noun"}],"time":1473233325367},{"phrase":[{"title":"o","slug":"o","type":"noun","class":"derive","parent":"main","style":"gridType-noun"},{"title":"seviyorum","slug":"seviyorum","type":"noun","class":"derive","parent":"main","style":"gridType-noun"}],"time":1473235018102},{"phrase":[{"title":"evet","slug":"evet","type":"noun","class":"word","parent":"sohbet","style":"gridType-noun"},{"title":"istiyorum","slug":"istiyorum","type":"noun","class":"derive","parent":"main","style":"gridType-noun"},{"title":"değil","slug":"değil","type":"noun","class":"word","parent":"main","style":"gridType-noun"},{"title":"gidiyorum","slug":"gidiyorum","type":"noun","class":"derive","parent":"main","style":"gridType-noun"},{"title":"omuz","slug":"omuz","type":"noun","class":"word","parent":"vücut","style":"gridType-noun"},{"title":"kulak","slug":"kulak","type":"noun","class":"word","parent":"vücut","style":"gridType-noun"}],"time":1473236466056},{"phrase":[{"title":"o","slug":"o","type":"noun","class":"derive","parent":"main","style":"gridType-noun"},{"title":"seviyorum","slug":"seviyorum","type":"noun","class":"derive","parent":"main","style":"gridType-noun"},{"title":"diş macunu","slug":"diş-macunu","type":"noun","class":"word","parent":"eşyalar","style":"gridType-noun"}],"time":1473236841817},{"phrase":[{"title":"iyi geceler","slug":"iyi-geceler","type":"noun","class":"word","parent":"sohbet","style":"gridType-noun"},{"title":"günaydın","slug":"günaydın","type":"noun","class":"word","parent":"sohbet","style":"gridType-noun"}],"time":1473236882704},{"phrase":[{"title":"istiyorum","slug":"istiyorum","type":"noun","class":"derive","parent":"main","style":"gridType-noun"},{"title":"istiyorum","slug":"istiyorum","type":"noun","class":"derive","parent":"main","style":"gridType-noun"},{"title":"değil","slug":"değil","type":"noun","class":"word","parent":"main","style":"gridType-noun"}],"time":1473237347528},{"phrase":[{"title":"kulak","slug":"kulak","type":"noun","class":"word","parent":"vücut","style":"gridType-noun"},{"title":"ayak","slug":"ayak","type":"noun","class":"word","parent":"vücut","style":"gridType-noun"},{"title":"dudak","slug":"dudak","type":"noun","class":"word","parent":"vücut","style":"gridType-noun"},{"title":"kol","slug":"kol","type":"noun","class":"word","parent":"vücut","style":"gridType-noun"}],"time":1473238803888}]'; 25 | 26 | }); 27 | 28 | it('should return an array', () => { 29 | expect(localstorage.getHistoryAsArray() 30 | .length) 31 | .toBeGreaterThan(0); 32 | }); 33 | describe('returned array', () => { 34 | it('should have objects with phrase and time properties', () => { 35 | localstorage.getHistoryAsArray() 36 | .forEach((h) => { 37 | expect(h.phrase) 38 | .toBeDefined(); 39 | expect(h.time) 40 | .toBeDefined(); 41 | }); 42 | }); 43 | }); 44 | 45 | describe('time property', () => { 46 | it('should be a number', () => { 47 | localstorage.getHistoryAsArray() 48 | .forEach((h) => { 49 | expect(h.time) 50 | .toBeGreaterThan(0); 51 | }); 52 | }); 53 | }); 54 | 55 | describe('phrase property', () => { 56 | it('should be array of symbol objects with atleast 1 object', () => { 57 | localstorage.getHistoryAsArray() 58 | .forEach((h) => { 59 | expect(h.phrase.length) 60 | .toBeGreaterThan(0); 61 | }); 62 | }); 63 | }); 64 | 65 | describe('symbol objects in the phrase', () => { 66 | it('should have title, slug, type, class, parent and style property', () => { 67 | 68 | localstorage.getHistoryAsArray() 69 | .forEach((h) => { 70 | h.phrase.forEach((p) => { 71 | expect(p.title) 72 | .toBeDefined(); 73 | expect(p.slug) 74 | .toBeDefined(); 75 | expect(p.type) 76 | .toBeDefined(); 77 | expect(p.class) 78 | .toBeDefined(); 79 | expect(p.parent) 80 | .toBeDefined(); 81 | expect(p.style) 82 | .toBeDefined(); 83 | }); 84 | }); 85 | }); 86 | }); 87 | describe('updateHistoryAsString', () => { 88 | it('should update the localStorage.phraseHistory data to the given string', () => { 89 | localstorage.updateHistoryAsString('asdasdasd'); 90 | expect(localStorage.phraseHistory) 91 | .toBe('"asdasdasd"'); // With quotes 92 | }); 93 | }); 94 | 95 | describe('addPhrase2History', () => { 96 | it('should push a new phrase to the history', () => { 97 | localstorage.addPhrase2History(samplePhrase); 98 | expect(localstorage.getHistoryAsArray()[localstorage.getHistoryAsArray() 99 | .length - 1].phrase.length) 100 | .toBe(2); 101 | expect(localstorage.getHistoryAsArray()[localstorage.getHistoryAsArray() 102 | .length - 1].phrase[0].title) 103 | .toBe('sample1'); 104 | expect(localstorage.getHistoryAsArray()[localstorage.getHistoryAsArray() 105 | .length - 1].phrase[1].title) 106 | .toBe('sample2'); 107 | expect(localstorage.getHistoryAsArray()[localstorage.getHistoryAsArray() 108 | .length - 1].time) 109 | .toBeGreaterThan(0); 110 | }); 111 | it('should not add duplicate phrase entity', () => { 112 | localstorage.addPhrase2History(samplePhrase); 113 | localstorage.addPhrase2History(samplePhrase); 114 | expect(localstorage.getHistoryAsArray()[localstorage.getHistoryAsArray() 115 | .length - 1].phrase) 116 | .not 117 | .toBe(localstorage.getHistoryAsArray()[localstorage.getHistoryAsArray() 118 | .length - 2].phrase); 119 | }); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /app/services/localstorage.js: -------------------------------------------------------------------------------- 1 | import { returnTime } from '../js/utils' 2 | /** 3 | * LSManager handles actions with localstorage utility 4 | The recent phrase history is stored in the localstorage utility of HTML5 5 | This service contains an interface for modules to interact with localstorage. 6 | * @export 7 | * @class LSManager 8 | */ 9 | export default class LSManager { 10 | 11 | /** 12 | * Returns the phrase history as an array 13 | * @returns {array} lastPhrases 14 | */ 15 | getHistoryAsArray() { 16 | return JSON.parse(localStorage.phraseHistory); 17 | } 18 | 19 | /** 20 | * Updates the history object with given 21 | * @param {string} json string of the history 22 | */ 23 | updateHistoryAsString(tempArr) { 24 | localStorage.phraseHistory = JSON.stringify(tempArr); 25 | } 26 | 27 | /** 28 | * Adds a phrase (array of word objects) to the history 29 | * @param {array} array of word objects 30 | */ 31 | addPhrase2History(arrPhrase) { 32 | let tempHistoryArr = this.getHistoryAsArray(); 33 | let stringifyLast; 34 | if (tempHistoryArr.length > 0) { 35 | stringifyLast = JSON.stringify(tempHistoryArr[tempHistoryArr.length - 1].phrase); 36 | } 37 | if (stringifyLast !== JSON.stringify(arrPhrase)) { 38 | let obj2Push = {}; 39 | obj2Push.phrase = arrPhrase; 40 | obj2Push.time = returnTime(); 41 | tempHistoryArr.push(obj2Push); 42 | this.updateHistoryAsString(tempHistoryArr); 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/services/otsimo-handler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * OtsimoHandler handles otsimo object in the tests 3 | * @export 4 | * @class OtsimoHandler 5 | */ 6 | export default class OtsimoHandler { 7 | init() { 8 | return otsimo; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/services/tts.js: -------------------------------------------------------------------------------- 1 | import otsimo from "otsimo"; 2 | /** 3 | * TTSManager handles tts actions 4 | * 5 | * @export 6 | * @class TTSManager 7 | */ 8 | export default class TTSManager { 9 | setVoiceDriver(id) { 10 | if (!otsimo.isWKWebView && !otsimo.android) { 11 | const vd = new DummmyVoiceDriver(); 12 | otsimo.tts.setDriver(vd); 13 | } 14 | } 15 | /** 16 | * speak 17 | * @param {string} text2Speak the text to speak 18 | */ 19 | speak(text2Speak) { 20 | otsimo.tts.speak(text2Speak); 21 | } 22 | } 23 | 24 | /** 25 | * Dummy voice driver for development, using responsivevoice.com api. 26 | * 27 | * @export 28 | * @class DummmyVoiceDriver 29 | */ 30 | class DummmyVoiceDriver { 31 | speak(text) { 32 | console.log("speak", text); 33 | } 34 | 35 | setVoice(voice) { 36 | console.log("setVoice", voice); 37 | } 38 | 39 | getVoice() { 40 | return ""; 41 | } 42 | 43 | voiceList() { 44 | return []; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/symbols/aac-test-en/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "symbols": [ 3 | { 4 | "title": "i", 5 | "slug": "i", 6 | "type": "noun", 7 | "class": "derive", 8 | "parent": "main", 9 | "synonym": [] 10 | }, 11 | { 12 | "title": "we", 13 | "slug": "we", 14 | "type": "noun", 15 | "class": "word", 16 | "parent": "i", 17 | "synonym": [] 18 | }, 19 | { 20 | "title": "they", 21 | "slug": "they", 22 | "type": "noun", 23 | "class": "word", 24 | "parent": "i", 25 | "synonym": [] 26 | }, 27 | { 28 | "title": "it", 29 | "slug": "it", 30 | "type": "noun", 31 | "class": "word", 32 | "parent": "i", 33 | "synonym": [] 34 | }, 35 | { 36 | "title": "you", 37 | "slug": "you", 38 | "type": "noun", 39 | "class": "derive", 40 | "parent": "main", 41 | "synonym": [] 42 | }, 43 | { 44 | "title": "it", 45 | "slug": "it", 46 | "type": "noun", 47 | "class": "word", 48 | "parent": "you", 49 | "synonym": [] 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /app/symbols/aac-test-en/images/i.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/symbols/aac-test-en/images/it.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/symbols/aac-test-en/images/they.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/symbols/aac-test-en/images/we.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/symbols/aac-test-en/images/you.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/symbols/aac-test-en/main.js: -------------------------------------------------------------------------------- 1 | export default class LanguagePack { 2 | constructor() { 3 | this.name = "test-en"; 4 | this.poss = [""]; 5 | this.conjtype = [""]; 6 | this.nounCondition = [""]; 7 | } 8 | 9 | conjVerb(verb, tence, poss) { 10 | // Verb conjunction function 11 | } 12 | 13 | conjNoun(noun, type) { 14 | // Noun condition function 15 | } 16 | addPoss(verb, poss, tence, identifier) { 17 | // Possessor function 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/symbols/aac-test-en/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": "data.json", 3 | "images": "images", 4 | "language": "en", 5 | "voiceId": "com.apple.ttsbundle.Samantha-compact" 6 | } -------------------------------------------------------------------------------- /app/symbols/back.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/symbols/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/symbols/prev.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/woff/font-latin-ext.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otsimo-archive/aac/d213b8123748365ab5fe6ee6469b011ff9ee7e69/app/woff/font-latin-ext.woff -------------------------------------------------------------------------------- /app/woff/font.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otsimo-archive/aac/d213b8123748365ab5fe6ee6469b011ff9ee7e69/app/woff/font.woff -------------------------------------------------------------------------------- /app/woff/icon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otsimo-archive/aac/d213b8123748365ab5fe6ee6469b011ff9ee7e69/app/woff/icon.woff -------------------------------------------------------------------------------- /ci/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.11 2 | 3 | ENV OTSIMOCTL_VERSION=v0.17.2 4 | ENV OTSIMO_ROOT_DIR=/opt/otsimo-games 5 | 6 | RUN apk add --update bash ca-certificates curl wget ffmpeg nodejs yarn git python3 py3-setuptools \ 7 | && python3 -m ensurepip \ 8 | && pip3 install --upgrade awscli s3cmd python-magic \ 9 | && rm -rf /var/cache/apk/* \ 10 | && wget -O /usr/local/bin/otsimoctl https://s3.eu-central-1.amazonaws.com/repos.otsimo.com/gitlab/15371508/${OTSIMOCTL_VERSION}/otsimoctl-linux-amd64 \ 11 | && chmod +x /usr/local/bin/otsimoctl 12 | 13 | ADD start.sh /opt/otsimo-generator/start.sh 14 | WORKDIR /opt/otsimo-games 15 | 16 | ENTRYPOINT ["sh", "/opt/otsimo-generator/start.sh"] 17 | -------------------------------------------------------------------------------- /ci/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export OTSIMO_ROOT_DIR="${OTSIMO_ROOT_DIR:-/opt/otsimo-games}" 4 | 5 | echo "clone '$OTSIMO_GIT_BRANCH' branch of '$OTSIMO_GIT_REPOSITORY' repository to $OTSIMO_ROOT_DIR" 6 | git clone -b $OTSIMO_GIT_BRANCH $OTSIMO_GIT_REPOSITORY $OTSIMO_ROOT_DIR 7 | 8 | cd $OTSIMO_ROOT_DIR 9 | 10 | echo "install node packages..." 11 | yarn 12 | 13 | echo "start generator..." 14 | yarn run generator 15 | -------------------------------------------------------------------------------- /e2e-tests/protractor.conf.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | allScriptsTimeout: 11000, 3 | specs: [ 4 | '*.js' 5 | ], 6 | // capabilities: { 7 | // 'browserName': 'chrome' 8 | // }, 9 | baseUrl: 'http://localhost:3000/', 10 | framework: 'jasmine', 11 | jasmineNodeOpts: { 12 | defaultTimeoutInterval: 30000 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /e2e-tests/scenarios.js: -------------------------------------------------------------------------------- 1 | /* eslint-env protractor, jasmine */ 2 | 'use strict' 3 | 4 | /* https://github.com/angular/protractor/blob/master/docs/toc.md */ 5 | 6 | describe('my app', function () { 7 | it('should automatically redirect to /view1 when location hash/fragment is empty', function () { 8 | browser.get('index.html') 9 | expect(browser.getLocationAbsUrl()).toMatch('/view1') 10 | }) 11 | 12 | describe('view1', function () { 13 | beforeEach(function () { 14 | browser.get('index.html#!/view1') 15 | }) 16 | 17 | it('should render view1 when user navigates to /view1', function () { 18 | expect(element.all(by.css('ng-view p')).first().getText()).toMatch(/partial for view 1/) 19 | }) 20 | }) 21 | 22 | describe('view2', function () { 23 | beforeEach(function () { 24 | browser.get('index.html#!/view2') 25 | }) 26 | 27 | it('should render view2 when user navigates to /view2', function () { 28 | expect(element.all(by.css('ng-view p')).first().getText()).toMatch(/partial for view 2/) 29 | }) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /generator.js: -------------------------------------------------------------------------------- 1 | const generator = require("otsimo-game-generator"); 2 | const child_process = require("child_process"); 3 | const path = require("path"); 4 | function execPromise(command) { 5 | return new Promise((resolve, reject) => { 6 | const cmd = child_process.execFile("sh", ["-c", command], { 7 | maxBuffer: 2000 * 1024, 8 | }); 9 | cmd.stdout.pipe(process.stdout); 10 | cmd.stderr.pipe(process.stderr); 11 | cmd.on("exit", (code, signal) => { 12 | console.log("child process exited with", code, "and", signal); 13 | if (code == 0) { 14 | resolve(code); 15 | } else { 16 | reject("failed to build"); 17 | } 18 | }); 19 | }); 20 | } 21 | var gopts = [ 22 | { 23 | id: "gridsize", 24 | type: "string", 25 | default: "grid-6x4", 26 | enum: [ 27 | "grid-3x2", 28 | "grid-3x3", 29 | "grid-4x3", 30 | "grid-5x3", 31 | "grid-5x4", 32 | "grid-6x4", 33 | "grid-6x5", 34 | "grid-8x5", 35 | "grid-10x7", 36 | "grid-11x9", 37 | ], 38 | }, 39 | ]; 40 | 41 | function optionAnnotations(lang) { 42 | if (lang === "tr") { 43 | return { 44 | "settings/gridsize/title": "Resim Izgarası Boyutu", 45 | "settings/gridsize/description": "Resim panelinin ızgara boyutu", 46 | "settings/gridsize/keys/grid-3x2/title": "3x2 Boyutu", 47 | "settings/gridsize/keys/grid-3x2/description": "3x2 Boyutu", 48 | "settings/gridsize/keys/grid-3x3/title": "3x3 Boyutu", 49 | "settings/gridsize/keys/grid-3x3/description": "3x3 Boyutu", 50 | "settings/gridsize/keys/grid-4x3/title": "4x3 Boyutu", 51 | "settings/gridsize/keys/grid-4x3/description": "4x3 Boyutu", 52 | "settings/gridsize/keys/grid-5x3/title": "5x3 Boyutu", 53 | "settings/gridsize/keys/grid-5x3/description": "5x3 Boyutu", 54 | "settings/gridsize/keys/grid-5x4/title": "5x4 Boyutu", 55 | "settings/gridsize/keys/grid-5x4/description": "5x4 Boyutu", 56 | "settings/gridsize/keys/grid-6x4/title": "6x4 Boyutu", 57 | "settings/gridsize/keys/grid-6x4/description": "6x4 Boyutu", 58 | "settings/gridsize/keys/grid-6x5/title": "6x5 Boyutu", 59 | "settings/gridsize/keys/grid-6x5/description": "6x5 Boyutu", 60 | "settings/gridsize/keys/grid-8x5/title": "8x5 Boyutu", 61 | "settings/gridsize/keys/grid-8x5/description": "8x5 Boyutu", 62 | "settings/gridsize/keys/grid-10x7/title": "10x7 Boyutu", 63 | "settings/gridsize/keys/grid-10x7/description": "10x7 Boyutu", 64 | "settings/gridsize/keys/grid-11x9/title": "11x9 Boyutu", 65 | "settings/gridsize/keys/grid-11x9/description": "11x9 Boyutu", 66 | }; 67 | } 68 | return { 69 | "settings/gridsize/title": "Picture Grid Size", 70 | "settings/gridsize/description": "The grid size of the picture panel", 71 | "settings/gridsize/keys/grid-3x2/title": "3x2 Size", 72 | "settings/gridsize/keys/grid-3x2/description": "3x2 Size", 73 | "settings/gridsize/keys/grid-3x3/title": "3x3 Size", 74 | "settings/gridsize/keys/grid-3x3/description": "3x3 Size", 75 | "settings/gridsize/keys/grid-4x3/title": "4x3 Size", 76 | "settings/gridsize/keys/grid-4x3/description": "4x3 Size", 77 | "settings/gridsize/keys/grid-5x3/title": "5x3 Size", 78 | "settings/gridsize/keys/grid-5x3/description": "5x3 Size", 79 | "settings/gridsize/keys/grid-5x4/title": "5x4 Size", 80 | "settings/gridsize/keys/grid-5x4/description": "5x4 Size", 81 | "settings/gridsize/keys/grid-6x4/title": "6x4 Size", 82 | "settings/gridsize/keys/grid-6x4/description": "6x4 Size", 83 | "settings/gridsize/keys/grid-6x5/title": "6x5 Size", 84 | "settings/gridsize/keys/grid-6x5/description": "6x5 Size", 85 | "settings/gridsize/keys/grid-8x5/title": "8x5 Size", 86 | "settings/gridsize/keys/grid-8x5/description": "8x5 Size", 87 | "settings/gridsize/keys/grid-10x7/title": "10x7 Size", 88 | "settings/gridsize/keys/grid-10x7/description": "10x7 Size", 89 | "settings/gridsize/keys/grid-11x9/title": "11x9 Size", 90 | "settings/gridsize/keys/grid-11x9/description": "11x9 Size", 91 | }; 92 | } 93 | 94 | const symbolLocation = path.join(__dirname, "app", "symbols"); 95 | const getTr = process.env["AAC_TR_REPO"]; 96 | const getEn = process.env["AAC_EN_REPO"]; 97 | 98 | const command = ` 99 | cd ${symbolLocation} 100 | if [ ! -d "aac-tr" ]; then 101 | git clone ${getTr} 102 | fi 103 | if [ ! -d "aac-en" ]; then 104 | git clone ${getEn} 105 | fi 106 | `; 107 | 108 | console.log("build command", command); 109 | execPromise(command) 110 | .then(() => { 111 | generator({ 112 | outputDir: "dist", 113 | gameOptions: gopts, 114 | annotations: optionAnnotations, 115 | deviceFilter: { 116 | edition: { 117 | exclude: ["kid"], 118 | }, 119 | }, 120 | }); 121 | }) 122 | .catch(err => { 123 | console.error(err); 124 | process.exit(2); 125 | }); 126 | -------------------------------------------------------------------------------- /i18n/en/ss1_en_1024x768.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otsimo-archive/aac/d213b8123748365ab5fe6ee6469b011ff9ee7e69/i18n/en/ss1_en_1024x768.png -------------------------------------------------------------------------------- /i18n/en/ss2_en_1024x768.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otsimo-archive/aac/d213b8123748365ab5fe6ee6469b011ff9ee7e69/i18n/en/ss2_en_1024x768.png -------------------------------------------------------------------------------- /i18n/en/ss3_en_1024x768.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otsimo-archive/aac/d213b8123748365ab5fe6ee6469b011ff9ee7e69/i18n/en/ss3_en_1024x768.png -------------------------------------------------------------------------------- /i18n/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otsimo-archive/aac/d213b8123748365ab5fe6ee6469b011ff9ee7e69/i18n/icon.png -------------------------------------------------------------------------------- /i18n/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otsimo-archive/aac/d213b8123748365ab5fe6ee6469b011ff9ee7e69/i18n/logo.png -------------------------------------------------------------------------------- /i18n/tr/ss1_tr_1024x768.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otsimo-archive/aac/d213b8123748365ab5fe6ee6469b011ff9ee7e69/i18n/tr/ss1_tr_1024x768.png -------------------------------------------------------------------------------- /i18n/tr/ss2_tr_1024x768.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otsimo-archive/aac/d213b8123748365ab5fe6ee6469b011ff9ee7e69/i18n/tr/ss2_tr_1024x768.png -------------------------------------------------------------------------------- /i18n/tr/ss3_tr_1024x768.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otsimo-archive/aac/d213b8123748365ab5fe6ee6469b011ff9ee7e69/i18n/tr/ss3_tr_1024x768.png -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=759670 3 | // for the documentation about the jsconfig.json format 4 | "compilerOptions": { 5 | "target": "es6", 6 | "module": "commonjs", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "exclude": [ 10 | "node_modules", 11 | "bower_components", 12 | "jspm_packages", 13 | "tmp", 14 | "temp" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path'); 3 | const isTravis = !!process.env.TRAVIS 4 | module.exports = function (config) { 5 | let sourcePath = './app' 6 | let cr = null 7 | let browsers = ['Chrome'] 8 | if (isTravis) { 9 | cr = { 10 | type: 'text' 11 | } 12 | browsers = ['Chrome_travis_ci']; 13 | } 14 | config.set({ 15 | basePath: '.', 16 | files: [ 17 | 'karma.shim.js' 18 | ], 19 | reporters: ['progress', 'coverage'], 20 | preprocessors: { 21 | 'karma.shim.js': ['webpack'], 22 | }, 23 | webpack: { 24 | cache: true, 25 | devtool: 'inline-source-map', 26 | entry: {}, 27 | output: {}, 28 | isparta: { 29 | embedSource: true, 30 | noAutoWrap: true, 31 | // these babel options will be passed only to isparta and not to babel-loader 32 | babel: { 33 | presets: ['es2015'] 34 | } 35 | }, 36 | module: { 37 | loaders: [ 38 | { 39 | test: /\.js$/, 40 | exclude: /(node_modules)/, 41 | loader: 'babel', 42 | query: { 43 | presets: ['es2015'] 44 | } 45 | }, 46 | // transpile and instrument only testing sources with isparta 47 | { 48 | test: /\.js$/, 49 | include: /^(?!.*\-test.js$)[\/\w\.-]+$/, 50 | exclude: /(node_modules|mocks.js)/, 51 | loader: 'isparta' 52 | } 53 | ] 54 | }, 55 | resolve: { 56 | root: [sourcePath], 57 | modules: [ 58 | 'node_modules', 59 | sourcePath 60 | ] 61 | } 62 | }, 63 | coverageReporter: cr, 64 | autoWatch: true, 65 | frameworks: ['jasmine'], 66 | browsers: browsers, 67 | plugins: [ 68 | 'karma-chrome-launcher', 69 | 'karma-firefox-launcher', 70 | 'karma-jasmine', 71 | 'karma-webpack', 72 | 'karma-coverage', 73 | ], 74 | customLaunchers: { 75 | Chrome_travis_ci: { 76 | base: 'Chrome', 77 | flags: ['--no-sandbox'] 78 | } 79 | } 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /karma.shim.js: -------------------------------------------------------------------------------- 1 | // Angular Mocks workaround to the issue 2 | // "angular tried to load more than once". 3 | 4 | 'use strict' 5 | 6 | require('angular') 7 | require('angular-mocks') 8 | let context = require.context('./app', true, /-test$/) 9 | context.keys().forEach(context) 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "otsimo-symbol-aac", 3 | "version": "2.4.32", 4 | "description": "Otsimo symbol based AAC", 5 | "scripts": { 6 | "build": "NODE_ENV=production webpack", 7 | "update-symbols": "node tasks/update-symbols.js", 8 | "staging": "node tasks/version.js;rm -rf ./dist; NODE_ENV=production webpack --progress; otsimoctl -s game publish --release --dir ./dist --aws-sync", 9 | "production": "node tasks/version.js;rm -rf ./dist; NODE_ENV=production webpack --progress; otsimoctl game publish --release --dir ./dist --aws-sync", 10 | "watch": "yarn run build -- --watch", 11 | "server": "webpack-dev-server --port 3001 --host 0.0.0.0", 12 | "start": "yarn run server", 13 | "test": "karma start karma.conf.js", 14 | "test-travis": "karma start karma.conf.js --single-run", 15 | "lint": "eslint app", 16 | "update-webdriver": "webdriver-manager update", 17 | "preprotractor": "yarn run update-webdriver", 18 | "protractor": "protractor e2e-tests/protractor.conf.js", 19 | "generator": "node generator.js" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/otsimo/acc.git" 24 | }, 25 | "keywords": [ 26 | "otsimo", 27 | "aac" 28 | ], 29 | "author": "Burak Tokak ", 30 | "license": "GPL-3.0", 31 | "bugs": { 32 | "url": "https://github.com/otsimo/acc/issues" 33 | }, 34 | "homepage": "https://github.com/otsimo/acc/#readme", 35 | "standard": { 36 | "parser": "babel-eslint" 37 | }, 38 | "devDependencies": { 39 | "angular-mocks": "^1.5.7", 40 | "babel-core": "^6.11.4", 41 | "babel-eslint": "^6.1.2", 42 | "babel-loader": "^6.2.4", 43 | "babel-preset-es2015": "^6.9.0", 44 | "copy-webpack-plugin": "^3.0.1", 45 | "eslint": "^3.5.0", 46 | "eslint-config-airbnb": "^11.1.0", 47 | "eslint-plugin-import": "^1.15.0", 48 | "eslint-plugin-jsx-a11y": "^2.2.2", 49 | "eslint-plugin-react": "^6.3.0", 50 | "fs": "0.0.1-security", 51 | "isparta-loader": "^2.0.0", 52 | "jasmine-core": "^2.4.1", 53 | "jasmine-jquery": "^2.1.1", 54 | "karma": "^1.1.1", 55 | "karma-chrome-launcher": "^1.0.1", 56 | "karma-coverage": "^1.1.1", 57 | "karma-firefox-launcher": "^1.0.0", 58 | "karma-jasmine": "^1.0.2", 59 | "karma-jasmine-jquery": "^0.1.1", 60 | "karma-json-fixtures-preprocessor": "0.0.6", 61 | "karma-read-json": "^1.1.0", 62 | "karma-webpack": "^1.7.0", 63 | "ng-annotate-webpack-plugin": "^0.1.3", 64 | "protractor": "^4.0.0", 65 | "standard": "^7.1.2", 66 | "svgo": "^0.7.1", 67 | "webpack": "2.1.0-beta.19", 68 | "webpack-dev-server": "2.1.0-beta.0", 69 | "webpack-uglify-js-plugin": "^1.1.9" 70 | }, 71 | "dependencies": { 72 | "angular": "^1.6.0", 73 | "angular-touch": "^1.5.8", 74 | "async": "^2.6.1", 75 | "fastclick": "^1.0.6", 76 | "ng-sortable": "^1.3.6", 77 | "ngtouch": "^1.0.1", 78 | "otsimo": "2.6.190909", 79 | "otsimo-game-generator": "^1.1.3" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tasks/update-symbols.js: -------------------------------------------------------------------------------- 1 | var exec = require("child_process").exec; 2 | 3 | const symbolPath = "app/symbols"; 4 | // relative to /tasks dir 5 | 6 | const gitServer = "git@gitlab.com:otsimo/aac-"; 7 | // git host, which will need authentication. 8 | var langArr = ["en", "tr"]; 9 | langArr.forEach(function(lan) { 10 | exec("rm -rf aac-" + symbolPath + "/" + lan + "/", function(err, stdo, stde) { 11 | console.log("Old files removed!"); 12 | console.log("Start Cloning.."); 13 | exec("git clone " + gitServer + lan + ".git", { cwd: symbolPath }, function( 14 | err, 15 | stdo, 16 | stde, 17 | ) { 18 | console.log(stdo); 19 | console.log(lan + " Loaded!!"); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /tasks/version.js: -------------------------------------------------------------------------------- 1 | /* 2 | a nodejs script to update the otsimo game 3 | manifest version. 4 | */ 5 | 6 | var fs = require('fs'); 7 | 8 | var directory = "app/otsimo.json"; 9 | var file = JSON.parse(fs.readFileSync(directory)); 10 | var version = file['version']; 11 | var newVersion = version.split(".")[0] + "." + version.split(".")[1] + "." + parseInt(parseInt(version.split(".")[2]) + 1); 12 | 13 | file['version'] = newVersion; 14 | var fileJSON = JSON.stringify(file, null, 4); 15 | console.log("The version updated to: " + newVersion); 16 | fs.writeFileSync(directory, fileJSON); 17 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CopyWebpackPlugin = require('copy-webpack-plugin') 4 | const webpackUglifyJsPlugin = require('webpack-uglify-js-plugin'); 5 | const webpackDevConfig = require('./webpack.dev.config.js'); 6 | const webpackProdConfig = require('./webpack.prod.config.js'); 7 | 8 | let outputPath = path.resolve(__dirname, 'dist') 9 | let sourcePath = path.resolve(__dirname, 'app') 10 | let fastClickPath = path.resolve(__dirname, 'node_modules', 'fastclick', 'lib') 11 | 12 | let isProduction = process.env.NODE_ENV === 'production' 13 | 14 | if (isProduction) { 15 | module.exports = webpackProdConfig; 16 | } else { 17 | module.exports = webpackDevConfig; 18 | } 19 | -------------------------------------------------------------------------------- /webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 4 | const webpackUglifyJsPlugin = require("webpack-uglify-js-plugin"); 5 | 6 | let outputPath = path.resolve(__dirname, "dist"); 7 | let sourcePath = path.resolve(__dirname, "app"); 8 | let otsimoPath = path.resolve(__dirname, "node_modules", "otsimo"); 9 | let initPath = path.resolve(__dirname, "app", "js"); 10 | let fastClickPath = path.resolve(__dirname, "node_modules", "fastclick", "lib"); 11 | let responsivevoicePath = path.resolve(__dirname, "app", "js"); 12 | 13 | module.exports = { 14 | devtool: "source-map", 15 | cache: true, 16 | entry: { 17 | vendor: ["angular", "angular-mocks", "ngtouch"], 18 | app: path.join(sourcePath, "app.js"), 19 | }, 20 | output: { 21 | path: outputPath, 22 | filename: "[name].js", 23 | sourceMapFilename: "[name].map", 24 | chunkFilename: "[id].chunk.js", 25 | }, 26 | module: { 27 | loaders: [ 28 | { 29 | test: /\.js$/, 30 | exclude: /(node_modules)/, 31 | loader: "babel", 32 | query: { 33 | presets: ["es2015"], 34 | }, 35 | }, 36 | ], 37 | }, 38 | resolve: { 39 | root: [sourcePath], 40 | modules: ["node_modules", sourcePath], 41 | }, 42 | devServer: { 43 | progress: true, 44 | contentBase: outputPath, 45 | outputPath: outputPath, 46 | }, 47 | plugins: [ 48 | new webpack.optimize.CommonsChunkPlugin({ 49 | name: ["app", "vendor"], 50 | minChunks: Infinity, 51 | }), 52 | new CopyWebpackPlugin([ 53 | { 54 | context: sourcePath, 55 | from: "**/*.{html,css,woff,json,svg}", 56 | }, 57 | { 58 | context: initPath, 59 | from: "init.js", 60 | }, 61 | { 62 | context: fastClickPath, 63 | from: "fastclick.js", 64 | }, 65 | { 66 | context: responsivevoicePath, 67 | from: "responsivevoice.js", 68 | }, 69 | ]), 70 | new webpack.NoErrorsPlugin(), 71 | ], 72 | }; 73 | -------------------------------------------------------------------------------- /webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 4 | const webpackUglifyJsPlugin = require("webpack-uglify-js-plugin"); 5 | 6 | let outputPath = path.resolve(__dirname, "dist"); 7 | let sourcePath = path.resolve(__dirname, "app"); 8 | let initPath = path.resolve(__dirname, "app", "js"); 9 | let fastClickPath = path.resolve(__dirname, "node_modules", "fastclick", "lib"); 10 | 11 | module.exports = { 12 | devtool: null, 13 | cache: true, 14 | entry: { 15 | vendor: ["angular", "angular-mocks", "ngtouch"], 16 | app: path.join(sourcePath, "app.js"), 17 | }, 18 | output: { 19 | path: outputPath, 20 | filename: "[name].js", 21 | sourceMapFilename: "[name].map", 22 | chunkFilename: "[id].chunk.js", 23 | }, 24 | module: { 25 | loaders: [ 26 | { 27 | test: /\.js$/, 28 | exclude: /(node_modules)/, 29 | loader: "babel", 30 | query: { 31 | presets: ["es2015"], 32 | }, 33 | }, 34 | ], 35 | }, 36 | resolve: { 37 | root: [sourcePath], 38 | modules: ["node_modules", sourcePath], 39 | }, 40 | plugins: [ 41 | new webpack.optimize.CommonsChunkPlugin({ 42 | name: ["app", "vendor"], 43 | minChunks: Infinity, 44 | }), 45 | new webpackUglifyJsPlugin({ 46 | cacheFolder: path.resolve(__dirname, "public/cached_uglify/"), 47 | debug: true, 48 | minimize: true, 49 | sourceMap: false, 50 | output: { 51 | comments: true, 52 | }, 53 | compressor: { 54 | warnings: false, 55 | }, 56 | }), 57 | new CopyWebpackPlugin([ 58 | { 59 | context: sourcePath, 60 | from: "**/*.{html,css,woff,json,svg}", 61 | ignore: ["symbols/test-en/**/*.{html,css,woff,json,svg}"], 62 | }, 63 | { 64 | context: initPath, 65 | from: "init.js", 66 | }, 67 | { 68 | from: "i18n", 69 | to: "i18n", 70 | }, 71 | { 72 | context: fastClickPath, 73 | from: "fastclick.js", 74 | }, 75 | ]), 76 | new webpack.NoErrorsPlugin(), 77 | ], 78 | }; 79 | --------------------------------------------------------------------------------