@saltcorn/data 0.9.4-beta.2 → 0.9.4-beta.20

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 (129) hide show
  1. package/dist/base-plugin/actions.d.ts +156 -67
  2. package/dist/base-plugin/actions.d.ts.map +1 -1
  3. package/dist/base-plugin/actions.js +147 -27
  4. package/dist/base-plugin/actions.js.map +1 -1
  5. package/dist/base-plugin/fieldviews.d.ts.map +1 -1
  6. package/dist/base-plugin/fieldviews.js +27 -10
  7. package/dist/base-plugin/fieldviews.js.map +1 -1
  8. package/dist/base-plugin/fileviews.d.ts +13 -0
  9. package/dist/base-plugin/fileviews.js +42 -2
  10. package/dist/base-plugin/fileviews.js.map +1 -1
  11. package/dist/base-plugin/index.d.ts +5 -2
  12. package/dist/base-plugin/index.d.ts.map +1 -1
  13. package/dist/base-plugin/types.d.ts.map +1 -1
  14. package/dist/base-plugin/types.js +25 -7
  15. package/dist/base-plugin/types.js.map +1 -1
  16. package/dist/base-plugin/viewtemplates/edit.d.ts +2 -0
  17. package/dist/base-plugin/viewtemplates/edit.d.ts.map +1 -1
  18. package/dist/base-plugin/viewtemplates/edit.js +194 -58
  19. package/dist/base-plugin/viewtemplates/edit.js.map +1 -1
  20. package/dist/base-plugin/viewtemplates/filter.d.ts.map +1 -1
  21. package/dist/base-plugin/viewtemplates/filter.js +88 -8
  22. package/dist/base-plugin/viewtemplates/filter.js.map +1 -1
  23. package/dist/base-plugin/viewtemplates/list.d.ts +3 -2
  24. package/dist/base-plugin/viewtemplates/list.d.ts.map +1 -1
  25. package/dist/base-plugin/viewtemplates/list.js +276 -41
  26. package/dist/base-plugin/viewtemplates/list.js.map +1 -1
  27. package/dist/base-plugin/viewtemplates/show.d.ts.map +1 -1
  28. package/dist/base-plugin/viewtemplates/show.js +130 -64
  29. package/dist/base-plugin/viewtemplates/show.js.map +1 -1
  30. package/dist/base-plugin/viewtemplates/viewable_fields.d.ts +4 -3
  31. package/dist/base-plugin/viewtemplates/viewable_fields.d.ts.map +1 -1
  32. package/dist/base-plugin/viewtemplates/viewable_fields.js +160 -47
  33. package/dist/base-plugin/viewtemplates/viewable_fields.js.map +1 -1
  34. package/dist/db/fixtures.d.ts.map +1 -1
  35. package/dist/db/fixtures.js +196 -30
  36. package/dist/db/fixtures.js.map +1 -1
  37. package/dist/db/state.d.ts +5 -0
  38. package/dist/db/state.d.ts.map +1 -1
  39. package/dist/db/state.js +20 -0
  40. package/dist/db/state.js.map +1 -1
  41. package/dist/migrate.d.ts +1 -0
  42. package/dist/migrate.d.ts.map +1 -1
  43. package/dist/migrate.js +6 -3
  44. package/dist/migrate.js.map +1 -1
  45. package/dist/migrations/202402071125.d.ts +2 -0
  46. package/dist/migrations/202402071125.d.ts.map +1 -0
  47. package/dist/migrations/202402071125.js +4 -0
  48. package/dist/migrations/202402071125.js.map +1 -0
  49. package/dist/models/config.d.ts.map +1 -1
  50. package/dist/models/config.js +33 -0
  51. package/dist/models/config.js.map +1 -1
  52. package/dist/models/email.d.ts.map +1 -1
  53. package/dist/models/email.js +6 -2
  54. package/dist/models/email.js.map +1 -1
  55. package/dist/models/expression.d.ts +2 -0
  56. package/dist/models/expression.d.ts.map +1 -1
  57. package/dist/models/expression.js +9 -0
  58. package/dist/models/expression.js.map +1 -1
  59. package/dist/models/field.d.ts +1 -1
  60. package/dist/models/field.d.ts.map +1 -1
  61. package/dist/models/field.js +4 -4
  62. package/dist/models/field.js.map +1 -1
  63. package/dist/models/file.d.ts +2 -0
  64. package/dist/models/file.d.ts.map +1 -1
  65. package/dist/models/file.js +9 -0
  66. package/dist/models/file.js.map +1 -1
  67. package/dist/models/internal/query.d.ts +6 -0
  68. package/dist/models/internal/query.d.ts.map +1 -0
  69. package/dist/models/internal/query.js +77 -0
  70. package/dist/models/internal/query.js.map +1 -0
  71. package/dist/models/page.d.ts.map +1 -1
  72. package/dist/models/page.js +4 -0
  73. package/dist/models/page.js.map +1 -1
  74. package/dist/models/page_group.d.ts +1 -0
  75. package/dist/models/page_group.d.ts.map +1 -1
  76. package/dist/models/page_group.js +1 -0
  77. package/dist/models/page_group.js.map +1 -1
  78. package/dist/models/table.d.ts +18 -4
  79. package/dist/models/table.d.ts.map +1 -1
  80. package/dist/models/table.js +78 -50
  81. package/dist/models/table.js.map +1 -1
  82. package/dist/models/trigger.d.ts +3 -3
  83. package/dist/models/trigger.d.ts.map +1 -1
  84. package/dist/models/trigger.js +69 -7
  85. package/dist/models/trigger.js.map +1 -1
  86. package/dist/models/user.d.ts.map +1 -1
  87. package/dist/models/user.js +5 -4
  88. package/dist/models/user.js.map +1 -1
  89. package/dist/models/view.d.ts +1 -0
  90. package/dist/models/view.d.ts.map +1 -1
  91. package/dist/models/view.js +6 -0
  92. package/dist/models/view.js.map +1 -1
  93. package/dist/plugin-helper.d.ts +8 -20
  94. package/dist/plugin-helper.d.ts.map +1 -1
  95. package/dist/plugin-helper.js +108 -76
  96. package/dist/plugin-helper.js.map +1 -1
  97. package/dist/tests/actions.test.js +143 -2
  98. package/dist/tests/actions.test.js.map +1 -1
  99. package/dist/tests/auth.test.js +101 -1
  100. package/dist/tests/auth.test.js.map +1 -1
  101. package/dist/tests/auxtest.test.js +4 -0
  102. package/dist/tests/auxtest.test.js.map +1 -1
  103. package/dist/tests/calc.test.js +11 -1
  104. package/dist/tests/calc.test.js.map +1 -1
  105. package/dist/tests/edit.test.js +394 -0
  106. package/dist/tests/edit.test.js.map +1 -1
  107. package/dist/tests/filter.test.d.ts +2 -0
  108. package/dist/tests/filter.test.d.ts.map +1 -0
  109. package/dist/tests/filter.test.js +438 -0
  110. package/dist/tests/filter.test.js.map +1 -0
  111. package/dist/tests/list.test.d.ts +2 -0
  112. package/dist/tests/list.test.d.ts.map +1 -0
  113. package/dist/tests/list.test.js +735 -0
  114. package/dist/tests/list.test.js.map +1 -0
  115. package/dist/tests/show.test.d.ts +2 -0
  116. package/dist/tests/show.test.d.ts.map +1 -0
  117. package/dist/tests/show.test.js +325 -0
  118. package/dist/tests/show.test.js.map +1 -0
  119. package/dist/tests/table.test.js +184 -56
  120. package/dist/tests/table.test.js.map +1 -1
  121. package/dist/tests/table_history.test.js +51 -0
  122. package/dist/tests/table_history.test.js.map +1 -1
  123. package/dist/tests/view.test.js +21 -1
  124. package/dist/tests/view.test.js.map +1 -1
  125. package/dist/utils.d.ts +4 -17
  126. package/dist/utils.d.ts.map +1 -1
  127. package/dist/utils.js +33 -32
  128. package/dist/utils.js.map +1 -1
  129. package/package.json +8 -7
