@operato/gantt 2.0.0-beta.0 → 2.0.0-beta.1

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/src/locale.d.ts +1 -0
  3. package/dist/src/locale.js +49 -0
  4. package/dist/src/locale.js.map +1 -0
  5. package/dist/src/ox-gantt-chart.d.ts +39 -0
  6. package/dist/src/ox-gantt-chart.js +707 -0
  7. package/dist/src/ox-gantt-chart.js.map +1 -0
  8. package/dist/src/ox-gantt-resize-splitter.d.ts +13 -0
  9. package/dist/src/ox-gantt-resize-splitter.js +117 -0
  10. package/dist/src/ox-gantt-resize-splitter.js.map +1 -0
  11. package/dist/src/ox-gantt.d.ts +24 -0
  12. package/dist/src/ox-gantt.js +186 -0
  13. package/dist/src/ox-gantt.js.map +1 -0
  14. package/dist/src/types.d.ts +10 -5
  15. package/dist/src/types.js.map +1 -1
  16. package/dist/src/utils.d.ts +3 -0
  17. package/dist/src/utils.js +10 -0
  18. package/dist/src/utils.js.map +1 -0
  19. package/dist/stories/ox-gantt-chart.stories.d.ts +41 -0
  20. package/dist/stories/{ox-lite-gantt.stories.js → ox-gantt-chart.stories.js} +53 -21
  21. package/dist/stories/ox-gantt-chart.stories.js.map +1 -0
  22. package/dist/stories/ox-gantt.stories.d.ts +6 -1
  23. package/dist/stories/ox-gantt.stories.js +286 -15
  24. package/dist/stories/ox-gantt.stories.js.map +1 -1
  25. package/dist/tsconfig.tsbuildinfo +1 -1
  26. package/package.json +12 -4
  27. package/src/locale.ts +51 -0
  28. package/src/ox-gantt-chart.ts +773 -0
  29. package/src/ox-gantt-resize-splitter.ts +137 -0
  30. package/src/ox-gantt.ts +177 -0
  31. package/src/types.ts +12 -6
  32. package/src/utils.ts +13 -0
  33. package/stories/{ox-lite-gantt.stories.ts → ox-gantt-chart.stories.ts} +65 -22
  34. package/stories/ox-gantt.stories.ts +289 -16
  35. package/themes/dark-hc.css +151 -0
  36. package/themes/dark-mc.css +151 -0
  37. package/themes/dark.css +151 -0
  38. package/themes/grist-theme.css +169 -0
  39. package/themes/light-hc.css +151 -0
  40. package/themes/light-mc.css +151 -0
  41. package/themes/light.css +151 -0
  42. package/themes/md-typescale-styles.css +100 -0
  43. package/themes/spacing.css +43 -0
  44. package/themes/state-color.css +6 -0
  45. package/dist/src/gantt.d.ts +0 -28
  46. package/dist/src/gantt.js +0 -178
  47. package/dist/src/gantt.js.map +0 -1
  48. package/dist/src/lite-gantt.d.ts +0 -27
  49. package/dist/src/lite-gantt.js +0 -146
  50. package/dist/src/lite-gantt.js.map +0 -1
  51. package/dist/stories/ox-lite-gantt.stories.d.ts +0 -22
  52. package/dist/stories/ox-lite-gantt.stories.js.map +0 -1
  53. package/src/gantt.ts +0 -191
  54. package/src/lite-gantt.ts +0 -152
  55. package/src/renderer.txt +0 -775
