aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts4
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx44
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTableCell.tsx158
3 files changed, 167 insertions, 39 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index bd878ca8a..c5b6546d7 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -68,10 +68,10 @@ class EmptyBox {
return '';
}
}
-export abstract class FInfo {
+export class FInfo {
description: string = '';
readOnly: boolean = false;
- fieldType?: string;
+ fieldType?: string = '';
values?: Field[];
// format?: string; // format to display values (e.g, decimal places, $, etc)
// parse?: ScriptField; // parse a value from a string
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 39e223c66..1f76c8099 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -31,8 +31,18 @@ export enum ColumnType {
String,
Boolean,
Date,
+ Image,
+ Any,
}
+export const FInfotoColType: { [key: string]: ColumnType } = {
+ string: ColumnType.String,
+ number: ColumnType.Number,
+ boolean: ColumnType.Boolean,
+ date: ColumnType.Date,
+ image: ColumnType.Image,
+};
+
const defaultColumnKeys: string[] = ['title', 'type', 'author', 'creationDate', 'text'];
@observer
@@ -42,7 +52,7 @@ export class CollectionSchemaView extends CollectionSubView() {
private _makeNewColumn: boolean = false;
private _documentOptions: DocumentOptions = new DocumentOptions();
- public static _rowHeight: number = 40;
+ public static _rowHeight: number = 50;
public static _minColWidth: number = 25;
public static _rowMenuWidth: number = 60;
public static _previewDividerWidth: number = 4;
@@ -55,7 +65,7 @@ export class CollectionSchemaView extends CollectionSubView() {
@observable _colEles: HTMLDivElement[] = [];
@observable _displayColumnWidths: number[] | undefined;
@observable _columnMenuIndex: number | undefined;
- @observable _menuOptions: [string, { description: string; type: string; readOnly: boolean }][] = [];
+ @observable _fieldInfos: [string, FInfo][] = [];
@observable _newFieldWarning: string = '';
@observable _makeNewField: boolean = false;
@observable _newFieldDefault: any = 0;
@@ -64,7 +74,7 @@ export class CollectionSchemaView extends CollectionSubView() {
@observable _filterColumnIndex: number | undefined;
@observable _filterSearchValue: string = '';
- get keyInfos() {
+ get fieldInfos() {
const docs = this.childDocs;
const keys: { [key: string]: boolean } = {};
// bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields.
@@ -75,23 +85,23 @@ export class CollectionSchemaView extends CollectionSubView() {
//TODO Types
untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false)))));
- // this.columns.forEach(key => (keys[key.heading] = true));
+ let computedKeys: { [key: string]: FInfo } = {};
- let computedKeys: { [key: string]: { description: string; type: string; readOnly: boolean } } = {};
- Object.keys(keys).forEach((key: string) => {
- computedKeys[key] = { description: '', type: '', readOnly: false };
+ Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => {
+ computedKeys[pair[0]] = pair[1];
});
- Object.entries(this._documentOptions).forEach((pair: [string, any]) => {
- const info: FInfo = pair[1];
- computedKeys[pair[0]] = { description: info.description, type: info.fieldType ?? '', readOnly: info.readOnly };
+ Object.keys(keys).forEach((key: string) => {
+ if (!(key in computedKeys)) {
+ computedKeys[key] = new FInfo('');
+ }
});
return computedKeys;
}
get documentKeys() {
- return Object.keys(this.keyInfos);
+ return Object.keys(this.fieldInfos);
}
@computed get previewWidth() {
@@ -497,7 +507,7 @@ export class CollectionSchemaView extends CollectionSubView() {
onSearchKeyDown = (e: React.KeyboardEvent) => {
switch (e.key) {
case 'Enter':
- const menuKeys = Object.keys(this._menuOptions);
+ const menuKeys = Object.keys(this._fieldInfos);
menuKeys.length > 0 && this._menuValue.length > 0 ? this.setKey(menuKeys[0]) : action(() => (this._makeNewField = true))();
break;
case 'Escape':
@@ -527,7 +537,7 @@ export class CollectionSchemaView extends CollectionSubView() {
this._makeNewColumn = false;
this._columnMenuIndex = index;
this._menuValue = '';
- this._menuOptions = Object.entries(this.keyInfos);
+ this._fieldInfos = Object.entries(this.fieldInfos);
this._makeNewField = false;
this._newFieldWarning = '';
this._makeNewField = false;
@@ -573,7 +583,7 @@ export class CollectionSchemaView extends CollectionSubView() {
@action
updateKeySearch = (e: React.ChangeEvent<HTMLInputElement>) => {
this._menuValue = e.target.value;
- this._menuOptions = Object.entries(this.keyInfos).filter(value => value[0].toLowerCase().includes(this._menuValue.toLowerCase()));
+ this._fieldInfos = Object.entries(this.fieldInfos).filter(value => value[0].toLowerCase().includes(this._menuValue.toLowerCase()));
};
getFieldFilters = (field: string) => StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field);
@@ -675,7 +685,7 @@ export class CollectionSchemaView extends CollectionSubView() {
{ passive: false }
)
}>
- {this._menuOptions.map(([key, info]) => (
+ {this._fieldInfos.map(([key, info]) => (
<div
className="schema-search-result"
onPointerDown={e => {
@@ -685,10 +695,10 @@ export class CollectionSchemaView extends CollectionSubView() {
<p>
<span className="schema-search-result-key">
{key}
- {info.type ? ', ' : ''}
+ {info.fieldType ? ', ' : ''}
</span>
<span className="schema-search-result-type" style={{ color: info.readOnly ? 'red' : 'inherit' }}>
- {info.type}
+ {info.fieldType}
</span>
</p>
<p className="schema-search-result-desc">{info.description}</p>
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index f9319050e..4e31b1e1e 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -1,15 +1,20 @@
import React = require('react');
import { observer } from 'mobx-react';
-import { Doc, Field } from '../../../../fields/Doc';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils';
+import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
+import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils';
import { Transform } from '../../../util/Transform';
import { EditableView } from '../../EditableView';
import { FieldView, FieldViewProps } from '../../nodes/FieldView';
import { KeyValueBox } from '../../nodes/KeyValueBox';
import { DefaultStyleProvider } from '../../StyleProvider';
-import { CollectionSchemaView } from './CollectionSchemaView';
+import { CollectionSchemaView, ColumnType, FInfotoColType } from './CollectionSchemaView';
import './CollectionSchemaView.scss';
-import { computed } from 'mobx';
+import { action, computed, observable } from 'mobx';
+import { extname } from 'path';
+import { Cast, DateCast } from '../../../../fields/Types';
+import { ImageField } from '../../../../fields/URLField';
+import { DateField } from '../../../../fields/DateField';
+import DatePicker from 'react-datepicker';
export interface SchemaTableCellProps {
Document: Doc;
@@ -23,10 +28,10 @@ export interface SchemaTableCellProps {
@observer
export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
get readOnly() {
- return this.props.schemaView?.keyInfos[this.props.fieldKey].readOnly;
+ return this.props.schemaView?.fieldInfos[this.props.fieldKey]?.readOnly ?? false;
}
- render() {
+ get defaultCellContent() {
const props: FieldViewProps = {
Document: this.props.Document,
docFilters: returnEmptyFilter,
@@ -53,21 +58,134 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
};
return (
- <div className="schema-table-cell" style={{ width: this.props.columnWidth }}>
- <div className="schemacell-edit-wrapper" style={this.props.isRowActive() && !this.readOnly ? { cursor: 'text', pointerEvents: 'auto' } : { cursor: 'default', pointerEvents: 'none' }}>
- <EditableView
- contents={<FieldView {...props} />}
- GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
- SetValue={(value: string, shiftDown?: boolean, enterKey?: boolean) => {
- if (shiftDown && enterKey) {
- this.props.setColumnValues(this.props.fieldKey, value);
- }
- return KeyValueBox.SetField(this.props.Document, this.props.fieldKey, value);
- }}
- editing={this.props.isRowActive() ? undefined : false}
- />
- </div>
+ <div className="schemacell-edit-wrapper">
+ <EditableView
+ contents={<FieldView {...props} />}
+ GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
+ SetValue={(value: string, shiftDown?: boolean, enterKey?: boolean) => {
+ if (shiftDown && enterKey) {
+ this.props.setColumnValues(this.props.fieldKey, value);
+ }
+ return KeyValueBox.SetField(this.props.Document, this.props.fieldKey, value);
+ }}
+ editing={this.props.isRowActive() ? undefined : false}
+ />
+ </div>
+ );
+ }
+
+ getCellWithContent(content: any) {
+ return (
+ <div
+ className="schema-table-cell"
+ style={this.props.isRowActive() && !this.readOnly ? { width: this.props.columnWidth, cursor: 'text', pointerEvents: 'auto' } : { width: this.props.columnWidth, cursor: 'default', pointerEvents: 'none' }}>
+ {content}
</div>
);
}
+
+ get getCellType() {
+ const columnTypeStr = this.props.schemaView?.fieldInfos[this.props.fieldKey]?.fieldType;
+ if (columnTypeStr) {
+ if (columnTypeStr in FInfotoColType) {
+ return FInfotoColType[columnTypeStr];
+ }
+
+ return ColumnType.Any;
+ }
+
+ const cellValue = this.props.Document[this.props.fieldKey];
+ if (cellValue instanceof ImageField) return ColumnType.Image;
+ if (cellValue instanceof DateField) return ColumnType.Date;
+
+ return ColumnType.Any;
+ }
+
+ render() {
+ const cellType: ColumnType = this.getCellType;
+ switch (cellType) {
+ case ColumnType.Image:
+ return <SchemaImageCell {...this.props} />;
+ case ColumnType.Date:
+ return <SchemaDateCell {...this.props} />;
+ default:
+ return this.getCellWithContent(this.defaultCellContent);
+ }
+ }
+}
+
+// mj: most of this is adapted from old schema code so I'm not sure what it does tbh
+@observer
+export class SchemaImageCell extends SchemaTableCell {
+ choosePath(url: URL) {
+ if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href
+ if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver
+ if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; //Why is this here — good question
+
+ const ext = extname(url.href);
+ return url.href.replace(ext, '_o' + ext);
+ }
+
+ get content() {
+ const field = Cast(this.props.Document[this.props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
+ const alts = DocListCast(this.props.Document[this.props.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
+ const altpaths = alts
+ .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
+ .filter(url => url)
+ .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
+ const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
+ // If there is a path, follow it; otherwise, follow a link to a default image icon
+ const url = paths.length ? paths : [Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')];
+
+ const aspect = Doc.NativeAspect(this.props.Document); // aspect ratio
+ let width = Math.max(75, this.props.columnWidth); // get a with that is no smaller than 75px
+ const height = Math.max(75, width / aspect); // get a height either proportional to that or 75 px
+ width = height * aspect; // increase the width of the image if necessary to maintain proportionality
+
+ return <img src={url[0]} width={paths.length ? width : '20px'} height={paths.length ? height : '20px'} />;
+ }
+
+ render() {
+ return this.getCellWithContent(this.content);
+ }
+}
+
+@observer
+export class SchemaDateCell extends SchemaTableCell {
+ @observable _pickingDate: boolean = false;
+
+ @computed get date(): DateField {
+ // if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined.
+ return DateCast(this.props.Document[this.props.fieldKey]);
+ }
+
+ @action
+ handleChange = (date: any) => {
+ // const script = CompileScript(date.toString(), { requiredType: "Date", addReturn: true, params: { this: Doc.name } });
+ // if (script.compiled) {
+ // this.applyToDoc(this._document, this.props.row, this.props.col, script.run);
+ // } else {
+ // ^ DateCast is always undefined for some reason, but that is what the field should be set to
+ this.props.Document[this.props.fieldKey] = new DateField(date as Date);
+ //}
+ };
+
+ get content() {
+ return !this._pickingDate ? (
+ <div onPointerDown={action(() => (this._pickingDate = true))}>{this.defaultCellContent}</div>
+ ) : (
+ <DatePicker
+ selected={this.date.date}
+ onSelect={(date: any) => {
+ this.handleChange(date);
+ this._pickingDate = false;
+ }}
+ onChange={(date: any) => this.handleChange(date)}
+ />
+ );
+ }
+
+ render() {
+ return this.getCellWithContent(this.content);
+ }
}