public/components/Spreadsheet/SpreadsheetBuilder.react.js (265 lines of code) (raw):
import React from "react";
import _startCase from "lodash.startcase";
import { createSpreadsheet } from "../../util/spreadSheetApi";
import debounce from "lodash.debounce";
const DEFAULT_FILTER = {
type: "internalName",
value: ""
};
const FILTER_TYPES = ["internalName", "externalName", "path", "type", "hasFields", "description"];
const DEFAULT_COLUMN = "internalName";
const AVAILABLE_COLUMNS = [
"id",
"internalName",
"externalName",
"description",
"slug",
"section",
"path",
"type",
"hyperlink",
"description"
];
const MAX_PREVIEW_ROWS = 20;
export default class SpreadsheetBuilder extends React.Component {
constructor(props) {
super(props);
this.state = {
columns: ["internalName", "hyperlink"],
filters: [Object.assign({}, DEFAULT_FILTER)],
previewRows: []
};
this.addColumn = this.addColumn.bind(this);
this.updateColumnType = this.updateColumnType.bind(this);
this.deleteColumn = this.deleteColumn.bind(this);
this.moveColumn = this.moveColumn.bind(this);
this.addFilter = this.addFilter.bind(this);
this.updateFilterType = this.updateFilterType.bind(this);
this.updateFilterValue = this.updateFilterValue.bind(this);
this.fetchRows = this.fetchRows.bind(this);
this.debounceFetchRows = debounce(this.fetchRows, 500);
this.copySpreadSheet = this.copySpreadSheet.bind(this);
this.renderFilter = this.renderFilter.bind(this);
this.renderColumnHeader = this.renderColumnHeader.bind(this);
}
componentDidMount() {
this.fetchRows();
}
componentDidUpdate(prevProps, prevState) {
if (this.state.filters !== prevState.filters) {
this.debounceFetchRows();
}
}
addColumn() {
this.setState({
columns: [...this.state.columns, DEFAULT_COLUMN]
});
}
addFilter() {
this.setState({
filters: [...this.state.filters, Object.assign({}, DEFAULT_FILTER)]
});
}
updateFilterType(index, type) {
const clone = [...this.state.filters];
clone[index].type = type;
this.setState({
filters: clone
});
}
updateFilterValue(index, value) {
const clone = [...this.state.filters];
clone[index].value = value;
this.setState({
filters: clone
});
}
deleteFilter(index) {
const { filters } = this.state;
this.setState({
filters: [...filters.slice(0, index), ...filters.slice(index + 1)]
});
}
deleteColumn(index) {
const { columns } = this.state;
this.setState({
columns: [...columns.slice(0, index), ...columns.slice(index + 1)]
});
}
updateColumnType(index, column) {
const clone = [...this.state.columns];
clone[index] = column;
this.setState({
columns: clone
});
}
moveColumn(index, offset) {
const clone = [...this.state.columns];
const tmp = clone[index + offset];
clone[index + offset] = clone[index];
clone[index] = tmp;
this.setState({
columns: clone
});
}
fetchRows() {
createSpreadsheet(this.state.filters, 10).then(paginatedResponse => {
this.setState({
previewRows: paginatedResponse.tags
});
});
}
copyStringToClipboard(str) {
var el = document.createElement("textarea");
el.value = str;
el.setAttribute("readonly", "");
el.style = { position: "absolute", left: "-9999px" };
document.body.appendChild(el);
el.select();
document.execCommand("copy");
document.body.removeChild(el);
}
copySpreadSheet() {
let rows = this.state.previewRows.map(tag => {
return this.state.columns
.reduce((acc, column) => {
if (column === "hyperlink") {
return [
...acc,
`=HYPERLINK("https://${window.location.host}/tag/${tag.id}", "Open In Tag Manager")`
];
} else {
return [...acc, tag[column]];
}
}, [])
.join("\t");
});
const entireSheet =
this.state.columns.map(s => _startCase(s)).join("\t") +
"\n" +
rows.join("\n");
this.copyStringToClipboard(entireSheet);
alert("Copied to clipboard...");
}
renderFilter(filter, index) {
return (
<div key={filter.type + index} className="spreadsheet-builder__filter">
<select
value={filter.type}
onChange={e => this.updateFilterType(index, e.target.value)}
>
{FILTER_TYPES.map(filter => (
<option key={filter} value={filter}>
{_startCase(filter)}
</option>
))}
</select>
<input
value={filter.value}
onChange={e => this.updateFilterValue(index, e.target.value)}
/>
<i className="i-delete clickable-icon" onClick={() => this.deleteFilter(index)} />
</div>
);
}
renderColumnHeader(column, index) {
return (
<th key={column + index}>
<div className="spreadsheet-builder__table-header">
{index > 0 ? (
<div onClick={() => this.moveColumn(index, -1)}>⬅️</div>
) : (
<div />
)}
<div className="spreadsheet-builder__table-header-picker">
<select
onChange={e => this.updateColumnType(index, e.target.value)}
value={column}
>
{AVAILABLE_COLUMNS.map(c => (
<option key={c} value={c}>
{_startCase(c)}
</option>
))}
</select>
<div onClick={() => this.deleteColumn(index)}>🗑️</div>
</div>
{index < this.state.columns.length - 1 ? (
<div onClick={() => this.moveColumn(index, 1)}>➡️️</div>
) : (
<div />
)}
</div>
</th>
);
}
render() {
return (
<div>
<div className="spreadsheet-builder__panel">
<div>
<h1>Filters</h1>
{this.state.filters.map(this.renderFilter)}
<button onClick={() => this.addFilter()}>New Filter</button>
<button onClick={() => this.copySpreadSheet()}>
Copy sheet to clipboard
</button>
</div>
<h1>Table Preview</h1>
</div>
<div className="spreadsheet-builder__table">
<table>
<thead>
<tr>
{this.state.columns.map(this.renderColumnHeader)}
<th className="spreadsheet-builder__table-new-column"></th>
</tr>
</thead>
<tbody>
{this.state.previewRows
.slice(0, MAX_PREVIEW_ROWS)
.map((row, i, previews) => {
return (
<tr>
{this.state.columns.map(column => (
<td>
{column === "hyperlink" ? (
<a
href={`/tag/${row.id}`}
rel="noopener noreferrer"
target="_blank"
>
Open in Tag Manager
</a>
) : row[column] ? (
row[column]
) : (
"Unknown field: " + column
)}
</td>
))}
<td className="spreadsheet-builder__table-new-column-button">
{Math.ceil(previews.length / 2) === i ? (
<button onClick={this.addColumn}>Add Column</button>
) : null}
</td>
</tr>
);
})}
{this.state.previewRows.length > MAX_PREVIEW_ROWS ? (
<tr>
<td
className="spreadsheet-builder__table-hidden-fields"
colSpan={this.state.columns.length}
>
More rows hidden...
</td>
</tr>
) : null}
</tbody>
</table>
</div>
</div>
);
}
}