├── .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 |
7 | Saturday
8 | Sunday
9 | Monday
10 | Tuesday
11 | Wednesday
12 | Thursday
13 | Friday
14 |
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 |
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 |
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 |
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 |
35 | You need to enable JavaScript to run this app.
36 |
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 |
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 |
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 | this.handleGenerate()}>
137 | Generate
138 |
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 |
--------------------------------------------------------------------------------