├── .babelrc ├── public ├── favicon.ico ├── favicons │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── mstile-150x150.png │ ├── android-chrome-192x192.png │ ├── android-chrome-384x384.png │ ├── browserconfig.xml │ ├── manifest.json │ └── safari-pinned-tab.svg ├── manifest.json └── index.html ├── src ├── index.css ├── App.test.js ├── ui │ ├── TimePicker.js │ ├── Calendar.js │ ├── FormDaysOfTheWeek.js │ ├── Preferences.js │ ├── Constraints.js │ └── Events.js ├── index.js ├── App.css ├── cSearch.js ├── data │ ├── miect4.js │ └── miect2.js ├── schedule.test.js ├── suppClasses.test.js ├── suppClasses.js ├── registerServiceWorker.js ├── App.js ├── logo.svg └── schedule.js ├── .travis.yml ├── package.json ├── LICENSE ├── README.md └── .gitignore /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"] 3 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bnan/dhroraryus/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /public/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bnan/dhroraryus/HEAD/public/favicons/favicon.ico -------------------------------------------------------------------------------- /public/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bnan/dhroraryus/HEAD/public/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bnan/dhroraryus/HEAD/public/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bnan/dhroraryus/HEAD/public/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bnan/dhroraryus/HEAD/public/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bnan/dhroraryus/HEAD/public/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bnan/dhroraryus/HEAD/public/favicons/android-chrome-384x384.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6 4 | cache: 5 | directories: 6 | - node_modules 7 | script: 8 | - npm run test 9 | - npm run build 10 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /src/ui/TimePicker.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Datetime from 'react-datetime'; 3 | 4 | export const TimePicker = ({ onChange }) => ( 5 | 6 | ) 7 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import registerServiceWorker from './registerServiceWorker'; 6 | 7 | 8 | ReactDOM.render(, document.getElementById('root')); 9 | registerServiceWorker(); 10 | -------------------------------------------------------------------------------- /public/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #2c3e50 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App-logo-wrapper { 2 | padding: 20px; 3 | text-align: center; 4 | } 5 | 6 | .App-logo { 7 | width: 40%; 8 | fill: #2c3e50; 9 | } 10 | 11 | .form-inline .rdt { 12 | display: inline-block; 13 | } 14 | 15 | .rdt .form-control { 16 | width: 75px; 17 | } 18 | 19 | .row { 20 | margin-bottom: 20px; 21 | } 22 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /public/favicons/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dhroraryus", 3 | "icons": [ 4 | { 5 | "src": "/favicons/android-chrome-192x192.png", 6 | "sizes": "192x192", 7 | "type": "image/png" 8 | }, 9 | { 10 | "src": "/favicons/android-chrome-384x384.png", 11 | "sizes": "384x384", 12 | "type": "image/png" 13 | } 14 | ], 15 | "theme_color": "#2c3e50", 16 | "background_color": "#2c3e50", 17 | "display": "standalone" 18 | } -------------------------------------------------------------------------------- /src/ui/Calendar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import BigCalendar from 'react-big-calendar'; 4 | import 'react-big-calendar/lib/css/react-big-calendar.css'; 5 | import moment from 'moment' 6 | BigCalendar.momentLocalizer(moment); // or globalizeLocalizer 7 | 8 | export const Calendar = ({ events }) => ( 9 | 18 | ) 19 | -------------------------------------------------------------------------------- /src/ui/FormDaysOfTheWeek.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const WeekDays = ['Saturday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'] 4 | 5 | export const FormDaysOfTheWeek = ({ defaultValue, onChange }) => ( 6 | 15 | ) 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dhroraryus", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "https://bnan.github.io/dhroraryus", 6 | "dependencies": { 7 | "gh-pages": "^1.2.0", 8 | "moment": "^2.22.2", 9 | "react": "^16.4.2", 10 | "react-big-calendar": "^0.19.2", 11 | "react-datetime": "^2.15.0", 12 | "react-dom": "^16.4.2", 13 | "react-scripts": "1.1.5" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test --env=jsdom --verbose", 19 | "eject": "react-scripts eject", 20 | "predeploy": "react-scripts build", 21 | "deploy": "gh-pages -d build" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ui/Preferences.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export class Preferences extends React.Component { 4 | constructor(props) { 5 | super(props) 6 | this.state = { } 7 | } 8 | 9 | render() { 10 | return ( 11 |
12 |
Preferences
13 |
14 | {Object.keys(this.props.preferences).map((p, index) => ( 15 |
16 | this.props.handleChange(e, p)} /> {p} 17 |
18 | ))} 19 |
20 |
21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Fábio Maia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 |  Build Status 7 | 8 |

9 | 10 |

11 | Live at https://bnan.github.io/dhroraryus/ 12 |

