@tailor-cms/tce-preview-runtime 0.4.0-beta.2 → 0.4.0-beta.3

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.
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@tailor-cms/tce-preview-runtime",
3
- "version": "0.4.0-beta.2",
3
+ "version": "0.4.0-beta.3",
4
4
  "dependencies": {
5
5
  "@mdi/font": "7.2.96",
6
6
  "@vitejs/plugin-vue": "4.3.4",
7
7
  "@vueuse/core": "10.4.1",
8
+ "date-fns": "^3.2.0",
8
9
  "ky": "1.0.1",
9
10
  "lodash": "^4.17.21",
10
11
  "sass": "1.68.0",
@@ -15,7 +16,7 @@
15
16
  "vue": "3.3.4",
16
17
  "vue-tsc": "1.8.13",
17
18
  "vue3-ts-jsoneditor": "2.9.0",
18
- "vuetify": "3.3.17"
19
+ "vuetify": "3.4.0"
19
20
  },
20
21
  "devDependencies": {
21
22
  "@tailor-cms/eslint-config": "0.0.2"
@@ -27,7 +28,7 @@
27
28
  "dev": "vite",
28
29
  "build": "vue-tsc --noEmit && vite build",
29
30
  "serve": "vite preview",
30
- "lint": "eslint --ext .js,.ts,.vue .",
31
+ "lint": "eslint --ext .ts,.vue .",
31
32
  "lint:fix": "pnpm lint --fix"
32
33
  }
33
34
  }
package/src/App.vue CHANGED
@@ -5,17 +5,33 @@ import ky from 'ky';
5
5
  import AppBar from './components/AppBar.vue';
6
6
  import MainLayout from './components/MainLayout.vue';
7
7
 
8
+ interface ContentElement {
9
+ id: number;
10
+ }
11
+
8
12
  const appUrl = new URL(window.location.href);
9
13
  const apiPrefix = '/tce-server';
10
14
  const api = ky.create({ prefixUrl: apiPrefix });
11
15
  const wsProtocol = appUrl.protocol === 'http:' ? 'ws:' : 'wss:';
12
16
  const ws = new WebSocket(`${wsProtocol}//${appUrl.host}${apiPrefix}`);
13
17
 
