@event-calendar/core 0.8.1 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +3 -3
- package/package.json +4 -3
- package/src/Buttons.svelte +41 -0
- package/src/Calendar.svelte +91 -0
- package/src/Toolbar.svelte +34 -0
- package/src/index.js +3 -0
- package/src/index.scss +507 -0
- package/src/storage/options.js +150 -0
- package/src/storage/state.js +114 -0
- package/src/storage/stores.js +206 -0
package/index.js
CHANGED
|
@@ -439,7 +439,7 @@ function parseOpts(opts, state) {
|
|
|
439
439
|
}
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
-
/* packages/core/src/Buttons.svelte generated by Svelte v3.46.
|
|
442
|
+
/* packages/core/src/Buttons.svelte generated by Svelte v3.46.4 */
|
|
443
443
|
|
|
444
444
|
function get_each_context$1(ctx, list, i) {
|
|
445
445
|
const child_ctx = ctx.slice();
|
|
@@ -842,7 +842,7 @@ class Buttons extends SvelteComponent {
|
|
|
842
842
|
}
|
|
843
843
|
}
|
|
844
844
|
|
|
845
|
-
/* packages/core/src/Toolbar.svelte generated by Svelte v3.46.
|
|
845
|
+
/* packages/core/src/Toolbar.svelte generated by Svelte v3.46.4 */
|
|
846
846
|
|
|
847
847
|
function get_each_context(ctx, list, i) {
|
|
848
848
|
const child_ctx = ctx.slice();
|
|
@@ -1212,7 +1212,7 @@ class Toolbar extends SvelteComponent {
|
|
|
1212
1212
|
}
|
|
1213
1213
|
}
|
|
1214
1214
|
|
|
1215
|
-
/* packages/core/src/Calendar.svelte generated by Svelte v3.46.
|
|
1215
|
+
/* packages/core/src/Calendar.svelte generated by Svelte v3.46.4 */
|
|
1216
1216
|
|
|
1217
1217
|
function create_fragment(ctx) {
|
|
1218
1218
|
let div;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@event-calendar/core",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"title": "Event Calendar Core package",
|
|
5
5
|
"description": "Full-sized drag & drop event calendar with resource view",
|
|
6
6
|
"keywords": ["calendar", "event", "resource", "full-sized"],
|
|
@@ -12,13 +12,14 @@
|
|
|
12
12
|
},
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"type": "module",
|
|
15
|
+
"svelte": "src/index.js",
|
|
15
16
|
"exports": {
|
|
16
17
|
".": "./index.js",
|
|
17
18
|
"./index.css": "./index.css",
|
|
18
19
|
"./package.json": "./package.json"
|
|
19
20
|
},
|
|
20
21
|
"dependencies": {
|
|
21
|
-
"@event-calendar/common": "~0.8.
|
|
22
|
-
"svelte": "^3.46.
|
|
22
|
+
"@event-calendar/common": "~0.8.2",
|
|
23
|
+
"svelte": "^3.46.4"
|
|
23
24
|
}
|
|
24
25
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import {getContext} from 'svelte';
|
|
3
|
+
import {createDate, cloneDate, subtractDay, addDuration, subtractDuration, setMidnight} from '@event-calendar/common';
|
|
4
|
+
|
|
5
|
+
export let buttons;
|
|
6
|
+
|
|
7
|
+
let {_currentRange, _viewTitle, buttonText, date, duration, hiddenDays, theme, view} = getContext('state');
|
|
8
|
+
|
|
9
|
+
let today = setMidnight(createDate()), isToday;
|
|
10
|
+
|
|
11
|
+
$: isToday = today >= $_currentRange.start && today < $_currentRange.end || null;
|
|
12
|
+
|
|
13
|
+
function prev() {
|
|
14
|
+
let d = subtractDuration($date, $duration);
|
|
15
|
+
if ($hiddenDays.length && $hiddenDays.length < 7) {
|
|
16
|
+
while ($hiddenDays.includes(d.getUTCDay())) {
|
|
17
|
+
subtractDay(d);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
$date = d;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function next() {
|
|
24
|
+
$date = addDuration($date, $duration);
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
{#each buttons as button}
|
|
29
|
+
{#if button == ''}
|
|
30
|
+
{:else if button == 'title'}
|
|
31
|
+
<h2 class="{$theme.title}">{$_viewTitle}</h2>
|
|
32
|
+
{:else if button == 'prev'}
|
|
33
|
+
<button class="{$theme.button} ec-{button}" on:click={prev}><i class="{$theme.icon} ec-{button}"></i></button>
|
|
34
|
+
{:else if button === 'next'}
|
|
35
|
+
<button class="{$theme.button} ec-{button}" on:click={next}><i class="{$theme.icon} ec-{button}"></i></button>
|
|
36
|
+
{:else if button === 'today'}
|
|
37
|
+
<button class="{$theme.button} ec-{button}" on:click={() => $date = cloneDate(today)} disabled={isToday}>{$buttonText[button]}</button>
|
|
38
|
+
{:else}
|
|
39
|
+
<button class="{$theme.button}{$view === button ? ' ' + $theme.active : ''} ec-{button}" on:click={() => $view = button}>{$buttonText[button]}</button>
|
|
40
|
+
{/if}
|
|
41
|
+
{/each}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import './index.scss';
|
|
3
|
+
import {setContext} from 'svelte';
|
|
4
|
+
import {get} from 'svelte/store';
|
|
5
|
+
import {diff} from './storage/options';
|
|
6
|
+
import State from './storage/state';
|
|
7
|
+
import Toolbar from './Toolbar.svelte';
|
|
8
|
+
import {assign, toEventWithLocalDates, toViewWithLocalDates} from '@event-calendar/common';
|
|
9
|
+
|
|
10
|
+
export let plugins = [];
|
|
11
|
+
export let options = {};
|
|
12
|
+
|
|
13
|
+
let state = new State(plugins, options);
|
|
14
|
+
setContext('state', state);
|
|
15
|
+
|
|
16
|
+
let {_viewComponent, _interaction, _events, events, eventSources, height, theme} = state;
|
|
17
|
+
|
|
18
|
+
// Reactively update options that did change
|
|
19
|
+
$: for (let [name, value] of diff(options)) {
|
|
20
|
+
setOption(name, value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function setOption(name, value) {
|
|
24
|
+
if (state.hasOwnProperty(name)) {
|
|
25
|
+
if (state[name].parse) {
|
|
26
|
+
value = state[name].parse(value);
|
|
27
|
+
}
|
|
28
|
+
state[name].set(value);
|
|
29
|
+
}
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getOption(name) {
|
|
34
|
+
return state.hasOwnProperty(name) ? get(state[name]) : undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function refetchEvents() {
|
|
38
|
+
state._fetchedRange.set({start: undefined, end: undefined});
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getEventById(id) {
|
|
43
|
+
for (let event of get(state._events)) {
|
|
44
|
+
if (event.id == id) {
|
|
45
|
+
return toEventWithLocalDates(event);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function addEvent(event) {
|
|
52
|
+
updateEvents(events => events.concat(state.events.parse([event])));
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function updateEvent(event) {
|
|
57
|
+
updateEvents(events => {
|
|
58
|
+
for (let e of events) {
|
|
59
|
+
if (e.id == event.id) {
|
|
60
|
+
assign(e, state.events.parse([event])[0]);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return events;
|
|
65
|
+
});
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function removeEventById(id) {
|
|
70
|
+
updateEvents(events => events.filter(event => event.id != id));
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function getView() {
|
|
75
|
+
return toViewWithLocalDates(state._view.get());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function updateEvents(func) {
|
|
79
|
+
if ($eventSources.length) {
|
|
80
|
+
$_events = func($_events);
|
|
81
|
+
} else {
|
|
82
|
+
$events = func($events);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
<div class="{$theme.calendar}" style="height: {$height}">
|
|
88
|
+
<Toolbar/>
|
|
89
|
+
<svelte:component this={$_viewComponent}/>
|
|
90
|
+
<svelte:component this={$_interaction.component}/>
|
|
91
|
+
</div>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import {getContext} from 'svelte';
|
|
3
|
+
import Buttons from './Buttons.svelte';
|
|
4
|
+
|
|
5
|
+
let {headerToolbar, theme} = getContext('state');
|
|
6
|
+
|
|
7
|
+
let sections = {
|
|
8
|
+
start: [],
|
|
9
|
+
center: [],
|
|
10
|
+
end: []
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
$: {
|
|
14
|
+
for (let key of Object.keys(sections)) {
|
|
15
|
+
sections[key] = $headerToolbar[key].split(' ').map(group => group.split(','));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<div class="{$theme.toolbar}">
|
|
21
|
+
{#each Object.keys(sections) as key}
|
|
22
|
+
<div>
|
|
23
|
+
{#each sections[key] as buttons}
|
|
24
|
+
{#if buttons.length > 1}
|
|
25
|
+
<div class="{$theme.buttonGroup}">
|
|
26
|
+
<Buttons {buttons}/>
|
|
27
|
+
</div>
|
|
28
|
+
{:else}
|
|
29
|
+
<Buttons {buttons}/>
|
|
30
|
+
{/if}
|
|
31
|
+
{/each}
|
|
32
|
+
</div>
|
|
33
|
+
{/each}
|
|
34
|
+
</div>
|
package/src/index.js
ADDED
package/src/index.scss
ADDED
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
/* Grid */
|
|
2
|
+
.ec-flex {
|
|
3
|
+
display: flex;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.ec-body.ec-month,
|
|
7
|
+
.ec-days,
|
|
8
|
+
.ec-day,
|
|
9
|
+
.ec-day-title,
|
|
10
|
+
.ec-resource {
|
|
11
|
+
flex: 1 1 0%; /* % for ie11 */
|
|
12
|
+
min-width: 0;
|
|
13
|
+
max-width: 100%;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.ec {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
|
|
20
|
+
/* Scrollbar */
|
|
21
|
+
::-webkit-scrollbar {
|
|
22
|
+
background: #fff;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
::-webkit-scrollbar-thumb {
|
|
26
|
+
border: 4px solid #fff;
|
|
27
|
+
box-shadow: none;
|
|
28
|
+
background: #dadce0;
|
|
29
|
+
border-radius: 8px;
|
|
30
|
+
min-height: 40px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
:hover::-webkit-scrollbar-thumb {
|
|
34
|
+
background: #bdc1c6;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.ec-hidden-scroll {
|
|
39
|
+
display: none;
|
|
40
|
+
overflow-y: scroll;
|
|
41
|
+
visibility: hidden;
|
|
42
|
+
flex-shrink: 0;
|
|
43
|
+
|
|
44
|
+
.ec-with-scroll & {
|
|
45
|
+
display: block;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Toolbar */
|
|
50
|
+
.ec-toolbar {
|
|
51
|
+
flex: 0 0 auto;
|
|
52
|
+
display: flex;
|
|
53
|
+
justify-content: space-between;
|
|
54
|
+
align-items: center;
|
|
55
|
+
margin-bottom: 1em;
|
|
56
|
+
|
|
57
|
+
> * {
|
|
58
|
+
margin-bottom: -.5em;
|
|
59
|
+
|
|
60
|
+
> * {
|
|
61
|
+
margin-bottom: .5em;
|
|
62
|
+
|
|
63
|
+
&:not(:last-child) {
|
|
64
|
+
margin-right: .75em;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.ec-title {
|
|
71
|
+
margin: 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.ec-button {
|
|
75
|
+
background-color: #fff;
|
|
76
|
+
border: 1px solid #ced4da;
|
|
77
|
+
padding: .375rem .75rem;
|
|
78
|
+
font-size: 1rem;
|
|
79
|
+
line-height: 1.5;
|
|
80
|
+
border-radius: .25rem;
|
|
81
|
+
|
|
82
|
+
&:not(:disabled) {
|
|
83
|
+
color: #212529;
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
&:not(:disabled):hover,
|
|
88
|
+
&.ec-active {
|
|
89
|
+
background-color: #ececec;
|
|
90
|
+
border-color: #b1bbc4;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.ec-button-group {
|
|
95
|
+
display: inline-flex;
|
|
96
|
+
|
|
97
|
+
.ec-button:not(:first-child) {
|
|
98
|
+
border-top-left-radius: 0;
|
|
99
|
+
border-bottom-left-radius: 0;
|
|
100
|
+
margin-left: -1px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.ec-button:not(:last-child) {
|
|
104
|
+
border-top-right-radius: 0;
|
|
105
|
+
border-bottom-right-radius: 0;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.ec-icon {
|
|
110
|
+
display: inline-block;
|
|
111
|
+
width: 1em;
|
|
112
|
+
|
|
113
|
+
&.ec-prev:after,
|
|
114
|
+
&.ec-next:after {
|
|
115
|
+
content: '';
|
|
116
|
+
position: relative;
|
|
117
|
+
width: .5em;
|
|
118
|
+
height: .5em;
|
|
119
|
+
border-top: 2px solid #212529;
|
|
120
|
+
border-right: 2px solid #212529;
|
|
121
|
+
display: inline-block;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
&.ec-prev:after {
|
|
125
|
+
transform: rotate(-135deg) translate(-2px, 2px);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
&.ec-next:after {
|
|
129
|
+
transform: rotate(45deg) translate(-2px, 2px);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Header */
|
|
134
|
+
.ec-header,
|
|
135
|
+
.ec-body,
|
|
136
|
+
.ec-days,
|
|
137
|
+
.ec-day {
|
|
138
|
+
border: 1px solid #dadce0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.ec-header {
|
|
142
|
+
display: flex;
|
|
143
|
+
flex-shrink: 0;
|
|
144
|
+
|
|
145
|
+
.ec-resource {
|
|
146
|
+
flex-direction: column;
|
|
147
|
+
|
|
148
|
+
.ec-days {
|
|
149
|
+
border-top-style: solid;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.ec-days {
|
|
154
|
+
border-bottom: none;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.ec-day {
|
|
158
|
+
min-height: 24px;
|
|
159
|
+
line-height: 24px;
|
|
160
|
+
text-align: center;
|
|
161
|
+
overflow: hidden;
|
|
162
|
+
text-overflow: ellipsis;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* Body */
|
|
167
|
+
.ec-body {
|
|
168
|
+
position: relative;
|
|
169
|
+
overflow-x: hidden;
|
|
170
|
+
overflow-y: auto;
|
|
171
|
+
|
|
172
|
+
&:not(.ec-list) {
|
|
173
|
+
border-top: none;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.ec-sidebar {
|
|
178
|
+
flex: 0 0 auto;
|
|
179
|
+
width: auto;
|
|
180
|
+
max-width: 100%;
|
|
181
|
+
padding: 0 4px 0 8px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.ec-content {
|
|
185
|
+
display: flex;
|
|
186
|
+
|
|
187
|
+
.ec-month & {
|
|
188
|
+
flex-direction: column;
|
|
189
|
+
height: 100%; /* ie11 */
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.ec-month.ec-uniform & {
|
|
193
|
+
overflow: hidden;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.ec-list & {
|
|
197
|
+
flex-direction: column;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.ec-resource {
|
|
202
|
+
display: flex;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.ec-days {
|
|
206
|
+
display: flex;
|
|
207
|
+
border-style: none none solid;
|
|
208
|
+
|
|
209
|
+
&:last-child {
|
|
210
|
+
border-bottom: none;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.ec-month &,
|
|
214
|
+
.ec-resource & {
|
|
215
|
+
flex: 1 0 auto; /* ie11 */
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.ec-month.ec-uniform & {
|
|
219
|
+
flex: 1 1 0%;
|
|
220
|
+
min-height: 0;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.ec-day {
|
|
225
|
+
border-style: none none none solid;
|
|
226
|
+
|
|
227
|
+
&.ec-today {
|
|
228
|
+
background-color: #fcf8e3;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
&.ec-highlight {
|
|
232
|
+
background-color: #e5f7fe;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.ec-month.ec-body & {
|
|
236
|
+
min-height: 5em;
|
|
237
|
+
position: relative;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.ec-month.ec-uniform & {
|
|
241
|
+
min-height: 0;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.ec-month &:first-child {
|
|
245
|
+
border-left: none;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
&.ec-other-month .ec-day-head {
|
|
249
|
+
opacity: .3;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.ec-list & {
|
|
253
|
+
flex: 1 0 auto; /* ie11 */
|
|
254
|
+
background-color: #fff;
|
|
255
|
+
border-style: solid none;
|
|
256
|
+
padding: 8px 14px;
|
|
257
|
+
font-weight: bold;
|
|
258
|
+
position: sticky;
|
|
259
|
+
top: 0;
|
|
260
|
+
z-index: 2;
|
|
261
|
+
|
|
262
|
+
&:first-child {
|
|
263
|
+
border-top: none;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.ec-month {
|
|
269
|
+
.ec-day-head {
|
|
270
|
+
text-align: right;
|
|
271
|
+
padding: 4px 4px 3px;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.ec-day-foot {
|
|
275
|
+
position: absolute;
|
|
276
|
+
bottom: 0;
|
|
277
|
+
padding: 2px;
|
|
278
|
+
font-size: .85em;
|
|
279
|
+
|
|
280
|
+
a {
|
|
281
|
+
cursor: pointer;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.ec-list {
|
|
287
|
+
.ec-day-side {
|
|
288
|
+
float: right;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.ec-no-events {
|
|
292
|
+
text-align: center;
|
|
293
|
+
padding: 5em 0;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.ec-events {
|
|
298
|
+
margin: 0 6px 0 0;
|
|
299
|
+
|
|
300
|
+
.ec-week &,
|
|
301
|
+
&.ec-preview {
|
|
302
|
+
position: relative;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.ec-event {
|
|
307
|
+
display: flex;
|
|
308
|
+
flex-direction: column;
|
|
309
|
+
padding: 2px;
|
|
310
|
+
color: #fff;
|
|
311
|
+
box-sizing: border-box;
|
|
312
|
+
box-shadow: 0 0 1px 0 #dadce0;
|
|
313
|
+
background-color: #039be5;
|
|
314
|
+
border-radius: 3px;
|
|
315
|
+
font-size: .85em;
|
|
316
|
+
line-height: 1.5;
|
|
317
|
+
z-index: 1; // put it above the pointer event (for multi-day events in month view)
|
|
318
|
+
|
|
319
|
+
.ec-month & {
|
|
320
|
+
position: relative;
|
|
321
|
+
flex-direction: row;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.ec-week & {
|
|
325
|
+
position: absolute;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.ec-list & {
|
|
329
|
+
flex-direction: row;
|
|
330
|
+
padding: 8px 14px;
|
|
331
|
+
color: inherit;
|
|
332
|
+
background-color: transparent;
|
|
333
|
+
border-radius: 0;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
&.ec-preview {
|
|
337
|
+
cursor: pointer;
|
|
338
|
+
position: absolute;
|
|
339
|
+
z-index: 1000;
|
|
340
|
+
width: 100%;
|
|
341
|
+
user-select: none;
|
|
342
|
+
opacity: .8;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
&.ec-pointer {
|
|
346
|
+
color: inherit;
|
|
347
|
+
pointer-events: none;
|
|
348
|
+
user-select: none;
|
|
349
|
+
position: absolute;
|
|
350
|
+
z-index: 0;
|
|
351
|
+
box-shadow: none;
|
|
352
|
+
display: none;
|
|
353
|
+
.ec-day:hover & {
|
|
354
|
+
display: flex;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.ec-event-tag {
|
|
360
|
+
width: 4px;
|
|
361
|
+
border-radius: 2px;
|
|
362
|
+
margin-right: 8px;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.ec-event-time {
|
|
366
|
+
overflow: hidden;
|
|
367
|
+
white-space: nowrap;
|
|
368
|
+
margin: 0 0 1px 0;
|
|
369
|
+
flex-shrink: 0;
|
|
370
|
+
|
|
371
|
+
.ec-month & {
|
|
372
|
+
margin: 0 3px 0 0;
|
|
373
|
+
max-width: 100%;
|
|
374
|
+
text-overflow: ellipsis;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.ec-event-title {
|
|
379
|
+
overflow: hidden;
|
|
380
|
+
|
|
381
|
+
.ec-month & {
|
|
382
|
+
white-space: nowrap;
|
|
383
|
+
text-overflow: ellipsis;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.ec-week & {
|
|
387
|
+
position: sticky;
|
|
388
|
+
top: 0;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.ec-list & {
|
|
392
|
+
font-size: 1rem;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.ec-draggable {
|
|
397
|
+
cursor: pointer;
|
|
398
|
+
user-select: none;
|
|
399
|
+
touch-action: none;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.ec-ghost {
|
|
403
|
+
opacity: .5;
|
|
404
|
+
user-select: none;
|
|
405
|
+
touch-action: none;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.ec-bg-events {
|
|
409
|
+
position: relative;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.ec-bg-event {
|
|
413
|
+
position: absolute;
|
|
414
|
+
background-color: #dadce0;
|
|
415
|
+
opacity: 0.3;
|
|
416
|
+
width: 100%;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.ec-hidden-times {
|
|
420
|
+
visibility: hidden;
|
|
421
|
+
overflow-y: hidden;
|
|
422
|
+
height: 0;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.ec-time,
|
|
426
|
+
.ec-line {
|
|
427
|
+
height: 24px;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.ec-time {
|
|
431
|
+
position: relative;
|
|
432
|
+
line-height: 24px;
|
|
433
|
+
top: -12px;
|
|
434
|
+
text-align: right;
|
|
435
|
+
white-space: nowrap;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.ec-lines {
|
|
439
|
+
width: 8px;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.ec-line:not(:first-child):after {
|
|
443
|
+
content: '';
|
|
444
|
+
position: absolute;
|
|
445
|
+
width: 100%;
|
|
446
|
+
border-bottom: 1px solid #dadce0;
|
|
447
|
+
pointer-events: none;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.ec-body:not(.ec-compact) .ec-line:nth-child(even):after {
|
|
451
|
+
border-bottom-style: dotted;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.ec-popup {
|
|
455
|
+
position: absolute;
|
|
456
|
+
top: 0;
|
|
457
|
+
width: 110%;
|
|
458
|
+
min-width: 180px;
|
|
459
|
+
z-index: 1010;
|
|
460
|
+
padding: 8px 10px 14px;
|
|
461
|
+
background-color: #fff;
|
|
462
|
+
border-radius: 6px;
|
|
463
|
+
outline: 1px solid transparent;
|
|
464
|
+
box-shadow: 0 1px 3px 0 rgba(60, 64, 67, .3), 0 4px 8px 3px rgba(60, 64, 67, .15);
|
|
465
|
+
|
|
466
|
+
.ec-day-head {
|
|
467
|
+
text-align: left;
|
|
468
|
+
display: flex;
|
|
469
|
+
justify-content: space-between;
|
|
470
|
+
|
|
471
|
+
a {
|
|
472
|
+
cursor: pointer;
|
|
473
|
+
font-size: 1.5em;
|
|
474
|
+
line-height: .8;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.ec-events {
|
|
479
|
+
margin: 0;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.ec-extra {
|
|
484
|
+
position: relative;
|
|
485
|
+
height: 100%;
|
|
486
|
+
overflow: hidden;
|
|
487
|
+
margin-left: -6.5px;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.ec-now-indicator {
|
|
491
|
+
position: absolute;
|
|
492
|
+
z-index: 1005;
|
|
493
|
+
width: 100%;
|
|
494
|
+
border-top: #ea4335 solid 2px;
|
|
495
|
+
pointer-events: none;
|
|
496
|
+
|
|
497
|
+
&:before {
|
|
498
|
+
background: #ea4335;
|
|
499
|
+
border-radius: 50%;
|
|
500
|
+
content: "";
|
|
501
|
+
position: absolute;
|
|
502
|
+
height: 12px;
|
|
503
|
+
margin-top: -7px;
|
|
504
|
+
width: 12px;
|
|
505
|
+
pointer-events: none;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import {assign, createDate, createDuration, setMidnight} from '@event-calendar/common';
|
|
2
|
+
import {createEvents, createEventSources} from '@event-calendar/common';
|
|
3
|
+
import {is_function} from 'svelte/internal';
|
|
4
|
+
|
|
5
|
+
export function createOptions(plugins) {
|
|
6
|
+
let options = {
|
|
7
|
+
buttonText: {
|
|
8
|
+
today: 'today',
|
|
9
|
+
},
|
|
10
|
+
date: new Date(),
|
|
11
|
+
dateClick: undefined,
|
|
12
|
+
datesSet: undefined,
|
|
13
|
+
dayHeaderFormat: {
|
|
14
|
+
weekday: 'short',
|
|
15
|
+
month: 'numeric',
|
|
16
|
+
day: 'numeric'
|
|
17
|
+
},
|
|
18
|
+
displayEventEnd: true,
|
|
19
|
+
duration: {weeks: 1},
|
|
20
|
+
events: [],
|
|
21
|
+
eventBackgroundColor: undefined,
|
|
22
|
+
eventClick: undefined,
|
|
23
|
+
eventColor: undefined,
|
|
24
|
+
eventContent: undefined,
|
|
25
|
+
eventDidMount: undefined,
|
|
26
|
+
eventMouseEnter: undefined,
|
|
27
|
+
eventMouseLeave: undefined,
|
|
28
|
+
eventSources: [],
|
|
29
|
+
eventTimeFormat: {
|
|
30
|
+
hour: 'numeric',
|
|
31
|
+
minute: '2-digit'
|
|
32
|
+
},
|
|
33
|
+
firstDay: 0,
|
|
34
|
+
flexibleSlotTimeLimits: false, // ec option
|
|
35
|
+
headerToolbar: {
|
|
36
|
+
start: 'title',
|
|
37
|
+
center: '',
|
|
38
|
+
end: 'today prev,next'
|
|
39
|
+
},
|
|
40
|
+
height: 'auto',
|
|
41
|
+
hiddenDays: [],
|
|
42
|
+
highlightedDates: [], // ec option
|
|
43
|
+
lazyFetching: true,
|
|
44
|
+
loading: undefined,
|
|
45
|
+
locale: undefined,
|
|
46
|
+
monthMode: false,
|
|
47
|
+
nowIndicator: false,
|
|
48
|
+
scrollTime: '06:00:00',
|
|
49
|
+
slotDuration: '00:30:00',
|
|
50
|
+
slotHeight: 24, // ec option
|
|
51
|
+
slotLabelFormat: {
|
|
52
|
+
hour: 'numeric',
|
|
53
|
+
minute: '2-digit'
|
|
54
|
+
},
|
|
55
|
+
slotMaxTime: '24:00:00',
|
|
56
|
+
slotMinTime: '00:00:00',
|
|
57
|
+
theme: {
|
|
58
|
+
active: 'ec-active',
|
|
59
|
+
bgEvent: 'ec-bg-event',
|
|
60
|
+
bgEvents: 'ec-bg-events',
|
|
61
|
+
body: 'ec-body',
|
|
62
|
+
button: 'ec-button',
|
|
63
|
+
buttonGroup: 'ec-button-group',
|
|
64
|
+
calendar: 'ec',
|
|
65
|
+
compact: 'ec-compact',
|
|
66
|
+
content: 'ec-content',
|
|
67
|
+
day: 'ec-day',
|
|
68
|
+
dayHead: 'ec-day-head',
|
|
69
|
+
days: 'ec-days',
|
|
70
|
+
event: 'ec-event',
|
|
71
|
+
eventTime: 'ec-event-time',
|
|
72
|
+
eventTitle: 'ec-event-title',
|
|
73
|
+
events: 'ec-events',
|
|
74
|
+
extra: 'ec-extra',
|
|
75
|
+
handle: 'ec-handle',
|
|
76
|
+
header: 'ec-header',
|
|
77
|
+
hiddenScroll: 'ec-hidden-scroll',
|
|
78
|
+
hiddenTimes: 'ec-hidden-times',
|
|
79
|
+
highlight: 'ec-highlight',
|
|
80
|
+
icon: 'ec-icon',
|
|
81
|
+
line: 'ec-line',
|
|
82
|
+
lines: 'ec-lines',
|
|
83
|
+
nowIndicator: 'ec-now-indicator',
|
|
84
|
+
otherMonth: 'ec-other-month',
|
|
85
|
+
sidebar: 'ec-sidebar',
|
|
86
|
+
today: 'ec-today',
|
|
87
|
+
time: 'ec-time',
|
|
88
|
+
title: 'ec-title',
|
|
89
|
+
toolbar: 'ec-toolbar',
|
|
90
|
+
week: 'ec-week',
|
|
91
|
+
withScroll: 'ec-with-scroll'
|
|
92
|
+
},
|
|
93
|
+
titleFormat: {
|
|
94
|
+
year: 'numeric',
|
|
95
|
+
month: 'short',
|
|
96
|
+
day: 'numeric'
|
|
97
|
+
},
|
|
98
|
+
view: undefined,
|
|
99
|
+
viewDidMount: undefined,
|
|
100
|
+
views: {}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
for (let plugin of plugins) {
|
|
104
|
+
if ('createOptions' in plugin) {
|
|
105
|
+
plugin.createOptions(options);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return options;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function createParsers(options, plugins) {
|
|
113
|
+
let parsers = {
|
|
114
|
+
buttonText: input => is_function(input) ? input(options.buttonText) : input,
|
|
115
|
+
date: date => setMidnight(createDate(date)),
|
|
116
|
+
duration: createDuration,
|
|
117
|
+
events: createEvents,
|
|
118
|
+
eventSources: createEventSources,
|
|
119
|
+
hiddenDays: days => [...new Set(days)],
|
|
120
|
+
highlightedDates: dates => dates.map(createDate),
|
|
121
|
+
scrollTime: createDuration,
|
|
122
|
+
slotDuration: createDuration,
|
|
123
|
+
slotMaxTime: createDuration,
|
|
124
|
+
slotMinTime: createDuration,
|
|
125
|
+
theme: input => is_function(input) ? input(options.theme) : input
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
for (let plugin of plugins) {
|
|
129
|
+
if ('createParsers' in plugin) {
|
|
130
|
+
plugin.createParsers(parsers, options);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return parsers;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let prev;
|
|
138
|
+
export function diff(options) {
|
|
139
|
+
let diff = [];
|
|
140
|
+
if (prev) {
|
|
141
|
+
for (let name of Object.keys(options)) {
|
|
142
|
+
if (options[name] !== prev[name]) {
|
|
143
|
+
diff.push([name, options[name]]);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
prev = assign({}, options);
|
|
148
|
+
|
|
149
|
+
return diff;
|
|
150
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {writable} from 'svelte/store';
|
|
2
|
+
import {is_function, tick, noop, identity} from 'svelte/internal';
|
|
3
|
+
import {createOptions, createParsers} from './options';
|
|
4
|
+
import {
|
|
5
|
+
activeRange,
|
|
6
|
+
currentRange,
|
|
7
|
+
events,
|
|
8
|
+
viewDates,
|
|
9
|
+
viewTitle,
|
|
10
|
+
view as view2 // hack to avoid a runtime error in SvelteKit dev mode (ReferenceError: view is not defined)
|
|
11
|
+
} from './stores';
|
|
12
|
+
import {writable2, intl, intlRange} from '@event-calendar/common';
|
|
13
|
+
import {assign} from '@event-calendar/common';
|
|
14
|
+
|
|
15
|
+
export default class {
|
|
16
|
+
constructor(plugins, input) {
|
|
17
|
+
plugins = plugins || [];
|
|
18
|
+
|
|
19
|
+
// Create options
|
|
20
|
+
let options = createOptions(plugins);
|
|
21
|
+
let parsers = createParsers(options, plugins);
|
|
22
|
+
|
|
23
|
+
// Create stores for options
|
|
24
|
+
for (let [option, value] of Object.entries(options)) {
|
|
25
|
+
this[option] = writable2(value, parsers[option]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Private stores
|
|
29
|
+
this._currentRange = currentRange(this);
|
|
30
|
+
this._activeRange = activeRange(this);
|
|
31
|
+
this._fetchedRange = writable({start: undefined, end: undefined});
|
|
32
|
+
this._events = events(this);
|
|
33
|
+
this._intlEventTime = intl(this.locale, this.eventTimeFormat);
|
|
34
|
+
this._intlSlotLabel = intl(this.locale, this.slotLabelFormat);
|
|
35
|
+
this._intlDayHeader = intl(this.locale, this.dayHeaderFormat);
|
|
36
|
+
this._titleIntlRange = intlRange(this.locale, this.titleFormat);
|
|
37
|
+
this._scrollable = writable(false);
|
|
38
|
+
this._viewTitle = viewTitle(this);
|
|
39
|
+
this._viewDates = viewDates(this);
|
|
40
|
+
this._view = view2(this);
|
|
41
|
+
this._viewComponent = writable(undefined);
|
|
42
|
+
// Interaction
|
|
43
|
+
this._interaction = writable({});
|
|
44
|
+
this._interactionEvents = writable([null, null]); // drag, pointer
|
|
45
|
+
this._draggable = writable(noop);
|
|
46
|
+
this._classes = writable(identity);
|
|
47
|
+
this._scroll = writable(undefined);
|
|
48
|
+
|
|
49
|
+
// Let plugins create their private stores
|
|
50
|
+
for (let plugin of plugins) {
|
|
51
|
+
if ('createStores' in plugin) {
|
|
52
|
+
plugin.createStores(this);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (input.view) {
|
|
57
|
+
// Set initial view based on input
|
|
58
|
+
this.view.set(input.view);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Set options for each view
|
|
62
|
+
let commonOpts = assign({}, options, input);
|
|
63
|
+
parseOpts(commonOpts, this);
|
|
64
|
+
let views = new Set([...Object.keys(options.views), ...Object.keys(input.views || {})]);
|
|
65
|
+
for (let view of views) {
|
|
66
|
+
let viewOpts = assign({}, options.views[view] || {}, input.views && input.views[view] || {});
|
|
67
|
+
parseOpts(viewOpts, this);
|
|
68
|
+
let opts = assign({}, commonOpts, viewOpts);
|
|
69
|
+
// Change view component when view changes
|
|
70
|
+
this.view.subscribe(newView => {
|
|
71
|
+
if (newView === view) {
|
|
72
|
+
this._viewComponent.set(opts.component);
|
|
73
|
+
if (is_function(opts.viewDidMount)) {
|
|
74
|
+
tick().then(() => opts.viewDidMount(this._view.get()));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
for (let key of Object.keys(opts)) {
|
|
79
|
+
if (this.hasOwnProperty(key) && key[0] !== '_') {
|
|
80
|
+
let {set, _set, ...rest} = this[key];
|
|
81
|
+
|
|
82
|
+
if (!_set) {
|
|
83
|
+
// Original set
|
|
84
|
+
_set = set;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this[key] = {
|
|
88
|
+
// Set value in all views
|
|
89
|
+
set: value => {opts[key] = value; set(value);},
|
|
90
|
+
_set,
|
|
91
|
+
...rest
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Change value when view changes
|
|
95
|
+
this.view.subscribe(newView => {
|
|
96
|
+
if (newView === view) {
|
|
97
|
+
_set(opts[key]);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function parseOpts(opts, state) {
|
|
107
|
+
for (let key of Object.keys(opts)) {
|
|
108
|
+
if (state.hasOwnProperty(key) && key[0] !== '_') {
|
|
109
|
+
if (state[key].parse) {
|
|
110
|
+
opts[key] = state[key].parse(opts[key]);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import {derived, writable} from 'svelte/store';
|
|
2
|
+
import {is_function, noop, tick} from 'svelte/internal';
|
|
3
|
+
import {
|
|
4
|
+
DAY_IN_SECONDS,
|
|
5
|
+
cloneDate,
|
|
6
|
+
addDuration,
|
|
7
|
+
addDay,
|
|
8
|
+
subtractDay,
|
|
9
|
+
toISOString,
|
|
10
|
+
nextClosestDay,
|
|
11
|
+
prevClosestDay,
|
|
12
|
+
setMidnight,
|
|
13
|
+
toLocalDate
|
|
14
|
+
} from '@event-calendar/common';
|
|
15
|
+
import {derived2} from '@event-calendar/common';
|
|
16
|
+
import {createEvents} from '@event-calendar/common';
|
|
17
|
+
import {createView} from '@event-calendar/common';
|
|
18
|
+
import {assign} from '@event-calendar/common';
|
|
19
|
+
|
|
20
|
+
export function activeRange(state) {
|
|
21
|
+
let _activeRange = derived(
|
|
22
|
+
[state._currentRange, state.firstDay, state.monthMode, state.slotMinTime, state.slotMaxTime],
|
|
23
|
+
([$_currentRange, $firstDay, $monthMode, $slotMinTime, $slotMaxTime]) => {
|
|
24
|
+
let start = cloneDate($_currentRange.start);
|
|
25
|
+
let end = cloneDate($_currentRange.end);
|
|
26
|
+
|
|
27
|
+
if ($monthMode) {
|
|
28
|
+
// First day of week
|
|
29
|
+
prevClosestDay(start, $firstDay);
|
|
30
|
+
nextClosestDay(end, $firstDay);
|
|
31
|
+
} else if ($slotMaxTime.days || $slotMaxTime.seconds > DAY_IN_SECONDS) {
|
|
32
|
+
addDuration(subtractDay(end), $slotMaxTime);
|
|
33
|
+
let start2 = subtractDay(cloneDate(end));
|
|
34
|
+
if (start2 < start) {
|
|
35
|
+
start = start2;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {start, end};
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
let debounce = 0;
|
|
44
|
+
derived([_activeRange, state.datesSet], ([$_activeRange, $datesSet]) => {
|
|
45
|
+
if ($datesSet && !debounce) {
|
|
46
|
+
++debounce;
|
|
47
|
+
tick().then(() => {
|
|
48
|
+
--debounce;
|
|
49
|
+
$datesSet({
|
|
50
|
+
start: toLocalDate($_activeRange.start),
|
|
51
|
+
end: toLocalDate($_activeRange.end),
|
|
52
|
+
startStr: toISOString($_activeRange.start),
|
|
53
|
+
endStr: toISOString($_activeRange.end)
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}).subscribe(noop);
|
|
58
|
+
|
|
59
|
+
return _activeRange;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function currentRange(state) {
|
|
63
|
+
return derived(
|
|
64
|
+
[state.date, state.duration, state.monthMode, state.firstDay],
|
|
65
|
+
([$date, $duration, $monthMode, $firstDay]) => {
|
|
66
|
+
let start = cloneDate($date), end;
|
|
67
|
+
if ($monthMode) {
|
|
68
|
+
start.setDate(1);
|
|
69
|
+
} else if ($duration.inWeeks) {
|
|
70
|
+
// First day of week
|
|
71
|
+
prevClosestDay(start, $firstDay);
|
|
72
|
+
}
|
|
73
|
+
end = addDuration(cloneDate(start), $duration);
|
|
74
|
+
|
|
75
|
+
return {start, end};
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function viewDates(state) {
|
|
81
|
+
return derived2([state._activeRange, state.hiddenDays], ([$_activeRange, $hiddenDays]) => {
|
|
82
|
+
let dates = [];
|
|
83
|
+
let date = setMidnight(cloneDate($_activeRange.start));
|
|
84
|
+
let end = setMidnight(cloneDate($_activeRange.end));
|
|
85
|
+
while (date < end) {
|
|
86
|
+
if (!$hiddenDays.includes(date.getUTCDay())) {
|
|
87
|
+
dates.push(cloneDate(date));
|
|
88
|
+
}
|
|
89
|
+
addDay(date);
|
|
90
|
+
}
|
|
91
|
+
if (!dates.length && $hiddenDays.length && $hiddenDays.length < 7) {
|
|
92
|
+
// Try to move the date
|
|
93
|
+
state.date.update(date => {
|
|
94
|
+
while ($hiddenDays.includes(date.getUTCDay())) {
|
|
95
|
+
addDay(date);
|
|
96
|
+
}
|
|
97
|
+
return date;
|
|
98
|
+
});
|
|
99
|
+
dates = state._viewDates.get();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return dates;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function viewTitle(state) {
|
|
107
|
+
return derived(
|
|
108
|
+
[state.date, state._activeRange, state._titleIntlRange, state.monthMode],
|
|
109
|
+
([$date, $_activeRange, $_titleIntlRange, $monthMode]) => {
|
|
110
|
+
return $monthMode
|
|
111
|
+
? $_titleIntlRange.format($date, $date)
|
|
112
|
+
: $_titleIntlRange.format($_activeRange.start, subtractDay(cloneDate($_activeRange.end)));
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function view(state) {
|
|
118
|
+
return derived2([state.view, state._viewTitle, state._currentRange, state._activeRange], args => createView(...args));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function events(state) {
|
|
122
|
+
let _events = writable([]);
|
|
123
|
+
let abortController;
|
|
124
|
+
let fetching = 0;
|
|
125
|
+
derived(
|
|
126
|
+
[state.events, state.eventSources, state._activeRange, state._fetchedRange, state.lazyFetching, state.loading],
|
|
127
|
+
(values, set) => tick().then(() => {
|
|
128
|
+
let [$events, $eventSources, $_activeRange, $_fetchedRange, $lazyFetching, $loading] = values;
|
|
129
|
+
if (!$eventSources.length) {
|
|
130
|
+
set($events);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// Do not fetch if new range is within the previous one
|
|
134
|
+
if (!$_fetchedRange.start || $_fetchedRange.start > $_activeRange.start || $_fetchedRange.end < $_activeRange.end || !$lazyFetching) {
|
|
135
|
+
if (abortController) {
|
|
136
|
+
// Abort previous request
|
|
137
|
+
abortController.abort();
|
|
138
|
+
}
|
|
139
|
+
// Create new abort controller
|
|
140
|
+
abortController = new AbortController();
|
|
141
|
+
// Call loading hook
|
|
142
|
+
if (is_function($loading) && !fetching) {
|
|
143
|
+
$loading(true);
|
|
144
|
+
}
|
|
145
|
+
let stopLoading = () => {
|
|
146
|
+
if (--fetching === 0 && is_function($loading)) {
|
|
147
|
+
$loading(false);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
let events = [];
|
|
151
|
+
// Prepare handlers
|
|
152
|
+
let failure = e => stopLoading();
|
|
153
|
+
let success = data => {
|
|
154
|
+
events = events.concat(createEvents(data));
|
|
155
|
+
set(events);
|
|
156
|
+
stopLoading();
|
|
157
|
+
};
|
|
158
|
+
// Prepare other stuff
|
|
159
|
+
let startStr = toISOString($_activeRange.start)
|
|
160
|
+
let endStr = toISOString($_activeRange.end);
|
|
161
|
+
// Loop over event sources
|
|
162
|
+
for (let source of $eventSources) {
|
|
163
|
+
if (is_function(source.events)) {
|
|
164
|
+
// Events as a function
|
|
165
|
+
let result = source.events({
|
|
166
|
+
start: toLocalDate($_activeRange.start),
|
|
167
|
+
end: toLocalDate($_activeRange.end),
|
|
168
|
+
startStr,
|
|
169
|
+
endStr
|
|
170
|
+
}, success, failure);
|
|
171
|
+
if (result !== undefined) {
|
|
172
|
+
Promise.resolve(result).then(success, failure);
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
// Events as a JSON feed
|
|
176
|
+
// Prepare params
|
|
177
|
+
let params = is_function(source.extraParams) ? source.extraParams() : assign({}, source.extraParams);
|
|
178
|
+
params.start = startStr;
|
|
179
|
+
params.end = endStr;
|
|
180
|
+
params = new URLSearchParams(params);
|
|
181
|
+
// Prepare fetch
|
|
182
|
+
let url = source.url, headers = {}, body;
|
|
183
|
+
if (['GET', 'HEAD'].includes(source.method)) {
|
|
184
|
+
url += (url.includes('?') ? '&' : '?') + params;
|
|
185
|
+
} else {
|
|
186
|
+
headers['content-type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
|
|
187
|
+
body = String(params); // Safari 10.1 doesn't convert to string automatically
|
|
188
|
+
}
|
|
189
|
+
// Do the fetch
|
|
190
|
+
fetch(url, {method: source.method, headers, body, signal: abortController.signal, credentials: 'same-origin'})
|
|
191
|
+
.then(response => response.json())
|
|
192
|
+
.then(success)
|
|
193
|
+
.catch(failure);
|
|
194
|
+
}
|
|
195
|
+
++fetching;
|
|
196
|
+
}
|
|
197
|
+
// Save current range for future requests
|
|
198
|
+
$_fetchedRange.start = $_activeRange.start;
|
|
199
|
+
$_fetchedRange.end = $_activeRange.end;
|
|
200
|
+
}
|
|
201
|
+
}),
|
|
202
|
+
[]
|
|
203
|
+
).subscribe(_events.set);
|
|
204
|
+
|
|
205
|
+
return _events;
|
|
206
|
+
}
|