@@ -0,0 +1,137 @@
1
+ import { LitElement, css, html } from 'lit'
2
+
3
+ import { customElement } from 'lit/decorators.js'
4
+
5
+ @customElement('ox-gantt-resize-splitter')
6
+ export class OxGanttResizeSplitter extends LitElement {
7
+ static styles = css`
8
+ :host {
9
+ position: relative;
10
+ opacity: 0.7;
11
+ background-color: var(--splitter-background-color);
12
+ }
13
+
14
+ :host(:hover) {
15
+ background-color: var(--splitter-hover-background-color);
16
+ }
17
+
18
+ div {
19
+ position: absolute;
20
+ width: 100%;
21
+ height: 100%;
22
+ }
23
+ `
24
+
25
+ private dragstart?: { x: number; y: number }
26
+
27
+ connectedCallback() {
28
+ super.connectedCallback()
29
+
30
+ if (this.hasAttribute('vertical')) {
31
+ this.style.width = '3px'
32
+ this.style.height = '100%'
33
+ this.style.cursor = 'col-resize'
34
+ } else {
35
+ this.style.width = '100%'
36
+ this.style.height = '3px'
37
+ this.style.cursor = 'row-resize'
38
+ }
39
+ }
40
+
41
+ render() {
42
+ return html` <div @mousedown=${(e: MouseEvent) => this.onMouseDown(e)}></div> `
43
+ }
44
+
45
+ // TODO onDrag 이벤트가 계속 발생하므로 처리하는 성능 저하됨. 그래서 throttling 하도록 함
46
+ _throttled(delay: number, fn: (...args: any) => void) {
47
+ let lastCall = 0
48
+ return function (...args: any) {
49
+ const now = new Date().getTime()
50
+ if (now - lastCall < delay) {
51
+ return
52
+ }
53
+ lastCall = now
54
+ return fn(...args)
55
+ }
56
+ }
57
+
58
+ private onMouseDown(e: MouseEvent) {
59
+ this.dragstart = {
60
+ x: e.clientX,
61
+ y: e.clientY
62
+ }
63
+
64
+ this.dispatchEvent(
65
+ new CustomEvent('splitter-dragstart', {
66
+ bubbles: true,
67
+ composed: true
68
+ })
69
+ )
70
+
71
+ window.addEventListener('mousemove', this.onMouseMoveHandler)
72
+ window.addEventListener('mouseup', this.onMouseUpHandler)
73
+
74
+ e.stopPropagation()
75
+ }
76
+
77
+ onMouseMoveHandler = this._throttled(100, this.onMouseMove.bind(this)) // this.onMouseMove.bind(this)
78
+ onMouseUpHandler = this.onMouseUp.bind(this)
79
+
80
+ private onMouseMove(e: MouseEvent) {
81
+ if (!this.dragstart) {
82
+ return
83
+ }
84
+
85
+ e.preventDefault()
86
+
87
+ this.dispatchEvent(
88
+ new CustomEvent('splitter-drag', {
89
+ bubbles: true,
90
+ composed: true,
91
+ detail: {
92
+ x: e.clientX - this.dragstart!.x,
93
+ y: e.clientY - this.dragstart!.y
94
+ }
95
+ })
96
+ )
97
+ requestAnimationFrame(() => {
98
+ dispatchEvent(new Event('resize'))
99
+ })
100
+
101
+ if (this.hasAttribute('vertical')) {
102
+ document.body.style.cursor = 'col-resize'
103
+ } else {
104
+ document.body.style.cursor = 'row-resize'
105
+ }
106
+
107
+ document.body.style.userSelect = 'none'
108
+
109
+ e.stopPropagation()
110
+ }
111
+
112
+ private onMouseUp(e: MouseEvent) {
113
+ window.removeEventListener('mousemove', this.onMouseMoveHandler)
114
+ window.removeEventListener('mouseup', this.onMouseUpHandler)
115
+
116
+ this.dispatchEvent(
117
+ new CustomEvent('splitter-dragend', {
118
+ bubbles: true,
119
+ composed: true,
120
+ detail: {
121
+ x: e.clientX - this.dragstart!.x,
122
+ y: e.clientY - this.dragstart!.y
123
+ }
124
+ })
125
+ )
126
+
127
+ requestAnimationFrame(() => {
128
+ dispatchEvent(new Event('resize'))
129
+ delete this.dragstart
130
+ })
131
+
132
+ document.body.style.cursor = ''
133
+ document.body.style.userSelect = ''
134
+
135
+ e.stopPropagation()
136
+ }
137
+ }
@@ -0,0 +1,177 @@
1
+ import { LitElement, html, css, PropertyValues, PropertyValueMap } from 'lit'
2
+ import { customElement, property, query } from 'lit/decorators.js'
3
+ import { styles as MDTypeScaleStyles } from '@material/web/typography/md-typescale-styles'
4
+ import { ScrollbarStyles } from '@operato/styles'
5
+
6
+ import './ox-gantt-chart.js'
7
+ import './ox-gantt-resize-splitter.js'
8
+ import { OxGanttChart } from './ox-gantt-chart.js'
9
+ import { Task } from './types.js'
10
+
11
+ @customElement('ox-gantt')
12
+ export class OxGantt extends LitElement {
13
+ static styles = [
14
+ MDTypeScaleStyles,
15
+ ScrollbarStyles,
16
+ css`
17
+ :host {
18
+ display: block;
19
+ font-family: Arial, sans-serif;
20
+ text-wrap: nowrap;
21
+ }
22
+
23
+ .container {
24
+ display: flex;
25
+ width: 100%;
26
+ height: 100%;
27
+ box-sizing: border-box;
28
+ }
29
+
30
+ .task-container {
31
+ padding: 0;
32
+ overflow: auto;
33
+ overflow-y: overlay;
34
+ box-sizing: border-box;
35
+ border: 1px solid var(--ox-gantt-border-color, var(--md-sys-color-on-surface));
36
+ }
37
+
38
+ .chart-container {
39
+ flex: 1;
40
+ padding: 0;
41
+ box-sizing: border-box;
42
+ border: 1px solid var(--ox-gantt-border-color, var(--md-sys-color-on-surface));
43
+ }
44
+
45
+ ox-gantt-resize-splitter {
46
+ width: 4px;
47
+ }
48
+
49
+ table {
50
+ width: 100%;
51
+ border-collapse: collapse;
52
+ border: 1px solid var(--ox-gantt-border-color, var(--md-sys-color-on-surface));
53
+ margin: -1px;
54
+ margin-right: 0;
55
+ }
56
+
57
+ thead th {
58
+ position: sticky;
59
+ top: -1px;
60
+ z-index: 1;
61
+ background-color: var(--ox-gantt-header-background-color, var(--md-sys-color-secondary-container));
62
+ }
63
+
64
+ th,
65
+ td {
66
+ border: 1px solid var(--ox-gantt-border-color, var(--md-sys-color-on-surface));
67
+ padding: 4px;
68
+ text-align: left;
69
+ }
70
+
71
+ th {
72
+ height: 40px;
73
+ box-sizing: border-box;
74
+ }
75
+
76
+ td {
77
+ height: 30px;
78
+ box-sizing: border-box;
79
+ }
80
+ `
81
+ ]
82
+
83
+ @property({ type: Array }) tasks: Task[] = []
84
+ @property({ type: String, attribute: 'from-date' })
85
+ fromDate: string = new Date(new Date().getFullYear(), 0, 1).toISOString().split('T')[0] /* beginning of this year */
86
+ @property({ type: String, attribute: 'to-date' })
87
+ toDate: string = new Date(new Date().getFullYear(), 11, 31).toISOString().split('T')[0] /* end of this year */
88
+
89
+ @query('.container') container!: HTMLDivElement
90
+ @query('.task-container') taskContainer!: HTMLDivElement
91
+ @query('.chart-container') chartContainer!: HTMLDivElement
92
+ @query('table') taskTable!: HTMLTableElement
93
+ @query('ox-gantt-chart') ganttChart!: OxGanttChart
94
+
95
+ private _startWidth: number = 0
96
+
97
+ render() {
98
+ return html`
99
+ <div class="container md-typescale-body-medium">
100
+ <div class="task-container" @scroll=${this.syncScroll}>${this.renderTaskTable()}</div>
101
+ <ox-gantt-resize-splitter
102
+ vertical
103
+ @splitter-dragstart=${(e: CustomEvent) => this.resizeStart(e)}
104
+ @splitter-drag=${(e: CustomEvent) => this.resizeDrag(e)}
105
+ ></ox-gantt-resize-splitter>
106
+ <ox-gantt-chart
107
+ class="chart-container"
108
+ @scroll=${this.syncScroll}
109
+ .fromDate=${this.fromDate}
110
+ .toDate=${this.toDate}
111
+ .tasks=${this.tasks}
112
+ axis-scale="week-day"
113
+ extend-grid-lines
114
+ ></ox-gantt-chart>
115
+ </div>
116
+ `
117
+ }
118
+
119
+ renderTaskTable() {
120
+ return html`
121
+ <table>
122
+ <thead>
123
+ <tr>
124
+ <th>ID</th>
125
+ <th>Section</th>
126
+ <th>Title</th>
127
+ <th>Type</th>
128
+ <th>Start Time</th>
129
+ <th>End Time</th>
130
+ <th>Tags</th>
131
+ <th>Progress</th>
132
+ <th>Prev. Task</th>
133
+ </tr>
134
+ </thead>
135
+ <tbody>
136
+ ${this.tasks.map(
137
+ task => html`
138
+ <tr>
139
+ <td>${task.id}</td>
140
+ <td>${task.section?.name || ''}</td>
141
+ <td>${task.title}</td>
142
+ <td>${task.type}</td>
143
+ <td>${task.startTime.toISOString().split('T')[0]}</td>
144
+ <td>${task.endTime.toISOString().split('T')[0]}</td>
145
+ <td>${task.tags?.join(', ') || ''}</td>
146
+ <td>${task.progress || ''}</td>
147
+ <td>${task.after || ''}</td>
148
+ </tr>
149
+ `
150
+ )}
151
+ </tbody>
152
+ </table>
153
+ `
154
+ }
155
+
156
+ protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
157
+ this.taskContainer.style.width = '254px'
158
+ }
159
+
160
+ updated(changes: PropertyValues<this>) {}
161
+
162
+ syncScroll() {
163
+ this.ganttChart.setVerticalOffset(this.taskContainer.scrollTop)
164
+ }
165
+
166
+ resizeStart(e: CustomEvent) {
167
+ this._startWidth = ((e.target as HTMLElement)?.previousElementSibling as HTMLElement)?.offsetWidth
168
+ }
169
+
170
+ resizeDrag(e: CustomEvent) {
171
+ var delta = e.detail
172
+ var x = (e.target as HTMLElement)?.previousElementSibling as HTMLElement
173
+ if (x) {
174
+ x.style.width = `${this._startWidth + delta.x}px`
175
+ }
176
+ }
177
+ }
package/src/types.ts CHANGED
@@ -1,16 +1,22 @@
1
- export type TaskTag = 'done' | 'active' | 'crit' | 'milestone'
1
+ export type TaskTag = 'done' | 'active' | 'critical' | 'milestone'
2
+ export type TaskType = 'task' | 'milestone' | 'phase'
2
3
 