@@ -19,11 +19,11 @@ const { getState } = require("../../db/state");
19
19
  const { text, text_attr, script, domReady, div, button, i, pre, } = require("@saltcorn/markup/tags");
20
20
  const { renderForm } = require("@saltcorn/markup");
21
21
  const FieldRepeat = require("../../models/fieldrepeat");
22
- const { get_expression_function, expressionChecker, eval_expression, freeVariables, } = require("../../models/expression");
22
+ const { get_expression_function, expressionChecker, eval_expression, freeVariables, freeVariablesInInterpolation, } = require("../../models/expression");
23
23
  const { InvalidConfiguration, isNode, isOfflineMode, mergeIntoWhere, dollarizeObject, getSessionId, } = require("../../utils");
24
24
  const Library = require("../../models/library");
25
25
  const { check_view_columns } = require("../../plugin-testing");
26
- const { initial_config_all_fields, calcfldViewOptions, calcfldViewConfig, get_parent_views, picked_fields_to_query, stateFieldsToWhere, stateFieldsToQuery, getActionConfigFields, run_action_column, add_free_variables_to_joinfields, readState, stateToQueryString, pathToState, relationTypeFromPath, } = require("../../plugin-helper");
26
+ const { initial_config_all_fields, calcfldViewOptions, calcfldViewConfig, get_parent_views, picked_fields_to_query, stateFieldsToWhere, stateFieldsToQuery, getActionConfigFields, run_action_column, add_free_variables_to_joinfields, readState, stateToQueryString, pathToState, displayType, } = require("../../plugin-helper");
27
27
  const { splitUniques, getForm, fill_presets, parse_view_select, get_view_link_query, objToQueryString, action_url, action_link, view_linker, } = require("./viewable_fields");
