Просмотр исходного кода

move rendering to eventui, daygrid dot-event rendering

Adam Shaw 5 лет назад
Родитель
Сommit
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 title(): string { return this._def.title }
   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 durationEditable(): boolean { return this._def.ui.durationEditable }
   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) {
     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,
       timeText: props.timeText,
+      textColor: ui.textColor,
+      backgroundColor: ui.backgroundColor,
+      borderColor: ui.borderColor,
       isDraggable: !props.disableDragging && computeSegDraggable(seg, context),
       isStartResizable: !props.disableResizing && computeSegStartResizable(seg, context),
       isEndResizable: !props.disableResizing && computeSegEndResizable(seg, context),
@@ -60,8 +66,8 @@ export class EventRoot extends BaseComponent<EventRootProps> {
       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 (
       <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) {
     let def = eventStore.defs[defId]
+    let ui = eventUis[def.defId]
 
-    if (def.rendering === 'inverse-background') {
+    if (ui.rendering === 'inverse-background') {
       if (def.groupId) {
         inverseBgByGroupId[def.groupId] = []
 
@@ -61,15 +62,15 @@ export function sliceEventStore(eventStore: EventStore, eventUiBases: EventUiHas
     let slicedRange = intersectRanges(normalRange, framingRange)
 
     if (slicedRange) {
-      if (def.rendering === 'inverse-background') {
+      if (ui.rendering === 'inverse-background') {
         if (def.groupId) {
           inverseBgByGroupId[def.groupId].push(slicedRange)
         } else {
           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,
           ui,
           instance,
@@ -121,7 +122,7 @@ export function sliceEventStore(eventStore: EventStore, eventUiBases: EventUiHas
 
 
 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
   event: EventApi
   timeText: string
+  backgroundColor: string // TODO: add other EventUi props?
+  borderColor: string     //
+  textColor: string       //
   isDraggable: boolean
   isStartResizable: boolean
   isEndResizable: boolean

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

@@ -23,6 +23,7 @@ export interface UnscopedEventUiInput {
 }
 
 export interface EventUi {
+  rendering: string
   startEditable: boolean | null
   durationEditable: boolean | null
   constraints: Constraint[]
@@ -37,6 +38,7 @@ export interface EventUi {
 export type EventUiHash = { [defId: string]: EventUi }
 
 export const UNSCOPED_EVENT_UI_PROPS = {
+  rendering: String,
   editable: Boolean,
   startEditable: Boolean,
   durationEditable: Boolean,
@@ -56,6 +58,7 @@ export function processUnscopedUiProps(rawProps: UnscopedEventUiInput, calendar:
   let constraint = normalizeConstraint(props.constraint, calendar)
 
   return {
+    rendering: props.rendering,
     startEditable: props.startEditable != null ? props.startEditable : props.editable,
     durationEditable: props.durationEditable != null ? props.durationEditable : props.editable,
     constraints: constraint != null ? [ constraint ] : [],
@@ -94,6 +97,7 @@ export function processScopedUiProps(prefix: string, rawScoped: any, calendar: C
 }
 
 const EMPTY_EVENT_UI: EventUi = {
+  rendering: '',
   startEditable: null,
   durationEditable: null,
   constraints: [],
@@ -112,6 +116,7 @@ export function combineEventUis(uis: EventUi[]): EventUi {
 
 function combineTwoEventUis(item0: EventUi, item1: EventUi): EventUi { // hash1 has higher precedence
   return {
+    rendering: item1.rendering || item0.rendering,
     startEditable: item1.startEditable != null ? item1.startEditable : item0.startEditable,
     durationEditable: item1.durationEditable != null ? item1.durationEditable : item0.durationEditable,
     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 eventConfigBase = fromApi ?
     { '': { // HACK. always allow API to mutate events
+      rendering: '',
       startEditable: true,
       durationEditable: true,
       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.
 */
 
-export type EventRenderingChoice = '' | 'background' | 'inverse-background' | 'none'
-
 export interface EventNonDateInput extends UnscopedEventUiInput {
   id?: string | number
   groupId?: string | number
   title?: string
   url?: string
-  rendering?: EventRenderingChoice
   extendedProps?: object
   [extendedProp: string]: any
 }
@@ -44,7 +41,6 @@ export interface EventDef {
   recurringDef: { typeId: number, typeData: any, duration: Duration | null } | null
   title: string
   url: string
-  rendering: EventRenderingChoice
   ui: EventUi
   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 TableSeg from './TableSeg'
 import TableEvent from './TableEvent'
+import TableDotEvent from './TableDotEvent'
 import Popover from './Popover'
+import { isDotRendering } from './event-rendering'
 
 
 export interface MorePopoverProps {
@@ -50,20 +52,30 @@ export default class MorePopover extends DateComponent<MorePopoverProps> {
 
               return (
                 <div
+                  className='fc-daygrid-event-harness'
                   key={instanceId}
                   style={{
                     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>
               )
             })}

+ 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'
-
-
-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 {
@@ -19,8 +12,8 @@ export default class TableEvent extends BaseComponent<TableEventProps> {
     return (
       <StandardEvent
         {...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}
         disableResizing={!props.seg.eventRange.def.allDay}
       />

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

@@ -18,8 +18,10 @@ import {
 } from '@fullcalendar/core'
 import TableSeg, { splitSegsByFirstCol } from './TableSeg'
 import TableCell, { TableCellModel, MoreLinkArg } from './TableCell'
+import TableDotEvent from './TableDotEvent'
 import TableEvent from './TableEvent'
 import { computeFgSegPlacement } from './event-placement'
+import { isDotRendering } from './event-rendering'
 
 
 // TODO: attach to window resize?
@@ -260,15 +262,24 @@ export default class TableRow extends DateComponent<TableRowProps, TableRowState
               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>
         )
       }

+ 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 {
   position: relative;
   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;
-  border: 1px solid #3788d8;
-  background-color: #3788d8;
-  color: #fff;
-  border-radius: 3px;
-
-  .fc-event-main {
-    padding: 1px;
-    overflow: hidden;
-  }
 
   .fc-event-time,
   .fc-event-title {
     display: inline;
   }
-
-  .fc-event-time {
-    font-weight: bold;
-  }
 }
 
-
 // spacing between time and title
-
 .fc-dir-ltr .fc-daygrid-event .fc-event-time {
   margin-right: 3px; // TODO: var
 }
-
 .fc-dir-rtl .fc-daygrid-event .fc-event-time {
   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-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-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
 
 .fc-daygrid-event-harness {
+  &:before {
+    @include clearfix; // for when event element has top margin
+  }
   &:after {
     @include clearfix; // for when event element has bottom margin
   }
@@ -123,6 +126,7 @@ $daygrid-popover-z: 6;
   position: relative;
   z-index: $daygrid-text-z;
   font-size: .85em;
+  margin: 2px 3px 0;
 }
 
 .fc-daygrid-more-link {