@fmsim/fmsim 2.0.0-beta.2 → 2.0.0-beta.21

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.
@@ -98,12 +98,11 @@ MenuTools.styles = [
98
98
  padding: 5px 0px;
99
99
  text-align: center;
100
100
  text-decoration: none;
101
- color: var(--menu-tools-color);
101
+ color: var(--md-sys-color-on-secondary);
102
102
  max-width: 54px;
103
103
  }
104
104
 
105
105
  a[active] {
106
- color: var(--menu-tools-active-color);
107
106
  background-color: rgba(0, 0, 0, 0.2);
108
107
  }
109
108
 
@@ -1 +1 @@
1
- {"version":3,"file":"menu-tools.js","sourceRoot":"","sources":["../../client/viewparts/menu-tools.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AACnC,OAAO,0BAA0B,CAAA;AAEjC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAGxC,IAAM,SAAS,GAAf,MAAM,SAAU,SAAQ,OAAO,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC;IAqEvD,MAAM;QACJ,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC;YAClD,CAAC,CAAC,IAAI,CAAA;;;6CAGiC,IAAI,CAAC,IAAI,IAAI,YAAY;;;;;;4CAM1B,IAAI,CAAC,IAAI,IAAI,WAAW;;;;;;kDAMlB,IAAI,CAAC,IAAI,IAAI,iBAAiB;;;;;;6CAMnC,IAAI,CAAC,IAAI,IAAI,YAAY;;;;;;kDAMpB,IAAI,CAAC,IAAI,IAAI,iBAAiB;;;;;;gDAMhC,IAAI,CAAC,IAAI,IAAI,eAAe;;;;;;SAMnE;YACH,CAAC,CAAC,IAAI,CAAA,EAAE,CAAA;IACZ,CAAC;IAED,YAAY,CAAC,KAAK;QAChB,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAA;QAC5B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAA;QAC/B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAA;IACpC,CAAC;;AArHM,gBAAM,GAAG;IACd,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA2DF;CACF,AA7DY,CA6DZ;AAE2B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;uCAAc;AACE;IAA1C,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;wCAAe;AAC5B;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;;2CAAmB;AACnB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;0CAAa;AAnE7B,SAAS;IADrB,aAAa,CAAC,YAAY,CAAC;GACf,SAAS,CAuHrB","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/i18n/ox-i18n.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { connect, store } from '@operato/shell'\n\n@customElement('menu-tools')\nexport class MenuTools extends connect(store)(LitElement) {\n static styles = [\n css`\n :host {\n display: flex;\n background-color: var(--menu-tools-background-color);\n\n /* for narrow mode */\n flex-direction: column;\n width: 100%;\n }\n\n :host([width='WIDE']) {\n /* for wide mode */\n flex-direction: row;\n width: initial;\n height: 100%;\n }\n\n ul {\n display: flex;\n flex-direction: row;\n\n margin: auto;\n padding: 0;\n list-style: none;\n height: 100%;\n overflow: none;\n }\n\n :host([width='WIDE']) ul {\n flex-direction: column;\n }\n\n :host([width='WIDE']) li {\n border-top: 1px solid rgba(255, 255, 255, 0.1);\n border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n }\n\n a {\n display: block;\n padding: 5px 0px;\n text-align: center;\n text-decoration: none;\n color: var(--menu-tools-color);\n max-width: 54px;\n }\n\n a[active] {\n color: var(--menu-tools-active-color);\n background-color: rgba(0, 0, 0, 0.2);\n }\n\n md-icon {\n padding: 5px 15px 0px 15px;\n vertical-align: bottom;\n }\n\n div {\n font-size: 0.6em;\n }\n `\n ]\n\n @property({ type: String }) page?: string\n @property({ type: String, reflect: true }) width?: string\n @property({ type: Boolean }) editable?: boolean\n @property({ type: Object }) context: any\n\n render() {\n return this.context && this.context['board_topmenu']\n ? html`\n <ul>\n <li>\n <a href=\"board-list\" ?active=${this.page == 'board-list'}>\n <md-icon>dvr</md-icon>\n <div><ox-i18n msgid=\"label.board\"></ox-i18n></div>\n </a>\n </li>\n <li>\n <a href=\"font-list\" ?active=${this.page == 'font-list'}>\n <md-icon>font_download</md-icon>\n <div><ox-i18n msgid=\"label.font\"></ox-i18n></div>\n </a>\n </li>\n <li>\n <a href=\"attachment-list\" ?active=${this.page == 'attachment-list'}>\n <md-icon>attachment</md-icon>\n <div><ox-i18n msgid=\"label.attachment\"></ox-i18n></div>\n </a>\n </li>\n <li>\n <a href=\"theme-list\" ?active=${this.page == 'theme-list'}>\n <md-icon>draw</md-icon>\n <div><ox-i18n msgid=\"label.theme\"></ox-i18n></div>\n </a>\n </li>\n <li>\n <a href=\"connection-list\" ?active=${this.page == 'connection-list'}>\n <md-icon>device_hub</md-icon>\n <div><ox-i18n msgid=\"label.connection\"></ox-i18n></div>\n </a>\n </li>\n <li>\n <a href=\"scenario-list\" ?active=${this.page == 'scenario-list'}>\n <md-icon>format_list_numbered</md-icon>\n <div><ox-i18n msgid=\"label.scenario\"></ox-i18n></div>\n </a>\n </li>\n </ul>\n `\n : html``\n }\n\n stateChanged(state) {\n this.page = state.route.page\n this.width = state.layout.width\n this.context = state.route.context\n }\n}\n"]}
1
+ {"version":3,"file":"menu-tools.js","sourceRoot":"","sources":["../../client/viewparts/menu-tools.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AACnC,OAAO,0BAA0B,CAAA;AAEjC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAGxC,IAAM,SAAS,GAAf,MAAM,SAAU,SAAQ,OAAO,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC;IAoEvD,MAAM;QACJ,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC;YAClD,CAAC,CAAC,IAAI,CAAA;;;6CAGiC,IAAI,CAAC,IAAI,IAAI,YAAY;;;;;;4CAM1B,IAAI,CAAC,IAAI,IAAI,WAAW;;;;;;kDAMlB,IAAI,CAAC,IAAI,IAAI,iBAAiB;;;;;;6CAMnC,IAAI,CAAC,IAAI,IAAI,YAAY;;;;;;kDAMpB,IAAI,CAAC,IAAI,IAAI,iBAAiB;;;;;;gDAMhC,IAAI,CAAC,IAAI,IAAI,eAAe;;;;;;SAMnE;YACH,CAAC,CAAC,IAAI,CAAA,EAAE,CAAA;IACZ,CAAC;IAED,YAAY,CAAC,KAAK;QAChB,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAA;QAC5B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAA;QAC/B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAA;IACpC,CAAC;;AApHM,gBAAM,GAAG;IACd,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA0DF;CACF,AA5DY,CA4DZ;AAE2B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;uCAAc;AACE;IAA1C,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;wCAAe;AAC5B;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;;2CAAmB;AACnB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;0CAAa;AAlE7B,SAAS;IADrB,aAAa,CAAC,YAAY,CAAC;GACf,SAAS,CAsHrB","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/i18n/ox-i18n.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { connect, store } from '@operato/shell'\n\n@customElement('menu-tools')\nexport class MenuTools extends connect(store)(LitElement) {\n static styles = [\n css`\n :host {\n display: flex;\n background-color: var(--menu-tools-background-color);\n\n /* for narrow mode */\n flex-direction: column;\n width: 100%;\n }\n\n :host([width='WIDE']) {\n /* for wide mode */\n flex-direction: row;\n width: initial;\n height: 100%;\n }\n\n ul {\n display: flex;\n flex-direction: row;\n\n margin: auto;\n padding: 0;\n list-style: none;\n height: 100%;\n overflow: none;\n }\n\n :host([width='WIDE']) ul {\n flex-direction: column;\n }\n\n :host([width='WIDE']) li {\n border-top: 1px solid rgba(255, 255, 255, 0.1);\n border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n }\n\n a {\n display: block;\n padding: 5px 0px;\n text-align: center;\n text-decoration: none;\n color: var(--md-sys-color-on-secondary);\n max-width: 54px;\n }\n\n a[active] {\n background-color: rgba(0, 0, 0, 0.2);\n }\n\n md-icon {\n padding: 5px 15px 0px 15px;\n vertical-align: bottom;\n }\n\n div {\n font-size: 0.6em;\n }\n `\n ]\n\n @property({ type: String }) page?: string\n @property({ type: String, reflect: true }) width?: string\n @property({ type: Boolean }) editable?: boolean\n @property({ type: Object }) context: any\n\n render() {\n return this.context && this.context['board_topmenu']\n ? html`\n <ul>\n <li>\n <a href=\"board-list\" ?active=${this.page == 'board-list'}>\n <md-icon>dvr</md-icon>\n <div><ox-i18n msgid=\"label.board\"></ox-i18n></div>\n </a>\n </li>\n <li>\n <a href=\"font-list\" ?active=${this.page == 'font-list'}>\n <md-icon>font_download</md-icon>\n <div><ox-i18n msgid=\"label.font\"></ox-i18n></div>\n </a>\n </li>\n <li>\n <a href=\"attachment-list\" ?active=${this.page == 'attachment-list'}>\n <md-icon>attachment</md-icon>\n <div><ox-i18n msgid=\"label.attachment\"></ox-i18n></div>\n </a>\n </li>\n <li>\n <a href=\"theme-list\" ?active=${this.page == 'theme-list'}>\n <md-icon>draw</md-icon>\n <div><ox-i18n msgid=\"label.theme\"></ox-i18n></div>\n </a>\n </li>\n <li>\n <a href=\"connection-list\" ?active=${this.page == 'connection-list'}>\n <md-icon>device_hub</md-icon>\n <div><ox-i18n msgid=\"label.connection\"></ox-i18n></div>\n </a>\n </li>\n <li>\n <a href=\"scenario-list\" ?active=${this.page == 'scenario-list'}>\n <md-icon>format_list_numbered</md-icon>\n <div><ox-i18n msgid=\"label.scenario\"></ox-i18n></div>\n </a>\n </li>\n </ul>\n `\n : html``\n }\n\n stateChanged(state) {\n this.page = state.route.page\n this.width = state.layout.width\n this.context = state.route.context\n }\n}\n"]}
@@ -4,14 +4,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.schema = exports.entities = void 0;
5
5
  /* IMPORT ENTITIES AND RESOLVERS */