28
28
  const { traverse, getStringsForI18n, translateLayout, traverseSync, } = require("../../models/layout");
29
29
  const { asyncMap, isWeb, removeEmptyStrings } = require("../../utils");
@@ -31,6 +31,7 @@ const { extractFromLayout } = require("../../diagram/node_extract_utils");
31
31
  const db = require("../../db");
32
32
  const { prepare_update_row } = require("../../web-mobile-commons");
33
33
  const _ = require("underscore");
34
+ const { Relation, RelationType } = require("@saltcorn/common-code");
34
35
  const builtInActions = [
35
36
  "Save",
36
37
  "SaveAndContinue",
@@ -73,6 +74,7 @@ const configuration_workflow = (req) => new Workflow({
73
74
  const triggerActions = [];
74
75
  (await Trigger.find({
75
76
  when_trigger: { or: ["API call", "Never"] },
77
+ table_id: null,
76
78
  })).forEach((tr) => {
77
79
  actions.push(tr.name);
78
80
  triggerActions.push(tr.name);
@@ -141,7 +143,7 @@ const configuration_workflow = (req) => new Workflow({
141
143
  field_view_options.remember = ["edit"];
142
144
  }
143
145
  const library = (await Library.find({})).filter((l) => l.suitableFor("edit"));
144
- const myviewrow = await View.findOne({ name: context.viewname });
146
+ const myviewrow = View.findOne({ name: context.viewname });
145
147
  const { parent_field_list } = await table.get_parent_relations(true, true);
146
148
  const pages = await Page.find();
147
149
  const groups = (await PageGroup.find()).map((g) => ({
@@ -149,7 +151,7 @@ const configuration_workflow = (req) => new Workflow({
149
151
  }));
150
152
  return {
151
153
  tableName: table.name,
152
- fields: fields.map((f) => f.toBuilder),
154
+ fields: fields.map((f) => f.toBuilder || f),
153
155
  field_view_options,
154
156
  parent_field_list,
155
157
  handlesTextStyle,
@@ -376,12 +378,6 @@ const setDateLocales = (form, locale) => {
376
378
  }
377
379
  });
378
380
  };
379
- /**
380
- * check if a relation path has a CHildList structure
381
- */
382
- const isChildListPath = (viewSelect, subView) => viewSelect.type === "RelationPath" &&
383
- relationTypeFromPath(subView, viewSelect.path, viewSelect.sourcetable) ===
384
- "ChildList";
385
381
  /**
386
382
  * update viewSelect so that it looks like a normal ChildList
387
383
  */
@@ -469,7 +465,18 @@ const runMany = async (table_id, viewname, { columns, layout, auto_save, split_p
469
465
  * @returns {Promise<void>}
470
466
  */
471
467
  const transformForm = async ({ form, table, req, row, res, getRowQuery, viewname, optionsQuery, }) => {
468
+ let pseudo_row = {};
469
+ if (!row) {
470
+ table.fields.forEach((f) => {
471
+ pseudo_row[f.name] = undefined;
472
+ });
473
+ }
472
474
  await traverse(form.layout, {
475
+ container(segment) {
476
+ if (segment.click_action) {
477
+ segment.url = `javascript:view_post('${viewname}', 'run_action', {click_action: '${segment.click_action}', ...get_form_record({viewname: '${viewname}'}) })`;
478
+ }
479
+ },
473
480
  async action(segment) {
474
481
  if (segment.action_style === "on_page_load") {
475
482
  //TODO check segment.min_role
@@ -481,11 +488,11 @@ const transformForm = async ({ form, table, req, row, res, getRowQuery, viewname
481
488
  try {
482
489
  const actionResult = await run_action_column({
483
490
  col: { ...segment },
484
- referrer: req.get("Referrer"),
491
+ referrer: req?.get?.("Referrer"),
485
492
  req,
486
493
  res,
487
494
  table,
488
- row,
495
+ row: row || pseudo_row,
489
496
  });
490
497
  segment.type = "blank";
491
498
  segment.style = {};
@@ -526,6 +533,30 @@ const transformForm = async ({ form, table, req, row, res, getRowQuery, viewname
526
533
  const qs = objToQueryString(segment.configuration);
527
534
  segment.sourceURL = `/field/show-calculated/${table.name}/${segment.join_field}/${segment.fieldview}?${qs}`;
528
535
  },
536
+ tabs(segment) {
537
+ const to_delete = new Set();
538
+ (segment.showif || []).forEach((sif, ix) => {
539
+ if (sif) {
540
+ const showit = eval_expression(sif, row || pseudo_row, req.user);
541
+ if (!showit)
542
+ to_delete.add(ix);
543
+ }
544
+ });
545
+ segment.titles = segment.titles.filter((v, ix) => !to_delete.has(ix));
546
+ segment.contents = segment.contents.filter((v, ix) => !to_delete.has(ix));
547
+ (segment.titles || []).forEach((t, ix) => {
548
+ if (typeof t === "string" && t.includes("{{")) {
549
+ const template = _.template(t, {
550
+ interpolate: /\{\{([^#].+?)\}\}/g,
551
+ });
552
+ segment.titles[ix] = template({
553
+ user: req.user,
554
+ row,
555
+ ...(row || {}),
556
+ });
557
+ }
558
+ });
559
+ },
529
560
  view_link(segment) {
530
561
  segment.type = "blank";
531
562
  const view_select = parse_view_select(segment.view);
@@ -545,11 +576,17 @@ const transformForm = async ({ form, table, req, row, res, getRowQuery, viewname
545
576
  const view = View.findOne({ name: view_select.viewname });
546
577
  if (!view)
547
578
  throw new InvalidConfiguration(`Cannot find embedded view: ${view_select.viewname}`);
548
- const childListPath = isChildListPath(view_select, view);
579
+ // check if the relation path matches a ChildList relations
580
+ let childListRelPath = false;
581
+ if (segment.relation && view.table_id) {
582
+ const targetTbl = Table.findOne({ id: view.table_id });
583
+ const relation = new Relation(segment.relation, targetTbl.name, displayType(await view.get_state_fields()));
584
+ childListRelPath = relation.type === RelationType.CHILD_LIST;
585
+ }
549
586
  // Edit-in-edit
550
587
  if (view.viewtemplate === "Edit" &&
551
- (view_select.type === "ChildList" || childListPath)) {
552
- if (childListPath)
588
+ (view_select.type === "ChildList" || childListRelPath)) {
589
+ if (childListRelPath)
553
590
  updateViewSelect(view_select);
554
591
  const childTable = Table.findOne({ id: view.table_id });
555
592
  const childForm = await getForm(childTable, view.name, view.configuration.columns, view.configuration.layout, row?.id, req, !isWeb(req));
@@ -596,42 +633,51 @@ const transformForm = async ({ form, table, req, row, res, getRowQuery, viewname
596
633
  segment.field_repeat = fr;
597
634
  return;
598
635
  }
599
- const isIndependent = view_select.type === "Independent" ||
600
- (view_select.type === "RelationPath" &&
601
- relationTypeFromPath(view, view_select.path, table) ===
602
- "Independent");
603
- if (!row && !isIndependent) {
604
- segment.type = "blank";
605
- segment.contents = "";
606
- return;
636
+ let state = {};
637
+ if (view_select.type === "RelationPath" && view.table_id) {
638
+ const targetTbl = Table.findOne({ id: view.table_id });
639
+ if (targetTbl) {
640
+ const relation = new Relation(segment.relation, targetTbl.name, displayType(await view.get_state_fields()));
641
+ const type = relation.type;
642
+ if (!row && type !== RelationType.INDEPENDENT) {
643
+ segment.type = "blank";
644
+ segment.contents = "";
645
+ return;
646
+ }
647
+ state = pathToState(relation, (k) => row[k]);
648
+ }
607
649
  }
608
- if (!view)
609
- throw new InvalidConfiguration(`Edit view incorrectly configured: cannot find embedded view ${view_select.viewname}`);
610
- let state;
611
- switch (view_select.type) {
612
- case "RelationPath": {
613
- state = pathToState(view, segment.relation, view_select.path, (k) => row[k], table);
614
- break;
650
+ else {
651
+ const isIndependent = view_select.type === "Independent";
652
+ // legacy none check ?
653
+ if (!row && !isIndependent) {
654
+ segment.type = "blank";
655
+ segment.contents = "";
656
+ return;
657
+ }
658
+ if (!view)
659
+ throw new InvalidConfiguration(`Edit view incorrectly configured: cannot find embedded view ${view_select.viewname}`);
660
+ switch (view_select.type) {
661
+ case "Own":
662
+ state = { id: row.id };
663
+ break;
664
+ case "Independent":
665
+ state = {};
666
+ break;
667
+ case "ChildList":
668
+ case "OneToOneShow":
669
+ state = { [view_select.field_name]: row.id };
670
+ break;
671
+ case "ParentShow":
672
+ state = { id: row[view_select.field_name] };
673
+ break;
615
674
  }
616
- case "Own":
617
- state = { id: row.id };
618
- break;
619
- case "Independent":
620
- state = {};
621
- break;
622
- case "ChildList":
623
- case "OneToOneShow":
624
- state = { [view_select.field_name]: row.id };
625
- break;
626
- case "ParentShow":
627
- state = { id: row[view_select.field_name] };
628
- break;
629
675
  }
630
676
  const extra_state = segment.extra_state_fml
631
677
  ? eval_expression(segment.extra_state_fml, {
632
678
  ...dollarizeObject(req.query),
633
679
  session_id: getSessionId(req),
634
- ...(row || {}),
680
+ ...(row || pseudo_row),
635
681
  }, req.user)
636
682
  : {};
637
683
  const qs = stateToQueryString({ ...state, ...extra_state });
@@ -795,6 +841,8 @@ const runPost = async (table_id, viewname, { columns, layout, fixed, view_when_d
795
841
  fixed,
796
842
  auto_save,
797
843
  }, { req, res }, body, { getRowQuery, saveFileQuery, optionsQuery, getRowByIdQuery }, remote);
844
+ const view = View.findOne({ name: viewname });
845
+ const pagetitle = { title: viewname, no_menu: view?.attributes?.no_menu };
798
846
  if (prepResult) {
799
847
  let { form, row, pk, id } = prepResult;
800
848
  const cancel = body._cancel;
@@ -827,7 +875,7 @@ const runPost = async (table_id, viewname, { columns, layout, fixed, view_when_d
827
875
  }
828
876
  else {
829
877
  req.flash("error", text_attr(ins_upd_error));
830
- res.sendWrap(viewname, renderForm(form, req.csrfToken()));
878
+ res.sendWrap(pagetitle, renderForm(form, req.csrfToken()));
831
879
  }
832
880
  return;
833
881
  }
@@ -835,8 +883,15 @@ const runPost = async (table_id, viewname, { columns, layout, fixed, view_when_d
835
883
  for (const field of form.fields.filter((f) => f.isRepeat)) {
836
884
  const view_select = parse_view_select(field.metadata.view, field.metadata.relation_path);
837
885
  const childView = View.findOne({ name: view_select.viewname });
838
- if (isChildListPath(view_select, childView))
839
- updateViewSelect(view_select);
886
+ if (!childView)
887
+ throw new InvalidConfiguration(`Cannot find embedded view: ${view_select.viewname}`);
888
+ if (field.metadata.relation_path &&
889
+ view_select.type === "RelationPath") {
890
+ const targetTbl = Table.findOne({ id: childView.table_id });
891
+ const relation = new Relation(field.metadata.relation_path, targetTbl.name, displayType(await childView.get_state_fields()));
892
+ if (relation.type === RelationType.CHILD_LIST)
893
+ updateViewSelect(view_select);
894
+ }
840
895
  const childTable = Table.findOne({ id: field.metadata?.table_id });
841
896
  const submitted_row_ids = new Set((form.values[field.name] || []).map((srow) => `${srow[childTable.pk_name]}`));
842
897
  const childFields = new Set(childTable.fields.map((f) => f.name));
@@ -853,7 +908,7 @@ const runPost = async (table_id, viewname, { columns, layout, fixed, view_when_d
853
908
  const upd_res = await childTable.tryUpdateRow(childRow, childRow[childTable.pk_name], req.user || { role_id: 100 });
854
909
  if (upd_res.error) {
855
910
  req.flash("error", text_attr(upd_res.error));
856
- res.sendWrap(viewname, renderForm(form, req.csrfToken()));
911
+ res.sendWrap(pagetitle, renderForm(form, req.csrfToken()));
857
912
  return;
858
913
  }
859
914
  }
@@ -861,7 +916,7 @@ const runPost = async (table_id, viewname, { columns, layout, fixed, view_when_d
861
916
  const ins_res = await childTable.tryInsertRow(childRow, req.user || { role_id: 100 });
862
917
  if (ins_res.error) {
863
918
  req.flash("error", text_attr(ins_res.error));
864
- res.sendWrap(viewname, renderForm(form, req.csrfToken()));
919
+ res.sendWrap(pagetitle, renderForm(form, req.csrfToken()));
865
920
  return;
866
921
  }
867
922
  else if (ins_res.success) {
@@ -1002,6 +1057,56 @@ const doAuthPost = async ({ body, table_id, req }) => {
1002
1057
  const authorise_post = async ({ body, table_id, req }, { authorizePostQuery }) => {
1003
1058
  return await authorizePostQuery(body, table_id);
1004
1059
  };
1060
+ const openDataStream = async (tableId, viewName, id, fieldName, fieldView, user, configuration, targetOpts) => {
1061
+ const table = Table.findOne({ id: tableId });
1062
+ const field = table.getField(fieldName);
1063
+ if (!field)
1064
+ throw new InvalidConfiguration(`Field ${fieldName} not found`);
1065
+ if (field.type === "File") {
1066
+ const cfgCol = configuration.columns.find((col) => col.fieldview === fieldView && col.field_name === fieldName);
1067
+ const fileView = getState().fileviews[fieldView];
1068
+ if (!fileView)
1069
+ throw new InvalidConfiguration(`File view ${fieldView} not found`);
1070
+ return await fileView.openDataStream(tableId, id, fieldName, user, cfgCol.configuration, targetOpts);
1071
+ }
1072
+ };
1073
+ // TODO is owner check
1074
+ const authorizeDataStream = async (view, id, fieldName, user, targetOpts) => {
1075
+ if (!user || user.role_id > view.min_role)
1076
+ return false;
1077
+ else {
1078
+ const table = Table.findOne({ id: view.table_id });
1079
+ if (!table || user.role_id > table.min_role_write)
1080
+ return false;
1081
+ else {
1082
+ const field = table.getField(fieldName);
1083
+ if (field.type === "File") {
1084
+ if (targetOpts?.oldTarget) {
1085
+ // continue old file ?
1086
+ const file = await File.findOne(targetOpts.oldTarget);
1087
+ if (file)
1088
+ return file.min_role_read >= user.role_id;
1089
+ }
1090
+ else if (id) {
1091
+ // continue file of existing row ?
1092
+ const row = await table.getRow({ [table.pk_name]: id });
1093
+ const fileCol = row[fieldName];
1094
+ if (fileCol) {
1095
+ const file = await File.findOne(row[fieldName]);
1096
+ if (file)
1097
+ return file.min_role_read >= user.role_id;
1098
+ }
1099
+ }
1100
+ // stream is new or the file does not exist
1101
+ return true;
1102
+ }
1103
+ else {
1104
+ // only files for now
1105
+ return false;
1106
+ }
1107
+ }
1108
+ }
1109
+ };
1005
1110
  /**
1006
1111
  * @param {number} table_id
1007
1112
  * @param {*} viewname
@@ -1112,7 +1217,8 @@ const prepare = async (viewname, table, fields, { columns, layout, fixed, auto_s
1112
1217
  if (req.xhr)
1113
1218
  res.status(422);
1114
1219
  await form.fill_fkey_options(false, optionsQuery, req.user);
1115
- res.sendWrap(viewname, renderForm(form, req.csrfToken ? req.csrfToken() : false));
1220
+ const view = View.findOne({ name: viewname });
1221
+ res.sendWrap({ title: viewname, no_menu: view?.attributes?.no_menu }, renderForm(form, req.csrfToken ? req.csrfToken() : false));
1116
1222
  return null;
1117
1223
  }
1118
1224
  let row;
@@ -1135,7 +1241,7 @@ const prepare = async (viewname, table, fields, { columns, layout, fixed, auto_s
1135
1241
  }
1136
1242
  const file_fields = form.fields.filter((f) => f.type === "File");
1137
1243
  for (const field of file_fields) {
1138
- if (!field.fieldviewObj?.isEdit)
1244
+ if (!field.fieldviewObj?.isEdit || field.fieldviewObj?.isStream)
1139
1245
  continue;
1140
1246
  if (field.fieldviewObj?.setsFileId) {
1141
1247
  //do nothing
@@ -1304,6 +1410,8 @@ module.exports = {
1304
1410
  run,
1305
1411
  runMany,
1306
1412
  runPost,
1413
+ openDataStream,
1414
+ authorizeDataStream,
1307
1415
  get_state_fields,
1308
1416
  initial_config,
1309
1417
  /** @type {boolean} */
@@ -1335,7 +1443,7 @@ module.exports = {
1335
1443
  if (Object.keys(uniques).length > 0) {
1336
1444
  // add joinfields from certain locations if they are not fields in columns
1337
1445
  const joinFields = {};
1338
- const picked = picked_fields_to_query([], fields, layout);
1446
+ const picked = picked_fields_to_query([], fields, layout, req);
1339
1447
  const colFields = new Set(columns.map((c) => c.join_field ? c.join_field.split(".")[0] : c.field_name));
1340
1448
  Object.entries(picked.joinFields).forEach(([nm, jfv]) => {
1341
1449
  if (!colFields.has(jfv.ref))
@@ -1369,7 +1477,7 @@ module.exports = {
1369
1477
  async editManyQuery(state, { limit, offset, orderBy, orderDesc, where }) {
1370
1478
  const table = Table.findOne({ id: table_id });
1371
1479
  const fields = table.getFields();
1372
- const { joinFields, aggregations } = picked_fields_to_query(columns, fields);
1480
+ const { joinFields, aggregations } = picked_fields_to_query(columns, fields, undefined, req);
1373
1481
  const qstate = await stateFieldsToWhere({ fields, state, table });
1374
1482
  const q = await stateFieldsToQuery({ state, fields });
1375
1483
  if (where)
@@ -1474,7 +1582,7 @@ module.exports = {
1474
1582
  });
1475
1583
  },
1476
1584
  async actionQuery() {
1477
- const { rndid, _csrf, onchange_action, onchange_field, ...body } = req.body;
1585
+ const { rndid, _csrf, onchange_action, onchange_field, click_action, ...body } = req.body;
1478
1586
  const table = Table.findOne({ id: table_id });
1479
1587
  const dbrow = body.id
1480
1588
  ? await table.getRow({ id: body.id }, {
@@ -1484,7 +1592,28 @@ module.exports = {
1484
1592
  : undefined;
1485
1593
  const row = { ...dbrow, ...body };
1486
1594
  try {
1487
- if (onchange_action && !rndid) {
1595
+ if (click_action) {
1596
+ let container;
1597
+ traverseSync(layout, {
1598
+ container(segment) {
1599
+ if (segment.click_action === click_action)
1600
+ container = segment;
1601
+ },
1602
+ });
1603
+ if (!container)
1604
+ return { json: { error: "Action not found" } };
1605
+ const trigger = Trigger.findOne({ name: click_action });
1606
+ const result = await trigger.runWithoutRow({
1607
+ table,
1608
+ Table,
1609
+ req,
1610
+ row,
1611
+ referrer: req?.get?.("Referrer"),
1612
+ user: req.user,
1613
+ });
1614
+ return { json: { success: "ok", ...(result || {}) } };
1615
+ }
1616
+ else if (onchange_action && !rndid) {
1488
1617
  const fldCol = columns.find((c) => c.field_name === onchange_field &&
1489
1618
  c.onchange_action === onchange_action);
1490
1619
  if (!fldCol)
@@ -1495,6 +1624,7 @@ module.exports = {
1495
1624
  Table,
1496
1625
  req,
1497
1626
  row,
1627
+ referrer: req?.get?.("Referrer"),
1498
1628
  user: req.user,
1499
1629
  });
1500
1630
  return { json: { success: "ok", ...(result || {}) } };
@@ -1507,7 +1637,7 @@ module.exports = {
1507
1637
  table,
1508
1638
  row,
1509
1639
  res,
1510
- referrer: req.get("Referrer"),
1640
+ referrer: req?.get?.("Referrer"),
1511
1641
  });
1512
1642
  //console.log("result", result);
1513
1643
  return { json: { success: "ok", ...(result || {}) } };
@@ -1571,7 +1701,13 @@ module.exports = {
1571
1701
  async interpolate_title_string(table_id, title, state) {
1572
1702
  const tbl = Table.findOne(table_id);
1573
1703
  if (state?.[tbl.pk_name]) {
1574
- const row = await tbl.getRow({ [tbl.pk_name]: state.id });
1704
+ const freeVars = freeVariablesInInterpolation(title);
1705
+ const joinFields = {};
1706
+ add_free_variables_to_joinfields(freeVars, joinFields, tbl.fields);
1707
+ const row = await tbl.getJoinedRow({
1708
+ where: { [tbl.pk_name]: state[tbl.pk_name] },
1709
+ joinFields,
1710
+ });
1575
1711
  const template = _.template(title, {
1576
1712
  interpolate: /\{\{([^#].+?)\}\}/g,
1577
1713
  });
@@ -1591,7 +1727,7 @@ module.exports = {
1591
1727
  const errs = [];
1592
1728
  const warnings = [];
1593
1729
  if (!destination_type || destination_type === "View") {
1594
- const vwd = await View.findOne({
1730
+ const vwd = View.findOne({
1595
1731
  name: (view_when_done || "").split(".")[0],
1596
1732
  });
1597
1733
  if (!vwd)