tangram-core 0.3.0__cp310-cp310-manylinux_2_28_aarch64.whl

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 (162) hide show
  1. tangram_core/App.vue +441 -0
  2. tangram_core/CommandPalette.vue +200 -0
  3. tangram_core/HighlightText.vue +32 -0
  4. tangram_core/__Timeline.vue +300 -0
  5. tangram_core/__init__.py +5 -0
  6. tangram_core/__main__.py +331 -0
  7. tangram_core/_core.cpython-310-aarch64-linux-gnu.so +0 -0
  8. tangram_core/_core.pyi +38 -0
  9. tangram_core/api.ts +652 -0
  10. tangram_core/backend.py +458 -0
  11. tangram_core/components.ts +2 -0
  12. tangram_core/config.py +167 -0
  13. tangram_core/dist-frontend/aggregation-layers.js +521 -0
  14. tangram_core/dist-frontend/aggregation-layers.js.map +1 -0
  15. tangram_core/dist-frontend/assets/_commonjsHelpers-CqkleIqs.js +2 -0
  16. tangram_core/dist-frontend/assets/_commonjsHelpers-CqkleIqs.js.map +1 -0
  17. tangram_core/dist-frontend/assets/array-utils-flat-BBMak426.js +11 -0
  18. tangram_core/dist-frontend/assets/array-utils-flat-BBMak426.js.map +1 -0
  19. tangram_core/dist-frontend/assets/assert-cyW4mg7q.js +3 -0
  20. tangram_core/dist-frontend/assets/assert-cyW4mg7q.js.map +1 -0
  21. tangram_core/dist-frontend/assets/b612-latin-400-italic-DePNXA0a.woff +0 -0
  22. tangram_core/dist-frontend/assets/b612-latin-400-italic-a-4GLPtl.woff2 +0 -0
  23. tangram_core/dist-frontend/assets/b612-latin-400-normal-CC98FVm_.woff2 +0 -0
  24. tangram_core/dist-frontend/assets/b612-latin-400-normal-JbZ7xwUX.woff +0 -0
  25. tangram_core/dist-frontend/assets/b612-latin-700-normal-B_Snq1wd.woff +0 -0
  26. tangram_core/dist-frontend/assets/b612-latin-700-normal-BinQrnoB.woff2 +0 -0
  27. tangram_core/dist-frontend/assets/clip-extension-D-rbmFPj.js +26 -0
  28. tangram_core/dist-frontend/assets/clip-extension-D-rbmFPj.js.map +1 -0
  29. tangram_core/dist-frontend/assets/color-CUNNsFV-.js +17 -0
  30. tangram_core/dist-frontend/assets/color-CUNNsFV-.js.map +1 -0
  31. tangram_core/dist-frontend/assets/cube-geometry-v0HQ793i.js +2 -0
  32. tangram_core/dist-frontend/assets/cube-geometry-v0HQ793i.js.map +1 -0
  33. tangram_core/dist-frontend/assets/deep-equal-BTW2ZN6S.js +2 -0
  34. tangram_core/dist-frontend/assets/deep-equal-BTW2ZN6S.js.map +1 -0
  35. tangram_core/dist-frontend/assets/fly-to-interpolator-CIXGjOdo.js +2 -0
  36. tangram_core/dist-frontend/assets/fly-to-interpolator-CIXGjOdo.js.map +1 -0
  37. tangram_core/dist-frontend/assets/geojson-layer-DgMOQ4Qu.js +1010 -0
  38. tangram_core/dist-frontend/assets/geojson-layer-DgMOQ4Qu.js.map +1 -0
  39. tangram_core/dist-frontend/assets/globe-view-Day_n1iB.js +94 -0
  40. tangram_core/dist-frontend/assets/globe-view-Day_n1iB.js.map +1 -0
  41. tangram_core/dist-frontend/assets/globe-viewport-tqhQW7C4.js +2 -0
  42. tangram_core/dist-frontend/assets/globe-viewport-tqhQW7C4.js.map +1 -0
  43. tangram_core/dist-frontend/assets/image-loader-hHJsndO6.js +2 -0
  44. tangram_core/dist-frontend/assets/image-loader-hHJsndO6.js.map +1 -0
  45. tangram_core/dist-frontend/assets/inconsolata-latin-400-normal-DTZQ6lD6.woff2 +0 -0
  46. tangram_core/dist-frontend/assets/inconsolata-latin-400-normal-HYADljCo.woff +0 -0
  47. tangram_core/dist-frontend/assets/inconsolata-latin-700-normal-ByjKuJjN.woff2 +0 -0
  48. tangram_core/dist-frontend/assets/inconsolata-latin-700-normal-DzgUY3Rl.woff +0 -0
  49. tangram_core/dist-frontend/assets/inconsolata-latin-ext-400-normal-BaHVOdFB.woff2 +0 -0
  50. tangram_core/dist-frontend/assets/inconsolata-latin-ext-400-normal-yvPjCxxx.woff +0 -0
  51. tangram_core/dist-frontend/assets/inconsolata-latin-ext-700-normal-D0Kpgs_9.woff2 +0 -0
  52. tangram_core/dist-frontend/assets/inconsolata-latin-ext-700-normal-Dlt-daqV.woff +0 -0
  53. tangram_core/dist-frontend/assets/inconsolata-vietnamese-400-normal-ByiM2lek.woff +0 -0
  54. tangram_core/dist-frontend/assets/inconsolata-vietnamese-400-normal-DfC_iMic.woff2 +0 -0
  55. tangram_core/dist-frontend/assets/inconsolata-vietnamese-700-normal-DLCFFAUf.woff +0 -0
  56. tangram_core/dist-frontend/assets/inconsolata-vietnamese-700-normal-DuasYmn8.woff2 +0 -0
  57. tangram_core/dist-frontend/assets/index-CcogpxdD.js +824 -0
  58. tangram_core/dist-frontend/assets/index-CcogpxdD.js.map +1 -0
  59. tangram_core/dist-frontend/assets/index-SSLdizTv.css +1 -0
  60. tangram_core/dist-frontend/assets/layer-DPcO4AXQ.js +555 -0
  61. tangram_core/dist-frontend/assets/layer-DPcO4AXQ.js.map +1 -0
  62. tangram_core/dist-frontend/assets/layer-extension-CYwTXf73.js +2 -0
  63. tangram_core/dist-frontend/assets/layer-extension-CYwTXf73.js.map +1 -0
  64. tangram_core/dist-frontend/assets/mesh-layers-wiqredoy.js +1123 -0
  65. tangram_core/dist-frontend/assets/mesh-layers-wiqredoy.js.map +1 -0
  66. tangram_core/dist-frontend/assets/orthographic-viewport-B4nCj5tn.js +2 -0
  67. tangram_core/dist-frontend/assets/orthographic-viewport-B4nCj5tn.js.map +1 -0
  68. tangram_core/dist-frontend/assets/pick-layers-pass-C-3k0wbN.js +2 -0
  69. tangram_core/dist-frontend/assets/pick-layers-pass-C-3k0wbN.js.map +1 -0
  70. tangram_core/dist-frontend/assets/project-BTjD2Imj.js +760 -0
  71. tangram_core/dist-frontend/assets/project-BTjD2Imj.js.map +1 -0
  72. tangram_core/dist-frontend/assets/roboto-condensed-cyrillic-400-italic-4qS3_zkX.woff2 +0 -0
  73. tangram_core/dist-frontend/assets/roboto-condensed-cyrillic-400-italic-CDK-EZBY.woff +0 -0
  74. tangram_core/dist-frontend/assets/roboto-condensed-cyrillic-400-normal-Bgns473E.woff +0 -0
  75. tangram_core/dist-frontend/assets/roboto-condensed-cyrillic-400-normal-_T2aQlWs.woff2 +0 -0
  76. tangram_core/dist-frontend/assets/roboto-condensed-cyrillic-500-normal-CvEVpWxD.woff +0 -0
  77. tangram_core/dist-frontend/assets/roboto-condensed-cyrillic-500-normal-s4PklZE0.woff2 +0 -0
  78. tangram_core/dist-frontend/assets/roboto-condensed-cyrillic-700-normal-9RN-Z7cI.woff2 +0 -0
  79. tangram_core/dist-frontend/assets/roboto-condensed-cyrillic-700-normal-BGMkBBYx.woff +0 -0
  80. tangram_core/dist-frontend/assets/roboto-condensed-cyrillic-ext-400-italic-C7erd-g8.woff +0 -0
  81. tangram_core/dist-frontend/assets/roboto-condensed-cyrillic-ext-400-italic-DR5R5TWx.woff2 +0 -0
  82. tangram_core/dist-frontend/assets/roboto-condensed-cyrillic-ext-400-normal-DGo1Ayjq.woff2 +0 -0
  83. tangram_core/dist-frontend/assets/roboto-condensed-cyrillic-ext-400-normal-WtM1l1qc.woff +0 -0
  84. tangram_core/dist-frontend/assets/roboto-condensed-cyrillic-ext-500-normal-C8FNIdXm.woff2 +0 -0
  85. tangram_core/dist-frontend/assets/roboto-condensed-cyrillic-ext-500-normal-TLDmfi3Q.woff +0 -0
  86. tangram_core/dist-frontend/assets/roboto-condensed-cyrillic-ext-700-normal-CTXjXnze.woff2 +0 -0
  87. tangram_core/dist-frontend/assets/roboto-condensed-cyrillic-ext-700-normal-CWPRiRXS.woff +0 -0
  88. tangram_core/dist-frontend/assets/roboto-condensed-greek-400-italic-CR6qj4Z4.woff2 +0 -0
  89. tangram_core/dist-frontend/assets/roboto-condensed-greek-400-italic-DHRaIs10.woff +0 -0
  90. tangram_core/dist-frontend/assets/roboto-condensed-greek-400-normal-D5vBSIyg.woff2 +0 -0
  91. tangram_core/dist-frontend/assets/roboto-condensed-greek-400-normal-FabMgVmk.woff +0 -0
  92. tangram_core/dist-frontend/assets/roboto-condensed-greek-500-normal-BIN62cw9.woff +0 -0
  93. tangram_core/dist-frontend/assets/roboto-condensed-greek-500-normal-Hsn-wDIp.woff2 +0 -0
  94. tangram_core/dist-frontend/assets/roboto-condensed-greek-700-normal-89Up2Xly.woff +0 -0
  95. tangram_core/dist-frontend/assets/roboto-condensed-greek-700-normal-DWMOA2VK.woff2 +0 -0
  96. tangram_core/dist-frontend/assets/roboto-condensed-latin-400-italic-D_BR-3LG.woff2 +0 -0
  97. tangram_core/dist-frontend/assets/roboto-condensed-latin-400-italic-om57GXsO.woff +0 -0
  98. tangram_core/dist-frontend/assets/roboto-condensed-latin-400-normal-BICmKrXV.woff2 +0 -0
  99. tangram_core/dist-frontend/assets/roboto-condensed-latin-400-normal-D2e7XwB1.woff +0 -0
  100. tangram_core/dist-frontend/assets/roboto-condensed-latin-500-normal-3p2daRJW.woff2 +0 -0
  101. tangram_core/dist-frontend/assets/roboto-condensed-latin-500-normal-Dc9bsamC.woff +0 -0
  102. tangram_core/dist-frontend/assets/roboto-condensed-latin-700-normal-BOl6B_hI.woff +0 -0
  103. tangram_core/dist-frontend/assets/roboto-condensed-latin-700-normal-DRbp0YnP.woff2 +0 -0
  104. tangram_core/dist-frontend/assets/roboto-condensed-latin-ext-400-italic-BXrkWnoY.woff +0 -0
  105. tangram_core/dist-frontend/assets/roboto-condensed-latin-ext-400-italic-Bhem1d5z.woff2 +0 -0
  106. tangram_core/dist-frontend/assets/roboto-condensed-latin-ext-400-normal-DT8nEsYA.woff +0 -0
  107. tangram_core/dist-frontend/assets/roboto-condensed-latin-ext-400-normal-OHaX69iP.woff2 +0 -0
  108. tangram_core/dist-frontend/assets/roboto-condensed-latin-ext-500-normal-CcSTXKtO.woff2 +0 -0
  109. tangram_core/dist-frontend/assets/roboto-condensed-latin-ext-500-normal-JgPl2bDS.woff +0 -0
  110. tangram_core/dist-frontend/assets/roboto-condensed-latin-ext-700-normal-B004qtqu.woff2 +0 -0
  111. tangram_core/dist-frontend/assets/roboto-condensed-latin-ext-700-normal-O6H_RRvN.woff +0 -0
  112. tangram_core/dist-frontend/assets/roboto-condensed-vietnamese-400-italic-BwUYFJ2t.woff2 +0 -0
  113. tangram_core/dist-frontend/assets/roboto-condensed-vietnamese-400-italic-DV8QogUk.woff +0 -0
  114. tangram_core/dist-frontend/assets/roboto-condensed-vietnamese-400-normal-0o1laQ-g.woff2 +0 -0
  115. tangram_core/dist-frontend/assets/roboto-condensed-vietnamese-400-normal-CPsdS8_S.woff +0 -0
  116. tangram_core/dist-frontend/assets/roboto-condensed-vietnamese-500-normal-G9shSJ2z.woff +0 -0
  117. tangram_core/dist-frontend/assets/roboto-condensed-vietnamese-500-normal-TFWhjk13.woff2 +0 -0
  118. tangram_core/dist-frontend/assets/roboto-condensed-vietnamese-700-normal-BtNeb9D6.woff +0 -0
  119. tangram_core/dist-frontend/assets/roboto-condensed-vietnamese-700-normal-D35V1G0s.woff2 +0 -0
  120. tangram_core/dist-frontend/assets/shader-Cbdysp2j.js +843 -0
  121. tangram_core/dist-frontend/assets/shader-Cbdysp2j.js.map +1 -0
  122. tangram_core/dist-frontend/assets/solid-polygon-layer-DJFl_7Ca.js +392 -0
  123. tangram_core/dist-frontend/assets/solid-polygon-layer-DJFl_7Ca.js.map +1 -0
  124. tangram_core/dist-frontend/assets/tesselator-CENyUZ2p.js +2 -0
  125. tangram_core/dist-frontend/assets/tesselator-CENyUZ2p.js.map +1 -0
  126. tangram_core/dist-frontend/assets/webgl-developer-tools-utTNOsNf.js +7 -0
  127. tangram_core/dist-frontend/assets/webgl-developer-tools-utTNOsNf.js.map +1 -0
  128. tangram_core/dist-frontend/assets/webgl-device-BYRB-GQX.js +3 -0
  129. tangram_core/dist-frontend/assets/webgl-device-BYRB-GQX.js.map +1 -0
  130. tangram_core/dist-frontend/assets/widget-BjgEeHAL.js +2 -0
  131. tangram_core/dist-frontend/assets/widget-BjgEeHAL.js.map +1 -0
  132. tangram_core/dist-frontend/core.js +60 -0
  133. tangram_core/dist-frontend/core.js.map +1 -0
  134. tangram_core/dist-frontend/extensions.js +609 -0
  135. tangram_core/dist-frontend/extensions.js.map +1 -0
  136. tangram_core/dist-frontend/favicon.ico +0 -0
  137. tangram_core/dist-frontend/favicon.png +0 -0
  138. tangram_core/dist-frontend/geo-layers.js +115 -0
  139. tangram_core/dist-frontend/geo-layers.js.map +1 -0
  140. tangram_core/dist-frontend/index.html +39 -0
  141. tangram_core/dist-frontend/json.js +3 -0
  142. tangram_core/dist-frontend/json.js.map +1 -0
  143. tangram_core/dist-frontend/layers.js +268 -0
  144. tangram_core/dist-frontend/layers.js.map +1 -0
  145. tangram_core/dist-frontend/mapbox.js +2 -0
  146. tangram_core/dist-frontend/mapbox.js.map +1 -0
  147. tangram_core/dist-frontend/mesh-layers.js +2 -0
  148. tangram_core/dist-frontend/mesh-layers.js.map +1 -0
  149. tangram_core/dist-frontend/widgets.js +3 -0
  150. tangram_core/dist-frontend/widgets.js.map +1 -0
  151. tangram_core/main.ts +28 -0
  152. tangram_core/package.json +62 -0
  153. tangram_core/plugin.py +109 -0
  154. tangram_core/plugin.ts +47 -0
  155. tangram_core/redis.py +89 -0
  156. tangram_core/user.css +114 -0
  157. tangram_core/utils.ts +143 -0
  158. tangram_core/vite-plugin-tangram.mjs +155 -0
  159. tangram_core-0.3.0.dist-info/METADATA +101 -0
  160. tangram_core-0.3.0.dist-info/RECORD +162 -0
  161. tangram_core-0.3.0.dist-info/WHEEL +4 -0
  162. tangram_core-0.3.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,300 @@