6
6
  const board_favorite_1 = require("./board-favorite");
7
+ const playback_1 = require("./playback");
7
8
  exports.entities = [
8
9
  /* ENTITIES */
9
- ...board_favorite_1.entities
10
+ ...board_favorite_1.entities,
11
+ ...playback_1.entities
10
12
  ];
11
13
  exports.schema = {
12
14
  resolverClasses: [
13
15
  /* RESOLVER CLASSES */
14
- ...board_favorite_1.resolvers
16
+ ...board_favorite_1.resolvers,
17
+ ...playback_1.resolvers
15
18
  ]
16
19
  };
17
20
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";AAAA,yBAAyB;;;AAEzB,mCAAmC;AACnC,qDAAyG;AAE5F,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,GAAG,yBAAqB;CACzB,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,0BAAsB;KAC1B;CACF,CAAA","sourcesContent":["/* EXPORT ENTITY TYPES */\n\n/* IMPORT ENTITIES AND RESOLVERS */\nimport { entities as BoardFavoriteEntities, resolvers as BoardFavoriteResolvers } from './board-favorite'\n\nexport const entities = [\n /* ENTITIES */\n ...BoardFavoriteEntities\n]\n\nexport const schema = {\n resolverClasses: [\n /* RESOLVER CLASSES */\n ...BoardFavoriteResolvers\n ]\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";AAAA,yBAAyB;;;AAEzB,mCAAmC;AACnC,qDAAyG;AACzG,yCAAyF;AAE5E,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,GAAG,yBAAqB;IACxB,GAAG,mBAAgB;CACpB,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,0BAAsB;QACzB,GAAG,oBAAiB;KACrB;CACF,CAAA","sourcesContent":["/* EXPORT ENTITY TYPES */\n\n/* IMPORT ENTITIES AND RESOLVERS */\nimport { entities as BoardFavoriteEntities, resolvers as BoardFavoriteResolvers } from './board-favorite'\nimport { entities as PlaybackEntities, resolvers as PlaybackResolvers } from './playback'\n\nexport const entities = [\n /* ENTITIES */\n ...BoardFavoriteEntities,\n ...PlaybackEntities\n]\n\nexport const schema = {\n resolverClasses: [\n /* RESOLVER CLASSES */\n ...BoardFavoriteResolvers,\n ...PlaybackResolvers\n ]\n}\n"]}
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolvers = exports.entities = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const type_graphql_1 = require("type-graphql");
6
+ const mock_data_1 = require("./mock-data");
7
+ let PlaybackRecord = class PlaybackRecord {
8
+ };
9
+ tslib_1.__decorate([
10
+ (0, type_graphql_1.Field)(),
11
+ tslib_1.__metadata("design:type", String)
12
+ ], PlaybackRecord.prototype, "type", void 0);
13
+ tslib_1.__decorate([
14
+ (0, type_graphql_1.Field)(),
15
+ tslib_1.__metadata("design:type", String)
16
+ ], PlaybackRecord.prototype, "time", void 0);
17
+ tslib_1.__decorate([
18
+ (0, type_graphql_1.Field)(),
19
+ tslib_1.__metadata("design:type", String)
20
+ ], PlaybackRecord.prototype, "data", void 0);
21
+ PlaybackRecord = tslib_1.__decorate([
22
+ (0, type_graphql_1.ObjectType)()
23
+ ], PlaybackRecord);
24
+ let PlaybackScenarioType = class PlaybackScenarioType {
25
+ };
26
+ tslib_1.__decorate([
27
+ (0, type_graphql_1.Field)(),
28
+ tslib_1.__metadata("design:type", String)
29
+ ], PlaybackScenarioType.prototype, "scenarioName", void 0);
30
+ tslib_1.__decorate([
31
+ (0, type_graphql_1.Field)(() => [PlaybackRecord]),
32
+ tslib_1.__metadata("design:type", Array)
33
+ ], PlaybackScenarioType.prototype, "snapshots", void 0);
34
+ PlaybackScenarioType = tslib_1.__decorate([
35
+ (0, type_graphql_1.ObjectType)()
36
+ ], PlaybackScenarioType);
37
+ let PlaybackResult = class PlaybackResult {
38
+ };
39
+ tslib_1.__decorate([
40
+ (0, type_graphql_1.Field)(() => [PlaybackScenarioType]),
41
+ tslib_1.__metadata("design:type", Array)
42
+ ], PlaybackResult.prototype, "scenarios", void 0);
43
+ PlaybackResult = tslib_1.__decorate([
44
+ (0, type_graphql_1.ObjectType)()
45
+ ], PlaybackResult);
46
+ let PlaybackResponse = class PlaybackResponse {
47
+ };
48
+ tslib_1.__decorate([
49
+ (0, type_graphql_1.Field)(),
50
+ tslib_1.__metadata("design:type", Boolean)
51
+ ], PlaybackResponse.prototype, "status", void 0);
52
+ tslib_1.__decorate([
53
+ (0, type_graphql_1.Field)({ nullable: true }),
54
+ tslib_1.__metadata("design:type", String)
55
+ ], PlaybackResponse.prototype, "errorMessage", void 0);
56
+ tslib_1.__decorate([
57
+ (0, type_graphql_1.Field)(() => PlaybackResult, { nullable: true }),
58
+ tslib_1.__metadata("design:type", PlaybackResult)
59
+ ], PlaybackResponse.prototype, "playback", void 0);
60
+ PlaybackResponse = tslib_1.__decorate([
61
+ (0, type_graphql_1.ObjectType)()
62
+ ], PlaybackResponse);
63
+ let MockVehicles = class MockVehicles {
64
+ };
65
+ tslib_1.__decorate([
66
+ (0, type_graphql_1.Field)(() => [String]),
67
+ tslib_1.__metadata("design:type", Array)
68
+ ], MockVehicles.prototype, "agvs", void 0);
69
+ tslib_1.__decorate([
70
+ (0, type_graphql_1.Field)(() => [String]),
71
+ tslib_1.__metadata("design:type", Array)
72
+ ], MockVehicles.prototype, "ohts", void 0);
73
+ MockVehicles = tslib_1.__decorate([
74
+ (0, type_graphql_1.ObjectType)()
75
+ ], MockVehicles);
76
+ let MockEntities = class MockEntities {
77
+ };
78
+ tslib_1.__decorate([
79
+ (0, type_graphql_1.Field)(() => MockVehicles),
80
+ tslib_1.__metadata("design:type", MockVehicles)
81
+ ], MockEntities.prototype, "vehicles", void 0);
82
+ tslib_1.__decorate([
83
+ (0, type_graphql_1.Field)(() => [String]),
84
+ tslib_1.__metadata("design:type", Array)
85
+ ], MockEntities.prototype, "ports", void 0);
86
+ tslib_1.__decorate([
87
+ (0, type_graphql_1.Field)(() => [String]),
88
+ tslib_1.__metadata("design:type", Array)
89
+ ], MockEntities.prototype, "zones", void 0);
90
+ tslib_1.__decorate([
91
+ (0, type_graphql_1.Field)(() => [String]),
92
+ tslib_1.__metadata("design:type", Array)
93
+ ], MockEntities.prototype, "stockers", void 0);
94
+ MockEntities = tslib_1.__decorate([
95
+ (0, type_graphql_1.ObjectType)()
96
+ ], MockEntities);
97
+ let PlaybackResolver = class PlaybackResolver {
98
+ async playback(startTime, endTime, count, includeSnapshot) {
99
+ try {
100
+ // count 기반이면 endTime 계산
101
+ let effectiveEndTime = endTime;
102
+ if (!effectiveEndTime && count) {
103
+ const start = new Date(startTime.replace(' ', 'T'));
104
+ effectiveEndTime = new Date(start.getTime() + count * 1000)
105
+ .toISOString()
106
+ .replace('T', ' ')
107
+ .replace(/\.\d+Z$/, '');
108
+ }
109
+ if (!effectiveEndTime) {
110
+ // 기본 1시간
111
+ const start = new Date(startTime.replace(' ', 'T'));
112
+ effectiveEndTime = new Date(start.getTime() + 3600000)
113
+ .toISOString()
114
+ .replace('T', ' ')
115
+ .replace(/\.\d+Z$/, '');
116
+ }
117
+ const scenarios = (0, mock_data_1.generatePlaybackData)(startTime, effectiveEndTime);
118
+ // includeSnapshot=false이면 snapshot 프레임 제거
119
+ if (!includeSnapshot) {
120
+ for (const s of scenarios) {
121
+ s.snapshots = s.snapshots.filter(f => f.type !== 'snapshot');
122
+ }
123
+ }
124
+ // data 배열을 JSON string으로 직렬화
125
+ const serialized = scenarios.map(s => (Object.assign(Object.assign({}, s), { snapshots: s.snapshots.map(f => ({
126
+ type: f.type,
127
+ time: f.time,
128
+ data: JSON.stringify(f.data)
129
+ })) })));
130
+ return {
131
+ status: true,
132
+ playback: { scenarios: serialized }
133
+ };
134
+ }
135
+ catch (e) {
136
+ return {
137
+ status: false,
138
+ errorMessage: e.message || String(e)
139
+ };
140
+ }
141
+ }
142
+ async playbackEntities() {
143
+ return mock_data_1.MOCK_ENTITIES;
144
+ }
145
+ };
146
+ tslib_1.__decorate([
147
+ (0, type_graphql_1.Query)(() => PlaybackResponse, { description: 'Playback 데이터 조회 (목업)' }),
148
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('startTime')),
149
+ tslib_1.__param(1, (0, type_graphql_1.Arg)('endTime', { nullable: true })),
150
+ tslib_1.__param(2, (0, type_graphql_1.Arg)('count', () => type_graphql_1.Int, { nullable: true })),
151
+ tslib_1.__param(3, (0, type_graphql_1.Arg)('includeSnapshot', { nullable: true, defaultValue: true })),
152
+ tslib_1.__metadata("design:type", Function),
153
+ tslib_1.__metadata("design:paramtypes", [String, String, Number, Boolean]),
154
+ tslib_1.__metadata("design:returntype", Promise)
155
+ ], PlaybackResolver.prototype, "playback", null);
156
+ tslib_1.__decorate([
157
+ (0, type_graphql_1.Query)(() => MockEntities, { description: '목업 엔티티 목록 (보드 작화용)' }),
158
+ tslib_1.__metadata("design:type", Function),
159
+ tslib_1.__metadata("design:paramtypes", []),
160
+ tslib_1.__metadata("design:returntype", Promise)
161
+ ], PlaybackResolver.prototype, "playbackEntities", null);
162
+ PlaybackResolver = tslib_1.__decorate([
163
+ (0, type_graphql_1.Resolver)()
164
+ ], PlaybackResolver);
165
+ exports.entities = [];
166
+ exports.resolvers = [PlaybackResolver];
167
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/playback/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { Resolver, Query, Arg, ObjectType, Field, Int } from 'type-graphql'\nimport { generatePlaybackData, MOCK_ENTITIES } from './mock-data'\n\n@ObjectType()\nclass PlaybackRecord {\n @Field() type!: string\n @Field() time!: string\n @Field() data!: string // JSON string\n}\n\n@ObjectType()\nclass PlaybackScenarioType {\n @Field() scenarioName!: string\n @Field(() => [PlaybackRecord]) snapshots!: PlaybackRecord[]\n}\n\n@ObjectType()\nclass PlaybackResult {\n @Field(() => [PlaybackScenarioType]) scenarios!: PlaybackScenarioType[]\n}\n\n@ObjectType()\nclass PlaybackResponse {\n @Field() status!: boolean\n @Field({ nullable: true }) errorMessage?: string\n @Field(() => PlaybackResult, { nullable: true }) playback?: PlaybackResult\n}\n\n@ObjectType()\nclass MockVehicles {\n @Field(() => [String]) agvs!: string[]\n @Field(() => [String]) ohts!: string[]\n}\n\n@ObjectType()\nclass MockEntities {\n @Field(() => MockVehicles) vehicles!: MockVehicles\n @Field(() => [String]) ports!: string[]\n @Field(() => [String]) zones!: string[]\n @Field(() => [String]) stockers!: string[]\n}\n\n@Resolver()\nclass PlaybackResolver {\n @Query(() => PlaybackResponse, { description: 'Playback 데이터 조회 (목업)' })\n async playback(\n @Arg('startTime') startTime: string,\n @Arg('endTime', { nullable: true }) endTime: string,\n @Arg('count', () => Int, { nullable: true }) count: number,\n @Arg('includeSnapshot', { nullable: true, defaultValue: true }) includeSnapshot: boolean\n ): Promise<PlaybackResponse> {\n try {\n // count 기반이면 endTime 계산\n let effectiveEndTime = endTime\n if (!effectiveEndTime && count) {\n const start = new Date(startTime.replace(' ', 'T'))\n effectiveEndTime = new Date(start.getTime() + count * 1000)\n .toISOString()\n .replace('T', ' ')\n .replace(/\\.\\d+Z$/, '')\n }\n\n if (!effectiveEndTime) {\n // 기본 1시간\n const start = new Date(startTime.replace(' ', 'T'))\n effectiveEndTime = new Date(start.getTime() + 3600000)\n .toISOString()\n .replace('T', ' ')\n .replace(/\\.\\d+Z$/, '')\n }\n\n const scenarios = generatePlaybackData(startTime, effectiveEndTime)\n\n // includeSnapshot=false이면 snapshot 프레임 제거\n if (!includeSnapshot) {\n for (const s of scenarios) {\n s.snapshots = s.snapshots.filter(f => f.type !== 'snapshot')\n }\n }\n\n // data 배열을 JSON string으로 직렬화\n const serialized = scenarios.map(s => ({\n ...s,\n snapshots: s.snapshots.map(f => ({\n type: f.type,\n time: f.time,\n data: JSON.stringify(f.data)\n }))\n }))\n\n return {\n status: true,\n playback: { scenarios: serialized }\n }\n } catch (e: any) {\n return {\n status: false,\n errorMessage: e.message || String(e)\n }\n }\n }\n\n @Query(() => MockEntities, { description: '목업 엔티티 목록 (보드 작화용)' })\n async playbackEntities(): Promise<MockEntities> {\n return MOCK_ENTITIES\n }\n}\n\nexport const entities = []\nexport const resolvers = [PlaybackResolver]\n"]}
@@ -0,0 +1,382 @@
1
+ "use strict";
2
+ /**
3
+ * Playback 목업 데이터 생성기
4
+ *
5
+ * 대규모 팩토리 레이아웃 시뮬레이션:
6
+ * - AGV 20대, OHT 10대 (VEHICLE 시나리오)
7
+ * - Port 200개 (PORT 시나리오)
8
+ * - Zone 50개 (ZONE 시나리오)
9
+ * - Stocker 10기 (STOCKER 시나리오)
10
+ *
11
+ * 캐리어 이동 시뮬레이션:
12
+ * Vehicle이 Port에서 캐리어를 픽업 → 이동 → 다른 Port에 전달
13
+ * 상태 전이: IDLE → MOVING → LOADING → MOVING → UNLOADING → IDLE
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.MOCK_ENTITIES = void 0;
17
+ exports.generatePlaybackData = generatePlaybackData;
18
+ // ── 엔티티 정의 ──
19
+ const AGVS = Array.from({ length: 20 }, (_, i) => `AGV${String(i + 1).padStart(2, '0')}`);
20
+ const OHTS = Array.from({ length: 10 }, (_, i) => `OHT${String(i + 1).padStart(2, '0')}`);
21
+ const VEHICLES = [...AGVS, ...OHTS];
22
+ const PORTS = Array.from({ length: 200 }, (_, i) => {
23
+ const zone = String(Math.floor(i / 10) + 1).padStart(2, '0');
24
+ const port = String((i % 10) + 1).padStart(2, '0');
25
+ return `ZONE${zone}_PORT${port}`;
26
+ });
27
+ const ZONES = Array.from({ length: 50 }, (_, i) => `ZONE${String(i + 1).padStart(2, '0')}`);
28
+ const STOCKERS = Array.from({ length: 10 }, (_, i) => `STK${String(i + 1).padStart(2, '0')}`);
29
+ // ── 노드 경로 (Vehicle 이동 경로) ──
30
+ const NODE_IDS = Array.from({ length: 100 }, (_, i) => String(i + 1).padStart(4, '0'));
31
+ // 포트별 연결 노드 (포트와 가까운 노드)
32
+ const PORT_NODES = {};
33
+ PORTS.forEach((port, i) => {
34
+ PORT_NODES[port] = NODE_IDS[i % NODE_IDS.length];
35
+ });
36
+ // ── 상태 값 ──
37
+ const CARRIER_STATUSES = ['NORMAL', 'WARN', 'CRITICAL'];
38
+ const PORT_STATUSES = ['AUTO', 'MANUAL', 'DOWN'];
39
+ const ZONE_STATUSES = ['AUTO', 'MANUAL', 'DOWN'];
40
+ // ── 유틸리티 ──
41
+ function pick(arr) {
42
+ return arr[Math.floor(Math.random() * arr.length)];
43
+ }
44
+ function randInt(min, max) {
45
+ return Math.floor(Math.random() * (max - min + 1)) + min;
46
+ }
47
+ function formatTime(date) {
48
+ const pad = (n) => String(n).padStart(2, '0');
49
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
50
+ }
51
+ // ── 시뮬레이션 엔진 ──
52
+ class FactorySimulator {
53
+ constructor() {
54
+ this.vehicles = [];
55
+ this.ports = [];
56
+ this.zones = [];
57
+ this.stockers = [];
58
+ this.carrierSeq = 1;
59
+ this.initVehicles();
60
+ this.initPorts();
61
+ this.initZones();
62
+ this.initStockers();
63
+ }
64
+ initVehicles() {
65
+ this.vehicles = VEHICLES.map(name => ({
66
+ name,
67
+ phase: 'IDLE',
68
+ nodeId: pick(NODE_IDS),
69
+ targetNodeId: pick(NODE_IDS),
70
+ carrierName: null,
71
+ carrierStatus: null,
72
+ emptyType: 'EMPTY',
73
+ batteryRate: name.startsWith('AGV') ? randInt(30, 100) : null,
74
+ phaseTimer: randInt(1, 5),
75
+ sourcePort: null,
76
+ destPort: null,
77
+ }));
78
+ }
79
+ initPorts() {
80
+ this.ports = PORTS.map(name => ({
81
+ name,
82
+ status: pick(PORT_STATUSES),
83
+ carrierName: Math.random() > 0.5 ? this.newCarrierName() : null,
84
+ carrierStatus: null,
85
+ emptyType: null,
86
+ }));
87
+ // 캐리어가 있는 포트의 상태 설정
88
+ this.ports.forEach(p => {
89
+ if (p.carrierName) {
90
+ p.emptyType = 'FULL';
91
+ p.carrierStatus = pick(CARRIER_STATUSES);
92
+ }
93
+ else {
94
+ p.emptyType = 'EMPTY';
95
+ }
96
+ });
97
+ }
98
+ initZones() {
99
+ this.zones = ZONES.map(name => {
100
+ const max = randInt(20, 100);
101
+ return {
102
+ name,
103
+ status: pick(ZONE_STATUSES),
104
+ currentCapacity: randInt(0, max),
105
+ maxCapacity: max,
106
+ highWaterMark: String(Math.round(max * 0.8)),
107
+ lowWaterMark: String(Math.round(max * 0.6)),
108
+ };
109
+ });
110
+ }
111
+ initStockers() {
112
+ this.stockers = STOCKERS.map(name => {
113
+ const max = randInt(50, 200);
114
+ return {
115
+ name,
116
+ status: pick(ZONE_STATUSES),
117
+ currentCapacity: randInt(0, max),
118
+ maxCapacity: max,
119
+ };
120
+ });
121
+ }
122
+ newCarrierName() {
123
+ return `PLATE-${String(this.carrierSeq++).padStart(4, '0')}`;
124
+ }
125
+ // ── 1초 진행 ──
126
+ tick() {
127
+ const vehicleChanges = [];
128
+ const portChanges = [];
129
+ for (const v of this.vehicles) {
130
+ const changed = this.tickVehicle(v, portChanges);
131
+ if (changed)
132
+ vehicleChanges.push(changed);
133
+ }
134
+ const zoneChanges = this.tickZones();
135
+ const stockerChanges = this.tickStockers();
136
+ return { vehicleChanges, portChanges, zoneChanges, stockerChanges };
137
+ }
138
+ tickVehicle(v, portChanges) {
139
+ v.phaseTimer--;
140
+ // AGV 배터리 감소
141
+ if (v.batteryRate != null && v.phase !== 'IDLE') {
142
+ v.batteryRate = Math.max(0, v.batteryRate - (Math.random() > 0.8 ? 1 : 0));
143
+ }
144
+ if (v.phaseTimer > 0)
145
+ return null;
146
+ // Phase 전이
147
+ switch (v.phase) {
148
+ case 'IDLE': {
149
+ // 캐리어가 있는 포트를 찾아 픽업하러 출발
150
+ const sourcePort = this.ports.find(p => p.carrierName && p.status === 'AUTO');
151
+ if (!sourcePort) {
152
+ v.phaseTimer = randInt(2, 5);
153
+ return null;
154
+ }
155
+ v.phase = 'MOVING_TO_PICKUP';
156
+ v.sourcePort = sourcePort.name;
157
+ v.targetNodeId = PORT_NODES[sourcePort.name] || pick(NODE_IDS);
158
+ v.phaseTimer = randInt(3, 10); // 이동 시간
159
+ return this.vehicleRecord(v, 'MOVING');
160
+ }
161
+ case 'MOVING_TO_PICKUP': {
162
+ // 이동 중 노드 변경 (경로 시뮬레이션)
163
+ v.nodeId = v.targetNodeId;
164
+ v.phase = 'LOADING';
165
+ v.phaseTimer = randInt(2, 4); // 로딩 시간
166
+ return this.vehicleRecord(v, 'LOADING');
167
+ }
168
+ case 'LOADING': {
169
+ // 포트에서 캐리어 픽업
170
+ const port = this.ports.find(p => p.name === v.sourcePort);
171
+ if (port && port.carrierName) {
172
+ v.carrierName = port.carrierName;
173
+ v.carrierStatus = port.carrierStatus;
174
+ v.emptyType = 'FULL';
175
+ // 포트에서 캐리어 제거
176
+ port.carrierName = null;
177
+ port.carrierStatus = null;
178
+ port.emptyType = 'EMPTY';
179
+ portChanges.push(this.portRecord(port));
180
+ }
181
+ // 드롭할 빈 포트 선택
182
+ const destPort = this.ports.find(p => !p.carrierName && p.status === 'AUTO' && p.name !== v.sourcePort);
183
+ v.destPort = (destPort === null || destPort === void 0 ? void 0 : destPort.name) || pick(PORTS);
184
+ v.targetNodeId = PORT_NODES[v.destPort] || pick(NODE_IDS);
185
+ v.phase = 'MOVING_TO_DROP';
186
+ v.phaseTimer = randInt(3, 12); // 이동 시간
187
+ return this.vehicleRecord(v, 'MOVING');
188
+ }
189
+ case 'MOVING_TO_DROP': {
190
+ // 이동 중 노드 이동
191
+ v.nodeId = v.targetNodeId;
192
+ v.phase = 'UNLOADING';
193
+ v.phaseTimer = randInt(2, 4); // 언로딩 시간
194
+ return this.vehicleRecord(v, 'UNLOADING');
195
+ }
196
+ case 'UNLOADING': {
197
+ // 포트에 캐리어 전달
198
+ const port = this.ports.find(p => p.name === v.destPort);
199
+ if (port && !port.carrierName && v.carrierName) {
200
+ port.carrierName = v.carrierName;
201
+ port.carrierStatus = v.carrierStatus;
202
+ port.emptyType = 'FULL';
203
+ portChanges.push(this.portRecord(port));
204
+ }
205
+ v.carrierName = null;
206
+ v.carrierStatus = null;
207
+ v.emptyType = 'EMPTY';
208
+ v.sourcePort = null;
209
+ v.destPort = null;
210
+ v.phase = 'IDLE';
211
+ v.phaseTimer = randInt(1, 5); // 대기 시간
212
+ // AGV 충전 (낮은 배터리)
213
+ if (v.batteryRate != null && v.batteryRate < 20) {
214
+ v.phaseTimer = randInt(5, 15);
215
+ return this.vehicleRecord(v, 'CHARGING');
216
+ }
217
+ return this.vehicleRecord(v, 'IDLE');
218
+ }
219
+ }
220
+ return null;
221
+ }
222
+ // 이동 중 매 초 노드 이동 시뮬레이션
223
+ tickVehicleMoving(v) {
224
+ if (v.phase !== 'MOVING_TO_PICKUP' && v.phase !== 'MOVING_TO_DROP')
225
+ return null;
226
+ if (v.phaseTimer <= 0)
227
+ return null;
228
+ // 경로 상 중간 노드로 이동
229
+ const currentIdx = NODE_IDS.indexOf(v.nodeId);
230
+ const targetIdx = NODE_IDS.indexOf(v.targetNodeId);
231
+ if (currentIdx !== targetIdx) {
232
+ const step = currentIdx < targetIdx ? 1 : -1;
233
+ v.nodeId = NODE_IDS[currentIdx + step] || v.nodeId;
234
+ return this.vehicleRecord(v, 'MOVING');
235
+ }
236
+ return null;
237
+ }
238
+ vehicleRecord(v, status) {
239
+ return Object.assign({ NAME: v.name, STATUS: status, EMPTYTYPE: v.emptyType, NODEMACHINE: v.name, CARRIERNAME: v.carrierName, CARRIERSTATUS: v.carrierStatus, SUBNAME: `V${String(randInt(1, 999)).padStart(3, '0')}`, NODEID: v.nodeId }, (v.batteryRate != null ? { BATTERYRATE: v.batteryRate } : {}));
240
+ }
241
+ portRecord(p) {
242
+ return {
243
+ NAME: p.name,
244
+ STATUS: p.status,
245
+ EMPTYTYPE: p.emptyType,
246
+ CARRIERNAME: p.carrierName,
247
+ CARRIERSTATUS: p.carrierStatus,
248
+ };
249
+ }
250
+ tickZones() {
251
+ const changes = [];
252
+ for (const z of this.zones) {
253
+ if (Math.random() > 0.15)
254
+ continue;
255
+ const delta = randInt(-3, 3);
256
+ const newCap = Math.max(0, Math.min(z.maxCapacity, z.currentCapacity + delta));
257
+ if (newCap === z.currentCapacity)
258
+ continue;
259
+ z.currentCapacity = newCap;
260
+ changes.push({
261
+ NAME: z.name,
262
+ STATUS: z.status,
263
+ CURRENTCAPACITY: z.currentCapacity,
264
+ MAXCAPACITY: z.maxCapacity,
265
+ HIGHWATERMARK: z.highWaterMark,
266
+ LOWWATERMARK: z.lowWaterMark,
267
+ FULLUP: z.currentCapacity >= z.maxCapacity ? 'T' : 'F',
268
+ });
269
+ }
270
+ return changes;
271
+ }
272
+ tickStockers() {
273
+ const changes = [];
274
+ for (const s of this.stockers) {
275
+ if (Math.random() > 0.1)
276
+ continue;
277
+ const delta = randInt(-2, 2);
278
+ const newCap = Math.max(0, Math.min(s.maxCapacity, s.currentCapacity + delta));
279
+ if (newCap === s.currentCapacity)
280
+ continue;
281
+ s.currentCapacity = newCap;
282
+ changes.push({
283
+ NAME: s.name,
284
+ STATUS: s.status,
285
+ CURRENTCAPACITY: s.currentCapacity,
286
+ MAXCAPACITY: s.maxCapacity,
287
+ });
288
+ }
289
+ return changes;
290
+ }
291
+ // ── Snapshot 생성 ──
292
+ getVehicleSnapshot() {
293
+ return this.vehicles.map(v => {
294
+ const status = v.phase === 'IDLE' ? 'IDLE'
295
+ : v.phase === 'LOADING' ? 'LOADING'
296
+ : v.phase === 'UNLOADING' ? 'UNLOADING'
297
+ : 'MOVING';
298
+ return this.vehicleRecord(v, status);
299
+ });
300
+ }
301
+ getPortSnapshot() {
302
+ return this.ports.map(p => this.portRecord(p));
303
+ }
304
+ getZoneSnapshot() {
305
+ return this.zones.map(z => ({
306
+ NAME: z.name,
307
+ STATUS: z.status,
308
+ CURRENTCAPACITY: z.currentCapacity,
309
+ MAXCAPACITY: z.maxCapacity,
310
+ HIGHWATERMARK: z.highWaterMark,
311
+ LOWWATERMARK: z.lowWaterMark,
312
+ FULLUP: z.currentCapacity >= z.maxCapacity ? 'T' : 'F',
313
+ }));
314
+ }
315
+ getStockerSnapshot() {
316
+ return this.stockers.map(s => ({
317
+ NAME: s.name,
318
+ STATUS: s.status,
319
+ CURRENTCAPACITY: s.currentCapacity,
320
+ MAXCAPACITY: s.maxCapacity,
321
+ }));
322
+ }
323
+ }
324
+ function generatePlaybackData(startTime, endTime) {
325
+ const start = new Date(startTime.replace(' ', 'T'));
326
+ const end = new Date(endTime.replace(' ', 'T'));
327
+ const totalSeconds = Math.floor((end.getTime() - start.getTime()) / 1000);
328
+ if (totalSeconds <= 0 || totalSeconds > 7200)
329
+ return [];
330
+ const sim = new FactorySimulator();
331
+ const t0 = formatTime(start);
332
+ // 초기 snapshot
333
+ const vehicleFrames = [
334
+ { type: 'snapshot', time: t0, data: sim.getVehicleSnapshot() }
335
+ ];
336
+ const portFrames = [
337
+ { type: 'snapshot', time: t0, data: sim.getPortSnapshot() }
338
+ ];
339
+ const zoneFrames = [
340
+ { type: 'snapshot', time: t0, data: sim.getZoneSnapshot() }
341
+ ];
342
+ const stockerFrames = [
343
+ { type: 'snapshot', time: t0, data: sim.getStockerSnapshot() }
344
+ ];
345
+ // diff 프레임 생성
346
+ for (let s = 1; s <= totalSeconds; s++) {
347
+ const t = formatTime(new Date(start.getTime() + s * 1000));
348
+ // 이동 중인 차량의 노드 변경 (매 초)
349
+ const movingChanges = [];
350
+ for (const v of sim.vehicles) {
351
+ const mc = sim.tickVehicleMoving(v);
352
+ if (mc)
353
+ movingChanges.push(mc);
354
+ }
355
+ // 상태 전이 처리
356
+ const { vehicleChanges, portChanges, zoneChanges, stockerChanges } = sim.tick();
357
+ // 이동 변경 + 상태 전이 병합 (같은 vehicle은 상태 전이가 우선)
358
+ const vehicleChangeMap = new Map();
359
+ for (const c of movingChanges)
360
+ vehicleChangeMap.set(c.NAME, c);
361
+ for (const c of vehicleChanges)
362
+ vehicleChangeMap.set(c.NAME, c);
363
+ vehicleFrames.push({ type: 'diff', time: t, data: [...vehicleChangeMap.values()] });
364
+ portFrames.push({ type: 'diff', time: t, data: portChanges });
365
+ zoneFrames.push({ type: 'diff', time: t, data: zoneChanges });
366
+ stockerFrames.push({ type: 'diff', time: t, data: stockerChanges });
367
+ }
368
+ return [
369
+ { scenarioName: 'MCS_LV_VEHICLE', snapshots: vehicleFrames },
370
+ { scenarioName: 'MCS_LV_PORT', snapshots: portFrames },
371
+ { scenarioName: 'MCS_LV_ZONE', snapshots: zoneFrames },
372
+ { scenarioName: 'MCS_LV_STOCKER', snapshots: stockerFrames },
373
+ ];
374
+ }
375
+ // ── 엔티티 목록 (보드 작화용) ──
376
+ exports.MOCK_ENTITIES = {
377
+ vehicles: { agvs: AGVS, ohts: OHTS },
378
+ ports: PORTS,
379
+ zones: ZONES,
380
+ stockers: STOCKERS,
381
+ };
382
+ //# sourceMappingURL=mock-data.js.map