3
4
  export interface Task {
4
5
  id: string
5
- type: string
6
- section: any
6
+ title: string
7
+ type: TaskType
8
+ section?: Section
7
9
  startTime: Date
8
10
  endTime: Date
9
- after: string
10
- length: string
11
- tags: TaskTag[]
11
+ progress?: number
12
+ after?: string // 이전 태스크의 ID
13
+ length: string // 예: '3d' (3 days)
14
+ tags?: TaskTag[]
15
+ children?: Task[]
12
16
  }
13
17
 
14
18
  export interface Section {
15
19
  name: string
16
20
  }
21
+
22
+ export type LEVEL = 'year-quarter' | 'year-month' | 'quarter-week' | 'week-day'
package/src/utils.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { LEVEL } from './types'
2
+
3
+ const levels: LEVEL[] = ['week-day', 'quarter-week', 'year-month', 'year-quarter']
4
+
5
+ export function levelUp(level: 'year-quarter' | 'year-month' | 'quarter-week' | 'week-day'): LEVEL {
6
+ const current = Math.min(levels.length, levels.indexOf(level) + 1)
7
+ return levels[current]
8
+ }
9
+
10
+ export function levelDown(level: 'year-quarter' | 'year-month' | 'quarter-week' | 'week-day'): LEVEL {
11
+ const current = Math.max(0, levels.indexOf(level) - 1)
12
+ return levels[current]
13
+ }
@@ -1,15 +1,17 @@
1
+ import { html, TemplateResult } from 'lit'
1
2
  import { CommonHeaderStyles } from '@operato/styles'