13 | 14 | # dhroraryus 15 | 16 | Picking a schedule in universities where you have to make your own is difficult and time-consuming. We make it easy for you by using artificial intelligence techniques that generate the best schedules that fit your needs and preferences. 17 | 18 | ## Development 19 | 20 | 1. Install Node.js and npm. 21 | 22 | 2. Install dependencies. 23 | 24 | ```shell 25 | $ npm install 26 | ``` 27 | 28 | 3. Build, live-reload and open the browser at http://localhost:3000. 29 | 30 | ```shell 31 | $ npm run start 32 | ``` 33 | 34 | ## Testing 35 | 36 | ```shell 37 | $ npm run test 38 | ``` 39 | 40 | ## Deployment 41 | 42 | ```bash 43 | $ npm run deploy 44 | ``` 45 | -------------------------------------------------------------------------------- /src/cSearch.js: -------------------------------------------------------------------------------- 1 | import { WeekDate } from './suppClasses' 2 | 3 | function overlapConstraint(c1, c2) 4 | { 5 | for (const e1 of c1.instances) { 6 | for (const e2 of c2.instances) { 7 | if ((WeekDate.compare(e1.start, e2.end) < 0) && (WeekDate.compare(e2.start, e1.end) < 0)) { 8 | return true 9 | } 10 | } 11 | } 12 | } 13 | 14 | export function makeDomain(eventOptions, domain) 15 | { 16 | if ( typeof domain === 'undefined' ) 17 | domain = new Map(); 18 | 19 | let value = NaN; 20 | for (var c in eventOptions) { 21 | if (!domain.has(eventOptions[c].event.name)) { 22 | domain.set(eventOptions[c].event.name, [eventOptions[c],]); 23 | } 24 | else { 25 | value = domain.get(eventOptions[c].event.name); 26 | value.push(eventOptions[c]); 27 | domain.set(eventOptions[c].event.name, value); 28 | } 29 | } 30 | return domain 31 | } 32 | 33 | export function search(domain) 34 | { 35 | if (Array.from(domain.values()).some(v => v.length === 0)) { 36 | return null 37 | } 38 | 39 | if (Array.from(domain.values()).every(v => v.length === 1)) { 40 | return [domain] 41 | } 42 | 43 | const keys = Array.from(domain.keys()).concat().sort(e => domain.get(e).length) 44 | const key = keys.filter(k => domain.get(k).length > 1)[0] 45 | let solutions = [] 46 | 47 | for (const value of domain.get(key)) { 48 | const newDomain = new Map(domain) 49 | newDomain.set(key, [value]) 50 | for (const key2 of keys.filter(k => k !== key)) { 51 | const tmp = [] 52 | for (const c of newDomain.get(key2)) { 53 | if (!overlapConstraint(value, c)) 54 | tmp.push(c) 55 | } 56 | newDomain.set(key2, tmp) 57 | } 58 | 59 | const s = search(newDomain) 60 | 61 | if (s != null) { 62 | solutions = solutions.concat(s) 63 | } 64 | } 65 | return solutions 66 | } 67 | 68 | -------------------------------------------------------------------------------- /src/ui/Constraints.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { TimePicker } from './TimePicker'; 3 | import { FormDaysOfTheWeek } from './FormDaysOfTheWeek'; 4 | import moment from 'moment' 5 | 6 | export class Constraints extends React.Component { 7 | constructor(props) { 8 | super(props) 9 | this.state = { 10 | day: '2', // default to monday 11 | start: moment(), 12 | end: moment(), 13 | } 14 | } 15 | 16 | handleDayChange(e) { 17 | this.setState({ day: e.target.value }) 18 | } 19 | 20 | handleStartChange(e) { 21 | this.setState({ start: e }) 22 | } 23 | 24 | handleEndChange(e) { 25 | this.setState({ end: e }) 26 | } 27 | 28 | render() { 29 | return ( 30 |
31 |
Constraints
32 |
33 |
34 | {'On '} 35 |
36 | this.handleDayChange(e)} /> 37 |
38 | {' from '} 39 |
40 | this.handleStartChange(e)} /> 41 |
42 | {' to '} 43 |
44 | this.handleEndChange(e)} /> 45 |
46 | {' '} 47 | 50 |
51 |
52 |
53 | ) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/data/miect4.js: -------------------------------------------------------------------------------- 1 | import { Time, WeekDate, Event, EventOption, TimeInterval } from '../suppClasses' 2 | 3 | const EST = new Event('ES-T') 4 | const ESP = new Event('ES-P') 5 | const DDRT = new Event('DDR-T') 6 | const DDRP = new Event('DDR-P') 7 | const CRT = new Event('CR-T') 8 | const CRP = new Event('CR-P') 9 | const SDT = new Event('SD-T') 10 | const SDP = new Event('SD-P') 11 | 12 | const EST1 = new EventOption(EST, 1, [new TimeInterval(new WeekDate(2, new Time(9, 0)), new WeekDate(2, new Time(11, 0)))]) 13 | const ESP1 = new EventOption(ESP, 1, [new TimeInterval(new WeekDate(4, new Time(13, 0)), new WeekDate(4, new Time(15, 0)))]) 14 | const ESP2 = new EventOption(ESP, 2, [new TimeInterval(new WeekDate(4, new Time(11, 0)), new WeekDate(4, new Time(13, 0)))]) 15 | const ESP3 = new EventOption(ESP, 3, [new TimeInterval(new WeekDate(2, new Time(11, 0)), new WeekDate(2, new Time(13, 0)))]) 16 | const ESP4 = new EventOption(ESP, 4, [new TimeInterval(new WeekDate(2, new Time(14, 0)), new WeekDate(2, new Time(16, 0)))]) 17 | 18 | const DDRT1 = new EventOption(DDRT, 1, [new TimeInterval(new WeekDate(4, new Time(9, 0)), new WeekDate(4, new Time(11, 0)))]) 19 | const DDRP1 = new EventOption(DDRP, 1, [new TimeInterval(new WeekDate(5, new Time(11, 0)), new WeekDate(5, new Time(13, 0)))]) 20 | const DDRP2 = new EventOption(DDRP, 2, [new TimeInterval(new WeekDate(2, new Time(11, 0)), new WeekDate(2, new Time(13, 0)))]) 21 | const DDRP3 = new EventOption(DDRP, 3, [new TimeInterval(new WeekDate(5, new Time(15, 0)), new WeekDate(5, new Time(17, 0)))]) 22 | const DDRP4 = new EventOption(DDRP, 4, [new TimeInterval(new WeekDate(4, new Time(11, 0)), new WeekDate(4, new Time(13, 0)))]) 23 | 24 | const CRT1 = new EventOption(CRT, 1, [new TimeInterval(new WeekDate(3, new Time(13, 0)), new WeekDate(3, new Time(15, 0)))]) 25 | const CRP1 = new EventOption(CRP, 1, [new TimeInterval(new WeekDate(5, new Time(9, 0)), new WeekDate(5, new Time(11, 0)))]) 26 | const CRP2 = new EventOption(CRP, 2, [new TimeInterval(new WeekDate(5, new Time(11, 0)), new WeekDate(5, new Time(13, 0)))]) 27 | const CRP3 = new EventOption(CRP, 3, [new TimeInterval(new WeekDate(5, new Time(13, 0)), new WeekDate(5, new Time(15, 0)))]) 28 | 29 | const SDT1 = new EventOption(SDT, 1, [new TimeInterval(new WeekDate(3, new Time(16, 0)), new WeekDate(3, new Time(18, 0)))]) 30 | const SDP1 = new EventOption(SDP, 1, [new TimeInterval(new WeekDate(5, new Time(15, 0)), new WeekDate(5, new Time(17, 0)))]) 31 | const SDP2 = new EventOption(SDP, 2, [new TimeInterval(new WeekDate(6, new Time(9, 30)), new WeekDate(6, new Time(11, 30)))]) 32 | const SDP3 = new EventOption(SDP, 3, [new TimeInterval(new WeekDate(5, new Time(13, 0)), new WeekDate(5, new Time(15, 0)))]) 33 | const SDP4 = new EventOption(SDP, 4, [new TimeInterval(new WeekDate(5, new Time(9, 0)), new WeekDate(5, new Time(11, 0)))]) 34 | 35 | export const miect4 = [EST1, ESP1, ESP2, ESP3, ESP4, DDRT1, DDRP1, DDRP2, DDRP3, DDRP4, CRT1, CRP1, CRP2, CRP3, SDT1, SDP1, SDP2, SDP3, SDP4] 36 | 37 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Dhroraryus 32 | 33 | 34 | 37 |
38 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/schedule.test.js: -------------------------------------------------------------------------------- 1 | import { Time, WeekDate, Event, EventOption, TimeInterval } from '../src/suppClasses' 2 | import { makeDomain, search } from '../src/cSearch' 3 | import { Schedule , scheduleEvaluation } from '../src/schedule' 4 | import assert from 'assert' 5 | 6 | 7 | 8 | 9 | 10 | describe('Shedule', function(){ 11 | 12 | const ARAT = new Event('ARA-T'); 13 | const ARAP = new Event('ARA-P'); 14 | const ACAT = new Event('ACA-T'); 15 | const ACAP = new Event('ACA-P'); 16 | const SEGT = new Event('SEG-T'); 17 | const SEGP = new Event('SEG-P'); 18 | const CVT = new Event('CV-T'); 19 | const CVP = new Event('CV-P'); 20 | 21 | const ARAT1 = new EventOption(ARAT, 1,[ new TimeInterval( new WeekDate(3, new Time(13,0)) , new WeekDate(3,new Time(15,0))), ]); 22 | const ARAP3 = new EventOption(ARAP, 3,[ new TimeInterval( new WeekDate(2,new Time(17,0)) , new WeekDate(2,new Time(19,0))), ]); 23 | const ACAP4 = new EventOption(ACAP, 4,[ new TimeInterval( new WeekDate(2,new Time(14,0)) , new WeekDate(2,new Time(16,0))), ]); 24 | const ACAT1 = new EventOption(ACAT, 1,[ new TimeInterval( new WeekDate(4,new Time(9,0)) , new WeekDate(4,new Time(11,0))), ]); 25 | const CVP3 = new EventOption(CVP, 3,[ new TimeInterval( new WeekDate(5,new Time(11,0)), new WeekDate(5,new Time(13,0))), ]); 26 | const CVT1 = new EventOption(CVT, 1,[ new TimeInterval( new WeekDate(3,new Time(15,0)), new WeekDate(3,new Time(17,0))), ]); 27 | const SEGP1 = new EventOption(SEGP , 1,[ new TimeInterval( new WeekDate(5, new Time(9,0)), new WeekDate(5,new Time(11,0))), ]); 28 | let SEGT1 = new EventOption(SEGT , 1,[ new TimeInterval( new WeekDate(4,new Time(13,0)), new WeekDate(4,new Time(15,0))), ]); 29 | 30 | const solution = new Map(); 31 | solution.set(ARAT, [ARAT1]); 32 | solution.set(ARAP, [ARAP3]); 33 | solution.set(ACAT, [ACAT1]); 34 | solution.set(ACAP, [ACAP4]); 35 | solution.set(SEGT, [SEGT1]); 36 | solution.set(SEGP, [SEGP1]); 37 | solution.set(CVT, [CVT1]); 38 | solution.set(CVP, [CVP3]); 39 | 40 | let schedule = new Schedule(solution); 41 | 42 | 43 | describe('Heuristics', function(){ 44 | it('Free day preference', function(){ 45 | assert.equal(schedule.prefFreeDays, 1/5); 46 | }); 47 | it('Long weekend preference', function(){ 48 | assert.equal(schedule.prefLongWeekend, 49/(24*5)); 49 | }); 50 | it('Continuous classes preference', function(){ 51 | const prefContinuous = ((2+2)/(19-14) + (2+2)/(17-13) + (2+2)/(15-9) + (2+2)/(13-9) + 1)/5 52 | assert.equal(schedule.prefContinuous, prefContinuous); 53 | }); 54 | it('Free afternoons preference', function(){ 55 | assert.equal(schedule.prefFreeAfternoons, ((20-19)/8+(20-17)/8+(20-15)/8+(20-13)/8 + 8/8)/5); 56 | }); 57 | it('Free mornings preference', function(){ 58 | assert.equal(schedule.prefFreeMornings, (1+1+(9-7)/5+(9-7)/5 + 1)/5); 59 | }); 60 | it('Long lunch preference', function(){ 61 | assert.equal(schedule.prefLongLunch, (1+1+1+1+1)/5); 62 | }); 63 | it('Friday Morning preferece', function(){ 64 | assert.equal(schedule.prefFridayMorning, 1); 65 | }); 66 | }); 67 | }); 68 | 69 | -------------------------------------------------------------------------------- /src/suppClasses.test.js: -------------------------------------------------------------------------------- 1 | import { Time, WeekDate } from '../src/suppClasses' 2 | import assert from 'assert' 3 | 4 | describe('Time', function(){ 5 | 6 | const timeA = new Time(10, 0) 7 | const timeB = new Time(18, 0) 8 | const timeC = new Time(18, 1) 9 | const timeD = new Time(17, 59) 10 | 11 | describe('#compare()', function(){ 12 | it('Comparing ' + timeA + ' to ' + timeB + ' must return -1', function(){ 13 | assert.equal(Time.compare(timeA, timeB), -1); 14 | }); 15 | it('Comparing ' + timeB + ' to ' + timeC + ' must return -1', function(){ 16 | assert.equal(Time.compare(timeB, timeC), -1); 17 | }); 18 | 19 | it('Comparing ' + timeC + ' to ' + timeB + ' must return 1', function(){ 20 | assert.equal(Time.compare(timeC, timeB), 1); 21 | }); 22 | 23 | it('Comparing ' + timeD + ' to ' + timeC + ' must return -1', function(){ 24 | assert.equal(Time.compare(timeD, timeC), -1); 25 | }); 26 | 27 | it('Comparing ' + timeC + ' to ' + timeC + ' must return 0', function(){ 28 | assert.equal(Time.compare(timeC, timeC), 0); 29 | }); 30 | }); 31 | describe('#interval()', function(){ 32 | it('Interval between ' + timeA + ' and ' + timeB + ' must be ' + 8*60, function(){ 33 | assert.equal(Time.interval(timeA, timeB), 8*60); 34 | }); 35 | it('Interval between ' + timeB + ' and ' + timeA + ' must be ' + ((24-18)+(10))*60, function(){ 36 | assert.equal(Time.interval(timeB, timeA), ((24-18)+(10))*60); 37 | }); 38 | it('Interval between ' + timeB + ' and ' + timeC + ' must be ' + 1, function(){ 39 | assert.equal(Time.interval(timeB, timeC), 1); 40 | }); 41 | it('Interval between ' + timeC + ' and ' + timeB + ' must be ' + ((24-18)*60-1)+(18*60), function(){ 42 | assert.equal(Time.interval(timeC, timeB), ((24-18)*60-1)+(18*60)); 43 | }); 44 | }); 45 | 46 | }); 47 | 48 | describe('WeekDate', function(){ 49 | 50 | const weekDateA = new WeekDate(3, new Time(13,0)) 51 | const weekDateB = new WeekDate(4, new Time(13,0)) 52 | const weekDateC = new WeekDate(4, new Time(13,1)) 53 | const weekDateD = new WeekDate(6, new Time(13,0)) 54 | const weekDateE = new WeekDate(0, new Time(13,0)) 55 | 56 | describe('#compare()', function(){ 57 | it('Comparing ' + weekDateA + ' to ' + weekDateB + ' must return -1', function(){ 58 | assert.equal(WeekDate.compare(weekDateA, weekDateB), -1); 59 | }); 60 | it('Comparing ' + weekDateB + ' to ' + weekDateC + ' must return -1', function(){ 61 | assert.equal(WeekDate.compare(weekDateB, weekDateC), -1); 62 | }); 63 | it('Comparing ' + weekDateC + ' to ' + weekDateB + ' must return 1', function(){ 64 | assert.equal(WeekDate.compare(weekDateC, weekDateB), 1); 65 | }); 66 | it('Comparing ' + weekDateB + ' to ' + weekDateD + ' must return -1', function(){ 67 | assert.equal(WeekDate.compare(weekDateB, weekDateD), -1); 68 | }); 69 | it('Comparing ' + weekDateD + ' to ' + weekDateE + ' must return -1', function(){ 70 | assert.equal(WeekDate.compare(weekDateD, weekDateE), -1); 71 | }); 72 | it('Comparing ' + weekDateE + ' to ' + weekDateD + ' must return 1', function(){ 73 | assert.equal(WeekDate.compare(weekDateE, weekDateD), 1); 74 | }); 75 | }); 76 | }); -------------------------------------------------------------------------------- /src/suppClasses.js: -------------------------------------------------------------------------------- 1 | // Bunch of Supp Classes 2 | 3 | /* 4 | Saturday : 0 5 | Sunday : 1 6 | Monday : 2 7 | TUesday : 3 8 | Wednesday : 4 9 | Thursady : 5 10 | Friday : 6 11 | */ 12 | export class WeekDate{ 13 | constructor(day, time){ 14 | this.day = day; 15 | this.time = time; 16 | } 17 | 18 | toString(){ 19 | const weekdays = ['Saturday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursady', 'Friday'] 20 | return ("[" + weekdays[this.day] + "] " + this.time.hour + ":" + this.time.min); 21 | } 22 | /* 23 | if a < b : return -1 24 | if a == b : return 0 25 | if a > b : return 1 26 | 27 | TODO: fix this for different days 28 | */ 29 | static compare(a, b){ 30 | if (a.day === 6 && b.day === 0) 31 | return -1; 32 | if (a.day === 0 && b.day === 6) 33 | return 1; 34 | if (a.dayb.day) 37 | return 1; 38 | return Time.compare(a.time,b.time); 39 | } 40 | 41 | } 42 | 43 | export class Time{ 44 | constructor(hour, min){ 45 | this.hour = hour; 46 | this.min = min; 47 | } 48 | toString(){ 49 | return (this.hour + ":" + this.min) 50 | } 51 | static interval(start,end){ 52 | const start_mins = start.hour * 60 + start.min; 53 | const end_mins = end.hour * 60 + end.min; 54 | let duration = end_mins - start_mins; 55 | if (duration < 0) 56 | duration = duration + 1440; 57 | return duration; 58 | } 59 | static compare(a,b){ 60 | if (a.hourb.hour) 63 | return 1; 64 | if (a.minb.min) 67 | return 1; 68 | return 0; 69 | } 70 | 71 | static intersectionTime(intervalA, intervalB){ 72 | // console.log("Interval A: ", intervalA) 73 | // console.log("Interval B: ", intervalB) 74 | if ((Time.compare(intervalA[0],intervalB[1]) < 0) && (Time.compare(intervalB[0], intervalA[1]) <0)) 75 | return Time.interval(intervalB[0], intervalA[1]) 76 | if ((Time.compare(intervalB[0], intervalA[1])<0) && (Time.compare(intervalA[0], intervalB[1]) < 0)) 77 | return Time.interval(intervalA[0], intervalB[1]) 78 | return 0 79 | } 80 | 81 | 82 | } 83 | 84 | export class TimeInterval{ 85 | constructor(start, end){ 86 | this.start = start; 87 | this.end = end; 88 | } 89 | } 90 | 91 | 92 | export class Event{ 93 | constructor(name, isConstraint = false) { 94 | this.name = name; 95 | this.isConstraint = isConstraint 96 | } 97 | } 98 | 99 | export class EventOption{ 100 | constructor(event, option, instances = []) { 101 | this.event = event; 102 | this.option = option; 103 | this.instances = instances; 104 | } 105 | } 106 | 107 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (!isLocalhost) { 36 | // Is not local host. Just register service worker 37 | registerValidSW(swUrl); 38 | } else { 39 | // This is running on localhost. Lets check if a service worker still exists or not. 40 | checkValidServiceWorker(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === 'installed') { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log('New content is available; please refresh.'); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log('Content is cached for offline use.'); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error('Error during service worker registration:', error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get('content-type').indexOf('javascript') === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | 'No internet connection found. App is running in offline mode.' 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ('serviceWorker' in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/data/miect2.js: -------------------------------------------------------------------------------- 1 | import { Time, WeekDate, Event, EventOption, TimeInterval } from '../suppClasses' 2 | 3 | const ACT = new Event('AC-T') 4 | const ACP = new Event('AC-P') 5 | const MPEIT = new Event('MPEI-T') 6 | const MPEIP = new Event('MPEI-P') 7 | const MCETP = new Event('MCE-T') 8 | const MCEPL = new Event('MCE-PL') 9 | const MCEPN = new Event('MCE-PN') 10 | const P3T = new Event('P3-T') 11 | const P3P = new Event('P3-P') 12 | 13 | const ACTP3 = new EventOption(ACT, 3, [new TimeInterval(new WeekDate(2, new Time(17, 30)), new WeekDate(2, new Time(19, 0))), 14 | new TimeInterval(new WeekDate(4, new Time(13, 30)), new WeekDate(4, new Time(15, 0)))]) 15 | 16 | const ACTP4 = new EventOption(ACT, 4, [new TimeInterval(new WeekDate(2, new Time(14, 30)), new WeekDate(2, new Time(16, 0))), 17 | new TimeInterval(new WeekDate(3, new Time(14, 30)), new WeekDate(3, new Time(16, 0)))]) 18 | 19 | const ACP5a = new EventOption(ACP, 51, [new TimeInterval(new WeekDate(2, new Time(9, 0)), new WeekDate(2, new Time(11, 0)))]) 20 | const ACP5c = new EventOption(ACP, 53, [new TimeInterval(new WeekDate(2, new Time(14, 0)), new WeekDate(2, new Time(16, 0)))]) 21 | const ACP4a = new EventOption(ACP, 41, [new TimeInterval(new WeekDate(3, new Time(9, 0)), new WeekDate(3, new Time(11, 0)))]) 22 | const ACP3c = new EventOption(ACP, 33, [new TimeInterval(new WeekDate(3, new Time(11, 0)), new WeekDate(3, new Time(13, 0)))]) 23 | const ACP2 = new EventOption(ACP, 2, [new TimeInterval(new WeekDate(3, new Time(16, 0)), new WeekDate(3, new Time(18, 0)))]) 24 | const ACP1 = new EventOption(ACP, 1, [new TimeInterval(new WeekDate(4, new Time(9, 0)), new WeekDate(4, new Time(11, 0)))]) 25 | const ACP6a = new EventOption(ACP, 61, [new TimeInterval(new WeekDate(4, new Time(9, 0)), new WeekDate(4, new Time(11, 0)))]) 26 | 27 | const MPEIT1 = new EventOption(MPEIT, 1, [new TimeInterval(new WeekDate(2, new Time(16, 0)), new WeekDate(2, new Time(17, 30))), 28 | new TimeInterval(new WeekDate(3, new Time(16, 0)), new WeekDate(3, new Time(17, 30)))]) 29 | 30 | const MPEIT2 = new EventOption(MPEIT, 2, [new TimeInterval(new WeekDate(2, new Time(16, 0)), new WeekDate(2, new Time(17, 30))), 31 | new TimeInterval(new WeekDate(5, new Time(11, 0)), new WeekDate(5, new Time(12, 30)))]) 32 | 33 | const MPEIP6 = new EventOption(MPEIP, 6, [new TimeInterval(new WeekDate(2, new Time(9, 0)), new WeekDate(2, new Time(11, 0)))]) 34 | const MPEIP3 = new EventOption(MPEIP, 3, [new TimeInterval(new WeekDate(3, new Time(14, 0)), new WeekDate(3, new Time(16, 0)))]) 35 | const MPEIP7 = new EventOption(MPEIP, 7, [new TimeInterval(new WeekDate(5, new Time(9, 0)), new WeekDate(5, new Time(11, 0)))]) 36 | const MPEIP5 = new EventOption(MPEIP, 5, [new TimeInterval(new WeekDate(5, new Time(14, 0)), new WeekDate(5, new Time(16, 0)))]) 37 | 38 | const MCETA = new EventOption(MCETP, 1, [new TimeInterval(new WeekDate(2, new Time(11, 0)), new WeekDate(2, new Time(12, 30))), 39 | new TimeInterval(new WeekDate(5, new Time(9, 0)), new WeekDate(5, new Time(10, 30)))]) 40 | 41 | const MCETB = new EventOption(MCETP, 2, [new TimeInterval(new WeekDate(2, new Time(11, 0)), new WeekDate(2, new Time(12, 30))), 42 | new TimeInterval(new WeekDate(6, new Time(9, 30)), new WeekDate(6, new Time(11, 0)))]) 43 | 44 | const MCEPL1 = new EventOption(MCEPL, 1, [new TimeInterval(new WeekDate(3, new Time( 9, 0)), new WeekDate(3, new Time(11, 0)))]) 45 | const MCEPN1 = new EventOption(MCEPN, 1, [new TimeInterval(new WeekDate(3, new Time( 9, 0)), new WeekDate(3, new Time(11, 0)))]) 46 | const MCEPL2 = new EventOption(MCEPL, 2, [new TimeInterval(new WeekDate(3, new Time(11, 0)), new WeekDate(3, new Time(13, 0)))]) 47 | const MCEPL4 = new EventOption(MCEPL, 4, [new TimeInterval(new WeekDate(3, new Time(14, 0)), new WeekDate(3, new Time(16, 0)))]) 48 | const MCEPL5 = new EventOption(MCEPL, 5, [new TimeInterval(new WeekDate(3, new Time(16, 0)), new WeekDate(3, new Time(18, 0)))]) 49 | const MCEPN2 = new EventOption(MCEPN, 2, [new TimeInterval(new WeekDate(6, new Time(11, 0)), new WeekDate(6, new Time(13, 0)))]) 50 | const MCEPL3 = new EventOption(MCEPL, 3, [new TimeInterval(new WeekDate(6, new Time(11, 0)), new WeekDate(6, new Time(13, 0)))]) 51 | 52 | const P3T1 = new EventOption(P3T, 1, [new TimeInterval(new WeekDate(4, new Time(11, 0)), new WeekDate(4, new Time(12, 30))), 53 | new TimeInterval(new WeekDate(5, new Time(16, 0)), new WeekDate(5, new Time(17, 30)))]) 54 | 55 | const P3P2 = new EventOption(P3P, 2, [new TimeInterval(new WeekDate(2, new Time(14, 0)), new WeekDate(2, new Time(16, 0)))]) 56 | const P3P3 = new EventOption(P3P, 3, [new TimeInterval(new WeekDate(3, new Time(11, 0)), new WeekDate(3, new Time(13, 0)))]) 57 | const P3P5 = new EventOption(P3P, 5, [new TimeInterval(new WeekDate(4, new Time( 9, 0)), new WeekDate(4, new Time(11, 0)))]) 58 | 59 | export const miect2 = [ACTP3,ACTP4,ACP5a,ACP5c,ACP4a,ACP3c,ACP2,ACP1,ACP6a,MPEIT1,MPEIT2,MPEIP6,MPEIP3,MPEIP7,MPEIP5,MCETA,MCETB,MCEPL1,MCEPN1,MCEPL2,MCEPL4,MCEPL5,MCEPN2,MCEPL3,P3T1,P3P2,P3P3,P3P5] 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/ui/Events.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { TimePicker } from '../ui/TimePicker'; 3 | import { WeekDays, FormDaysOfTheWeek } from '../ui/FormDaysOfTheWeek'; 4 | import moment from 'moment' 5 | 6 | export class Events extends React.Component { 7 | constructor(props) { 8 | super(props) 9 | this.state = { 10 | name: '', 11 | option: '', 12 | day: '2', 13 | start: moment(), 14 | end: moment(), 15 | } 16 | } 17 | 18 | handleNameChange(e) { 19 | this.setState({ name: e.target.value }) 20 | } 21 | 22 | handleOptionChange(e) { 23 | this.setState({ option: e.target.value }) 24 | } 25 | 26 | handleDayChange(e) { 27 | this.setState({ day: e.target.value }) 28 | } 29 | 30 | handleStartChange(e) { 31 | this.setState({ start: e }) 32 | } 33 | 34 | handleEndChange(e) { 35 | this.setState({ end: e }) 36 | } 37 | 38 | render() { 39 | return ( 40 |
41 |
Events
42 |
43 |
44 |
45 | 48 | 49 | 52 |
53 | {' or manually specify '} 54 | this.handleNameChange(e)} 59 | className="mr-1 ml-1 form-control" 60 | /> 61 | {' '} 62 | this.handleOptionChange(e)} 67 | className="mr-1 form-control" 68 | /> 69 | {' on '} 70 |
71 | this.handleDayChange(e)} /> 72 |
73 | {' from '} 74 |
75 | this.handleStartChange(e)} /> 76 |
77 | {' to '} 78 |
79 | this.handleEndChange(e)} /> 80 |
81 | {' '} 82 | 85 | 86 | {this.props.events.length > 0 && 87 |
88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | {this.props.events.map((event, i) => 101 | event.instances.map((instance, j) => 102 | 103 | 104 | 105 | 106 | 107 | 112 | 113 | ))} 114 | 115 |
EventOptionFromToDelete
{event.event.name}{event.option}{WeekDays[instance.start.day]} at {instance.start.time.hour}:{instance.start.time.min}{WeekDays[instance.end.day]} at {instance.end.time.hour}:{instance.end.time.min} 108 | 111 |
116 |
117 | } 118 |
119 |
120 |
121 | ) 122 | } 123 | } 124 | 125 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Time, WeekDate, Event, EventOption, TimeInterval } from './suppClasses' 4 | import { makeDomain, search } from './cSearch' 5 | import { Schedule , scheduleEvaluation } from './schedule' 6 | //import { miect4 } from './data/miect4'; 7 | import { miect2 } from './data/miect2'; 8 | 9 | import logo from './logo.svg'; 10 | import './App.css'; 11 | 12 | import { Events } from './ui/Events' 13 | import { Constraints } from './ui/Constraints' 14 | import { Preferences } from './ui/Preferences' 15 | import { Calendar } from './ui/Calendar' 16 | 17 | class App extends React.Component { 18 | constructor(props) { 19 | super(props) 20 | this.state = { 21 | events: [], 22 | constraints: [], 23 | preferences: { 24 | 'Contiguous, unfragmented events': 0, 25 | 'Free afternoons': 0, 26 | 'Free mornings': 0, 27 | 'Long lunch breaks': 0, 28 | 'Longer weekends': 0, 29 | 'Free days': 0, 30 | 'Free friday morning for hangovers': 0, 31 | }, 32 | schedules: [], 33 | } 34 | } 35 | 36 | handleEventAdd(name, option, day, start, end) { 37 | const instance = new TimeInterval( 38 | new WeekDate(day, new Time(start.hours(), start.minutes())), new WeekDate(day, new Time(end.hours(), end.minutes())) 39 | ) 40 | 41 | const events = this.state.events.slice() 42 | const foundEvent = events.find(e => e.event.name === name && e.option === option) 43 | 44 | if (foundEvent) { 45 | foundEvent.instances = [...foundEvent.instances, instance] 46 | this.setState({ 47 | events: events 48 | }) 49 | } else { 50 | this.setState(prevState => ({ 51 | events: [...prevState.events, new EventOption(new Event(name), option, [instance])] 52 | })) 53 | } 54 | } 55 | 56 | handleOptionDelete(index) { 57 | this.setState({ 58 | events: [...this.state.events.slice(0, index), ...this.state.events.slice(index+1)] 59 | }) 60 | } 61 | 62 | handleConstraintAdd(day, start, end) { 63 | const instance = new TimeInterval( 64 | new WeekDate(day, new Time(start.hours(), start.minutes())), 65 | new WeekDate(day, new Time(end.hours(), end.minutes())) 66 | ) 67 | 68 | const event = new EventOption(new Event('Constraint', true), this.state.constraints.length+1, [instance]) 69 | 70 | this.setState(prevState => ({ 71 | events: [...prevState.events, event], 72 | constraints: [...prevState.constraints, event] 73 | })) 74 | } 75 | 76 | handlePreferenceChange(e, p) { 77 | const preferences = this.state.preferences 78 | preferences[p] = e.target.value 79 | this.setState({ preferences }) 80 | } 81 | 82 | handleGenerate() { 83 | // Compute solutions 84 | let domain = makeDomain(this.state.events) 85 | let solutions = search(domain) 86 | let schedules = solutions.map(solution => new Schedule(solution)) 87 | 88 | // Sort by preferences 89 | let weights = Object.values(this.state.preferences) 90 | schedules = schedules.sort((s1, s2) => scheduleEvaluation(s2, ...weights) - scheduleEvaluation(s1, ...weights)) 91 | 92 | // Set the events of the first 10 schedules 93 | this.setState({ 94 | schedules: schedules.slice(0, 10).map(s => s.events) 95 | }) 96 | alert("Done") 97 | } 98 | 99 | handleImport() { 100 | this.setState(prevState => ({ 101 | events: miect2 102 | })) 103 | } 104 | 105 | render() { 106 | return ( 107 |
108 |
109 | Dhroraryus 110 | beta 111 |
112 | 113 | this.handleEventAdd(name, option, day, start, end)} 116 | handleDelete={(index) => this.handleOptionDelete(index)} 117 | handleImport={() => this.handleImport()} 118 | /> 119 | 120 |
121 |
122 | this.handleConstraintAdd(day, start, end)} 125 | /> 126 |
127 | 128 |
129 | this.handlePreferenceChange(e, p)} 132 | /> 133 |
134 |
135 | 136 | 139 | 140 | {this.state.schedules.map((schedule, index) => ( 141 |
142 |
Schedule #{index+1}
143 |
144 | 148 |
149 |
150 | ))} 151 |
152 | ); 153 | } 154 | } 155 | 156 | export default App; 157 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.15, written by Peter Selinger 2001-2017 9 | 10 | 12 | 19 | 27 | 37 | 48 | 58 | 68 | 75 | 82 | 91 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Client 3 | ################################################################################ 4 | 5 | build/ 6 | 7 | ################################################################################ 8 | # Node 9 | ############################################################################### 10 | 11 | # Logs 12 | logs 13 | *.log 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # Runtime data 19 | pids 20 | *.pid 21 | *.seed 22 | *.pid.lock 23 | 24 | # Directory for instrumented libs generated by jscoverage/JSCover 25 | lib-cov 26 | 27 | # Coverage directory used by tools like istanbul 28 | coverage 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (http://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # Typescript v1 declaration files 50 | typings/ 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Optional REPL history 59 | .node_repl_history 60 | 61 | # Output of 'npm pack' 62 | *.tgz 63 | 64 | # Yarn Integrity file 65 | .yarn-integrity 66 | 67 | # dotenv environment variables file 68 | .env 69 | 70 | ################################################################################ 71 | # Sublime Text 72 | ############################################################################### 73 | 74 | # cache files for sublime text 75 | *.tmlanguage.cache 76 | *.tmPreferences.cache 77 | *.stTheme.cache 78 | 79 | # workspace files are user-specific 80 | *.sublime-workspace 81 | 82 | # project files should be checked into the repository, unless a significant 83 | # proportion of contributors will probably not be using SublimeText 84 | # *.sublime-project 85 | 86 | # sftp configuration file 87 | sftp-config.json 88 | 89 | # Package control specific files 90 | Package Control.last-run 91 | Package Control.ca-list 92 | Package Control.ca-bundle 93 | Package Control.system-ca-bundle 94 | Package Control.cache/ 95 | Package Control.ca-certs/ 96 | Package Control.merged-ca-bundle 97 | Package Control.user-ca-bundle 98 | oscrypto-ca-bundle.crt 99 | bh_unicode_properties.cache 100 | 101 | # Sublime-github package stores a github token in this file 102 | # https://packagecontrol.io/packages/sublime-github 103 | GitHub.sublime-settings 104 | 105 | ################################################################################ 106 | # Vim 107 | ################################################################################ 108 | 109 | # swap 110 | [._]*.s[a-v][a-z] 111 | [._]*.sw[a-p] 112 | [._]s[a-v][a-z] 113 | [._]sw[a-p] 114 | # session 115 | Session.vim 116 | # temporary 117 | .netrwhist 118 | # auto-generated tag files 119 | tags 120 | 121 | ################################################################################ 122 | # Emacs 123 | ################################################################################ 124 | 125 | # -*- mode: gitignore; -*- 126 | *~ 127 | \#*\# 128 | /.emacs.desktop 129 | /.emacs.desktop.lock 130 | *.elc 131 | auto-save-list 132 | tramp 133 | .\#* 134 | 135 | # Org-mode 136 | .org-id-locations 137 | *_archive 138 | 139 | # flymake-mode 140 | *_flymake.* 141 | 142 | # eshell files 143 | /eshell/history 144 | /eshell/lastdir 145 | 146 | # elpa packages 147 | /elpa/ 148 | 149 | # reftex files 150 | *.rel 151 | 152 | # AUCTeX auto folder 153 | /auto/ 154 | 155 | # cask packages 156 | .cask/ 157 | dist/ 158 | 159 | # Flycheck 160 | flycheck_*.el 161 | 162 | # server auth directory 163 | /server/ 164 | 165 | # projectiles files 166 | .projectile 167 | 168 | # directory configuration 169 | .dir-locals.el 170 | 171 | ################################################################################ 172 | # JetBrains 173 | ############################################################################### 174 | 175 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 176 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 177 | 178 | # User-specific stuff: 179 | .idea/**/workspace.xml 180 | .idea/**/tasks.xml 181 | 182 | # Sensitive or high-churn files: 183 | .idea/**/dataSources/ 184 | .idea/**/dataSources.ids 185 | .idea/**/dataSources.xml 186 | .idea/**/dataSources.local.xml 187 | .idea/**/sqlDataSources.xml 188 | .idea/**/dynamic.xml 189 | .idea/**/uiDesigner.xml 190 | 191 | # Gradle: 192 | .idea/**/gradle.xml 193 | .idea/**/libraries 194 | 195 | # Mongo Explorer plugin: 196 | .idea/**/mongoSettings.xml 197 | 198 | ## File-based project format: 199 | *.iws 200 | 201 | ## Plugin-specific files: 202 | 203 | # IntelliJ 204 | /out/ 205 | 206 | # mpeltonen/sbt-idea plugin 207 | .idea_modules/ 208 | 209 | # JIRA plugin 210 | atlassian-ide-plugin.xml 211 | 212 | # Crashlytics plugin (for Android Studio and IntelliJ) 213 | com_crashlytics_export_strings.xml 214 | crashlytics.properties 215 | crashlytics-build.properties 216 | fabric.properties 217 | 218 | ### JetBrains Patch ### 219 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 220 | 221 | # *.iml 222 | # modules.xml 223 | # .idea/misc.xml 224 | # *.ipr 225 | 226 | ################################################################################ 227 | # Linux 228 | ############################################################################### 229 | 230 | # temporary files which can be created if a process still has a handle open of a deleted file 231 | .fuse_hidden* 232 | 233 | # KDE directory preferences 234 | .directory 235 | 236 | # Linux trash folder which might appear on any partition or disk 237 | .Trash-* 238 | 239 | # .nfs files are created when an open file is removed but is still being accessed 240 | .nfs* 241 | 242 | ################################################################################ 243 | # macOS 244 | ############################################################################### 245 | 246 | *.DS_Store 247 | .AppleDouble 248 | .LSOverride 249 | 250 | # Icon must end with two \r 251 | Icon 252 | 253 | # Thumbnails 254 | ._* 255 | 256 | # Files that might appear in the root of a volume 257 | .DocumentRevisions-V100 258 | .fseventsd 259 | .Spotlight-V100 260 | .TemporaryItems 261 | .Trashes 262 | .VolumeIcon.icns 263 | .com.apple.timemachine.donotpresent 264 | 265 | # Directories potentially created on remote AFP share 266 | .AppleDB 267 | .AppleDesktop 268 | Network Trash Folder 269 | Temporary Items 270 | .apdisk 271 | 272 | ################################################################################ 273 | # Windows 274 | ################################################################################ 275 | 276 | # Windows thumbnail cache files 277 | Thumbs.db 278 | ehthumbs.db 279 | ehthumbs_vista.db 280 | 281 | # Folder config file 282 | Desktop.ini 283 | 284 | # Recycle Bin used on file shares 285 | $RECYCLE.BIN/ 286 | 287 | # Windows Installer files 288 | *.cab 289 | *.msi 290 | *.msm 291 | *.msp 292 | 293 | # Windows shortcuts 294 | *.lnk 295 | 296 | ################################################################################ 297 | # Vagrant 298 | ############################################################################### 299 | 300 | .vagrant/ 301 | 302 | -------------------------------------------------------------------------------- /src/schedule.js: -------------------------------------------------------------------------------- 1 | import {Time, TimeInterval} from './suppClasses' 2 | 3 | export class Schedule{ 4 | constructor(solution) { 5 | this.schedule = this.processWorkdays(solution); 6 | this.events = this.getEvents(solution); 7 | this.prefContinuous = NaN; 8 | this.prefFreeAfternoons = NaN; 9 | this.prefFreeMornings = NaN; 10 | this.prefLongLunch = NaN; 11 | this.prefFreeDays = NaN; 12 | this.prefLongWeekend = NaN; 13 | this.prefFridayMorning = false; 14 | 15 | this.parseSchedule() 16 | } 17 | 18 | parseSchedule(){ 19 | let prefCont = 0 20 | let prefFreeAfternoons = 0 21 | let prefFreeMornings = 0 22 | let prefLongLunch = 0 23 | let prefFreeDays = 0 24 | 25 | for ( var w of this.schedule.keys()){ 26 | if (w === 'Saturday' || w === 'Sunday') 27 | continue; 28 | 29 | let workday = this.schedule.get(w) 30 | prefCont += prefContinuous(workday) 31 | prefFreeAfternoons += prefFreeAfternoon(workday) 32 | prefFreeMornings += prefFreeMorning(workday) 33 | prefLongLunch += prefLongLunchtimes(workday) 34 | if (this.schedule.get(w).free_day) 35 | prefFreeDays += 1 36 | 37 | } 38 | this.prefContinuous = prefCont/5 39 | this.prefFreeAfternoons = prefFreeAfternoons/5 40 | this.prefFreeMornings = prefFreeMornings/5 41 | this.prefLongLunch = prefLongLunch/5 42 | this.prefFreeDays = prefFreeDays/5 43 | this.prefFridayMorning = prefFreeMorning(this.schedule.get('Friday')) 44 | this.prefLongWeekend = prefLongWeekend(this.schedule) 45 | 46 | 47 | } 48 | processWorkdays(solution){ 49 | 50 | const temp_workdays = new Map(); 51 | const week_days = ['Saturday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; 52 | for (var week_day of week_days) 53 | temp_workdays.set(week_day, []); 54 | for (var event of solution.keys()) 55 | for (var instance of solution.get(event)[0].instances) 56 | temp_workdays.get(week_days[instance.start.day]).push(instance); 57 | 58 | const workdays = new Map(); 59 | for (var day of temp_workdays.keys()) 60 | workdays.set(day, new Workday(temp_workdays.get(day))); 61 | return workdays; 62 | } 63 | 64 | getEvents(solution){ 65 | const eventOptions = Array.from(solution.values()).map(eventOption => eventOption[0]) 66 | const events = [] 67 | 68 | for (const event of eventOptions){ 69 | if (!event.event.isConstraint) { 70 | for (const instance of event.instances){ 71 | events.push({ 72 | title: event.event.name + event.option, 73 | start: new Date(2018, 8, 1 + parseInt(instance.start.day, 10), instance.start.time.hour, instance.start.time.min, 0, 0), 74 | end: new Date(2018, 8, 1 + parseInt(instance.end.day, 10), instance.end.time.hour, instance.end.time.min, 0, 0) 75 | }) 76 | } 77 | } 78 | } 79 | return events; 80 | } 81 | static freeTimeIntersection(scheduleA, scheduleB){ 82 | let intersection_time = 0 83 | const week_days = ['Monday','Tuesday','Wednesday','Thursday','Friday'] 84 | week_days.forEach(function(week_day){ 85 | scheduleA.schedule.get(week_day).free_intervals.forEach(function(free_timeA){ 86 | scheduleB.schedule.get(week_day).free_intervals.forEach(function(free_timeB){ 87 | intersection_time += Time.intersectionTime(free_timeA, free_timeB) 88 | }) 89 | }) 90 | }) 91 | return intersection_time 92 | } 93 | } 94 | 95 | 96 | export function scheduleEvaluation(s,weightContinuous = 0, weightFreeAfternoons = 0, weightFreeMornings = 0, weightLongLunch = 0, weightFreeDays = 0, weightFridayMorning = 0, weightLongWeekend = 0){ 97 | return weightContinuous * s.prefContinuous + weightFreeAfternoons * s.prefFreeAfternoons + weightFreeMornings * s.prefFreeMornings + weightLongLunch * s.prefLongLunch + weightFreeDays * s.prefFreeDays*4 + weightFridayMorning * s.prefFridayMorning + weightLongWeekend * s.prefLongWeekend*4; 98 | 99 | } 100 | // todo: how to handle with events that starts in one day but ends in another 101 | export class Workday{ 102 | constructor(events){ 103 | this.events = events; 104 | this.workload = 0; 105 | this.begin_morning = NaN; 106 | this.end_morning = NaN; 107 | this.begin_afternoon = NaN; 108 | this.end_afternoon = NaN; 109 | this.free_day = true; 110 | this.lunch_time = false; 111 | this.parseWorkDay(); 112 | this.free_intervals = this.getFreeIntervals(); 113 | } 114 | 115 | 116 | parseWorkDay(){ 117 | let noon = new Time(12,0) 118 | 119 | let bM = new Time(12,0) 120 | let eM = new Time(0,0) 121 | let bA = new Time(24,0) 122 | let eA = new Time(12,0) 123 | for ( var event of this.events){ 124 | this.free_day = false 125 | this.workload += Time.interval(event.start.time, event.end.time); 126 | if ( Time.compare(event.start.time,noon) < 0){ 127 | // Morning 128 | if ( Time.compare(event.start.time, bM) < 0) 129 | bM = event.start.time 130 | if ( Time.compare(event.end.time, eM) > 0) 131 | eM = event.end.time 132 | }else{ 133 | // Afternoon 134 | if ( Time.compare(event.start.time, bA) < 0) 135 | bA = event.start.time 136 | if ( Time.compare(event.end.time, eA) > 0) 137 | eA = event.end.time 138 | } 139 | } 140 | 141 | if ( Time.interval(eM,bA) > 0) 142 | this.lunch_time = true 143 | 144 | if ( Time.compare( bM, new Time(12,0)) !== 0 ) 145 | this.begin_morning = bM; 146 | if ( Time.compare( eM ,new Time(0,0)) !== 0) 147 | this.end_morning = eM; 148 | if ( Time.compare( bA ,new Time(24,0)) !== 0) 149 | this.begin_afternoon = bA; 150 | if ( Time.compare( eA ,new Time(12,0)) !== 0) 151 | this.end_afternoon = eA; 152 | } 153 | 154 | getFreeIntervals(){ 155 | const free_times = []; 156 | const events_sorted = this.events.sort(function(a,b){ 157 | return a.start.time.hour - b.start.time.hour; 158 | }) 159 | if (events_sorted.length > 0){ 160 | free_times.push(new TimeInterval(new Time(0,0), events_sorted[0].start.time)); 161 | let prev_end = events_sorted[0].end.time; 162 | let i = 1; 163 | while (i 0) 183 | return Time.interval(workday.end_morning, new Time(20,0)) / (8*60); 184 | return 1; 185 | } 186 | 187 | function prefFreeMorning(workday){ 188 | if(workday.begin_morning instanceof Time) 189 | return Time.interval(new Time(7, 0), workday.begin_morning) / (5*60); // five hours for the morning := 12-7 ") 190 | return 1; 191 | } 192 | 193 | function prefContinuous(workday){ 194 | let diff = 0; 195 | if ( !(workday.begin_morning instanceof Time) && !(workday.begin_afternoon instanceof Time)){ 196 | return 1 197 | } 198 | 199 | if (!(workday.begin_morning instanceof Time)){ 200 | diff = Time.interval(workday.begin_afternoon, workday.end_afternoon) 201 | return (workday.workload/diff) 202 | } 203 | 204 | if (!(workday.end_afternoon instanceof Time)){ 205 | diff = Time.interval(workday.begin_morning, workday.end_morning) 206 | return (workday.workload/diff) 207 | } 208 | 209 | diff = Time.interval(workday.begin_morning, workday.end_afternoon) 210 | return (workday.workload/diff) 211 | } 212 | 213 | function prefLongLunchtimes(workday){ 214 | let r = 0.5 215 | if (workday.lunch_time) 216 | if (Time.interval(workday.end_morning, workday.begin_afternoon) > 60) 217 | r = 1 218 | else 219 | r = 0 220 | if (!(workday.begin_morning instanceof Time) || !(workday.begin_afternoon instanceof Time)) 221 | r = 1 222 | return r 223 | } 224 | 225 | function prefLongWeekend(schedule){ 226 | const week_days = Array.from(schedule.keys()) 227 | 228 | let forward_time = 0 229 | let i = 2 230 | while(schedule.get(week_days[i]).free_day){ 231 | forward_time += 24*60; 232 | i += 1; 233 | } 234 | const next_filled_day = schedule.get(week_days[i]) 235 | if ( next_filled_day.begin_morning instanceof Time) 236 | forward_time += Time.interval(new Time(0,0), next_filled_day.begin_morning) 237 | else 238 | forward_time += Time.interval(new Time(0,0), next_filled_day.begin_afternoon) 239 | 240 | let backward_time = 0 241 | i = 6 242 | while(schedule.get(week_days[i]).free_day){ 243 | backward_time += 24*60; 244 | i -= 1; 245 | } 246 | 247 | const previous_filled_day = schedule.get(week_days[i]) 248 | if ( previous_filled_day.end_afternoon instanceof Time){ 249 | backward_time += Time.interval(previous_filled_day.end_afternoon, new Time(24, 0)) 250 | }else 251 | backward_time += Time.interval(previous_filled_day.end_morning, new Time(24, 0)) 252 | 253 | return (forward_time + backward_time)/(24*60*5) 254 | 255 | } 256 | -------------------------------------------------------------------------------- /public/favicons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 202 | 207 | 243 | 244 | 245 | --------------------------------------------------------------------------------