calcfldViewOptions = (fields, mode, noFollowKeys = false) => {
  const isEdit = mode === "edit";
  const isFilter = mode === "filter";
  let fvs = {};
  const handlesTextStyle = {};
  const blockDisplay = {};
  fields.forEach((f) => {
    handlesTextStyle[f.name] = [];
    blockDisplay[f.name] = [];
    if (f.type === "File") {
      if (!isEdit && !isFilter)
        fvs[f.name] = Object.entries(getState().fileviews)
          .filter(([k, v]) => !v.isEdit)
          .map(([k, v]) => k);
      else
        fvs[f.name] = Object.entries(getState().fileviews).map(([k, v]) => k);
    } else if (f.type === "Key" && !noFollowKeys) {
      if (isEdit) fvs[f.name] = Object.keys(getState().keyFieldviews);
      else if (isFilter) {
        fvs[f.name] = Object.keys(getState().keyFieldviews);
      } else {
        fvs[f.name] = ["show"];
      }
      if (f.reftable && f.reftable.fields) {
        const { field_view_options } = calcfldViewOptions(
          f.reftable.fields,
          isEdit ? "show" : mode
        );
        for (const jf of f.reftable.fields) {
          fvs[`${f.name}.${jf.name}`] = field_view_options[jf.name];
          if (jf.is_fkey) {
            const jtable = Table.findOne({ name: jf.reftable_name });
            if (jtable && jtable.fields) {
              const jfieldOpts = calcfldViewOptions(
                jtable.fields,
                isEdit ? "show" : mode
              );
              for (const jf2 of jtable.fields) {
                fvs[`${f.name}.${jf.name}.${jf2.name}`] =
                  jfieldOpts.field_view_options[jf2.name];
                if (jf2.is_fkey) {
                  const jtable2 = Table.findOne({ name: jf2.reftable_name });
                  if (jtable2 && jtable2.fields) {
                    const jfield2Opts = calcfldViewOptions(
                      jtable2.fields,
                      isEdit ? "show" : mode
                    );
                    for (const jf3 of jtable2.fields) {
                      fvs[`${f.name}.${jf.name}.${jf2.name}.${jf3.name}`] =
                        jfield2Opts.field_view_options[jf3.name];
                    }
                  }
                }
              }
            }
          }
        }
      }

      Object.entries(getState().keyFieldviews).forEach(([k, v]) => {
        if (v && v.handlesTextStyle) handlesTextStyle[f.name].push(k);
        if (v && v.blockDisplay) blockDisplay[f.name].push(k);
      });
    } else if (f.type && f.type.fieldviews) {
      const tfvs = Object.entries(f.type.fieldviews).filter(
        ([k, fv]) =>
          (isFilter && f.calculated && f.stored
            ? fv.isEdit || fv.isFilter
            : f.calculated
              ? !fv.isEdit
              : !fv.isEdit || isEdit || isFilter) &&
          !(mode !== "list" && fv.expandColumns)
      );
      let tfvs_ordered = [];
      if (isEdit) {
        tfvs_ordered = [
          ...tfvs.filter(([k, fv]) => fv.isEdit),
          ...tfvs.filter(([k, fv]) => !fv.isEdit && !fv.isFilter),
        ];
      } else if (isFilter) {
        tfvs_ordered = [
          ...tfvs.filter(([k, fv]) => fv.isFilter),
          ...tfvs.filter(([k, fv]) => fv.isEdit),
        ];
      } else tfvs_ordered = tfvs.filter(([k, fv]) => !fv.isFilter);
      fvs[f.name] = tfvs_ordered.map(([k, fv]) => {
        if (fv && fv.handlesTextStyle) handlesTextStyle[f.name].push(k);
        if (fv && fv.blockDisplay) blockDisplay[f.name].push(k);
        return k;
      });
    }
  });
  return { field_view_options: fvs, handlesTextStyle, blockDisplay };
}
@function
@param {Field[]} fields
@param mode
@param don't follow references to other tables (type === "Key")
@returns {object}