conflict detection
Signed-off-by: dartcafe <github@dartcafe.de>
This commit is contained in:
Родитель
19bc05f945
Коммит
795d414edf
|
@ -25,6 +25,7 @@ namespace OCA\Polls\Controller;
|
|||
|
||||
use DateTime;
|
||||
use DateInterval;
|
||||
use DateTimeZone;
|
||||
use OCP\IRequest;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
|
@ -142,16 +143,19 @@ class OptionController extends Controller {
|
|||
* findCalendarEvents
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function findCalendarEvents(int $optionId): DataResponse {
|
||||
return $this->response(function () use ($optionId) {
|
||||
public function findCalendarEvents(int $optionId, string $tz): DataResponse {
|
||||
return $this->response(function () use ($optionId, $tz) {
|
||||
$option = $this->optionService->get($optionId);
|
||||
$searchFrom = new DateTime();
|
||||
$searchTo = new DateTime();
|
||||
// Search calendar entries which end inside one hour before option start time
|
||||
$searchFrom = $searchFrom->setTimestamp($option->getTimestamp())->sub(new DateInterval('PT1H'));
|
||||
// Search calendar entries which start inside one hour after option end time
|
||||
$searchTo = $searchTo->setTimestamp($option->getTimestamp() + $option->getDuration())->add(new DateInterval('PT1H'));
|
||||
$events = $this->calendarService->getEvents($searchFrom, $searchTo);
|
||||
$timezone = new DateTimeZone($tz);
|
||||
$searchFrom = (new DateTime())->setTimeZone($timezone);
|
||||
$searchTo = (new DateTime())->setTimeZone($timezone);
|
||||
|
||||
// Search calendar entries which end inside one hour before option start time and one hour after option end time
|
||||
$searchFrom->setTimestamp($option->getTimestamp())->sub(new DateInterval('PT1H'));
|
||||
$searchTo->setTimestamp($option->getTimestamp() + $option->getDuration())->add(new DateInterval('PT1H'));
|
||||
|
||||
$events = $this->calendarService->getEvents($searchFrom, $searchTo, $timezone);
|
||||
|
||||
return ['events' => $events];
|
||||
});
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ namespace OCA\Polls\Model;
|
|||
|
||||
use DateTimeImmutable;
|
||||
use DateInterval;
|
||||
use DateTimeZone;
|
||||
use \OCP\Calendar\ICalendar;
|
||||
use RRule\RRule;
|
||||
|
||||
|
@ -52,27 +53,33 @@ class CalendarEvent implements \JsonSerializable {
|
|||
/** @var array */
|
||||
protected $event;
|
||||
|
||||
/** @var DateTimeImmutable */
|
||||
protected $filterFrom;
|
||||
|
||||
/** @var DateTimeImmutable */
|
||||
protected $filterTo;
|
||||
|
||||
/** @var ICalendar */
|
||||
protected $calendar;
|
||||
|
||||
/** @var DateTimeImmutable */
|
||||
protected $filterFrom;
|
||||
|
||||
/** @var DateTimeImmutable */
|
||||
protected $filterTo;
|
||||
|
||||
/** @var DateTimeZone */
|
||||
protected $timezone;
|
||||
|
||||
public function __construct(
|
||||
array $iCal,
|
||||
ICalendar $calendar,
|
||||
DateTimeImmutable $filterFrom = null,
|
||||
DateTimeImmutable $filterTo = null
|
||||
DateTimeImmutable $filterTo = null,
|
||||
DateTimeZone $timezone = null
|
||||
) {
|
||||
$this->iCal = $iCal;
|
||||
$this->calendar = $calendar;
|
||||
$this->event = $this->iCal['objects'][0];
|
||||
$this->filterFrom = $filterFrom;
|
||||
$this->filterTo = $filterTo;
|
||||
$this->timezone = $timezone;
|
||||
$this->event = $this->iCal['objects'][0];
|
||||
$this->hasRRule = isset($this->event['RRULE']);
|
||||
$this->fixAllDay();
|
||||
$this->buildRRule();
|
||||
$this->calculateOccurrences();
|
||||
}
|
||||
|
@ -202,6 +209,16 @@ class CalendarEvent implements \JsonSerializable {
|
|||
return $this->occurrences;
|
||||
}
|
||||
|
||||
private function fixAllDay(): void {
|
||||
// force all day events to 00:00 in the user's timezone
|
||||
if ($this->getType() === self::TYPE_DATE) {
|
||||
$this->event['DTSTART'][0] = $this->event['DTSTART'][0]->setTimezone($this->timezone);
|
||||
$this->event['DTEND'][0] = $this->event['DTEND'][0]->setTimezone($this->timezone);
|
||||
$this->event['DTSTART'][0] = $this->event['DTSTART'][0]->setTime(0, 0);
|
||||
$this->event['DTEND'][0] = $this->event['DTEND'][0]->setTime(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private function buildRRule() : void {
|
||||
if (!$this->getHasRRule()) {
|
||||
return;
|
||||
|
|
|
@ -26,6 +26,7 @@ namespace OCA\Polls\Service;
|
|||
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
use OCP\Calendar\ICalendar;
|
||||
use OCP\Calendar\IManager as CalendarManager;
|
||||
use OCP\Util;
|
||||
|
@ -97,7 +98,7 @@ class CalendarService {
|
|||
*
|
||||
* @psalm-return list<CalendarEvent>
|
||||
*/
|
||||
public function getEvents(DateTime $from, DateTime $to): array {
|
||||
public function getEvents(DateTime $from, DateTime $to, DateTimeZone $timezone): array {
|
||||
$from = DateTimeImmutable::createFromMutable($from);
|
||||
$to = DateTimeImmutable::createFromMutable($to);
|
||||
|
||||
|
@ -127,7 +128,7 @@ class CalendarService {
|
|||
continue;
|
||||
}
|
||||
|
||||
$calendarEvent = new CalendarEvent($event, $calendar, $from, $to);
|
||||
$calendarEvent = new CalendarEvent($event, $calendar, $from, $to, $timezone);
|
||||
if ($calendarEvent->getOccurrences()) {
|
||||
for ($index = 0; $index < count($calendarEvent->getOccurrences()); $index++) {
|
||||
$calendarEvent->setOccurrence($index);
|
||||
|
|
|
@ -24,11 +24,14 @@
|
|||
<div class="calendar-info"
|
||||
:class="[conflictLevel, statusClass]"
|
||||
:style="calendarStyle">
|
||||
<div v-if="!event.allDay" class="calendar-info__time">
|
||||
{{ formatDate(event.start) }} - {{ formatDate(event.end) }}
|
||||
<div v-if="calendarEvent.allDay" class="calendar-info__time">
|
||||
{{ dayStart }} {{ dayEnd }}
|
||||
</div>
|
||||
<div v-else class="calendar-info__time">
|
||||
{{ formatDate(calendarEvent.start) }} - {{ formatDate(calendarEvent.end) }}
|
||||
</div>
|
||||
<div class="summay" :class="statusClass">
|
||||
{{ event.summary }}
|
||||
{{ calendarEvent.summary }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -40,7 +43,7 @@ export default {
|
|||
name: 'CalendarInfo',
|
||||
|
||||
props: {
|
||||
event: {
|
||||
calendarEvent: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
|
@ -55,21 +58,35 @@ export default {
|
|||
computed: {
|
||||
calendarStyle() {
|
||||
return {
|
||||
backgroundColor: this.event.displayColor,
|
||||
backgroundColor: this.calendarEvent.displayColor,
|
||||
color: this.fontColor,
|
||||
}
|
||||
},
|
||||
|
||||
dayStart() {
|
||||
return moment.unix(this.calendarEvent.start).format('ddd')
|
||||
},
|
||||
|
||||
dayEnd() {
|
||||
const dayEnd = moment.unix(this.calendarEvent.end - 1).format('ddd')
|
||||
|
||||
if (dayEnd === this.dayStart) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return `- ${dayEnd}`
|
||||
},
|
||||
|
||||
statusClass() {
|
||||
return this.event.status.toLowerCase()
|
||||
return this.calendarEvent.status.toLowerCase()
|
||||
},
|
||||
|
||||
fontColor() {
|
||||
if (this.event.displayColor === 'transparent') {
|
||||
if (this.calendarEvent.displayColor === 'transparent') {
|
||||
return 'var(--color-main-text)'
|
||||
}
|
||||
|
||||
const hex = this.event.displayColor.replace(/#/, '')
|
||||
const hex = this.calendarEvent.displayColor.replace(/#/, '')
|
||||
const r = parseInt(hex.slice(0, 2), 16)
|
||||
const g = parseInt(hex.slice(2, 4), 16)
|
||||
const b = parseInt(hex.slice(4, 6), 16)
|
||||
|
@ -84,15 +101,17 @@ export default {
|
|||
},
|
||||
|
||||
conflictLevel() {
|
||||
if (this.event.calendarKey === 0) {
|
||||
if (this.calendarEvent.calendarKey === 0) {
|
||||
return 'conflict-ignore'
|
||||
}
|
||||
|
||||
if (this.event.start > this.option.timestamp + 3599) {
|
||||
// No conflict, if calendarEvent starts after end of option
|
||||
if (this.calendarEvent.start >= this.option.timestamp + this.option.duration) {
|
||||
return 'conflict-no'
|
||||
}
|
||||
|
||||
if (this.event.end - 1 < this.option.timestamp) {
|
||||
// No conflict, if calendarEvent ends before option
|
||||
if (this.calendarEvent.end <= this.option.timestamp) {
|
||||
return 'conflict-no'
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<div class="calendar-peek__grid">
|
||||
<CalendarInfo v-for="eventItem in sortedEvents"
|
||||
:key="eventItem.UID"
|
||||
:event="eventItem"
|
||||
:calendar-event="eventItem"
|
||||
:option="option" />
|
||||
</div>
|
||||
</Popover>
|
||||
|
@ -42,6 +42,7 @@
|
|||
import { mapState } from 'vuex'
|
||||
import orderBy from 'lodash/orderBy'
|
||||
import { Popover } from '@nextcloud/vue'
|
||||
import moment from '@nextcloud/moment'
|
||||
import CalendarInfo from '../Calendar/CalendarInfo.vue'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
|
||||
|
@ -71,6 +72,16 @@ export default {
|
|||
poll: (state) => state.poll,
|
||||
}),
|
||||
|
||||
detectAllDay() {
|
||||
const from = moment.unix(this.option.timestamp)
|
||||
const to = moment.unix(this.option.timestamp + Math.max(0, this.option.duration))
|
||||
const dayLongEvent = from.unix() === moment(from).startOf('day').unix() && to.unix() === moment(to).startOf('day').unix() && from.unix() !== to.unix()
|
||||
return {
|
||||
allDay: dayLongEvent,
|
||||
type: dayLongEvent ? 'date' : 'dateTime',
|
||||
}
|
||||
},
|
||||
|
||||
sortedEvents() {
|
||||
const sortedEvents = [...this.events]
|
||||
sortedEvents.push(this.thisOption)
|
||||
|
@ -85,14 +96,14 @@ export default {
|
|||
calendarKey: 0,
|
||||
calendarName: 'Polls',
|
||||
displayColor: 'transparent',
|
||||
allDay: '',
|
||||
allDay: this.detectAllDay.allDay,
|
||||
description: this.poll.description,
|
||||
start: this.option.timestamp,
|
||||
location: '',
|
||||
end: this.option.timestamp + this.option.duration,
|
||||
status: 'self',
|
||||
summary: this.poll.title,
|
||||
type: '',
|
||||
type: this.detectAllDay.type,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -302,7 +302,11 @@ const actions = {
|
|||
const endPoint = `apps/polls/option/${payload.option.id}/events`
|
||||
|
||||
try {
|
||||
return await axios.get(generateUrl(endPoint))
|
||||
return await axios.get(generateUrl(endPoint), {
|
||||
params: {
|
||||
tz: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
return { events: [] }
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче