Prechádzať zdrojové kódy

move rendering to eventui, daygrid dot-event rendering

Adam Shaw 5 rokov pred
rodič
commit
3eca2c226d

+ 1 - 1
packages/core/src/api/EventApi.ts

@@ -262,7 +262,7 @@ export default class EventApi {
   get allDay(): boolean { return this._def.allDay }
   get allDay(): boolean { return this._def.allDay }
   get title(): string { return this._def.title }
   get title(): string { return this._def.title }
   get url(): string { return this._def.url }
   get url(): string { return this._def.url }
-  get rendering(): string { return this._def.rendering }
+  get rendering(): string { return this._def.ui.rendering }
   get startEditable(): boolean { return this._def.ui.startEditable }
   get startEditable(): boolean { return this._def.ui.startEditable }
   get durationEditable(): boolean { return this._def.ui.durationEditable }
   get durationEditable(): boolean { return this._def.ui.durationEditable }
   get constraint(): any { return this._def.ui.constraints[0] || null }
   get constraint(): any { return this._def.ui.constraints[0] || null }

+ 10 - 4
packages/core/src/common/EventRoot.tsx

@@ -42,10 +42,16 @@ export class EventRoot extends BaseComponent<EventRootProps> {
 
 
   render(props: EventRootProps, state: {}, context: ComponentContext) {
   render(props: EventRootProps, state: {}, context: ComponentContext) {
     let { seg } = props
     let { seg } = props
-    let hookProps = {
-      event: new EventApi(context.calendar, seg.eventRange.def, seg.eventRange.instance),
+    let { eventRange } = seg
+    let { ui } = eventRange
+
+    let hookProps: EventMeta = {
+      event: new EventApi(context.calendar, eventRange.def, eventRange.instance),
       view: context.view,
       view: context.view,
       timeText: props.timeText,
       timeText: props.timeText,
+      textColor: ui.textColor,
+      backgroundColor: ui.backgroundColor,
+      borderColor: ui.borderColor,
       isDraggable: !props.disableDragging && computeSegDraggable(seg, context),
       isDraggable: !props.disableDragging && computeSegDraggable(seg, context),
       isStartResizable: !props.disableResizing && computeSegStartResizable(seg, context),
       isStartResizable: !props.disableResizing && computeSegStartResizable(seg, context),
       isEndResizable: !props.disableResizing && computeSegEndResizable(seg, context),
       isEndResizable: !props.disableResizing && computeSegEndResizable(seg, context),
@@ -60,8 +66,8 @@ export class EventRoot extends BaseComponent<EventRootProps> {
       isResizing: Boolean(props.isResizing)
       isResizing: Boolean(props.isResizing)
     }
     }
 
 
-    let style = getSkinCss(seg.eventRange.ui)
-    let standardClassNames = getEventClassNames(hookProps).concat(seg.eventRange.ui.classNames)
+    let style = getSkinCss(ui)
+    let standardClassNames = getEventClassNames(hookProps).concat(ui.classNames)
 
 
     return (
     return (
       <RenderHook
       <RenderHook

+ 9 - 5
packages/core/src/component/event-rendering.ts

@@ -34,8 +34,9 @@ export function sliceEventStore(eventStore: EventStore, eventUiBases: EventUiHas
 
 
   for (let defId in eventStore.defs) {
   for (let defId in eventStore.defs) {
     let def = eventStore.defs[defId]
     let def = eventStore.defs[defId]
+    let ui = eventUis[def.defId]
 
 
-    if (def.rendering === 'inverse-background') {
+    if (ui.rendering === 'inverse-background') {
       if (def.groupId) {
       if (def.groupId) {
         inverseBgByGroupId[def.groupId] = []
         inverseBgByGroupId[def.groupId] = []
 
 
@@ -61,15 +62,15 @@ export function sliceEventStore(eventStore: EventStore, eventUiBases: EventUiHas
     let slicedRange = intersectRanges(normalRange, framingRange)
     let slicedRange = intersectRanges(normalRange, framingRange)
 
 
     if (slicedRange) {
     if (slicedRange) {
-      if (def.rendering === 'inverse-background') {
+      if (ui.rendering === 'inverse-background') {
         if (def.groupId) {
         if (def.groupId) {
           inverseBgByGroupId[def.groupId].push(slicedRange)
           inverseBgByGroupId[def.groupId].push(slicedRange)
         } else {
         } else {
           inverseBgByDefId[instance.defId].push(slicedRange)
           inverseBgByDefId[instance.defId].push(slicedRange)
         }
         }
 
 
-      } else if (def.rendering !== 'none') {
-        (def.rendering === 'background' ? bgRanges : fgRanges).push({
+      } else if (ui.rendering !== 'none') {
+        (ui.rendering === 'background' ? bgRanges : fgRanges).push({
           def,
           def,
           ui,
           ui,
           instance,
           instance,
@@ -121,7 +122,7 @@ export function sliceEventStore(eventStore: EventStore, eventUiBases: EventUiHas
 
 
 
 
 export function hasBgRendering(def: EventDef) {
 export function hasBgRendering(def: EventDef) {
-  return def.rendering === 'background' || def.rendering === 'inverse-background'
+  return def.ui.rendering === 'background' || def.ui.rendering === 'inverse-background'
 }
 }
 
 
 
 
@@ -204,6 +205,9 @@ export function buildSegCompareObj(seg: Seg) {
 export interface EventMeta { // for *Content handlers
 export interface EventMeta { // for *Content handlers
   event: EventApi
   event: EventApi
   timeText: string
   timeText: string
+  backgroundColor: string // TODO: add other EventUi props?
+  borderColor: string     //
+  textColor: string       //
   isDraggable: boolean
   isDraggable: boolean
   isStartResizable: boolean
   isStartResizable: boolean
   isEndResizable: boolean
   isEndResizable: boolean

+ 5 - 0
packages/core/src/component/event-ui.ts

@@ -23,6 +23,7 @@ export interface UnscopedEventUiInput {
 }
 }
 
 
 export interface EventUi {
 export interface EventUi {
+  rendering: string
   startEditable: boolean | null
   startEditable: boolean | null
   durationEditable: boolean | null
   durationEditable: boolean | null
   constraints: Constraint[]
   constraints: Constraint[]
@@ -37,6 +38,7 @@ export interface EventUi {
 export type EventUiHash = { [defId: string]: EventUi }
 export type EventUiHash = { [defId: string]: EventUi }
 
 
 export const UNSCOPED_EVENT_UI_PROPS = {
 export const UNSCOPED_EVENT_UI_PROPS = {
+  rendering: String,
   editable: Boolean,
   editable: Boolean,
   startEditable: Boolean,
   startEditable: Boolean,
   durationEditable: Boolean,
   durationEditable: Boolean,
@@ -56,6 +58,7 @@ export function processUnscopedUiProps(rawProps: UnscopedEventUiInput, calendar:
   let constraint = normalizeConstraint(props.constraint, calendar)
   let constraint = normalizeConstraint(props.constraint, calendar)
 
 
   return {
   return {
+    rendering: props.rendering,
     startEditable: props.startEditable != null ? props.startEditable : props.editable,
     startEditable: props.startEditable != null ? props.startEditable : props.editable,
     durationEditable: props.durationEditable != null ? props.durationEditable : props.editable,
     durationEditable: props.durationEditable != null ? props.durationEditable : props.editable,
     constraints: constraint != null ? [ constraint ] : [],
     constraints: constraint != null ? [ constraint ] : [],
@@ -94,6 +97,7 @@ export function processScopedUiProps(prefix: string, rawScoped: any, calendar: C
 }
 }
 
 
 const EMPTY_EVENT_UI: EventUi = {
 const EMPTY_EVENT_UI: EventUi = {
+  rendering: '',
   startEditable: null,
   startEditable: null,
   durationEditable: null,
   durationEditable: null,
   constraints: [],
   constraints: [],
@@ -112,6 +116,7 @@ export function combineEventUis(uis: EventUi[]): EventUi {
 
 
 function combineTwoEventUis(item0: EventUi, item1: EventUi): EventUi { // hash1 has higher precedence
 function combineTwoEventUis(item0: EventUi, item1: EventUi): EventUi { // hash1 has higher precedence
   return {
   return {
+    rendering: item1.rendering || item0.rendering,
     startEditable: item1.startEditable != null ? item1.startEditable : item0.startEditable,
     startEditable: item1.startEditable != null ? item1.startEditable : item0.startEditable,
     durationEditable: item1.durationEditable != null ? item1.durationEditable : item0.durationEditable,
     durationEditable: item1.durationEditable != null ? item1.durationEditable : item0.durationEditable,
     constraints: item0.constraints.concat(item1.constraints),
     constraints: item0.constraints.concat(item1.constraints),

+ 1 - 0
packages/core/src/reducers/eventStore.ts

@@ -158,6 +158,7 @@ function applyMutationToRelated(eventStore: EventStore, instanceId: string, muta
   let relevant = getRelevantEvents(eventStore, instanceId)
   let relevant = getRelevantEvents(eventStore, instanceId)
   let eventConfigBase = fromApi ?
   let eventConfigBase = fromApi ?
     { '': { // HACK. always allow API to mutate events
     { '': { // HACK. always allow API to mutate events
+      rendering: '',
       startEditable: true,
       startEditable: true,
       durationEditable: true,
       durationEditable: true,
       constraints: [],
       constraints: [],

+ 0 - 4
packages/core/src/structs/event.ts

@@ -13,14 +13,11 @@ Utils for parsing event-input data. Each util parses a subset of the event-input
 It's up to the caller to stitch them together into an aggregate object like an EventStore.
 It's up to the caller to stitch them together into an aggregate object like an EventStore.
 */
 */
 
 
-export type EventRenderingChoice = '' | 'background' | 'inverse-background' | 'none'
-
 export interface EventNonDateInput extends UnscopedEventUiInput {
 export interface EventNonDateInput extends UnscopedEventUiInput {
   id?: string | number
   id?: string | number
   groupId?: string | number
   groupId?: string | number
   title?: string
   title?: string
   url?: string
   url?: string
-  rendering?: EventRenderingChoice
   extendedProps?: object
   extendedProps?: object
   [extendedProp: string]: any
   [extendedProp: string]: any
 }
 }
@@ -44,7 +41,6 @@ export interface EventDef {
   recurringDef: { typeId: number, typeData: any, duration: Duration | null } | null
   recurringDef: { typeId: number, typeData: any, duration: Duration | null } | null
   title: string
   title: string
   url: string
   url: string
-  rendering: EventRenderingChoice
   ui: EventUi
   ui: EventUi
   extendedProps: any
   extendedProps: any
 }
 }

+ 21 - 9
packages/daygrid/src/MorePopover.tsx

@@ -1,7 +1,9 @@
 import { DateComponent, DateMarker, h, EventInstanceHash, ComponentContext, createFormatter, Hit, addDays, DateRange, getSegMeta, DayCellRoot, DayCellContent } from '@fullcalendar/core'
 import { DateComponent, DateMarker, h, EventInstanceHash, ComponentContext, createFormatter, Hit, addDays, DateRange, getSegMeta, DayCellRoot, DayCellContent } from '@fullcalendar/core'
 import TableSeg from './TableSeg'
 import TableSeg from './TableSeg'
 import TableEvent from './TableEvent'
 import TableEvent from './TableEvent'
+import TableDotEvent from './TableDotEvent'
 import Popover from './Popover'
 import Popover from './Popover'
+import { isDotRendering } from './event-rendering'
 
 
 
 
 export interface MorePopoverProps {
 export interface MorePopoverProps {
@@ -50,20 +52,30 @@ export default class MorePopover extends DateComponent<MorePopoverProps> {
 
 
               return (
               return (
                 <div
                 <div
+                  className='fc-daygrid-event-harness'
                   key={instanceId}
                   key={instanceId}
                   style={{
                   style={{
                     visibility: hiddenInstances[instanceId] ? 'hidden' : ''
                     visibility: hiddenInstances[instanceId] ? 'hidden' : ''
                   }}
                   }}
                 >
                 >
-                  <TableEvent
-                    seg={seg}
-                    isDragging={false}
-                    isResizing={false}
-                    isDateSelecting={false}
-                    isSelected={instanceId === props.selectedInstanceId}
-                    defaultDisplayEventEnd={false}
-                    {...getSegMeta(seg, todayRange)}
-                  />
+                  {isDotRendering(eventRange) ?
+                    <TableDotEvent
+                      seg={seg}
+                      isDragging={false}
+                      isSelected={instanceId === props.selectedInstanceId}
+                      defaultDisplayEventEnd={false}
+                      {...getSegMeta(seg, todayRange)}
+                    /> :
+                    <TableEvent
+                      seg={seg}
+                      isDragging={false}
+                      isResizing={false}
+                      isDateSelecting={false}
+                      isSelected={instanceId === props.selectedInstanceId}
+                      defaultDisplayEventEnd={false}
+                      {...getSegMeta(seg, todayRange)}
+                    />
+                  }
                 </div>
                 </div>
               )
               )
             })}
             })}

+ 80 - 0
packages/daygrid/src/TableDotEvent.tsx

@@ -0,0 +1,80 @@
+import { h, BaseComponent, Seg, EventRoot, ComponentContext, createFormatter, buildSegTimeText, EventMeta, Fragment } from '@fullcalendar/core'
+import { DEFAULT_TABLE_EVENT_TIME_FORMAT } from './event-rendering'
+
+
+export interface DotTableEventProps {
+  seg: Seg
+  isDragging: boolean
+  isSelected: boolean
+  isPast: boolean
+  isFuture: boolean
+  isToday: boolean
+  defaultDisplayEventEnd: boolean
+}
+
+export default class TableEvent extends BaseComponent<DotTableEventProps> {
+
+  render(props: DotTableEventProps, state: {}, context: ComponentContext) {
+    let { options } = context
+
+    // TODO: avoid createFormatter, cache!!!
+    // SOLUTION: require that props.defaultTimeFormat is a real formatter, a top-level const,
+    // which will require that defaultRangeSeparator be part of the DateEnv (possible already?),
+    // and have options.eventTimeFormat be preprocessed.
+    let timeFormat = createFormatter(
+      options.eventTimeFormat || DEFAULT_TABLE_EVENT_TIME_FORMAT,
+      options.defaultRangeSeparator
+    )
+
+    let timeText = buildSegTimeText(props.seg, timeFormat, context, true, props.defaultDisplayEventEnd)
+
+    return (
+      <EventRoot
+        seg={props.seg}
+        timeText={timeText}
+        defaultContent={renderInnerContent}
+        isDragging={props.isDragging}
+        isResizing={false}
+        isDateSelecting={false}
+        isSelected={props.isSelected}
+        isPast={props.isPast}
+        isFuture={props.isFuture}
+        isToday={props.isToday}
+      >
+        {(rootElRef, classNames, style, innerElRef, innerContent, innerProps) => ( // we don't use styles!
+          <a
+            className={[ 'fc-daygrid-event', 'fc-daygrid-dot-event' ].concat(classNames).join(' ')}
+            ref={rootElRef}
+            style={{ color: innerProps.textColor }}
+            {...getSegAnchorAttrs(props.seg)}
+          >
+            {innerContent}
+          </a>
+        )}
+      </EventRoot>
+    )
+  }
+
+}
+
+
+function renderInnerContent(innerProps: EventMeta) {
+  return [
+    <div
+      className='fc-daygrid-event-dot'
+      style={{ backgroundColor: innerProps.backgroundColor || innerProps.borderColor }}
+    />,
+    innerProps.timeText &&
+      <div class='fc-event-time'>{innerProps.timeText}</div>
+    ,
+    <div class='fc-event-title'>
+      {innerProps.event.title || <Fragment>&nbsp;</Fragment>}
+    </div>
+  ]
+}
+
+
+function getSegAnchorAttrs(seg: Seg) { // not dry. in StandardEvent too
+  let url = seg.eventRange.def.url
+  return url ? { href: url } : {}
+}

+ 3 - 10
packages/daygrid/src/TableEvent.tsx

@@ -1,12 +1,5 @@
 import { h, StandardEvent, BaseComponent, MinimalEventProps } from '@fullcalendar/core'
 import { h, StandardEvent, BaseComponent, MinimalEventProps } from '@fullcalendar/core'
-
-
-const DEFAULT_TIME_FORMAT = {
-  hour: 'numeric',
-  minute: '2-digit',
-  omitZeroMinute: true,
-  meridiem: 'narrow'
-}
+import { DEFAULT_TABLE_EVENT_TIME_FORMAT } from './event-rendering'
 
 
 
 
 export interface TableEventProps extends MinimalEventProps {
 export interface TableEventProps extends MinimalEventProps {
@@ -19,8 +12,8 @@ export default class TableEvent extends BaseComponent<TableEventProps> {
     return (
     return (
       <StandardEvent
       <StandardEvent
         {...props}
         {...props}
-        extraClassNames={[ 'fc-daygrid-event', 'fc-h-event' ]}
-        defaultTimeFormat={DEFAULT_TIME_FORMAT}
+        extraClassNames={[ 'fc-daygrid-event', 'fc-daygrid-block-event', 'fc-h-event' ]}
+        defaultTimeFormat={DEFAULT_TABLE_EVENT_TIME_FORMAT}
         defaultDisplayEventEnd={props.defaultDisplayEventEnd}
         defaultDisplayEventEnd={props.defaultDisplayEventEnd}
         disableResizing={!props.seg.eventRange.def.allDay}
         disableResizing={!props.seg.eventRange.def.allDay}
       />
       />

+ 20 - 9
packages/daygrid/src/TableRow.tsx

@@ -18,8 +18,10 @@ import {
 } from '@fullcalendar/core'
 } from '@fullcalendar/core'
 import TableSeg, { splitSegsByFirstCol } from './TableSeg'
 import TableSeg, { splitSegsByFirstCol } from './TableSeg'
 import TableCell, { TableCellModel, MoreLinkArg } from './TableCell'
 import TableCell, { TableCellModel, MoreLinkArg } from './TableCell'
+import TableDotEvent from './TableDotEvent'
 import TableEvent from './TableEvent'
 import TableEvent from './TableEvent'
 import { computeFgSegPlacement } from './event-placement'
 import { computeFgSegPlacement } from './event-placement'
+import { isDotRendering } from './event-rendering'
 
 
 
 
 // TODO: attach to window resize?
 // TODO: attach to window resize?
@@ -260,15 +262,24 @@ export default class TableRow extends DateComponent<TableRowProps, TableRowState
               right: right || ''
               right: right || ''
             }}
             }}
           >
           >
-            <TableEvent
-              seg={seg}
-              isDragging={isDragging}
-              isResizing={isResizing}
-              isDateSelecting={isDateSelecting}
-              isSelected={instanceId === eventSelection}
-              defaultDisplayEventEnd={defaultDisplayEventEnd}
-              {...getSegMeta(seg, todayRange)}
-            />
+            {isDotRendering(eventRange) ?
+              <TableDotEvent
+                seg={seg}
+                isDragging={isDragging}
+                isSelected={instanceId === eventSelection}
+                defaultDisplayEventEnd={defaultDisplayEventEnd}
+                {...getSegMeta(seg, todayRange)}
+              /> :
+              <TableEvent
+                seg={seg}
+                isDragging={isDragging}
+                isResizing={isResizing}
+                isDateSelecting={isDateSelecting}
+                isSelected={instanceId === eventSelection}
+                defaultDisplayEventEnd={defaultDisplayEventEnd}
+                {...getSegMeta(seg, todayRange)}
+              />
+            }
           </div>
           </div>
         )
         )
       }
       }

+ 16 - 0
packages/daygrid/src/event-rendering.ts

@@ -0,0 +1,16 @@
+import { EventRenderRange } from '@fullcalendar/core'
+
+
+export const DEFAULT_TABLE_EVENT_TIME_FORMAT = {
+  hour: 'numeric',
+  minute: '2-digit',
+  omitZeroMinute: true,
+  meridiem: 'narrow'
+}
+
+
+export function isDotRendering(eventRange: EventRenderRange) {
+  let { rendering } = eventRange.ui
+  let isAuto = !rendering || rendering === 'auto' // TODO: normalize earlier on
+  return rendering === 'dot' || (isAuto && !eventRange.def.allDay) // or auto and has-time. TODO: more DRY
+}

+ 48 - 24
packages/daygrid/src/styles/_daygrid-event.scss

@@ -2,52 +2,76 @@
 .fc-daygrid-event {
 .fc-daygrid-event {
   position: relative;
   position: relative;
   white-space: nowrap;
   white-space: nowrap;
-  margin-bottom: 1px; // spacing between events
+  margin: 1px 0; // spacing between events
+  border-radius: 3px; // dot event needs this to when selected
 
 
-  // vars for all of this...
+  // TODO: vars
   font-size: .85em;
   font-size: .85em;
-  border: 1px solid #3788d8;
-  background-color: #3788d8;
-  color: #fff;
-  border-radius: 3px;
-
-  .fc-event-main {
-    padding: 1px;
-    overflow: hidden;
-  }
 
 
   .fc-event-time,
   .fc-event-time,
   .fc-event-title {
   .fc-event-title {
     display: inline;
     display: inline;
   }
   }
-
-  .fc-event-time {
-    font-weight: bold;
-  }
 }
 }
 
 
-
 // spacing between time and title
 // spacing between time and title
-
 .fc-dir-ltr .fc-daygrid-event .fc-event-time {
 .fc-dir-ltr .fc-daygrid-event .fc-event-time {
   margin-right: 3px; // TODO: var
   margin-right: 3px; // TODO: var
 }
 }
-
 .fc-dir-rtl .fc-daygrid-event .fc-event-time {
 .fc-dir-rtl .fc-daygrid-event .fc-event-time {
   margin-left: 3px; // TODO: var
   margin-left: 3px; // TODO: var
 }
 }
 
 
 
 
-// kill radius when event continues into future/past
 
 
-.fc-dir-ltr .fc-daygrid-event:not(.fc-event-start),
-.fc-dir-rtl .fc-daygrid-event:not(.fc-event-end) {
+// --- the rectangle ("block") style of event ---
+
+.fc-daygrid-block-event {
+
+  // vars for all of this...
+  border: 1px solid #3788d8;
+  background-color: #3788d8;
+  color: #fff;
+
+  .fc-event-main {
+    padding: 1px;
+    overflow: hidden;
+  }
+
+  .fc-event-time {
+    font-weight: bold;
+  }
+}
+
+// kill radius when event continues into future/past
+.fc-dir-ltr .fc-daygrid-block-event:not(.fc-event-start),
+.fc-dir-rtl .fc-daygrid-block-event:not(.fc-event-end) {
   border-top-left-radius: 0;
   border-top-left-radius: 0;
   border-bottom-left-radius: 0;
   border-bottom-left-radius: 0;
 }
 }
-
-.fc-dir-ltr .fc-daygrid-event:not(.fc-event-end),
-.fc-dir-rtl .fc-daygrid-event:not(.fc-event-start) {
+.fc-dir-ltr .fc-daygrid-block-event:not(.fc-event-end),
+.fc-dir-rtl .fc-daygrid-block-event:not(.fc-event-start) {
   border-top-right-radius: 0;
   border-top-right-radius: 0;
   border-bottom-right-radius: 0;
   border-bottom-right-radius: 0;
 }
 }
+
+
+// --- the dot style of event ---
+// (time and title are already display:inline b/c they're spans)
+
+.fc-daygrid-dot-event {
+  padding: 2px 0;
+
+  .fc-event-title {
+    font-weight: bold;
+  }
+}
+
+.fc-daygrid-event-dot {
+  display: inline-block;
+  margin: 0 4px;
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  background: #3788d8; // TODO: var
+}

+ 4 - 0
packages/daygrid/src/styles/_daygrid.scss

@@ -100,6 +100,9 @@ $daygrid-popover-z: 6;
 // fg event harness
 // fg event harness
 
 
 .fc-daygrid-event-harness {
 .fc-daygrid-event-harness {
+  &:before {
+    @include clearfix; // for when event element has top margin
+  }
   &:after {
   &:after {
     @include clearfix; // for when event element has bottom margin
     @include clearfix; // for when event element has bottom margin
   }
   }
@@ -123,6 +126,7 @@ $daygrid-popover-z: 6;
   position: relative;
   position: relative;
   z-index: $daygrid-text-z;
   z-index: $daygrid-text-z;
   font-size: .85em;
   font-size: .85em;
+  margin: 2px 3px 0;
 }
 }
 
 
 .fc-daygrid-more-link {
 .fc-daygrid-more-link {