2
- import '../src/lite-gantt.js'
3
+ import '../src/ox-gantt-chart.js'
3
4
 
4
- import { html, TemplateResult } from 'lit'
5
5
  import { styles as MDTypeScaleStyles } from '@material/web/typography/md-typescale-styles'
6
6
 
7
- import { Task, Section } from '../src/types.js'
8
-
9
7
  export default {
10
- title: 'ox-lite-gantt',
11
- component: 'ox-lite-gantt',
8
+ title: 'ox-gantt-chart',
9
+ component: 'ox-gantt-chart',
12
10
  argTypes: {
11
+ axisScale: { control: 'select', options: ['year-quarter', 'year-month', 'quarter-week', 'week-day'] },
12
+ fromDate: { control: 'string' },
13
+ toDate: { control: 'string' },
14
+ extendGridLines: { control: 'boolean' },
13
15
  theme: { control: 'select', options: ['light', 'dark'] }
14
16
  }
15
17
  }
@@ -21,15 +23,26 @@ interface Story<T> {
21
23
  }
22
24
 
23
25
  interface ArgTypes {
26
+ axisScale: 'year-quarter' | 'year-month' | 'quarter-week' | 'week-day'
27
+ fromDate: string
28
+ toDate: string
29
+ extendGridLines: boolean
24
30
  theme: 'light' | 'dark'
25
31
  }