1
+ <!-- NOTE: This component is a relic of tangram v0.1 and is kept for reference.
2
+ It is scheduled for removal in v0.4 when playback is properly implemented
3
+ in the backend. -->
4
+ <template>
5
+ <div class="timeline-container" :style="styles">
6
+ <div
7
+ class="timeline-flex"
8
+ :class="{ 'flex-row': direction !== 'col', 'flex-col': direction === 'col' }"
9
+ >
10
+ <div
11
+ :class="{
12
+ 'progress-row': direction !== 'col',
13
+ 'progress-col': direction === 'col'
14
+ }"
15
+ >
16
+ <VueSlider
17
+ :min="dateArray[0].startOf('day').unix()"
18
+ :max="dateArray[dateArray.length - 1].endOf('day').unix()"
19
+ :tooltip-style="{ display: 'none' }"
20
+ :dot-style="{ display: 'none' }"
21
+ :process-style="{ background: '#0000bb50', borderRadius: '1px' }"
22
+ v-model="curTime"
23
+ :height="8"
24
+ :dot-size="8"
25
+ style="padding: 0"
26
+ @change="onChangeTime"
27
+ />
28
+ </div>
29
+ <div
30
+ v-if="curTime"
31
+ :style="{ ...getTooltip, 'z-index': styles.zIndex + 1 || '401' }"
32
+ :class="{
33
+ 'tooltip-row': direction !== 'col',
34
+ 'tooltip-col': direction === 'col'
35
+ }"
36
+ >
37
+ {{ getTooltipText() }}
38
+ </div>
39
+ <div
40
+ v-for="(item, index) in dateArray"
41
+ :key="index"
42
+ :style="{ width: 100 / dateArray.length + '%' }"
43
+ class="date-block"
44
+ >
45
+ <div style="width: 100%; display: flex">
46
+ <div
47
+ v-for="num in ticks"
48
+ :key="num"
49
+ :style="{ width: 100 / ticks.length + '%' }"
50
+ :class="{
51
+ 'ticks-row': direction !== 'col',
52
+ 'ticks-col': direction === 'col'
53
+ }"
54
+ >
55
+ <span
56
+ v-if="num !== '00' && showTick"
57
+ class="tick-num"
58
+ :class="{
59
+ 'tick-num-row': direction !== 'col',
60
+ 'tick-num-col': direction === 'col'
61
+ }"
62
+ >{{ num }}</span
63
+ >
64
+ </div>
65
+ </div>
66
+ <div v-if="showTime" class="date-content">{{ item.format(dateFormat) }}</div>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </template>
71
+
72
+ <script>
73
+ import dayjs from "dayjs";
74
+ import VueSlider from "vue-slider-component";
75
+ import "vue-slider-component/theme/antd.css";
76
+ export default {
77
+ components: {
78
+ VueSlider
79
+ },
80
+ props: {
81
+ progressColor: {
82
+ type: String,
83
+ default: "#0000bb50"
84
+ },
85
+ styles: {
86
+ type: Object,
87
+ default: {}
88
+ },
89
+ unitNum: {
90
+ type: Number,
91
+ default: 8
92
+ },
93
+ dateFormat: {
94
+ type: String,
95
+ default: "MM-DD"
96
+ },
97
+ showToolTip: {
98
+ type: Boolean,
99
+ default: true
100
+ },
101
+ showTime: {
102
+ type: Boolean,
103
+ default: true
104
+ },
105
+ showTick: {
106
+ type: Boolean,
107
+ default: true
108
+ },
109
+ draggable: {
110
+ type: Boolean,
111
+ default: true
112
+ },
113
+ dateArray: {
114
+ type: Array,
115
+ default: () => [
116
+ dayjs().subtract(3, "day"),
117
+ dayjs().subtract(2, "day"),
118
+ dayjs().subtract(1, "day"),
119
+ dayjs(),
120
+ dayjs().add(1, "day"),
121
+ dayjs().add(2, "day"),
122
+ dayjs().add(3, "day")
123
+ ]
124
+ },
125
+ currentTime: {
126
+ type: Object,
127
+ default: () => dayjs()
128
+ },
129
+ direction: {
130
+ type: String,
131
+ default: "row"
132
+ }
133
+ },
134
+ data() {
135
+ return {
136
+ curTime: this.currentTime.unix()
137
+ };
138
+ },
139
+ computed: {
140
+ getTooltip() {
141
+ const cur = this.curTime;
142
+ const start = this.dateArray[0].startOf("day").unix();
143
+ const end = this.dateArray[this.dateArray.length - 1].endOf("day").unix();
144
+ if (this.direction !== "col") {
145
+ return { left: ((cur - start) / (end - start)) * 100 + "%" };
146
+ } else {
147
+ return { top: ((cur - start) / (end - start)) * 100 + "%" };
148
+ }
149
+ },
150
+ ticks() {
151
+ const arr = [];
152
+ for (let i = 0; i < this.unitNum; i++) {
153
+ const time = 24 / this.unitNum;
154
+ const timeTxt = i * time < 10 ? "0" + i * time : i * time;
155
+ arr.push(timeTxt);
156
+ }
157
+ return arr;
158
+ }
159
+ },
160
+ methods: {
161
+ getTooltipText() {
162
+ return dayjs.unix(this.curTime).format("HH:mm");
163
+ },
164
+ onChangeTime(e) {
165
+ const num = e / 100;
166
+ const start = this.dateArray[0].startOf("day").unix();
167
+ const end = this.dateArray[this.dateArray.length - 1].endOf("day").unix();
168
+ const now = (end - start) * e;
169
+ },
170
+ handleDragStart(e) {
171
+ console.log(e);
172
+ e.preventDefault();
173
+ e.stopPropagation();
174
+ },
175
+ handleDrop(e) {
176
+ console.log(e);
177
+ e.preventDefault();
178
+ e.stopPropagation();
179
+ }
180
+ }
181
+ };
182
+ </script>
183
+ <style>
184
+ .timeline-container {
185
+ z-index: 100;
186
+ cursor: default;
187
+ }
188
+
189
+ .timeline-container .timeline-flex {
190
+ display: flex;
191
+ position: relative;
192
+ }
193
+
194
+ .timeline-container .flex-row {
195
+ flex-direction: row;
196
+ width: 100%;
197
+ }
198
+
199
+ .timeline-container .flex-col {
200
+ flex-direction: column;
201
+ height: 100%;
202
+ }
203
+
204
+ .timeline-container .date-block {
205
+ text-align: center;
206
+ border-right: 1px solid #808080;
207
+ border-top: 1px solid #808080;
208
+ z-index: 1;
209
+ }
210
+
211
+ .timeline-container .date-block:first-child {
212
+ border-left: none;
213
+ }
214
+
215
+ .timeline-container .date-block .date-content {
216
+ padding-top: 10px;
217
+ padding-bottom: 5px;
218
+ }
219
+
220
+ .timeline-container .ticks-row {
221
+ border-left: 1px solid #808080;
222
+ font-size: 8px;
223
+ height: 8px;
224
+ width: 8px;
225
+ position: relative;
226
+ }
227
+
228
+ .timeline-container .date-block .ticks-row:first-child {
229
+ border-left: none;
230
+ }
231
+
232
+ .timeline-container .tick-num {
233
+ position: absolute;
234
+ }
235
+
236
+ .timeline-container .tick-num-row {
237
+ left: 0;
238
+ transform: translateX(-50%);
239
+ top: 10px;
240
+ }
241
+
242
+ .timeline-container .tick-num-col {
243
+ top: 0;
244
+ transform: translateY(-50%);
245
+ left: 5px;
246
+ }
247
+
248
+ .timeline-container .progress-row {
249
+ height: 10px;
250
+ position: absolute;
251
+ top: 0;
252
+ width: 100%;
253
+ left: 0;
254
+ background: #00000030;
255
+ cursor: pointer;
256
+ }
257
+
258
+ .timeline-container .progress-col {
259
+ width: 10px;
260
+ position: absolute;
261
+ height: 100%;
262
+ top: 0;
263
+ left: 0;
264
+ background: #00000030;
265
+ cursor: pointer;
266
+ }
267
+
268
+ .timeline-container .tooltip-row {
269
+ position: absolute;
270
+ border-radius: 5px;
271
+ width: 50px;
272
+ height: 30px;
273
+ border: 1px solid #e0e0e0;
274
+ display: flex;
275
+ align-items: center;
276
+ justify-content: center;
277
+ transform: translateX(-50%);
278
+ top: -40px;
279
+ background: #0000bb60;
280
+ color: white;
281
+ }
282
+
283
+ .timeline-container .tooltip-row:after {
284
+ top: 100%;
285
+ left: 20px;
286
+ border: solid transparent;
287
+ content: " ";
288
+ height: 0;
289
+ width: 0;
290
+ position: absolute;
291
+ border-top-color: #0000bb60;
292
+ border-width: 5px;
293
+ margin-left: -5px;
294
+ -moz-user-select: none;
295
+ -webkit-user-select: none;
296
+ -ms-user-select: none;
297
+ -khtml-user-select: none;
298
+ user-select: none;
299
+ }
300
+ </style>
@@ -0,0 +1,5 @@
1
+ from .backend import BackendState, InjectBackendState, Runtime
2
+ from .config import Config
3
+ from .plugin import Plugin
4
+
5
+ __all__ = ["BackendState", "Config", "InjectBackendState", "Plugin", "Runtime"]
@@ -0,0 +1,331 @@
1
+ #!/usr/bin/env -S uv run --script
2
+ # /// script
3
+ # requires-python = ">=3.12"
4
+ # dependencies = ["typer"]
5
+ # ///
6
+ # NOTE: put all non-standard-library imports (including `tangram_core`) inside
7
+ # functions.
8
+
9
+ import asyncio
10
+ import logging
11
+ import logging.config
12
+ import re
13
+ import subprocess
14
+ import sys
15
+ from pathlib import Path
16
+ from typing import Annotated, Any, Generator, TypeAlias
17
+
18
+ if sys.version_info >= (3, 11):
19
+ from importlib.resources.abc import Traversable
20
+ else:
21
+ from importlib.abc import Traversable
22
+
23
+ import typer
24
+ from rich.console import Console
25
+
26
+ from .config import default_config_file
27
+
28
+ app = typer.Typer(no_args_is_help=True, pretty_exceptions_enable=False)
29
+ logger = logging.getLogger(__name__)
30
+ stderr = Console(stderr=True)
31
+ stdout = Console(stderr=False)
32
+
33
+
34
+ def print_error(v: Any) -> None:
35
+ stdout.print(f"[bold red]error[/bold red]: {v}")
36
+
37
+
38
+ def print_success(v: Any) -> None:
39
+ stdout.print(f"[bold green]success[/bold green]: {v}")
40
+
41
+
42
+ PathTangramConfig: TypeAlias = Annotated[
43
+ Path,
44
+ typer.Option(
45
+ help="Path to the tangram.toml config file.",
46
+ envvar="TANGRAM_CONFIG",
47
+ default_factory=default_config_file,
48
+ exists=True,
49
+ dir_okay=False,
50
+ ),
51
+ ]
52
+
53
+
54
+ @app.command()
55
+ def serve(
56
+ config: PathTangramConfig,
57
+ ) -> None:
58
+ """Serves the core tangram frontend and backend services."""
59
+ from .backend import Runtime, get_log_config_dict
60
+ from .config import Config
61
+
62
+ cfg = Config.from_file(config)
63
+ logging.config.dictConfig(get_log_config_dict(cfg))
64
+
65
+ async def run_server() -> None:
66
+ async with Runtime(cfg) as runtime:
67
+ await runtime.wait()
68
+
69
+ try:
70
+ asyncio.run(run_server())
71
+ except KeyboardInterrupt:
72
+ pass
73
+
74
+
75
+ @app.command()
76
+ def list_plugins(
77
+ config_path: PathTangramConfig | None = None,
78
+ load_all: Annotated[
79
+ bool, typer.Option("--all", "-a", help="Load all discovered plugins.")
80
+ ] = False,
81
+ ) -> None:
82
+ """Lists discovered plugins and their components."""
83
+ from rich.table import Table
84
+
85
+ from .backend import resolve_frontend
86
+ from .config import Config
87
+ from .plugin import load_plugin, scan_plugins
88
+
89
+ table = Table()
90
+ table.add_column("plugin")
91
+ table.add_column("status")
92
+ table.add_column("frontend")
93
+ table.add_column("routers")
94
+ table.add_column("services")
95
+
96
+ enabled_plugins: list[str] | None = None
97
+ if config_path and config_path.is_file():
98
+ config = Config.from_file(config_path)
99
+ enabled_plugins = config.core.plugins
100
+
101
+ for entry_point in scan_plugins():
102
+ plugin_name = entry_point.name
103
+ version = dist.version if (dist := entry_point.dist) is not None else "?"
104
+ plugin_str = f"{plugin_name}=={version}"
105
+ load_this_plugin = load_all or (
106
+ enabled_plugins is not None and plugin_name in enabled_plugins
107
+ )
108
+
109
+ if not load_this_plugin:
110
+ table.add_row(plugin_str, "[cyan]available[/cyan]", "?", "?", "?")
111
+ continue
112
+
113
+ if (plugin := load_plugin(entry_point)) is None:
114
+ status_str = "[red]load failed[/red]"
115
+ table.add_row(plugin_str, status_str, "!", "!", "!")
116
+ continue
117
+ status = (
118
+ "enabled"
119
+ if enabled_plugins and plugin_name in enabled_plugins
120
+ else "loaded"
121
+ )
122
+ if plugin.frontend_path is None:
123
+ frontend_str = ""
124
+ elif resolved_path := resolve_frontend(plugin):
125
+ size_kb = get_path_size(resolved_path) / 1024
126
+ frontend_str = f"{size_kb:.1f} B"
127
+ else:
128
+ frontend_str = f"[yellow]({plugin.frontend_path} not found)[/yellow]"
129
+
130
+ status_str = f"[green]{status}[/green]"
131
+ router_prefixes = [func.prefix for func in plugin.routers]
132
+ service_names = [func.__name__ for _, func in plugin.services]
133
+
134
+ table.add_row(
135
+ plugin_str,
136
+ status_str,
137
+ frontend_str,
138
+ "\n".join(router_prefixes),
139
+ "\n".join(service_names),
140
+ )
141
+
142
+ stderr.print(table)
143
+
144
+
145
+ def get_path_size(path: Path | Traversable) -> int:
146
+ total_size = 0
147
+ for item in path.iterdir():
148
+ if item.is_file():
149
+ with item.open("rb") as f:
150
+ total_size += len(f.read())
151
+ elif item.is_dir():
152
+ total_size += get_path_size(item)
153
+ return total_size
154
+
155
+
156
+ @app.command()
157
+ def init() -> None:
158
+ raise NotImplementedError
159
+
160
+
161
+ #
162
+ # developer utilities
163
+ #
164
+
165
+
166
+ def get_package_paths(
167
+ extra_paths: list[Path] | None, all_plugins: bool
168
+ ) -> Generator[tuple[str, Path], None, None]:
169
+ if all_plugins:
170
+ from .backend import get_distribution_path
171
+ from .plugin import scan_plugins
172
+
173
+ yield "core", get_distribution_path("tangram_core")
174
+
175
+ for ep in scan_plugins():
176
+ name = ep.dist.name if ep.dist else ep.name
177
+ yield name, get_distribution_path(name)
178
+ if extra_paths:
179
+ for path in extra_paths:
180
+ yield "(unknown name)", path
181
+
182
+
183
+ @app.command()
184
+ def check_plugin(
185
+ extra_paths: Annotated[
186
+ list[Path] | None,
187
+ typer.Argument(help="Extra paths to plugin directories.", exists=True),
188
+ ] = None,
189
+ all: bool = False,
190
+ ) -> None:
191
+ """Verifies that plugin `devDependencies` match those defined in the core."""
192
+ import json
193
+
194
+ from .backend import get_distribution_path
195
+
196
+ if extra_paths is None and not all:
197
+ all = True
198
+
199
+ core_path = get_distribution_path("tangram_core")
200
+
201
+ core_pkg_json = core_path / "package.json"
202
+ core_package_json = json.loads(core_pkg_json.read_text())
203
+ core_deps = core_package_json.get("dependencies", {}) | core_package_json.get(
204
+ "devDependencies", {}
205
+ )
206
+
207
+ has_error = False
208
+ for dist_name, path in get_package_paths(extra_paths, all):
209
+ pkg_json = path / "package.json"
210
+ if not pkg_json.exists():
211
+ continue # not all plugins have frontends, so we just skip silently
212
+ stderr.print(f"checking '{dist_name}' at {path}")
213
+ plugin_deps = json.loads(pkg_json.read_text()).get("devDependencies", {})
214
+ for dep, version in plugin_deps.items():
215
+ if dep in core_deps and core_deps[dep] != version:
216
+ print_error(
217
+ f"expected '{dep}@{core_deps[dep]}' but found '{dep}@{version}' "
218
+ f"in {path.name}"
219
+ )
220
+ has_error = True
221
+
222
+ if has_error:
223
+ raise typer.Exit(1)
224
+ print_success("all plugin dependencies verified")
225
+
226
+
227
+ RE_TOML = r'(^version\s*=\s*)"[^"]+"'
228
+ RE_JSON = r'("version"\s*:\s*)"[^"]+"'
229
+
230
+
231
+ def set_version_in_file(path: Path, regex: str, version: str) -> bool:
232
+ if not path.exists():
233
+ return False
234
+ content = path.read_text()
235
+ new_content = re.sub(
236
+ regex,
237
+ f'\\1"{version}"',
238
+ content,
239
+ flags=re.MULTILINE,
240
+ )
241
+ if content != new_content:
242
+ path.write_text(new_content)
243
+ return True
244
+ return False
245
+
246
+
247
+ def run_command(cmd: list[str], cwd: Path) -> None:
248
+ try:
249
+ stderr.print(f"running '{' '.join(cmd)}' in {cwd}")
250
+ subprocess.run(
251
+ cmd, cwd=cwd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE
252
+ )
253
+ except subprocess.CalledProcessError as e:
254
+ print_error(f"command failed in {cwd}: {' '.join(cmd)}\n{e.stderr.decode()}")
255
+
256
+
257
+ @app.command()
258
+ def set_plugin_version(
259
+ version: str,
260
+ extra_path: Annotated[
261
+ list[Path] | None,
262
+ typer.Argument(help="Paths to plugins to update.", exists=True),
263
+ ] = None,
264
+ all: bool = False,
265
+ update_lock: bool = True,
266
+ ) -> None:
267
+ """Updates version in pyproject.toml, package.json and Cargo.toml."""
268
+ # we allow bumping versions without bumping core.
269
+ cwd = Path.cwd()
270
+ is_in_workspace = (
271
+ cwd / "pnpm-workspace.yaml"
272
+ ).exists() or "[workspace.package]" in (cwd / "Cargo.toml").read_text()
273
+
274
+ if not extra_path and not all:
275
+ print_error(
276
+ "either --all must be specified or at least one extra path must be given"
277
+ )
278
+ raise typer.Exit(1)
279
+
280
+ update_root_uv_lock = False
281
+ for dist_name, path in get_package_paths(extra_path, all):
282
+ updated_py = False
283
+ updated_rs = False
284
+
285
+ pyproject_toml = path / "pyproject.toml"
286
+ if set_version_in_file(pyproject_toml, RE_TOML, version):
287
+ print_success(f"updated '{dist_name}' at {pyproject_toml}")
288
+ updated_py = True
289
+
290
+ pkg_json = path / "package.json"
291
+ if set_version_in_file(pkg_json, RE_JSON, version):
292
+ print_success(f"updated '{dist_name}' at {pkg_json}")
293
+
294
+ cargo_toml = path / "Cargo.toml"
295
+ if not cargo_toml.exists() and (path / "rust" / "Cargo.toml").exists():
296
+ cargo_toml = path / "rust" / "Cargo.toml"
297
+ if cargo_toml.exists():
298
+ content = cargo_toml.read_text()
299
+ if "version.workspace = true" not in content:
300
+ if set_version_in_file(cargo_toml, RE_TOML, version):
301
+ print_success(f"updated '{dist_name}' at {cargo_toml}")
302
+ updated_rs = True
303
+
304
+ if update_lock:
305
+ if is_in_workspace: # defer
306
+ update_root_uv_lock = True
307
+ continue
308
+ if updated_py:
309
+ run_command(["uv", "lock"], path)
310
+ if updated_rs:
311
+ run_command(["cargo", "check"], cargo_toml.parent)
312
+
313
+ if is_in_workspace:
314
+ if update_root_uv_lock:
315
+ run_command(["uv", "lock"], cwd)
316
+
317
+ if not all:
318
+ return
319
+ updated_rs_ws = False
320
+ root_cargo = cwd / "Cargo.toml"
321
+ if root_cargo.exists():
322
+ if set_version_in_file(root_cargo, RE_TOML, version):
323
+ print_success(f"updated workspace '{root_cargo}'")
324
+ updated_rs_ws = True
325
+ if update_lock:
326
+ if updated_rs_ws:
327
+ run_command(["cargo", "check", "--workspace"], cwd)
328
+
329
+
330
+ if __name__ == "__main__":
331
+ app()
tangram_core/_core.pyi ADDED
@@ -0,0 +1,38 @@
1
+ # This file is automatically generated by pyo3_stub_gen
2
+ # ruff: noqa: E501, F401
3
+
4
+ import builtins
5
+ import typing
6
+
7
+ @typing.final
8
+ class ChannelConfig:
9
+ @property
10
+ def host(self) -> builtins.str: ...
11
+ @host.setter
12
+ def host(self, value: builtins.str) -> None: ...
13
+ @property
14
+ def port(self) -> builtins.int: ...
15
+ @port.setter
16
+ def port(self, value: builtins.int) -> None: ...
17
+ @property
18
+ def redis_url(self) -> builtins.str: ...
19
+ @redis_url.setter
20
+ def redis_url(self, value: builtins.str) -> None: ...
21
+ @property
22
+ def jwt_secret(self) -> builtins.str: ...
23
+ @jwt_secret.setter
24
+ def jwt_secret(self, value: builtins.str) -> None: ...
25
+ @property
26
+ def jwt_expiration_secs(self) -> builtins.int: ...
27
+ @jwt_expiration_secs.setter
28
+ def jwt_expiration_secs(self, value: builtins.int) -> None: ...
29
+ @property
30
+ def id_length(self) -> builtins.int: ...
31
+ @id_length.setter
32
+ def id_length(self, value: builtins.int) -> None: ...
33
+ def __new__(cls, host: builtins.str, port: builtins.int, redis_url: builtins.str, jwt_secret: builtins.str, jwt_expiration_secs: builtins.int, id_length: builtins.int) -> ChannelConfig: ...
34
+
35
+ def init_tracing_stderr(filter_str: builtins.str) -> None: ...
36
+
37
+ def run(config: ChannelConfig) -> typing.Any: ...
38
+