14
- const element = ref({});
18
+ const element = ref<ContentElement>();
19
+ const userState = ref({});
20
+
15
21
  onMounted(async () => {
16
- element.value = await getElement();
17
- ws.addEventListener('message', (event) => {
18
- element.value = JSON.parse(event.data);
22
+ const { element: elementVal, userState: userStateVal } = await getElement();
23
+ element.value = elementVal;
24
+ userState.value = userStateVal;
25
+ ws.addEventListener('message', (message) => {
26
+ const event = JSON.parse(message.data);
27
+ const elementId = element.value?.id;
28
+ if (elementId && elementId !== event.entityId) return;
29
+ if (event.type === 'userState:update') {
30
+ userState.value = event.payload;
31
+ }
32
+ if (event.type === 'element:update') {
33
+ element.value = event.payload;
34
+ }
19
35
  });
20
36
  });
21
37
 
@@ -23,10 +39,9 @@ function timeout(ms: number) {
23
39
  return new Promise((resolve) => setTimeout(resolve, ms));
24
40
  }
25
41
 
26
- async function getElement() {
42
+ async function getElement(): Promise<any> {
27
43
  try {
28
- const response: any = await api('content-element').json();
29
- return response?.element || {};
44
+ return api('content-element').json();
30
45
  } catch (error) {
31
46
  console.log('Error on element get', error);
32
47
  await timeout(2000);
@@ -34,12 +49,33 @@ async function getElement() {
34
49
  return getElement();
35
50
  }
36
51
  }
52
+
53
+ async function resetElement() {
54
+ const id = element.value?.id;
55
+ if (!id) return;
56
+ await resetState();
57
+ const path = `content-element/${id}/reset-element`;
58
+ return api.post(path);
59
+ }
60
+
61
+ async function resetState() {
62
+ const id = element.value?.id;
63
+ if (!id) return;
64
+ const path = `content-element/${id}/reset-state`;
65
+ return api.post(path);
66
+ }
37
67
  </script>
38
68
 
39
69
  <template>
40
70
  <v-app>
41
71
  <AppBar />
42
- <MainLayout :element="element" class="mt-14" />
72
+ <MainLayout
73
+ :element="element"
74
+ :user-state="userState"
75
+ class="mt-14"
76
+ @reset-element="resetElement"
77
+ @reset-state="resetState"
78
+ />
43
79
  </v-app>
44
80
  </template>
45
81
 
@@ -20,7 +20,7 @@ const { showEdit, showDisplay, showBothComponents } = useGlobalState();
20
20
  density="compact"
21
21
  variant="tonal"
22
22
  >
23
- 0.3.0
23
+ 0.4.0
24
24
  </v-chip>
25
25
  </v-app-bar-title>
26
26
  <v-spacer />
@@ -1,26 +1,75 @@
1
1
  <template>
2
2
  <div v-show="isLoaded">
3
- <v-tabs density="compact" hide-slider>
4
- <v-tab>Server state</v-tab>
3
+ <v-tabs v-model="state.selectedTab">
4
+ <v-tab :value="1">
5
+ <VChip append-icon="mdi-pen" class="mr-2" color="indigo" size="small">
6
+ {{ state.elementHistory.length }}
7
+ </VChip>
8
+ Authoring history
9
+ </v-tab>
10
+ <v-tab :value="2">
11
+ <VChip
12
+ append-icon="mdi-account"
13
+ class="mr-2"
14
+ color="green"
15
+ size="small"
16
+ >
17
+ {{ state.userInteractions.length }}
18
+ </VChip>
19
+ End-User state history
20
+ </v-tab>
5
21
  </v-tabs>
6
22
  <div class="pa-6">
7
- <JsonEditor
8
- v-if="isLoaded"
9
- :main-menu-bar="false"
10
- :status-bar="false"
11
- :value="orderKeys(props.element)"
12
- mode="text"
13
- />
23
+ <v-window v-model="state.selectedTab">
24
+ <v-window-item :value="1">
25
+ <VBtn
26
+ class="mb-4"
27
+ color="indigo-darken-4"
28
+ variant="tonal"
29
+ @click="$emit('resetElement')"
30
+ >
31
+ <VIcon class="mr-2 left">mdi-refresh</VIcon>
32
+ Reset content element
33
+ </VBtn>
34
+ <MutationList :changes="state.elementHistory" />
35
+ </v-window-item>
36
+ <v-window-item :value="2">
37
+ <VBtn
38
+ class="mb-4"
39
+ color="indigo-darken-4"
40
+ variant="tonal"
41
+ @click="$emit('resetState')"
42
+ >
43
+ <VIcon class="mr-2 left">mdi-refresh</VIcon>
44
+ Reset state context
45
+ </VBtn>
46
+ <MutationList :changes="state.userInteractions" />
47
+ </v-window-item>
48
+ </v-window>
14
49
  </div>
15
50
  </div>
16
51
  </template>
17
52
 
18
53
  <script setup lang="ts">
19
- import JsonEditor from 'vue3-ts-jsoneditor';
54
+ import { reactive, watch } from 'vue';
55
+ import { format as formatDate } from 'date-fns';
20
56
 
21
- const props = defineProps<{ element: any; isLoaded: Boolean }>();
57
+ import MutationList from './MutationList.vue';
22
58
 
23
- const orderKeys = (val: any) => {
59
+ defineEmits(['resetElement', 'resetState']);
60
+ const props = defineProps<{
61
+ isLoaded: Boolean;
62
+ element: any;
63
+ userState: any;
64
+ }>();
65
+
66
+ const state = reactive({
67
+ selectedTab: 1,
68
+ elementHistory: [] as any[],
69
+ userInteractions: [] as any[],
70
+ });
71
+
72
+ const orderContentElementKeys = (val: any) => {
24
73
  return [
25
74
  'id',
26
75
  'uid',
@@ -41,4 +90,31 @@ const orderKeys = (val: any) => {
41
90
  return acc;
42
91
  }, {});
43
92
  };
93
+
94
+ watch(
95
+ () => props.element,
96
+ (val) => {
97
+ if (val.updatedAt === state.elementHistory[0]?.data?.updatedAt) return;
98
+ state.elementHistory.unshift({
99
+ id: state.elementHistory.length,
100
+ title: formatDate(val.updatedAt, 'HH:mm:ss'),
101
+ data: orderContentElementKeys(val),
102
+ });
103
+ state.selectedTab = 1;
104
+ },
105
+ { deep: true },
106
+ );
107
+
108
+ watch(
109
+ () => props.userState,
110
+ (val) => {
111
+ state.userInteractions.unshift({
112
+ id: state.userInteractions.length,
113
+ title: formatDate(new Date(), 'HH:mm:ss'),
114
+ data: val,
115
+ });
116
+ state.selectedTab = 2;
117
+ },
118
+ { deep: true },
119
+ );
44
120
  </script>
@@ -7,7 +7,8 @@ import PreviewPanel from './PreviewPanel.vue';
7
7
  import SplashLoader from './SplashLoader.vue';
8
8
  import { useGlobalState } from '../state';
9
9
 
10
- const props = defineProps<{ element: any }>();
10
+ defineEmits(['resetElement', 'resetState']);
11
+ const props = defineProps<{ element: any; userState: any }>();
11
12
 
12
13
  const { isDark } = useGlobalState();
13
14
  const isLoaded = ref(false);
@@ -51,7 +52,10 @@ onMounted(() => {
51
52
  id="panelBottom"
52
53
  :element="props.element"
53
54
  :is-loaded="isLoaded"
55
+ :user-state="props.userState"
54
56
  class="preview-panel-bottom"
57
+ @reset-element="$emit('resetElement')"
58
+ @reset-state="$emit('resetState')"
55
59
  />
56
60
  </main>
57
61
  </template>
@@ -0,0 +1,39 @@
1
+ <template>
2
+ <VDataIterator :items="changes" :items-per-page="50" expand-on-click>
3
+ <template #default="{ items, isExpanded, toggleExpand }">
4
+ <template v-for="(item, index) in items" :key="item.raw.id">
5
+ <v-card
6
+ :disabled="index === 0"
7
+ :ripple="false"
8
+ class="mb-1"
9
+ @click="toggleExpand(item)"
10
+ >
11
+ <v-card-title class="d-flex align-center">
12
+ <v-icon color="orange-darken-3" icon="mdi-update" size="18" start />
13
+ <div class="text-body-1">{{ item.raw.title }}</div>
14
+ </v-card-title>
15
+ <v-card-text v-if="isExpanded(item) || index === 0">
16
+ <JsonEditor
17
+ :main-menu-bar="false"
18
+ :status-bar="false"
19
+ :value="item.raw.data"
20
+ mode="text"
21
+ />
22
+ </v-card-text>
23
+ </v-card>
24
+ </template>
25
+ </template>
26
+ </VDataIterator>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ import JsonEditor from 'vue3-ts-jsoneditor';
31
+
32
+ defineProps<{ changes: any[] }>();
33
+ </script>
34
+
35
+ <style scoped lang="scss">
36
+ .v-card--disabled > :not(.v-card__loader) {
37
+ opacity: 1 !important;
38
+ }
39
+ </style>
@@ -6,7 +6,6 @@
6
6
  :src="VITE_EDIT_RUNTIME_URL"
7
7
  class="flex-1-1-100"
8
8
  frameBorder="0"
9
- sandbox="allow-scripts"
10
9
  title="Edit component container"
11
10
  >
12
11
  </iframe>
@@ -17,7 +16,6 @@
17
16
  :src="VITE_DISPLAY_RUNTIME_URL"
18
17
  class="flex-1-1-100"
19
18
  frameBorder="0"
20
- sandbox="allow-scripts"
21
19
  title="Display component container"
22
20
  >
23
21
  </iframe>
@@ -12,7 +12,7 @@
12
12
  :model-value="progress"
13
13
  class="my-3"
14
14
  />
15
- <div class="font-weight-bold">Beta preview v0.3.0</div>
15
+ <div class="font-weight-bold">Beta preview v0.4.0</div>
16
16
  </div>
17
17
  </div>
18
18
  </div>