26
32
 
27
- const Template: Story<ArgTypes> = ({ theme = 'light' }: ArgTypes) => html`
33
+ const Template: Story<ArgTypes> = ({
34
+ axisScale = 'week-day',
35
+ fromDate = '',
36
+ toDate = '',
37
+ extendGridLines,
38
+ theme = 'light'
39
+ }: ArgTypes) => html`
28
40
  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet" />
29
41
 
30
42
  <link href="/themes/light.css" rel="stylesheet" />
31
43
  <link href="/themes/dark.css" rel="stylesheet" />
32
44
  <link href="/themes/spacing.css" rel="stylesheet" />
45
+ <link href="/themes/md-typescale-styles.css" rel="stylesheet" />
33
46
 
34
47
  <link
35
48
  href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL@20..48,100..700,0..1"
@@ -55,15 +68,18 @@ const Template: Story<ArgTypes> = ({ theme = 'light' }: ArgTypes) => html`
55
68
  <style>
56
69
  body {
57
70
  width: 100%;
58
- height: 500px;
71
+ height: 800px;
59
72
  text-align: center;
60
73
 
61
74
  background-color: var(--md-sys-color-background);
62
75
  color: var(--md-sys-color-on-background);
63
76
  }
64
77
 
65
- .header {
66
- padding: var(--spacing-medium);
78
+ ox-gantt {
79
+ width: 90%;
80
+ height: 500px;
81
+ box-sizing: border-box;
82
+
67
83
  background-color: var(--md-sys-color-primary-container);
68
84
  color: var(--md-sys-color-on-primary-container);
69
85
  }
@@ -73,17 +89,44 @@ const Template: Story<ArgTypes> = ({ theme = 'light' }: ArgTypes) => html`
73
89
  document.body.classList.add('${theme}')
74
90
  </script>
75
91
 
76
- <div class="header">
77
- <div class="filters">
78
- <ox-lite-gantt
79
- @change=${(e: CustomEvent) => {
80
- console.log('search form changed', e.detail)
81
- }}
82
- >
83
- </ox-lite-gantt>
84
- </div>
85
- </div>
92
+ <style>
93
+ ox-gantt-chart {
94
+ height: 500px;
95
+ border: 1px solid black;
96
+ }
97
+ </style>
98
+
99
+ <ox-gantt-chart
100
+ .axisScale=${axisScale}
101
+ .fromDate=${fromDate}
102
+ .toDate=${toDate}
103
+ ?extend-grid-lines=${extendGridLines}
104
+ ></ox-gantt-chart>
86
105
  `
87
106
 
88
- export const Regular = Template.bind({})
89
- Regular.args = {}
107
+ export const YearMonth = Template.bind({})
108
+ YearMonth.args = {
109
+ axisScale: 'year-quarter',
110
+ fromDate: '2020-01-01',
111
+ toDate: '2025-12-31'
112
+ }
113
+
114
+ YearMonth.args = {
115
+ axisScale: 'year-month',
116
+ fromDate: '2022-01-01',
117
+ toDate: '2024-12-31'
118
+ }
119
+
120
+ export const MonthWeek = Template.bind({})
121
+ MonthWeek.args = {
122
+ axisScale: 'quarter-week',
123
+ fromDate: '2022-01-01',
124
+ toDate: '2023-03-31'
125
+ }
126
+
127
+ export const WeekDay = Template.bind({})
128
+ WeekDay.args = {
129
+ axisScale: 'week-day',
130
+ fromDate: '2022-01-01',
131
+ toDate: '2022-11-15'
132
+ }