EditGoogleSheets.vue 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. <template>
  2. <div class="mb-10">
  3. <ui-textarea
  4. :model-value="data.description"
  5. class="w-full mb-2"
  6. :placeholder="t('common.description')"
  7. @change="updateData({ description: $event })"
  8. />
  9. <ui-select
  10. :model-value="data.type"
  11. class="w-full mb-2"
  12. @change="onActionChange"
  13. >
  14. <option v-for="action in actions" :key="action" :value="action">
  15. {{ t(`workflow.blocks.google-sheets.select.${action}`) }}
  16. </option>
  17. </ui-select>
  18. <edit-autocomplete>
  19. <ui-input
  20. :model-value="data.spreadsheetId"
  21. class="w-full"
  22. placeholder="abcd123"
  23. @change="updateData({ spreadsheetId: $event }), checkPermission($event)"
  24. >
  25. <template #label>
  26. {{ t('workflow.blocks.google-sheets.spreadsheetId.label') }}*
  27. <a
  28. href="https://docs.automa.site/blocks/google-sheets.html#spreadsheet-id"
  29. target="_blank"
  30. rel="noopener"
  31. :title="t('workflow.blocks.google-sheets.spreadsheetId.link')"
  32. >
  33. <v-remixicon name="riInformationLine" size="18" class="inline" />
  34. </a>
  35. </template>
  36. </ui-input>
  37. </edit-autocomplete>
  38. <a
  39. v-if="!state.havePermission"
  40. href="https://docs.automa.site/blocks/google-sheets.html#access-to-spreadsheet"
  41. target="_blank"
  42. rel="noopener"
  43. class="text-sm leading-tight inline-block ml-1"
  44. >
  45. Automa doesn't have access to the spreadsheet
  46. <v-remixicon name="riInformationLine" size="18" class="inline" />
  47. </a>
  48. <edit-autocomplete>
  49. <ui-input
  50. :model-value="data.range"
  51. class="w-full mt-1"
  52. placeholder="Sheet1!A1:B2"
  53. @change="updateData({ range: $event })"
  54. >
  55. <template #label>
  56. {{ t('workflow.blocks.google-sheets.range.label') }}*
  57. <a
  58. href="https://docs.automa.site/blocks/google-sheets.html#range"
  59. target="_blank"
  60. rel="noopener"
  61. :title="t('workflow.blocks.google-sheets.range.link')"
  62. >
  63. <v-remixicon name="riInformationLine" size="18" class="inline" />
  64. </a>
  65. </template>
  66. </ui-input>
  67. </edit-autocomplete>
  68. <template v-if="data.type === 'get'">
  69. <ui-input
  70. :model-value="data.refKey"
  71. :label="t('workflow.blocks.google-sheets.refKey.label')"
  72. :placeholder="t('workflow.blocks.google-sheets.refKey.placeholder')"
  73. class="mt-1 w-full"
  74. @change="updateData({ refKey: $event })"
  75. />
  76. <ui-checkbox
  77. :model-value="data.firstRowAsKey"
  78. class="mt-3"
  79. @change="updateData({ firstRowAsKey: $event })"
  80. >
  81. {{ t('workflow.blocks.google-sheets.firstRow') }}
  82. </ui-checkbox>
  83. <ui-button
  84. :loading="previewDataState.status === 'loading'"
  85. variant="accent"
  86. class="mt-3"
  87. @click="previewData"
  88. >
  89. {{ t('workflow.blocks.google-sheets.previewData') }}
  90. </ui-button>
  91. <p v-if="previewDataState.status === 'error'" class="text-red-500">
  92. {{ previewDataState.errorMessage }}
  93. </p>
  94. </template>
  95. <template v-else-if="data.type === 'getRange'">
  96. <insert-workflow-data :data="data" variables @update="updateData" />
  97. <ui-button
  98. :loading="previewDataState.status === 'loading'"
  99. variant="accent"
  100. class="mt-4"
  101. @click="previewData"
  102. >
  103. {{ t('workflow.blocks.google-sheets.previewData') }}
  104. </ui-button>
  105. </template>
  106. <template v-else-if="['update', 'append'].includes(data.type)">
  107. <ui-select
  108. :model-value="data.valueInputOption"
  109. class="w-full mt-2"
  110. @change="updateData({ valueInputOption: $event })"
  111. >
  112. <template #label>
  113. {{ t('workflow.blocks.google-sheets.valueInputOption') }}
  114. <a
  115. href="https://developers.google.com/sheets/api/reference/rest/v4/ValueInputOption"
  116. target="_blank"
  117. rel="noopener"
  118. >
  119. <v-remixicon name="riInformationLine" size="18" class="inline" />
  120. </a>
  121. </template>
  122. <option
  123. v-for="option in valueInputOptions"
  124. :key="option"
  125. :value="option"
  126. >
  127. {{ option }}
  128. </option>
  129. </ui-select>
  130. <ui-select
  131. :model-value="data.insertDataOption || 'INSERT_ROWS'"
  132. class="w-full mt-2"
  133. @change="updateData({ insertDataOption: $event })"
  134. >
  135. <template #label>
  136. {{ t('workflow.blocks.google-sheets.insertDataOption') }}
  137. <a
  138. href="https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append#InsertDataOption"
  139. target="_blank"
  140. rel="noopener"
  141. >
  142. <v-remixicon name="riInformationLine" size="18" class="inline" />
  143. </a>
  144. </template>
  145. <option
  146. v-for="option in insertDataOptions"
  147. :key="option"
  148. :value="option"
  149. >
  150. {{ option }}
  151. </option>
  152. </ui-select>
  153. <ui-select
  154. :model-value="data.dataFrom"
  155. :label="t('workflow.blocks.google-sheets.dataFrom.label')"
  156. class="w-full mt-2"
  157. @change="updateData({ dataFrom: $event })"
  158. >
  159. <option v-for="item in dataFrom" :key="item" :value="item">
  160. {{ t(`workflow.blocks.google-sheets.dataFrom.options.${item}`) }}
  161. </option>
  162. </ui-select>
  163. <ui-checkbox
  164. v-if="data.dataFrom === 'data-columns'"
  165. :model-value="data.keysAsFirstRow"
  166. class="mt-2"
  167. @change="updateData({ keysAsFirstRow: $event })"
  168. >
  169. {{ t('workflow.blocks.google-sheets.keysAsFirstRow') }}
  170. </ui-checkbox>
  171. <ui-button
  172. v-else
  173. class="w-full mt-2"
  174. variant="accent"
  175. @click="customDataState.showModal = true"
  176. >
  177. {{ t('workflow.blocks.google-sheets.insertData') }}
  178. </ui-button>
  179. </template>
  180. <shared-codemirror
  181. v-if="
  182. previewDataState.data &&
  183. previewDataState.status !== 'error' &&
  184. data.type !== 'update'
  185. "
  186. :model-value="previewDataState.data"
  187. :line-numbers="false"
  188. readonly
  189. class="mt-4 max-h-96 scroll"
  190. />
  191. <ui-modal
  192. v-model="customDataState.showModal"
  193. title="Custom data"
  194. content-class="max-w-xl"
  195. >
  196. <shared-codemirror
  197. v-model="customDataState.data"
  198. style="height: calc(100vh - 10rem)"
  199. lang="json"
  200. @change="updateData({ customData: $event })"
  201. />
  202. </ui-modal>
  203. </div>
  204. </template>
  205. <script setup>
  206. import { shallowReactive, defineAsyncComponent } from 'vue';
  207. import { useI18n } from 'vue-i18n';
  208. import { googleSheets, fetchApi } from '@/utils/api';
  209. import { convert2DArrayToArrayObj, debounce } from '@/utils/helper';
  210. import EditAutocomplete from './EditAutocomplete.vue';
  211. import InsertWorkflowData from './InsertWorkflowData.vue';
  212. const SharedCodemirror = defineAsyncComponent(() =>
  213. import('@/components/newtab/shared/SharedCodemirror.vue')
  214. );
  215. const props = defineProps({
  216. data: {
  217. type: Object,
  218. default: () => ({}),
  219. },
  220. });
  221. const emit = defineEmits(['update:data']);
  222. const { t } = useI18n();
  223. const actions = ['get', 'getRange', 'update', 'append'];
  224. const dataFrom = ['data-columns', 'custom'];
  225. const valueInputOptions = ['RAW', 'USER_ENTERED'];
  226. const insertDataOptions = ['OVERWRITE', 'INSERT_ROWS'];
  227. const previewDataState = shallowReactive({
  228. data: '',
  229. status: 'idle',
  230. errorMessage: '',
  231. });
  232. const customDataState = shallowReactive({
  233. showModal: false,
  234. data: props.data.customData,
  235. });
  236. const state = shallowReactive({
  237. lastSheetId: null,
  238. havePermission: true,
  239. });
  240. const checkPermission = debounce(async (value) => {
  241. try {
  242. if (state.lastSheetId === value) return;
  243. const response = await fetchApi(
  244. `/services/google-sheets/meta?spreadsheetId=${value}`
  245. );
  246. state.havePermission = response.status !== 403;
  247. state.lastSheetId = value;
  248. } catch (error) {
  249. console.error(error);
  250. }
  251. }, 1000);
  252. function updateData(value) {
  253. emit('update:data', { ...props.data, ...value });
  254. }
  255. async function previewData() {
  256. try {
  257. previewDataState.status = 'loading';
  258. const isGetValues = props.data.type === 'get';
  259. const params = {
  260. range: props.data.range,
  261. spreadsheetId: props.data.spreadsheetId,
  262. };
  263. const response = await (isGetValues
  264. ? googleSheets.getValues(params)
  265. : googleSheets.getRange(params));
  266. if (!response.ok) {
  267. const error = await response.json();
  268. throw new Error(error.message || response.statusText);
  269. }
  270. let result = await response.json();
  271. if (isGetValues) {
  272. result = props.data.firstRowAsKey
  273. ? convert2DArrayToArrayObj(result.values)
  274. : result.values;
  275. } else {
  276. result = {
  277. tableRange: result.tableRange || null,
  278. lastRange: result.updates.updatedRange,
  279. };
  280. }
  281. previewDataState.data = JSON.stringify(result, null, 2);
  282. previewDataState.status = 'idle';
  283. } catch (error) {
  284. console.error(error);
  285. previewDataState.data = '';
  286. previewDataState.status = 'error';
  287. previewDataState.errorMessage = error.message;
  288. }
  289. }
  290. function onActionChange(value) {
  291. updateData({ type: value });
  292. previewDataState.data = '';
  293. previewDataState.status = '';
  294. previewDataState.errorMessage = '';
  295. }
  296. </script>