aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts49
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx11
-rw-r--r--src/client/views/collections/CollectionView.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx2
-rw-r--r--src/scraping/buxton/final/BuxtonImporter.ts393
-rw-r--r--src/scraping/buxton/final/json/buxton.json504
-rw-r--r--src/scraping/buxton/final/json/incomplete.json1
-rw-r--r--src/scraping/buxton/json/buxton.json116
-rw-r--r--src/scraping/buxton/json/incomplete.json468
-rw-r--r--src/scraping/buxton/node_scraper.ts256
-rw-r--r--src/server/ApiManagers/UtilManager.ts24
-rw-r--r--src/server/DashUploadUtils.ts5
-rw-r--r--src/server/authentication/models/current_user_utils.ts2
13 files changed, 953 insertions, 882 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2f12b50d0..6775d2302 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -56,6 +56,7 @@ import { InkField } from "../../new_fields/InkField";
import { InkingControl } from "../views/InkingControl";
import { RichTextField } from "../../new_fields/RichTextField";
import { Networking } from "../Network";
+import { extname } from "path";
const requestImageSize = require('../util/request-image-size');
const path = require('path');
@@ -350,8 +351,38 @@ export namespace Docs {
*/
export namespace Create {
- export async function Buxton() {
- console.log(await Networking.FetchFromServer("/newBuxton"));
+ export function Buxton() {
+ const loading = new Doc;
+ loading.title = "Please wait for the import script...";
+ const parent = TreeDocument([loading], {
+ title: "The Buxton Collection",
+ _width: 400,
+ _height: 400,
+ _LODdisable: true
+ });
+ Networking.FetchFromServer("/buxton").then(response => {
+ const parentProto = Doc.GetProto(parent);
+ parentProto.data = new List<Doc>();
+ const devices = JSON.parse(response);
+ if (!Array.isArray(devices)) {
+ alert("Improper Buxton import formatting!");
+ return;
+ }
+ devices.forEach(device => {
+ const { __images } = device;
+ delete device.__images;
+ const { ImageDocument, StackingDocument } = Docs.Create;
+ if (Array.isArray(__images)) {
+ const deviceImages = __images.map((url, i) => ImageDocument(url, { title: `image${i}.${extname(url)}` }));
+ const doc = StackingDocument(deviceImages, { title: device.title, _LODdisable: true });
+ const protoDoc = Doc.GetProto(doc);
+ protoDoc.hero = new ImageField(__images[0]);
+ Docs.Get.DocumentHierarchyFromJson(device, undefined, protoDoc);
+ Doc.AddDocToList(parentProto, "data", doc);
+ }
+ });
+ });
+ return parent;
}
Scripting.addGlobal(Buxton);
@@ -645,7 +676,7 @@ export namespace Docs {
* or the result of any JSON.parse() call.
* @param title an optional title to give to the highest parent document in the hierarchy
*/
- export function DocumentHierarchyFromJson(input: any, title?: string): Opt<Doc> {
+ export function DocumentHierarchyFromJson(input: any, title?: string, appendToTarget?: Doc): Opt<Doc> {
if (input === undefined || input === null || ![...primitives, "object"].includes(typeof input)) {
return undefined;
}
@@ -655,7 +686,7 @@ export namespace Docs {
}
let converted: Doc;
if (typeof parsed === "object" && !(parsed instanceof Array)) {
- converted = convertObject(parsed, title);
+ converted = convertObject(parsed, title, appendToTarget);
} else {
(converted = new Doc).json = toField(parsed);
}
@@ -670,12 +701,12 @@ export namespace Docs {
* @returns the object mapped from JSON to field values, where each mapping
* might involve arbitrary recursion (since toField might itself call convertObject)
*/
- const convertObject = (object: any, title?: string): Doc => {
- const target = new Doc();
+ const convertObject = (object: any, title?: string, target?: Doc): Doc => {
+ const resolved = target ?? new Doc;
let result: Opt<Field>;
- Object.keys(object).map(key => (result = toField(object[key], key)) && (target[key] = result));
- title && !target.title && (target.title = title);
- return target;
+ Object.keys(object).map(key => (result = toField(object[key], key)) && (resolved[key] = result));
+ title && !resolved.title && (resolved.title = title);
+ return resolved;
};
/**
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index ab4804cdd..be2947dff 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -633,11 +633,10 @@ export class CollectionTreeView extends CollectionSubView(Document) {
description: "Buxton Layout", icon: "eye", event: () => {
DocListCast(this.dataDoc[this.props.fieldKey]).map(d => {
DocListCast(d.data).map((img, i) => {
- const caption = (d.captions as any)[i]?.data;
- if (caption instanceof ObjectField) {
- Doc.GetProto(img).caption = ObjectField.MakeCopy(caption);
+ const caption = (d.captions as any)[i];
+ if (caption) {
+ Doc.GetProto(img).caption = caption;
}
- d.captions = undefined;
});
});
const { TextDocument, ImageDocument, CarouselDocument, TreeDocument } = Docs.Create;
@@ -649,13 +648,12 @@ export class CollectionTreeView extends CollectionSubView(Document) {
const detailView = Docs.Create.StackingDocument([
CarouselDocument([], { title: "data", _height: 350, _itemIndex: 0, backgroundColor: "#9b9b9b3F" }),
textDoc,
- TextDocument("", { title: "short_description", _autoHeight: true }),
+ TextDocument("", { title: "shortDescription", _autoHeight: true }),
TreeDocument([], { title: "narratives", _height: 75, treeViewHideTitle: true })
], { _chromeStatus: "disabled", _width: 300, _height: 300, _autoHeight: true, title: "detailView" });
textDoc.data = new RichTextField(detailedTemplate, "year company");
detailView.isTemplateDoc = makeTemplate(detailView);
-
const heroView = ImageDocument(fallbackImg, { title: "heroView", isTemplateDoc: true, isTemplateForField: "hero", }); // this acts like a template doc and a template field ... a little weird, but seems to work?
heroView.proto!.layout = ImageBox.LayoutString("hero");
heroView.showTitle = "title";
@@ -673,7 +671,6 @@ export class CollectionTreeView extends CollectionSubView(Document) {
dragFactory: detailView, removeDropProperties: new List<string>(["dropAction"]), title: "detail view", icon: "file-alt"
}));
-
Document.childLayout = heroView;
Document.childDetailed = detailView;
Document._viewType = CollectionViewType.Time;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index fb4d1c1ad..2f27e5273 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -133,7 +133,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
@action.bound
addDocument(doc: Doc): boolean {
- const targetDataDoc = Doc.Layout(this.props.Document);
+ const targetDataDoc = Doc.GetProto(this.props.DataDoc || this.props.Document); // bcz: shouldn't this be Doc.Layout(this.props.Document)? Right now, that causes problems with Buxton layout & adding things to a SLideView
Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
Doc.GetProto(doc).lastOpened = new DateField;
@@ -144,7 +144,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
removeDocument(doc: Doc): boolean {
const docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView);
docView && SelectionManager.DeselectDoc(docView);
- const value = Cast(Doc.Layout(this.props.Document)[this.props.fieldKey], listSpec(Doc), []);
+ const value = Cast(Doc.GetProto(this.props.DataDoc || this.props.Document)[this.props.fieldKey], listSpec(Doc), []);
let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8c7e841fd..c78a2a2cf 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -807,7 +807,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
doFreeformLayout(poolData: Map<string, any>) {
const layoutDocs = this.childLayoutPairs.map(pair => pair.layout);
const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
- let state = initResult && initResult.success ? initResult.result.scriptState : undefined;
+ const state = initResult && initResult.success ? initResult.result.scriptState : undefined;
const elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : [];
this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => {
diff --git a/src/scraping/buxton/final/BuxtonImporter.ts b/src/scraping/buxton/final/BuxtonImporter.ts
new file mode 100644
index 000000000..9da80e787
--- /dev/null
+++ b/src/scraping/buxton/final/BuxtonImporter.ts
@@ -0,0 +1,393 @@
+import { readdirSync, writeFile, mkdirSync, createWriteStream } from "fs";
+import * as path from "path";
+import { red, cyan, yellow } from "colors";
+import { Utils } from "../../../Utils";
+import rimraf = require("rimraf");
+import * as sharp from 'sharp';
+import { SizeSuffix, DashUploadUtils, InjectSize } from "../../../server/DashUploadUtils";
+import { AcceptibleMedia } from "../../../server/SharedMediaTypes";
+const StreamZip = require('node-stream-zip');
+const createImageSizeStream = require("image-size-stream");
+import { parseXml } from "libxmljs";
+import { strictEqual } from "assert";
+
+export interface DeviceDocument {
+ title: string;
+ shortDescription: string;
+ longDescription: string;
+ company: string;
+ year: number;
+ originalPrice: number | "NFS";
+ degreesOfFreedom: number;
+ dimensions: string;
+ primaryKey: string;
+ secondaryKey: string;
+ attribute: string;
+}
+
+interface DocumentContents {
+ body: string;
+ imageUrls: string[];
+ hyperlinks: string[];
+ captions: Caption[];
+}
+
+interface AnalysisResult {
+ device?: DeviceDocument;
+ errors?: any;
+}
+
+type Converter<T> = (raw: string) => { transformed?: T, error?: string };
+
+interface Processor<T> {
+ exp: RegExp;
+ matchIndex?: number;
+ transformer?: Converter<T>;
+ required?: boolean;
+}
+
+interface Caption {
+ fileName: string;
+ caption: string;
+}
+
+namespace Utilities {
+
+ export function numberValue(raw: string) {
+ const transformed = Number(raw);
+ if (isNaN(transformed)) {
+ return { error: `${transformed} cannot be parsed to a numeric value.` };
+ }
+ return { transformed };
+ }
+
+ export function collectUniqueTokens(raw: string) {
+ return { transformed: Array.from(new Set(raw.replace(/,|\s+and\s+/g, " ").split(/\s+/).map(token => token.toLowerCase().trim()))).map(capitalize).sort() };
+ }
+
+ export function correctSentences(raw: string) {
+ raw = raw.replace(/\./g, ". ").replace(/\:/g, ": ").replace(/\,/g, ", ").replace(/\?/g, "? ").trimRight();
+ raw = raw.replace(/\s{2,}/g, " ");
+ return { transformed: raw };
+ }
+
+ export function tryGetValidCapture(matches: RegExpExecArray | null, matchIndex: number): string | undefined {
+ let captured: string;
+ if (!matches || !(captured = matches[matchIndex])) {
+ return undefined;
+ }
+ const lower = captured.toLowerCase();
+ if (/to come/.test(lower)) {
+ return undefined;
+ }
+ if (lower.includes("xxx")) {
+ return undefined;
+ }
+ if (!captured.toLowerCase().replace(/[….\s]+/g, "").length) {
+ return undefined;
+ }
+ return captured;
+ }
+
+ export function capitalize(word: string): string {
+ const clean = word.trim();
+ if (!clean.length) {
+ return word;
+ }
+ return word.charAt(0).toUpperCase() + word.slice(1);
+ }
+
+}
+
+const RegexMap = new Map<keyof DeviceDocument, Processor<any>>([
+ ["title", {
+ exp: /contact\s+(.*)Short Description:/
+ }],
+ ["company", {
+ exp: /Company:\s+([^\|]*)\s+\|/,
+ transformer: (raw: string) => ({ transformed: raw.replace(/\./g, "") })
+ }],
+ ["year", {
+ exp: /Year:\s+([^\|]*)\s+\|/,
+ transformer: Utilities.numberValue
+ }],
+ ["primaryKey", {
+ exp: /Primary:\s+(.*)(Secondary|Additional):/,
+ transformer: raw => ({ transformed: Utilities.collectUniqueTokens(raw).transformed[0] })
+ }],
+ ["secondaryKey", {
+ exp: /(Secondary|Additional):\s+(.*)Attributes?:/,
+ transformer: raw => ({ transformed: Utilities.collectUniqueTokens(raw).transformed[0] }),
+ matchIndex: 2
+ }],
+ ["attribute", {
+ exp: /Attributes?:\s+(.*)Links/,
+ transformer: raw => ({ transformed: Utilities.collectUniqueTokens(raw).transformed[0] }),
+ }],
+ ["originalPrice", {
+ exp: /Original Price \(USD\)\:\s+(\$[0-9]+\.[0-9]+|NFS)/,
+ transformer: (raw: string) => {
+ if (raw === "NFS") {
+ return { transformed: raw };
+ }
+ return Utilities.numberValue(raw.slice(1));
+ }
+ }],
+ ["degreesOfFreedom", {
+ exp: /Degrees of Freedom:\s+([0-9]+)/,
+ transformer: Utilities.numberValue
+ }],
+ ["dimensions", {
+ exp: /Dimensions\s+\(L x W x H\):\s+([0-9\.]+\s+x\s+[0-9\.]+\s+x\s+[0-9\.]+\s\([A-Za-z]+\))/,
+ transformer: (raw: string) => {
+ const [length, width, group] = raw.split(" x ");
+ const [height, unit] = group.split(" ");
+ return {
+ transformed: {
+ dim_length: Number(length),
+ dim_width: Number(width),
+ dim_height: Number(height),
+ dim_unit: unit.replace(/[\(\)]+/g, "")
+ }
+ };
+ },
+ required: false
+ }],
+ ["shortDescription", {
+ exp: /Short Description:\s+(.*)Bill Buxton[’']s Notes/,
+ transformer: Utilities.correctSentences
+ }],
+ ["longDescription", {
+ exp: /Bill Buxton[’']s Notes(.*)Device Details/,
+ transformer: Utilities.correctSentences
+ }],
+]);
+
+const outDir = path.resolve(__dirname, "json");
+const imageDir = path.resolve(__dirname, "../../../server/public/files/images/buxton");
+const successOut = "buxton.json";
+const failOut = "incomplete.json";
+const deviceKeys = Array.from(RegexMap.keys());
+
+export default async function executeImport() {
+ [outDir, imageDir].forEach(dir => {
+ rimraf.sync(dir);
+ mkdirSync(dir);
+ });
+ return parseFiles();
+}
+
+async function parseFiles(): Promise<DeviceDocument[]> {
+ const source = path.resolve(`${__dirname}/source`);
+ const candidates = readdirSync(source).filter(file => /.*\.docx?$/.test(file)).map(file => `${source}/${file}`);
+
+ const imported: any[] = [];
+ for (const filePath of candidates) {
+ const fileName = path.basename(filePath).replace("Bill_Notes_", "");
+ console.log(cyan(`\nExtracting contents from ${fileName}...`));
+ imported.push({ fileName, contents: await extractFileContents(filePath) });
+ }
+
+ console.log(yellow("\nAnalyzing the extracted document text...\n"));
+ const results = imported.map(({ fileName, contents }) => analyze(fileName, contents));
+
+ const masterDevices: DeviceDocument[] = [];
+ const masterErrors: any[] = [];
+ results.forEach(({ device, errors }) => {
+ if (device) {
+ masterDevices.push(device);
+ } else {
+ masterErrors.push(errors);
+ }
+ });
+
+ const total = candidates.length;
+ if (masterDevices.length + masterErrors.length !== total) {
+ throw new Error(`Encountered a ${masterDevices.length} to ${masterErrors.length} mismatch in device / error split!`);
+ }
+
+ console.log();
+ await writeOutputFile(successOut, masterDevices, total, true);
+ await writeOutputFile(failOut, masterErrors, total, false);
+ console.log();
+
+ return masterDevices;
+}
+
+async function readAndParseXml(zip: any, relativePath: string) {
+ const contents = await new Promise<string>((resolve, reject) => {
+ let body = "";
+ zip.stream(relativePath, (error: any, stream: any) => {
+ if (error) {
+ reject(error);
+ }
+ stream.on('data', (chunk: any) => body += chunk.toString());
+ stream.on('end', () => resolve(body));
+ });
+ });
+
+ return parseXml(contents);
+}
+
+async function extractFileContents(pathToDocument: string): Promise<DocumentContents> {
+ console.log('Extracting text...');
+
+ const zip = new StreamZip({ file: pathToDocument, storeEntries: true });
+ await new Promise<void>(resolve => zip.on('ready', resolve));
+
+ // extract the body of the document and, specifically, its captions
+
+ const document = await readAndParseXml(zip, "word/document.xml");
+ const body = document.root()?.text() || "No body found.";
+ const captions: Caption[] = [];
+ const captionTargets = document.find('//*[name()="w:tbl"]/*[name()="w:tr"]/*[name()="w:tc"]').map(node => node.text());
+ const { length } = captionTargets;
+
+ strictEqual(length > 3, true, "No captions written.");
+ strictEqual(length % 3 === 0, true, "Improper caption formatting.");
+
+ for (let i = 3; i < captionTargets.length; i += 3) {
+ const [image, fileName, caption] = captionTargets.slice(i, i + 3);
+ strictEqual(image, "", `The image cell in one row was not the empty string: ${image}`);
+ captions.push({ fileName, caption });
+ }
+
+ // extract all hyperlinks embedded in the document
+ const rels = await readAndParseXml(zip, "word/_rels/document.xml.rels");
+ const hyperlinks = rels.find('//*[name()="Relationship" and contains(@Type, "hyperlink")]').map(el => el.attrs()[2].value());
+ console.log("Text extracted.");
+
+ console.log("Beginning image extraction...");
+ const imageUrls = await writeImages(zip);
+ console.log(`Extracted ${imageUrls.length} images.`);
+
+ zip.close();
+
+ return { body, imageUrls, captions, hyperlinks };
+}
+
+const imageEntry = /^word\/media\/\w+\.(jpeg|jpg|png|gif)/;
+const { pngs, jpgs } = AcceptibleMedia;
+const pngOptions = {
+ compressionLevel: 9,
+ adaptiveFiltering: true,
+ force: true
+};
+interface Dimensions {
+ width: number;
+ height: number;
+ type: string;
+}
+
+async function writeImages(zip: any): Promise<string[]> {
+ const allEntries = Object.values<any>(zip.entries()).map(({ name }) => name);
+ const imageEntries = allEntries.filter(name => imageEntry.test(name));
+
+ const imageUrls: string[] = [];
+ for (const mediaPath of imageEntries) {
+ const streamImage = () => new Promise<any>((resolve, reject) => {
+ zip.stream(mediaPath, (error: any, stream: any) => error ? reject(error) : resolve(stream));
+ });
+
+ const { width, height, type } = await new Promise<Dimensions>(async resolve => {
+ const sizeStream = createImageSizeStream().on('size', resolve);
+ (await streamImage()).pipe(sizeStream);
+ });
+ if (Math.abs(width - height) < 10) {
+ continue;
+ }
+
+ const ext = `.${type}`;
+ const generatedFileName = `upload_${Utils.GenerateGuid()}${ext}`;
+ for (const { resizer, suffix } of resizers(ext)) {
+ const resizedPath = path.resolve(imageDir, InjectSize(generatedFileName, suffix));
+ await new Promise<void>(async (resolve, reject) => {
+ const writeStream = createWriteStream(resizedPath);
+ const readStream = await streamImage();
+ let source = readStream;
+ if (resizer) {
+ source = readStream.pipe(resizer.withMetadata());
+ }
+ const out = source.pipe(writeStream);
+ out.on("close", resolve);
+ out.on("error", reject);
+ });
+ }
+ imageUrls.push(`http://localhost:1050/files/images/buxton/${generatedFileName}`);
+ }
+
+ return imageUrls;
+}
+
+function resizers(ext: string): DashUploadUtils.ImageResizer[] {
+ return [
+ { suffix: SizeSuffix.Original },
+ ...Object.values(DashUploadUtils.Sizes).map(size => {
+ let initial = sharp().resize(size.width, undefined, { withoutEnlargement: true });
+ if (pngs.includes(ext)) {
+ initial = initial.png(pngOptions);
+ } else if (jpgs.includes(ext)) {
+ initial = initial.jpeg();
+ }
+ return {
+ resizer: initial,
+ suffix: size.suffix
+ };
+ })
+ ];
+}
+
+function analyze(fileName: string, { body, imageUrls, captions, hyperlinks }: DocumentContents): AnalysisResult {
+ const device: any = { hyperlinks };
+ const errors: any = { fileName };
+
+ for (const key of deviceKeys) {
+ const { exp, transformer, matchIndex, required } = RegexMap.get(key)!;
+ const matches = exp.exec(body);
+
+ let captured = Utilities.tryGetValidCapture(matches, matchIndex ?? 1);
+ if (captured) {
+ captured = captured.replace(/\s{2,}/g, " ");
+ if (transformer) {
+ const { error, transformed } = transformer(captured);
+ if (error) {
+ errors[key] = `__ERR__${key.toUpperCase()}__TRANSFORM__: ${error}`;
+ continue;
+ }
+ captured = transformed;
+ }
+
+ device[key] = captured;
+ } else if (required ?? true) {
+ errors[key] = `ERR__${key.toUpperCase()}__: outer match ${matches === null ? "wasn't" : "was"} captured.`;
+ continue;
+ }
+ }
+
+ const errorKeys = Object.keys(errors);
+ if (errorKeys.length > 1) {
+ console.log(red(`@ ${cyan(fileName.toUpperCase())}...`));
+ errorKeys.forEach(key => key !== "filename" && console.log(red(errors[key])));
+ return { errors };
+ }
+
+ device.__images = imageUrls;
+
+ device.captions = [];
+ device.fileNames = [];
+ captions.forEach(({ caption, fileName }) => {
+ device.captions.push(caption);
+ device.fileNames.push(fileName);
+ });
+
+ return { device };
+}
+
+async function writeOutputFile(relativePath: string, data: any[], total: number, success: boolean) {
+ console.log(yellow(`Encountered ${data.length} ${success ? "valid" : "invalid"} documents out of ${total} candidates. Writing ${relativePath}...`));
+ return new Promise<void>((resolve, reject) => {
+ const destination = path.resolve(outDir, relativePath);
+ const contents = JSON.stringify(data, undefined, 4);
+ writeFile(destination, contents, err => err ? reject(err) : resolve());
+ });
+} \ No newline at end of file
diff --git a/src/scraping/buxton/final/json/buxton.json b/src/scraping/buxton/final/json/buxton.json
new file mode 100644
index 000000000..2048bf24b
--- /dev/null
+++ b/src/scraping/buxton/final/json/buxton.json
@@ -0,0 +1,504 @@
+[
+ {
+ "hyperlinks": [
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx",
+ "http://spacemice.org/index.php?title=Cadman",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/contact.aspx",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/acknowledgements.aspx",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/browse.aspx",
+ "3DCad_Brochure.pdf"
+ ],
+ "title": "3Dconnexion CadMan 3D Motion Controller",
+ "company": "3Dconnexion",
+ "year": 2003,
+ "primaryKey": "Joystick",
+ "secondaryKey": "Blank",
+ "attribute": "Isometric",
+ "originalPrice": 399,
+ "degreesOfFreedom": 6,
+ "dimensions": {
+ "dim_length": 175,
+ "dim_width": 122,
+ "dim_height": 43,
+ "dim_unit": "mm"
+ },
+ "shortDescription": "The CadMan is a 6 degree of freedom (DOF) joystick controller. It represented a significant step towards making this class of is controller affordable. It was mainly directed at 3D modelling and animation and was a “next generation” of the Magellan controller, which is also in the collection.",
+ "longDescription": "The CadMan is a 6 degree of freedom (DOF) joystick controller. It represented a significant step towards making this class of is controller more affordable. It was mainly directed at 3D modelling and animation and was a “next generation” of the Magellan/SpaceMouse controller, which is also in the collection. Like the Magellan, this is an isometric rate-control joystick. That is, it rests in a neutral central position, not sending and signal. When a force is applied to it, it emits a signal indicating the direction and strength of that force. This signal can then be mapped to a parameter of a selected object, such as a sphere, and – for example – cause that sphere to rotate for as long as, and as fast as, and in the direction determined by, the duration, force, and direction of the applied force. When released, it springs back to neutral position. Note that the force does not need to be directed along a single DOF. In fact, a core feature of the device is that one can simultaneously and independently apply force that asserts control over more than one DOF, and furthermore, vary those forces dynamically. As an aid to understanding, let me walk through some of the underlying concepts at play here by using a more familiar device: a computer mouse. If you move a mouse in a forward/backward direction, the mouse pointer on the screen moves between the screen’s top and bottom. If you think of the screen as a piece of graph paper, that corresponds to moving along the “Y” axis. That is one degree of freedom. On the other hand, you could move the mouse left and right, which causes the mouse to move between the left and right side of the screen. That would correspond to moving along the graph paper’s “X” axis – a second degree of freedom. Yet, you can also move the mouse diagonally. This is an example of independently controlling two degrees of freedom. Now imagine that if you lifted your mouse off your desktop, that your computer could dynamically sense its height as you did so. This would constitute a “flying mouse” (the literal translation of the German word for a “Bat”, which Canadian colleague, Colin Ware, applied to just such a mouse which he built in 1988). If you moved your Bat vertically up and down, perpendicular to the desktop, you would be controlling movement along the “Z” axis - a third degree of freedom. Having already seen that we can move a mouse diagonally, we have established that we need not be constrained to only moving along a single axis. That extends to the movement of our Bat and movement along the “Z” axis. We can control our hand movement in dependently in any or all directions in 3D space. But how does one reconcile the fact that we call the CadMan a “3D controller, and yet also describe it as having 6 degrees of freedom? Yes, as described, our bat can fly in 3D, but on the other hand, its range of movement within those 3 dimensions is much richer. To demonstrate this, move your hand in 3D space on and above your desktop. However, do so keeping your palm flat, parallel to the desktop with your fingers pointing directly forward. In so doing, you are still moving in 3D. Now, while moving, twist your wrist, while moving the hand, such that your palm is alternatively exposed to the left and right side. This constitutes rotation around the “Y” axis. A fourth DOF. Now add a waving motion to your hand, as if it were a paper airplane diving up and down, while also rocking left and right. But keep your fingers pointing forward. You have now added a fifth DOF, rotation around the “X” axis. Finally, add a twist to your wrist so that your fingers are no longer constrained to pointing forward. This is the sixth degree of freedom, rotation around the “Z” axis. Now don’t be fooled, this exercise could continue. We are not restricted to even six DOF. Imagine doing the above, but where the movement and rotations are measured relative to the Bat’s position and orientation, rather than to the holding/controlling hand, per se. One could imagine the Bat having a scroll wheel, like the one on most mice today. Furthermore, while flying your Bat around in 3D, that wheel could easily be rolled in either forward or backward, and thereby control the size of whatever was being controlled. Hence, with one hand we could assert simultaneous and independent control over 7 DOF in 3D space. This exercise has two intended take-aways. The first is a better working understanding between the notion of Degree of Freedom (DOF) and Dimension in space. Hopefully, the confusion frequently encountered when 3D and 6DOF are used in close context, can now be eliminated. Second, is that, with appropriate sensing, the human hand is capable of exercising control over far more degrees of freedom that six. And if we use the two hands together, the potential number of DOF that one can control goes even further. Finally, it is important to add one more take-away – one which both emerges from, and is frequently encountered when discussing, the previous two. That is, do not equate exercising simultaneous control over a high number of DOF with consciously doing the same number of different things all at once. The example that used to be thrown at me when I started talking about coordinated simultaneously bi-manual action went along the lines of, “Psychology tells us that we cannot do multiple things at once, for example, simultaneously tapping your head and rubbing your stomach. ”Well, first, I can tap my head with one hand while rubbing my stomach with the other. But that is not the point. The whole essence of skill – motor-sensory and cognitive – is “chunking” or task integration. When one appears to be doing many different things at once, if they are skilled, they are consciously doing only one thing. Playing a chord on the piano, for example, or skiing down the hill. Likewise, in flying your imaginary BAT in the previous exercise with the scroll wheel, were you doing 7 things at once, or one thing with 7 DOF? And if you had a Bat in each hand, does that mean you are now doing 14 things at once, or are you doing one thing with 14 DOF? Let me provide a different way of answering this question: if you have ever played air guitar, or “conducted” the orchestra that you are listening to on the radio, you are exercising control over more than 14 DOF. And you are doing exactly what I just said, “playing air guitar” or “conducting an orchestra”. One thing – at the conscious level, which is what matters – despite almost any one thing being able to be deconstructed into hundreds of sub-tasks. As I said the essence of skill: aggregation, or chunking. What is most important for both tool designers and users to be mindful of, is the overwhelming influence that our choice and design of tools impacts the degree to which such integration or chunking can take place. The degree to which the tool matches both the skills that we have already acquired through a lifetime of living in the everyday world, and the demands of the intended task, the more seamless that task can be performed, the more “natural” it will feel, and the less learning will be required. In my experience, it brought particular value when used bimanually, in combination with a mouse, where the preferred hand performed conventional pointing, selection and dragging tasks, while the non-preferred hand could manipulate the parameters of the thing being selected. First variation of the since the 2001 formation of 3Dconnextion. The CadMan came in 5 colours: smoke, orange, red, blue and green. See the notes for the LogiCad3D Magellan for more details on this class of device. It is the “parent” of the CadMan, and despite the change in company name, it comes from the same team.",
+ "__images": [
+ "http://localhost:1050/files/images/buxton/upload_665169c3-580b-42be-83e1-96024439fa37.png",
+ "http://localhost:1050/files/images/buxton/upload_95580577-a8bc-4b2f-b787-1132860e726e.png",
+ "http://localhost:1050/files/images/buxton/upload_2561220d-575f-4070-8fe3-b30fc2d01ead.png"
+ ],
+ "captions": [
+ "The 3Dconnexion CadMan 3D Motion Controller, a 6DOF joystick.",
+ "Brochure for the CadMan 3D Motion Controller.(Click on image to access full document.)"
+ ],
+ "fileNames": [
+ "3DCad_0410.JPG",
+ "3DCad_Brochure.jpg"
+ ]
+ },
+ {
+ "hyperlinks": [
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/acknowledgements.aspx",
+ "SpaceNavigator_Press_Release.pdf",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/browse.aspx",
+ "https://web.archive.org/web/20061205222533/http://www.3dconnexion.com:80/products/3a1d.php",
+ "3DConnexion_SpaceNavigator/SpaceNavigator_Launch_Web_Page.pdf",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx",
+ "https://www.pcmag.com/article2/0,2817,2082798,00.asp",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx",
+ "SpaceNavigator_Launch_Web_Page.pdf",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/contact.aspx",
+ "Navigator_Data_Sheet_2006.pdf",
+ "3DConnexion_SpaceNavigator/SpaceNavigator_Press_Release.pdf"
+ ],
+ "title": "3Dconnexion SpaceNavigator ",
+ "company": "3Dconnexion",
+ "year": 2006,
+ "primaryKey": "Joystick",
+ "secondaryKey": "Dial",
+ "attribute": "Isometric",
+ "originalPrice": 59,
+ "degreesOfFreedom": 6,
+ "dimensions": {
+ "dim_length": 78,
+ "dim_width": 78,
+ "dim_height": 53,
+ "dim_unit": "mm"
+ },
+ "shortDescription": "The SpaceNavigator is an entry level 6DOF joystick for the interactive 3D market. It came in a “Personal” and “Standard” edition, at $59. 00 and $99. 00 USD, respectively. These were break-through prices which opened up this technology (which cost $1, 595. 00 in 1991) to gamers and consumers. Doing so was necessary, since the high-end professional 3D graphics market was relatively small, and not growing anywhere near as fast as the consumer and gaming market.",
+ "longDescription": "The SpaceNavigator is an entry level 6DOF joystick for the interactive 3D market. It came in a “Personal” and “Standard” edition, at $59. 00 and $99. 00 USD, respectively. These were break-through prices which opened up this technology (which cost $1, 595. 00 in 1991) to gamers and consumers. Doing so was necessary, since the high-end professional 3D graphics market was relatively small, and not growing anywhere near as fast as the consumer and gaming market. As illustrated in an accompanying image, the direction of the force which controls each of the 6 degrees of freedom of the SpaceNavigator are: Move Left-Right: Push/Pull left-right parallel to the desktop. Move Forward-Backward: Push/Pull forward-backward parallel to the desktop. Move Vertically, Up-Down: Push down vertically into the table or pull up vertically away from the tableTilt Left-Right: Tilt the joystick left-rightTilt Forward-Backward: Tilt joystick forward-backwardRotate around vertical axis: Twist the joystick clockwise or counter clockwise. Control of these 6 DOF can be combined. For example, you can rotate/roll right while spinning. Besides gaming, one of the hopes was that this device would be used in interacting with 3D programs like Google Earth. The problem was, however, that there were few such programs then, just as now, relatively speaking, and even Google Earth, while remarkable, is not used anywhere as frequently of 2D Google Maps, for example. For those of us in the 3D graphics market, it was fantastic with respect to animation, games and industrial design. But it never took off, no matter how seductive it was. And, the interesting question is, will VR and AR change that? And if so, how will this class of 6DOF device play in that market?",
+ "__images": [
+ "http://localhost:1050/files/images/buxton/upload_62108b4e-aa02-44ee-8b2c-562237ece695.jpg",
+ "http://localhost:1050/files/images/buxton/upload_1d819dcc-458c-4e74-b2fc-5a04ff0965e6.png",
+ "http://localhost:1050/files/images/buxton/upload_569136d3-3358-444a-85b1-27f15a5ac2cc.png",
+ "http://localhost:1050/files/images/buxton/upload_97501ca2-9e68-434a-905e-c6a2995f066b.jpg",
+ "http://localhost:1050/files/images/buxton/upload_7bd955a9-df0a-4f6a-bcb8-bad1a0080c8b.png",
+ "http://localhost:1050/files/images/buxton/upload_7a15248a-5e9c-4e98-98de-3d918fe2013c.png",
+ "http://localhost:1050/files/images/buxton/upload_df219f3c-0fc1-4018-aa56-4390532d5edb.png"
+ ],
+ "captions": [
+ "The 3Dconnexion SpaceNavigator 6DOF Joystick.",
+ "The 3Dconnexion SpaceNavigator adjacent to ruler in order to show scale.",
+ "Diagram from SpaceNavigator Data Sheet illustrating the directions of force which control each of the 6DOF.",
+ "Page 1 of the SpaceNavigarot Data Sheet.(Click on image to access full document.)",
+ "A page from the launch web site of the SpaceNavigator.(Click on image to access full document.)",
+ "Front page of the press release announcing the launch of the SpaceNavigator.(Click on image to access full document.)"
+ ],
+ "fileNames": [
+ "SpaceNavigator_01.JPG",
+ "SpaceNavigator_02.JPG",
+ "SpaceNavigator_Control_Axes.jpg",
+ "Navigator_Data_Sheet_2006.jpg",
+ "SpaceNavigator_Launch_Web_Page.jpg",
+ "SpaceNavigator_Press_Release.jpg"
+ ]
+ },
+ {
+ "hyperlinks": [
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx",
+ "SpaceMouse_Plus_Data_Sheet.pdf",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/contact.aspx",
+ "http://youtu.be/w5OH3EfeL64",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/acknowledgements.aspx",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/browse.aspx",
+ "SpaceMouse_Plus_Info_Sheet.pdf"
+ ],
+ "title": "3Dconnexion Magellan/SpaceMouse Plus",
+ "company": "3Dconnexion",
+ "year": 1998,
+ "primaryKey": "Joystick",
+ "secondaryKey": "Blank",
+ "attribute": "Isometric",
+ "originalPrice": 745,
+ "degreesOfFreedom": 6,
+ "dimensions": {
+ "dim_length": 188,
+ "dim_width": 120,
+ "dim_height": 44,
+ "dim_unit": "mm"
+ },
+ "shortDescription": "The Magellan/SpaceMouse Plus is a refinement of the original LogiCad3D Magellan. From the industrial design perspective, the main difference is the switch from the original round “hockey puck” shaped handle to this asymmetric one.",
+ "longDescription": "The Magellan Plus (also known as the Spacemouse Plus) is a refinement of the original LogiCad3D Magellan (LogiCad3D evolved into 3DConnexion). From the industrial design perspective, the main difference between the two is the switch from the round “hockey puck” shaped handle to this asymmetric one. Despite this rather small change, both are included in the collection since that small change is a good example of my axiom that “everything being best for something and worst for something else. ” One of the things which most attracted me to the original Magellan was its hockey-puck shaped handle. The reason is that in my mind, it shouted out, “jog-shuttle wheel”. That is, the kind of controller that I was familiar with from editing audio and video. Since we were building software for 3D animation, the shape had value as a physical icon, or “phycon”, whose affordances suggested how this new control could employ existing skills. That is the good side. On the other hand, for 3D manipulation, when gripped, that same symmetry lacked any immediate tactile feed-forward as to orientation. That is, what axis of the 3D model would be affected by tilting the handle in any direction. On the other hand, the new asymmetric handle told the user, through touch, how the orientation of the handle aligned with that of the 3D model being controlled. One of the key habits leading to design literacy is to constantly prospect for patterns, rather than just individual examples. The reg the pattern means that one can separate the superficial features of the example, and see the underlying issue. For example, compare the Magellan Plus and the LogiCad3D Magellan, respectfully, with any Apple mouse and the 1988 Apple iMac G3 “hockey puck” mouse. While mice and 3D joysticks are very different devices, the two pairings reflect the same pattern. The suggested lesson is that patterns suggest that certain things are not mere exceptions – they are something which will likely reoccur, and therefore something that one can learn from – as long as one can recognize the deep pattern hidden beneath the superficial exterior.",
+ "__images": [
+ "http://localhost:1050/files/images/buxton/upload_30f61adc-4d96-4f0e-a25d-2483c270f608.jpg",
+ "http://localhost:1050/files/images/buxton/upload_1d9b5679-9acf-47f6-90a9-88a23ca2ba5a.png",
+ "http://localhost:1050/files/images/buxton/upload_a92f1c7c-fc12-4398-a598-3cab2ef553b9.png",
+ "http://localhost:1050/files/images/buxton/upload_7a7413c7-8425-4a4b-b5c5-17d33a75077e.png",
+ "http://localhost:1050/files/images/buxton/upload_efe56c7c-4b19-4fcd-9714-a0478ba970d2.png"
+ ],
+ "captions": [
+ "Overview of the Magellan Plus.",
+ "Overview of Magellan Plus in different colour.",
+ "Page one of the Magellan Plus / SpaceMouse Plus Data Sheet.(Click on image to access full document.)",
+ "Page one of the Magellan Plus / SpaceMouse Plus Information Sheet.(Click on image to access full document.)"
+ ],
+ "fileNames": [
+ "3DPlus_0396.JPG",
+ "3DPlus_0391.JPG",
+ "SpaceMouse_Plus_Data_Sheet.jpg",
+ "SpaceMouse_Plus_Info_Sheet.jpg"
+ ]
+ },
+ {
+ "hyperlinks": [
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx",
+ "http://www.evermotion.org/tutorials/show/7916/3dconnexion-s-spaceball-5000-review",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/contact.aspx",
+ "SpaceBall_5000_Data_Sheet.pdf",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/acknowledgements.aspx",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/browse.aspx",
+ "SP_SB_SM_Feature_Comparison_001.pdf"
+ ],
+ "title": "3Dconnexion Spaceball 5000",
+ "company": "3Dconnexion",
+ "year": 2003,
+ "primaryKey": "Joystick",
+ "secondaryKey": "Blank",
+ "attribute": "Isometric",
+ "originalPrice": 499,
+ "degreesOfFreedom": 6,
+ "dimensions": {
+ "dim_length": 213,
+ "dim_width": 76,
+ "dim_height": 152,
+ "dim_unit": "mm"
+ },
+ "shortDescription": "This is an improved version of the original 1991 SpaceBall, manufactured by SpaceBall Technologies. It is a good example of how products improve as the market grows, while the price goes down. The original model sold for $1, 595. 00 USD, while this for $499. 00.",
+ "longDescription": "This is an improved version of the original 1991 SpaceBall, manufactured by SpaceBall Technologies. It is a good example of how products improve as the market grows, while the price goes down. The original model sold for $1, 595. 00 USD, while this for $499. 00. This version of the SpaceBall illustrates how the form-factor of the original version has changed over time. There are now 12 programmable function keys, 9 to be operated by the fingers on one side, and 3 to be operated by the thumb on the other. Note how the button placement indicates that the device is intended to be used by the left hand, with an accompanying mouse by the right – that is, by being meant for the left hand, it is intended for a right handed person.",
+ "__images": [
+ "http://localhost:1050/files/images/buxton/upload_a449576a-798f-42a5-9bea-2bda2c617413.jpg",
+ "http://localhost:1050/files/images/buxton/upload_f753b1f9-2e89-4e62-bbdd-c0f96da3e412.png",
+ "http://localhost:1050/files/images/buxton/upload_fae1743b-6c9e-43ba-a8fe-a4149bdf02aa.png",
+ "http://localhost:1050/files/images/buxton/upload_c12978cc-fd4f-463b-b92a-f4e9aadaf51d.png",
+ "http://localhost:1050/files/images/buxton/upload_284fe4cb-d873-407a-93be-6816f4401796.jpg",
+ "http://localhost:1050/files/images/buxton/upload_2383fc94-1b15-406b-9818-eacbdacd9e45.jpg",
+ "http://localhost:1050/files/images/buxton/upload_70701ee4-6752-4755-a338-55a889e1c20a.jpg"
+ ],
+ "captions": [
+ "Side view of the Spaceball 5000.",
+ "Upper-left view of the Spaceball 5000, showing the position of 9 of the 12 programable buttons.",
+ "Back view of the Spaceball 5000, showing the position of buttons on either side of the ball.",
+ "View showing bimanual use, with the SpaceBall in the left hand, mouse in right. Note position of buttons relative to thumb and fingers.",
+ "Caption to come.(Click on image to access full document.)",
+ "Page 1 of SpaceBall 5000 Date Sheet.(Click on image to access full document.)"
+ ],
+ "fileNames": [
+ "3DSpace_5000_01.JPG",
+ "3DSpace_5000_02.JPG",
+ "3DSpace_5000_05.JPG",
+ "3DSpace_5000_06.JPG",
+ "SP_SB_SM_Feature_Comparison.jpg",
+ "SpaceBall_5000_Data_Sheet.jpg"
+ ]
+ },
+ {
+ "hyperlinks": [
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx",
+ "https://web.archive.org/web/20070104202529/http://solutions.3m.com:80/wps/portal/3M/en_US/ergonomics/home/products/ergonomicmouse/",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/contact.aspx",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/acknowledgements.aspx",
+ "3M_2006_Catalogue.pdf",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/browse.aspx",
+ "https://web.archive.org/web/20070106100452/http://solutions.3m.com/wps/portal/3M/en_US/ergonomics/home/products/ergonomicmouse/factsheet/"
+ ],
+ "title": "3M EM500 Ergonomic Mouse",
+ "company": "3M",
+ "year": 2006,
+ "primaryKey": "Mouse",
+ "secondaryKey": "Blank",
+ "attribute": "Blank",
+ "originalPrice": 72.5,
+ "degreesOfFreedom": 2,
+ "shortDescription": "Despite its form-factor suggesting that this is a joystick, it is actually a mouse. Ergonomic concerns drove this design. It forces the hand to assume a “thumb up” posture of the hand. This in turn reduces constriction of blood-flow through the relatively narrow channel of the wrist and reduces tension in the forearm. The compromise of this, however, is that one loses some fine-motion control which might otherwise have been possible using the fingers, rather than relying more on the wrist and forearm.",
+ "longDescription": "Despite its form-factor suggesting that this is a joystick, it is actually a mouse. Ergonomic concerns drove this design. It forces the hand to assume a “thumb up” posture of the hand. This in turn reduces constriction of blood-flow through the relatively narrow channel of the wrist and reduces tension in the forearm. The joystick also came in two sizes so as to better accommodate different hand sizes. There almost always trade-offs in design. Often more than one. In this case, one compromise is a loss of some fine-motion control which might otherwise have been possible using the fingers, rather than relying more on the wrist and forearm. Another is the increased time/attention required when moving from the keyboard to the mouse. To do a simple test, move your hand to a conventional mouse. Now place a water glass which is taller than it is wide in the same position as the mouse, and compare how fast you can get it “in hand” enough to control its movement as well as you could the mouse. Try the same thing while not looking – using motor memory. Then consider how often you make that change. And, by the same token, consider how many different ways you can hold your mouse while still using it, versus a joystick shaped mouse with the button on top. In none of this am I complaining about, nor criticizing this mouse design. Rather, I am trying to illustrate that there is a lot to consider, and as either a designer or consumer, these are things to train oneself to notice and question. From such experience emerges the basis for better choices in both design and purchase. .",
+ "__images": [
+ "http://localhost:1050/files/images/buxton/upload_84007f47-f842-46e9-9e99-e45f5ecc72ee.jpg",
+ "http://localhost:1050/files/images/buxton/upload_e1f1da29-4254-446c-85e8-c578ab770936.jpg",
+ "http://localhost:1050/files/images/buxton/upload_67547a81-89bc-4d38-a9d4-1a17510f0a86.jpg",
+ "http://localhost:1050/files/images/buxton/upload_0b94009a-251b-4c77-ae0a-c1df7a227d64.jpg",
+ "http://localhost:1050/files/images/buxton/upload_f591252d-7e21-4389-ab2b-dbc60d1db43e.jpg",
+ "http://localhost:1050/files/images/buxton/upload_60df3ea2-532b-4d3d-8f47-7ca9ddf30533.jpg"
+ ],
+ "captions": [
+ "A view of the 3M Ergonomic Mouse in the grasp of the right hand, with the thumb activating the button at the top of the stem.",
+ "A view of the 3M Ergonomic Mouse standing alone.",
+ "EM500 Ergonomic web page, Jan, 2007",
+ "EM500 Ergonomic Mouse Fact Sheet from 3M web site, Jan, 2007",
+ "Entry for the EM500 Ergonomic Mouse in the 2006 3M catalogue, p. 18.(To access full document, click on image.)"
+ ],
+ "fileNames": [
+ "3MErgo_01.JPG",
+ "3MErgo_02.JPG",
+ "3MErgo_Web_Jan_2007.JPG",
+ "3MErgo_Web_Fact Sheet_Jan_2007.jpg",
+ "3M_2006_Catalogue_p18.jpg"
+ ]
+ },
+ {
+ "hyperlinks": [
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx",
+ "Abaton_ProPoint_Brochure.pdf",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/contact.aspx",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/acknowledgements.aspx",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/browse.aspx"
+ ],
+ "title": "Abaton ProPoint Optical Trackball",
+ "company": "Abaton",
+ "year": 1989,
+ "primaryKey": "Trackball",
+ "secondaryKey": "Blank",
+ "attribute": "Blank",
+ "originalPrice": 140,
+ "degreesOfFreedom": 2,
+ "shortDescription": "A relatively early trackball for the Apple Macintosh computer. Note the positioning of the buttons, which bias the device for right-handed use, and using the thumb for the buttons and fingers for manipulating the ball.",
+ "longDescription": "A relatively early trackball for the Apple Macintosh computer. The larger button was functionally equivalent to the mouse button, and the smaller one was a ‘lock’ button. The lock button is analogous to the SHIFT LOCK or CAPS LOCK on a QWERTY keyboard: With the keyboard, it means that one does not have to hold the SHIFT key down while typing a string of upper-case characters. With the trackball, it means that you don’t have to hold the primary “mouse” button down while rolling the trackball. The need that this meets can be easily be seen if one compares the relative difficulty of drawing a line by moving a mouse while holding down its button, compared to doing the same task with a trackball. With the mouse, the wrist and forearm mainly move the mouse, and the fingers are used to hold it, as well as the button. With the trackball, the fingers are engaged in rolling the ball as well as holding the button. Hence, the probability of task interference is high, just as with typing a string of upper-case characters on a keyboard without a SHIFT LOCK key. Next, in looking at trackballs, pay attention to the position of the buttons relative to the trackball itself. How this relationship varies across devices and says a lot about how the designers envisioned the device being used. For example, the relationship may indicate the design intends for the thumb or fingers to operate the ball. How does the relationship impact the device’s ability to accommodate left and right hand usage equally well? This latter point is especially important when the trackball is used simultaneously with a mouse. An example would be if the trackball was used to scroll a spreadsheet up-down / left-right, while the mouse was used to point, select, and/or drag. In this case, for example, the trackball would usually be operated by the non-dominant hand and the mouse by the dominant one. Yet, when used alone, the same user would typically operate the trackball with the dominant hand. The lesson from this example is the recognition that handedness is a factor of use (one vs two handed), not just a factor of whether the user’s left or right hand is dominant.",
+ "__images": [
+ "http://localhost:1050/files/images/buxton/upload_fa8f6ec4-f909-40db-8b90-fc9ce6f11d8a.jpg",
+ "http://localhost:1050/files/images/buxton/upload_a5a08c72-5907-4ac2-8894-dcfb3708eaaf.jpg",
+ "http://localhost:1050/files/images/buxton/upload_87a4bfd6-1dc3-4fd9-bd86-0e04f024207a.jpg",
+ "http://localhost:1050/files/images/buxton/upload_5e57d61a-3f23-465e-85cb-0f27fa613055.jpg",
+ "http://localhost:1050/files/images/buxton/upload_c6f4c165-0e97-48c5-a046-719c3a73f7bf.jpg",
+ "http://localhost:1050/files/images/buxton/upload_9abd3f27-27b6-4ab7-8ff3-a7654a5ffe19.jpg",
+ "http://localhost:1050/files/images/buxton/upload_1301c7a6-8946-411c-b741-71f36eebbb9d.jpg"
+ ],
+ "captions": [
+ "An upper-left view of the Abaton ProPoint trackball.",
+ "Left side view of the Abaton ProPoint trackball showing the Apple Desktop Bus (ADB) socket.",
+ "Front-side view of the Abaton ProPoint trackball..",
+ "Back-side view of the Abaton ProPoint trackball.",
+ "Bottom view of the Abaton ProPoint trackball showing the serial number, etc.",
+ "View of the Abaton ProPoint with the trackball removed. The two shaft encoders that sense rotation in X and Y, respectively can be seen.",
+ "First page of the Abatron ProPoint Trackball product sheet.(Click on image to access full document.)"
+ ],
+ "fileNames": [
+ "Abaton_0105.JPG",
+ "Abaton_0098.JPG",
+ "Abaton_0099.JPG",
+ "Abaton_0100.JPG",
+ "Abaton_0103.JPG",
+ "Abaton_0106.JPG",
+ "Abaton_ProPoint_Brochure.jpg"
+ ]
+ },
+ {
+ "hyperlinks": [
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/browse.aspx",
+ "Active_Book_Brochure.pdf",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx",
+ "http://www.computinghistory.org.uk/det/21617/Active-Book-Prototype-Circuit-Boards/",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/contact.aspx",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/acknowledgements.aspx"
+ ],
+ "title": "Active Book Company Active Book Prototype",
+ "company": "Active Book Company",
+ "year": 1991,
+ "primaryKey": "Computer",
+ "secondaryKey": "Blank",
+ "attribute": "Prototype",
+ "originalPrice": "NFS",
+ "degreesOfFreedom": 2,
+ "shortDescription": "This device is a prototype pen computer, The Active Book, which was developed in Cambridge, UK, by The Active Book Company. It had a strong focus on user experience, and like the equally ill-fated Momenta pen computer, was implemented using the pioneering object-oriented language Smalltalk. At about the time that this working prototype was built, near going into production, the company was bought and merged with EO, and the Active Book never went into production. This is an exceptionally rare piece of the history of pen computing.",
+ "longDescription": "This device is a prototype pen computer, The Active Book, which was developed in Cambridge, UK, by The Active Book Company. It had a strong focus on user experience, and like the equally ill-fated Momenta pen computer, was implemented using the pioneering object-oriented language Smalltalk. At about the time that this working prototype was built, near going into production, the company was bought and merged with EO, and the Active Book never went into production. This is an exceptionally rare piece of the history of pen computing. .",
+ "__images": [
+ "http://localhost:1050/files/images/buxton/upload_711a1cdb-c895-426b-9cff-7e1f023d17a5.jpg",
+ "http://localhost:1050/files/images/buxton/upload_335794e7-c52b-4bdc-b193-b5f653407e4e.png"
+ ],
+ "captions": [
+ "View of the Active Book prototype.",
+ "Front page of the Active Book brochure.(Click on image to access full document.)"
+ ],
+ "fileNames": [
+ "Active_1206.JPG",
+ "Active_Book_Brochure_p1.jpg"
+ ]
+ },
+ {
+ "hyperlinks": [
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/contact.aspx",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/acknowledgements.aspx",
+ "https://web.archive.org/web/20030621224236/http://www.adesso.us:80/product_details.asp?dept_id=106&pf_id=KA33ACK-540",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/browse.aspx",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx",
+ "https://web.archive.org/web/20030507032656/http://www.adesso.us:80/product_details.asp?dept_id=106&pf_id=KA33ACK-540PB"
+ ],
+ "title": "Adesso ACK-540PB PS/2 Mini PS/2 Touchpad Keyboard",
+ "company": "Adesso",
+ "year": 2003,
+ "primaryKey": "Keyboard",
+ "secondaryKey": "Touchpad",
+ "attribute": "Blank",
+ "originalPrice": 69.95,
+ "degreesOfFreedom": 2,
+ "dimensions": {
+ "dim_length": 287,
+ "dim_width": 140,
+ "dim_height": 35.5,
+ "dim_unit": "mm"
+ },
+ "shortDescription": "The Mini-Touch Keyboard is a small-footprint keyboard with a centrally mounted touchpad. It initially was released with a PS/2 connector, and then in 2006 the connector was updated to USB. While keyboards with integrated touchpads had been available since the mid-1980s, small-footprint ones with centrally mounted touch pads were far less common.",
+ "longDescription": "Released in 2003, this is a small add-on keyboard with an integrated touchpad. While there had been keyboards released with touchpads earlier – see the 1985 KeyTronic LT Touchpad Keyboard in the collection, for example – these were full-sized keyboard, typically with the touchpad mounted at the side, rather than the middle. Keyboards such as the Adesso ACK-540PB, were styled after the smaller foot-print keyboards then becoming standard on laptops, in terms of the central placement of the touchpad, as well as size. This central placement was significant, since it gave equal access to either right or left hand. The touchpad used was a Glidepoint, a 1994 stand-alone version of which is in the collection, the 1994 Cirque Glidepoint. This first model of the ACK-540 was released in both black (ACK-540PB) and white (ACK-540PW) and came with a PS/2 connector. In 2006, black and white versions updated with a USB connectors were released (the ACK-540UB and ACK-540UW) were released – an indication that the product had sustained a place in the market. These same keyboards were also marketed under different brand names, including SolidTek and Daltaco.",
+ "__images": [
+ "http://localhost:1050/files/images/buxton/upload_9e20c2c2-8bbe-4f9e-90af-9c547f7dc7f4.jpg",
+ "http://localhost:1050/files/images/buxton/upload_8848eb38-a7cf-43a4-879d-4440ce475117.jpg",
+ "http://localhost:1050/files/images/buxton/upload_21b95d38-7a73-446b-be2f-18c4981aed18.jpg",
+ "http://localhost:1050/files/images/buxton/upload_051c82ac-c2be-4ab7-83d8-91f9619e0bc3.jpg",
+ "http://localhost:1050/files/images/buxton/upload_fcff645c-2332-4982-a9ed-e918f465fcad.jpg"
+ ],
+ "captions": [
+ "Top view of the Adesso Mini-Touch Keyboard with Touchpad.",
+ "Side view of the Adesso Mini-Touch Keyboard with Touchpad.",
+ "Adesso 2003 product web page for the black 540_PB model of the Mini Touchpad Keyboard.",
+ "Adesso 2003 product web page for the white 540_PW model of the Mini Touchpad Keyboard"
+ ],
+ "fileNames": [
+ "Adesso_540_Top.JPG",
+ "Adesso_540_Side.JPG",
+ "Adesso_ACK-540PB_May_7_2003.jpg",
+ "Adesso_ACK-540PW_June 21_2003.jpg"
+ ]
+ },
+ {
+ "hyperlinks": [
+ "https://web.archive.org/web/20070902035057/http://www.adesso.com/new_arrival.asp",
+ "Adesso_KP_Mouse_11_Product.pdf",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/contact.aspx",
+ "Adesso_KP_Mouse_10.pdf",
+ "http://www.notebookreview.com/review/adesso-usb-numeric-keypad-and-optical-mouse-review/",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/acknowledgements.aspx",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/browse.aspx",
+ "https://web.archive.org/web/20071025131401/http://www.adesso.com/products_detail.asp?productid=363",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx"
+ ],
+ "title": "Adesso 2-in-1 Optical Keypad Calculator Mouse AKP-170",
+ "company": "Adesso Inc",
+ "year": 2007,
+ "primaryKey": "Mouse",
+ "secondaryKey": "Keypad",
+ "attribute": "",
+ "originalPrice": 29.99,
+ "degreesOfFreedom": 3,
+ "dimensions": {
+ "dim_length": 120.7,
+ "dim_width": 57.15,
+ "dim_height": 38.1,
+ "dim_unit": "mm"
+ },
+ "shortDescription": "This is a mouse / keypad hybrid. With the transparent hinged cover down, it functions like a conventional scroll-wheel optical mouse. All the while, its additional capability as a numerical keypad / calculator is visible, and physically accessed by flipping up the cover. Since the design affords access to the mouse buttons and scroll wheel with the lid open, the opportunity to select cells in a spreadsheet, for example, and enter numbers without moving between the traditional keyboard and mouse is provided.",
+ "longDescription": "This is a mouse / numerical keypad hybrid. With the transparent hinged cover down, it functions like a conventional scroll-wheel optical mouse. All the while, its additional capability as a numerical keypad / calculator is visible, and physically accessed by flipping up the cover. Since the design affords access to the mouse buttons and scroll wheel with the lid open, the opportunity to select cells in a spreadsheet, for example, and enter numbers without moving between the traditional keyboard and mouse is provided. There are a few mice with integrated keypads included in the collection. Each takes a different approach in terms of intent as well as industrial design. Comparing them is a worthwhile exercise. By the same token, the approach taken by this example echoes that taken by a very different product, but with the same intent: layer complexity. In this case the president from the collection is a TV/VCR remote control: the 1990 Sony RMT-V5A. For comparison, see the accompanying photo, and for more information, look at the Sony’s detailed device description. Finally, in drawing attention to this specific example rather than one of the hybrid mouse/keypads referred to earlier, the intent is to show that inspiration for design solutions for one class of device can come from those of very different categories. For sure, look at previous in-class solutions. But that just puts you on par with most other designers. The best exercise their creativity by looking in far less explored places. Note to the sharp eyed: If you look at the bottom of the mouse, you will see it marked model KM-1411, while I have been referring to it, as well as the Adesso Web Site, as model AKP-170. Rest assured, these are the same thing, as can be seen on the bottom right corner of the back of the box, where both numbers appear. I too am confused as to why, but also reassured. .",
+ "__images": [
+ "http://localhost:1050/files/images/buxton/upload_3bbf35c9-587a-4731-9e0c-f53927703fcc.jpg",
+ "http://localhost:1050/files/images/buxton/upload_508b066c-1727-4830-b373-a10f55036dff.jpg",
+ "http://localhost:1050/files/images/buxton/upload_8610309e-ff5c-463b-ac6f-e08661d760b6.jpg",
+ "http://localhost:1050/files/images/buxton/upload_a3ee650e-3d86-4c3c-8d79-c2c68ba29aa4.jpg",
+ "http://localhost:1050/files/images/buxton/upload_84a69023-f88a-41ff-94b9-f7a639e1d895.jpg",
+ "http://localhost:1050/files/images/buxton/upload_3cc21d2b-1702-4095-91f0-a084a264baa0.jpg",
+ "http://localhost:1050/files/images/buxton/upload_7513ff62-a57e-441e-add3-edaf30ba5037.jpg",
+ "http://localhost:1050/files/images/buxton/upload_189c54c1-7e83-4d0a-8836-0e608d095b50.jpg",
+ "http://localhost:1050/files/images/buxton/upload_f50ebb26-8033-422f-99cc-f3a0581921cf.jpg",
+ "http://localhost:1050/files/images/buxton/upload_3a1d5977-3e1b-459d-bb6e-36b478f1bd15.jpg",
+ "http://localhost:1050/files/images/buxton/upload_f4414860-f5d5-46f2-881a-6d728050fb79.jpg"
+ ],
+ "captions": [
+ "An overview of the Adesso AKP-170 Mouse with the transparent hinged keypad cover open. Note that the mouse controls can still be accessed.",
+ "The Adesso AKP-170 Mouse with the transparent hinged keypad cover in its default closed position.",
+ "The 1990 Sony RMT-V5A Space Control. Note how the hinged cover of the Adesso mouse echoes this design. Stepping outside of one’s product class can provide a rich source of relevant ideas.",
+ "A view of the Adesso AKP-170 Mouse from the upper left side.",
+ "A front-end view of the Adesso AKP-170 Mouse.",
+ "A view of the bottom of the Adesso AKP-170 Mouse, showing serial number, etc.",
+ "A front view of the Adesso AKP-170 Mouse in its original box.",
+ "A back-side view of the original box for the Adesso AKP-170 Mouse.",
+ "1-page user manual for the Adesso AKP-170 Mouse.",
+ "Page 1 of a review of the Adesso AKP-170 from the Dec. 2007 Notebook Review web page. (Click on image to access full document.)",
+ "First page of Adesso Oct. 25, 2007 Product Web Page for the AKP-170 Mouse.(Click on image to access full document.)"
+ ],
+ "fileNames": [
+ "Adesso_KP_Mouse_01.JPG",
+ "Adesso_KP_Mouse_02.JPG",
+ "Adesso_KP_Mouse_03.JPG",
+ "Adesso_KP_Mouse_04.JPG",
+ "Adesso_KP_Mouse_05.JPG",
+ "Adesso_KP_Mouse_06.JPG",
+ "Adesso_KP_Mouse_07.JPG",
+ "Adesso_KP_Mouse_08.JPG",
+ "Adesso_KP_Mouse_09.JPG",
+ "Adesso_KP_Mouse_10.JPG",
+ "Adesso_KP_Mouse_11_Product.JPG"
+ ]
+ },
+ {
+ "hyperlinks": [
+ "https://web.archive.org/web/20051210101548/http://www.a4tech.com/en/product1.asp?CID=90&SCID=92",
+ "http://a4tech.com/product.asp?cid=142&scid=122&id=342",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx",
+ "https://microsoft-my.sharepoint.com/personal/bibuxton_microsoft_com/Documents/Buxton%20Collection/Collection/To%20Shoot/A4_Tech_NB-75D%20Mouse/NB75D_Mouse_Manual.pdf",
+ "http://www.ubergizmo.com/2007/11/a4tech-battery-free-wireless-mouse/",
+ "BATTERYfree_Mouse_Data_Sheet.pdf",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/contact.aspx",
+ "https://web.archive.org/web/20060707175129/http://ergoshops.com:80/store.asp?CID=200209271427192&SCID=200410281216151",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/acknowledgements.aspx",
+ "NB75D_Mouse_Manual.pdf",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/browse.aspx",
+ "https://web.archive.org/web/20060211161019/http://www.a4tech.com/en/product2.asp?CID=90&SCID=92&MNO=NB-75",
+ "http://research.microsoft.com/en-us/um/people/bibuxton/buxtoncollection/default.aspx"
+ ],
+ "title": "A4 Tech BatteryFREE Wireless Optical Mouse Model NB-75D",
+ "company": "A4Tech",
+ "year": 2005,
+ "primaryKey": "Mouse",
+ "secondaryKey": "Scroll-wheel",
+ "attribute": "Blank",
+ "originalPrice": 35.99,
+ "degreesOfFreedom": 4,
+ "shortDescription": "This is a little-known innovative mouse worth study. It is one of the first to have wireless charging – by sitting on its mouse pad. It has two scroll wheels which are mounted at right angles – like an Etch-a-Sketch, to conform to the direction to be scrolled (up/down vs left/right. It also has a dedicated button which “double clicks” with one push.",
+ "longDescription": "This mouse fell below the radar, and yet it had remarkable innovations in its design, especially given its price. Like any mouse, it provided 2 degrees of freedom for pointing. Unlike most scroll-wheel mouse, it had two separate scroll wheels mounted at right angles. Hence, the orientation of each wheel provided a cue as to which direction the affected document would scroll – up/down or left/right. It is interesting to consider this scroll-wheel layout with that on a companion mouse, the A4 Tech Model IRW-5 4D Wireless Mouse, also in the collection, and shown side-by-side in one of the accompanying photographs. While the scroll wheels of the NF-75 are at right angles, those of the IRW-5 are parallel. Such differences should always provoke the question “Why? ” – for would-be designer and consumer alike. As a memory aid, and the conform to what psychologists call “stimulus-response (S: -R) compatibility”, the right-angle arrangement seems to be far better mapping of action to effect. On the other hand, try an experiment. First, place your hand, palm facing down, on a desktop as if you were holding a mouse. All fingers and thumb should be touching the desktop, but not your palm. Maintaining that position, left your index (scrolling) finger, and repeatedly “fold” and “unfold” it between a pointing posture, and touching your palm. Next, keeping your hand in the same position, this time, again extend your index finger. But this time, move its tip back-and-forth in a left-right motion. What I hope you quickly perceive is that the finger was “optimized” for the folding rather than lateral motion. You “feel” that from the difference in tension in the hand in the two cases of the “study”. The two conditions of our “quick” study roughly mimic the motor actions required to operate the scroll wheels. Since the brief test suggests the possibility that repeated lateral movement may accelerate the onset of repetitive stress injury, it would therefore also suggest that further studies should be undertaken before using a left-right scroll wheel – despite the acknowledged advantage with respect to S: R compatibility. In addition to the select button on virtually all mice, there was an additional button which issued a double-click on a single push. Hence, clicking one button would select, while pushing the other would select and open. While for many, the cost of an extra button may not seem worth saving the effort of a double click, those with some motor impairment may disagree. This is an example where knowing about such things may trigger useful insights to improve future designs. However, perhaps the biggest innovation with this line of A4 mice is that they were powered wirelessly. That is, there were no batteries in the mouse that might die at the wrong moment. Power was provided wirelessly from the mouse pad. Yet, one again we see that design is full of trade-offs. The cost of providing this feature is that of requiring not just the mouse pad, but a special one – something not overly attractive to road-warriors whose brief-cases were already weighted down by cables and other paraphernalia which took up more space than the laptop itself. .",
+ "__images": [
+ "http://localhost:1050/files/images/buxton/upload_688cc54e-65b3-41c1-914d-132e8dcc780e.jpg",
+ "http://localhost:1050/files/images/buxton/upload_ac087ded-408c-48ba-855e-dab3cb43d79f.jpg",
+ "http://localhost:1050/files/images/buxton/upload_a55b8397-ed11-4566-8ee7-e1c25e507325.jpg",
+ "http://localhost:1050/files/images/buxton/upload_5f5b280c-2f63-4094-a6ca-37b3b292a55d.jpg",
+ "http://localhost:1050/files/images/buxton/upload_48929587-218d-48c4-ac89-fceb5fb87992.jpg",
+ "http://localhost:1050/files/images/buxton/upload_34b3f6d1-8c50-468e-ae88-fcf13e79f1e0.jpg",
+ "http://localhost:1050/files/images/buxton/upload_185fc393-b4bf-4f0c-836f-4304b6910c8b.jpg",
+ "http://localhost:1050/files/images/buxton/upload_4eb7d62e-efc7-45ac-99ef-1f474aab0201.jpg",
+ "http://localhost:1050/files/images/buxton/upload_25313cb4-1be5-4d18-8b06-2bbbcd102aca.jpg",
+ "http://localhost:1050/files/images/buxton/upload_58474787-7538-4df7-ac60-b27d45a6dd08.jpg",
+ "http://localhost:1050/files/images/buxton/upload_e088cb02-9ac3-4e7e-84d0-223b5d737966.jpg",
+ "http://localhost:1050/files/images/buxton/upload_f244be9b-ef53-409d-9b3e-6f4610010cec.jpg"
+ ],
+ "captions": [
+ "The A4 Tech Model NB-75D optical mouse on its charging mouse pad.",
+ "Front view of the A4 Tech NB-75D mouse beside its A4 Tech IRW-5 4D companion. Note the difference in the second scroll-wheel. See text for discussion.",
+ "Top view of the A4 Tech NB-75D mouse beside its A4 Tech IRW-5 4D companion.",
+ "Upper front quarter view of the A4 Tech Model NB-75D optical mouse.",
+ "Front view of the A4 Tech Model NB-75D optical mouse.",
+ "Upper rear-quarter view of the A4 Tech Model NB-75D optical mouse.",
+ "Top view of the A4 Tech Model NB-75D optical mouse.",
+ "The A4 Tech Model NB-75D optical mouse on its charging mouse pad, showing the mouse pad USB connector.",
+ "The A4 Tech Model NB-75D product web page.",
+ "Front page of Jandata sheet for the A4 BATTERYfree optical mouse product line. From A4 Tech web site.",
+ "The A4 Tech Model NB-75D User’s Guide.(Click on image to access full document.)"
+ ],
+ "fileNames": [
+ "NB75D_IMG_0682.JPG",
+ "NB75D_IMG_0666.JPG",
+ "NB75D_IMG_0676.JPG",
+ "NB75D_IMG_0671.JPG",
+ "NB75D_IMG_0672.JPG",
+ "NB75D_IMG_0673.JPG",
+ "NB75D_IMG_0679.JPG",
+ "NB75D_IMG_2016.JPG",
+ "NB75D_Product_Page.jpg",
+ "BATTERYfree_Mouse_Data_Sheet.jpg",
+ "NB75D_Mouse_Manual.jpg"
+ ]
+ }
+] \ No newline at end of file
diff --git a/src/scraping/buxton/final/json/incomplete.json b/src/scraping/buxton/final/json/incomplete.json
new file mode 100644
index 000000000..0637a088a
--- /dev/null
+++ b/src/scraping/buxton/final/json/incomplete.json
@@ -0,0 +1 @@
+[] \ No newline at end of file
diff --git a/src/scraping/buxton/json/buxton.json b/src/scraping/buxton/json/buxton.json
deleted file mode 100644
index 8371f2cf2..000000000
--- a/src/scraping/buxton/json/buxton.json
+++ /dev/null
@@ -1,116 +0,0 @@
-[
- {
- "title": "3Dconnexion CadMan 3D Motion Controller",
- "company": "3Dconnexion",
- "year": 2003,
- "primaryKey": [
- "Joystick"
- ],
- "secondaryKey": [
- "Isometric",
- "Joystick"
- ],
- "originalPrice": 399,
- "degreesOfFreedom": 6,
- "dimensions": {
- "length": 175,
- "width": 122,
- "height": 43,
- "unit": "mm"
- },
- "shortDescription": "The CadMan is a 6 degree of freedom (DOF) joystick controller. It represented a significant step towards making this class of is controller affordable. It was mainly directed at 3D modelling and animation and was a “next generation” of the Magellan controller, which is also in the collection.",
- "longDescription": "The CadMan is a 6 degree of freedom (DOF) joystick controller. It represented a significant step towards making this class of is controller more affordable. It was mainly directed at 3D modelling and animation and was a “next generation” of the Magellan/SpaceMouse controller, which is also in the collection. Like the Magellan, this is an isometric rate-control joystick. That is, it rests in a neutral central position, not sending and signal. When a force is applied to it, it emits a signal indicating the direction and strength of that force. This signal can then be mapped to a parameter of a selected object, such as a sphere, and – for example – cause that sphere to rotate for as long as, and as fast as, and in the direction determined by, the duration, force, and direction of the applied force. When released, it springs back to neutral position. Note that the force does not need to be directed along a single DOF. In fact, a core feature of the device is that one can simultaneously and independently apply force that asserts control over more than one DOF, and furthermore, vary those forces dynamically. As an aid to understanding, let me walk through some of the underlying concepts at play here by using a more familiar device: a computer mouse. If you move a mouse in a forward/backward direction, the mouse pointer on the screen moves between the screen’s top and bottom. If you think of the screen as a piece of graph paper, that corresponds to moving along the “Y” axis. That is one degree of freedom. On the other hand, you could move the mouse left and right, which causes the mouse to move between the left and right side of the screen. That would correspond to moving along the graph paper’s “X” axis – a second degree of freedom. Yet, you can also move the mouse diagonally. This is an example of independently controlling two degrees of freedom. Now imagine that if you lifted your mouse off your desktop, that your computer could dynamically sense its height as you did so. This would constitute a “flying mouse” (the literal translation of the German word for a “Bat”, which Canadian colleague, Colin Ware, applied to just such a mouse which he built in 1988). If you moved your Bat vertically up and down, perpendicular to the desktop, you would be controlling movement along the “Z” axis - a third degree of freedom. Having already seen that we can move a mouse diagonally, we have established that we need not be constrained to only moving along a single axis. That extends to the movement of our Bat and movement along the “Z” axis. We can control our hand movement in dependently in any or all directions in 3D space. But how does one reconcile the fact that we call the CadMan a “3D controller, and yet also describe it as having 6 degrees of freedom? After all, the example this far demonstrates that our Bat, as described thus far, has freedom on movement in 3 Dimensions. While true, we can extend our example to prove that that freedom to move in 3D is also highly constrained. To demonstrate this, move your hand in 3D space on and above your desktop. However, do so keeping your palm flat, parallel to the desktop with your fingers pointing directly forward. In so doing, you are still moving in 3D. Now, while moving, twist your wrist, while moving the hand, such that your palm is alternatively exposed to the left and right side. This constitutes rotation around the “Y” axis. A fourth DOF. Now add a waving motion to your hand, as if it were a paper airplane diving up and down, while also rocking left and right. But keep your fingers pointing forward. You have now added a fifth DOF, rotation around the “X” axis. Finally, add a twist to your wrist so that your fingers are no longer constrained to pointing forward. This is the sixth degree of freedom, rotation around the “Z” axis. Now don’t be fooled, this exercise could continue. We are not restricted to even six DOF. Imagine doing the above, but where the movement and rotations are measured relative to the Bat’s position and orientation, rather than to the holding/controlling hand, per se. One could imagine the Bat having a scroll wheel, like the one on most mice today. Furthermore, while flying your Bat around in 3D, that wheel could easily be rolled in either forward or backward, and thereby control the size of whatever was being controlled. Hence, with one hand we could assert simultaneous and independent control over 7 DOF in 3D space. This exercise has two intended take-aways. The first is a better working understanding between the notion of Degree of Freedom (DOF) and Dimension in space. Hopefully, the confusion frequently encountered when 3D and 6DOF are used in close context, can now be eliminated. Second, is that, with appropriate sensing, the human hand is capable of exercising control over far more degrees of freedom that six. And if we use the two hands together, the potential number of DOF that one can control goes even further. Finally, it is important to add one more take-away – one which both emerges from, and is frequently encountered when discussing, the previous two. That is, do not equate exercising simultaneous control over a high number of DOF with consciously doing the same number of different things all at once. The example that used to be thrown at me when I started talking about coordinated simultaneously bi-manual action went along the lines of, “Psychology tells us that we cannot do multiple things at once, for example, simultaneously tapping your head and rubbing your stomach. ”Well, first, I can tap my head with one hand while rubbing my stomach with the other. But that is not the point. The whole essence of skill – motor-sensory and cognitive – is “chunking” or task integration. When one appears to be doing many different things at once, if they are skilled, they are consciously doing only one thing. Playing a chord on the piano, for example, or skiing down the hill. Likewise, in flying your imaginary BAT in the previous exercise with the scroll wheel, were you doing 7 things at once, or one thing with 7 DOF? And if you had a Bat in each hand, does that mean you are now doing 14 things at once, or are you doing one thing with 14 DOF? Let me provide a different way of answering this question: if you have ever played air guitar, or “conducted” the orchestra that you are listening to on the radio, you are exercising control over more than 14 DOF. And you are doing exactly what I just said, “playing air guitar” or “conducting an orchestra”. One thing – at the conscious level, which is what matters – despite almost any one thing being able to be deconstructed into hundreds of sub-tasks. As I said the essence of skill: aggregation, or chunking. What is most important for both tool designers and users to be mindful of, is the overwhelming influence that our choice and design of tools impacts the degree to which such integration or chunking can take place. The degree to which the tool matches both the skills that we have already acquired through a lifetime of living in the everyday world, and the demands of the intended task, the more seamless that task can be performed, the more “natural” it will feel, and the less learning will be required. In my experience, it brought particular value when used bimanually, in combination with a mouse, where the preferred hand performed conventional pointing, selection and dragging tasks, while the non-preferred hand could manipulate the parameters of the thing being selected. First variation of the since the 2001 formation of 3Dconnextion. The CadMan came in 5 colours: smoke, orange, red, blue and green. See the notes for the LogiCad3D Magellan for more details on this class of device. It is the “parent” of the CadMan, and despite the change in company name, it comes from the same team."
- },
- {
- "title": "Adesso ACK-540UB USB Mini-Touch Keyboard with Touchpad",
- "company": "Adesso",
- "year": 2005,
- "primaryKey": [
- "Keyboard"
- ],
- "secondaryKey": [
- "Pad",
- "Touch"
- ],
- "originalPrice": 59.95,
- "degreesOfFreedom": 2,
- "dimensions": {
- "length": 287,
- "width": 140,
- "height": 35.5,
- "unit": "mm"
- },
- "shortDescription": "The Mini-Touch Keyboard is a surprisingly rare device: a laptop-style, small-footprint keyboard with a centrally mounted touch-pad. .",
- "longDescription": "First released in 2003 with a PS/2 connector (ACK-540PW &amp; ACK-540PB). USB version released in 2006 in either black (ACK-540UB) or white (ACK-540UW). Marketed under different brands, including SolidTek: http: //www. tigerdirect. com/applications/searchtools/item-details. asp? EdpNo=1472243https: //acecaddigital. com/index. php/products/keyboards/mini-keyboards/kb-540 Deltaco: https: //www. digitalimpuls. no/logitech/116652/deltaco-minitastatur-med-touchpad-usb"
- },
- {
- "title": "Contour Design UniTrap ",
- "company": "Contour Design",
- "year": 1999,
- "primaryKey": [
- "Re-skin"
- ],
- "secondaryKey": [
- "Mouse"
- ],
- "originalPrice": 14.99,
- "degreesOfFreedom": 2,
- "dimensions": {
- "length": 130.5,
- "width": 75.7,
- "height": 43,
- "unit": "mm"
- },
- "shortDescription": "This is a plastic shell within which the round Apple iMac G3 “Hockey Puck” mouse can be fit. While the G3 Mouse worked well mechanically, when gripped its round shape gave few cues as to its orientation. Hence, if you moved your hand up, the screen pointer may well have moved diagonally. By reskinning it with the inexpensive Contour UniTrap, the problem went away without the need to buy a whole new mouse.",
- "longDescription": "Also add back pointers from devices re-skinned"
- },
- {
- "title": "Depraz Swiss Mouse",
- "company": "Depraz",
- "year": 1980,
- "primaryKey": [
- "Mouse"
- ],
- "secondaryKey": [
- "Ball",
- "Chord",
- "Keyboard",
- "Mouse"
- ],
- "originalPrice": 295,
- "degreesOfFreedom": 2,
- "dimensions": {
- "length": 50.8,
- "width": 76.2,
- "height": 114.3,
- "unit": "mm"
- },
- "shortDescription": "This mouse is one of the first commercially available mice to be sold publicly. It is known as the Swiss mouse, and yes, the roller mechanism was designed by a Swiss watchmaker. Coincidentally, the company that made it, Depraz, is based in Apples, Switzerland. Their success in selling this mouse is what caused Logitech to switch from a software development shop to one of the world’s leading suppliers of mice and other input devices.",
- "longDescription": "DePraz began manufacturing in 1980, but following design built in 1979. Logitech started selling it in 1982. It was one of the first mass produced mice, one of the first available ball mice, as well as to have an optical shaft encoder – thereby improving linearity. An interesting fact, given its Swiss heritage, is that its designer, André Guignard, was trained as a Swiss watch maker. Unlike most modern mice, the DePraz, or “Swiss” mouse had a quasi-hemispherical shape. Hence, it was held in a so-called “power-grip”, much as one would grip a horizontally held ball – the thumb and small finger applying pressure on each side, with added support from the weight/friction of the palm on the back of the mouse. In this posture, the three middle fingers naturally positioning themselves over the three buttons mounted at the lower edge of the front. Largely freed of grip pressure, by grace of thumb and little finger, the middle fingers had essentially freedom of motion to independently operate the buttons. Each having a dedicated finger, the buttons could be easily pushed independently or in any combination. Like the three valves on a trumpet, this ability to “chord” extended the three physical buttons to have the power of seven. The down-side of this “turtle shell” form factor is that it placed the hand in a posture in which mouse movement relied more of the larger muscle groups of the arm to wrist, rather than wrist to fingers – the latter being the approach taken in most subsequent mice. The original Swiss Mouse was developed at École Polytechnique Fédérale de Lausanne by a project led by Jean-Daniel Nicoud, who was also responsible for the development of its optical shaft encoder. To augment their revenue stream, Logitech, then a software and hardware consulting company for the publishing industry, acquired marketing rights for North America. Mouse revenue quickly overshadowed that from software. In 1983, Logitech acquired DePraz, named the Swiss Mouse the “P4”, and grew to become one of the largest input device manufacturer in the world. One curious coincidence is that they were founded in the town of Apples, Switzerland."
- },
- {
- "title": "One Laptop Per Child (OLPC) XO-1",
- "company": "One Laptop Per Child (OLPC)",
- "year": 2007,
- "primaryKey": [
- "Computer"
- ],
- "secondaryKey": [
- "Keyboard",
- "Laptop",
- "Pad",
- "Slate",
- "Touch"
- ],
- "originalPrice": 199,
- "degreesOfFreedom": 2,
- "dimensions": {
- "length": 242,
- "width": 228,
- "height": 30,
- "unit": "mm"
- },
- "shortDescription": "The OLPC XO-1 is very innovative device that nevertheless raises serious issues about technology and social responsibility. It is included in the collection primarily as a warning against technological hubris, and the fact that no technologies are neutral from a social-cultural perspective.",
- "longDescription": "IntroductionI have this computer in my collection as a reminder of the delicate relationship between object and purpose, and how no matter how well one does on the former, it will likely have no impact on making a wanting concept achieve the stated (and even valid) purpose any better. I include it in the collection as a cautionary tale of how the object may help sell a concept, regardless how ill-conceived – even to those who should know better, had they applied the most basic critical thinking. For consumers, investors and designers, its story serves as a cautionary reminder to the importance of cultivating and retaining a critical mind and questioning perspective, regardless of how intrinsically seductive or well-intentioned a technology may be. From the perspective of hardware and software, what the One Laptop Per Child (OLPC) project was able to accomplish is impressive. In general, the team delivered a computer that could be produced at a remarkably low price – even if about double that which was targeted. Specifically, the display, for example, is innovative, and stands out due to its ability to work both in the bright sun (reflective) as well as in poorly lit spaces (emissive) – something that goes beyond pretty much anything else that is available on today’s (2017) slate computers or e-readers. In short, some excellent work went into this machine, something that is even more impressive, given the nature of the organization from which it emerged. The industrial design was equally impressive. Undertaken by Yves Behar’s FuseprojectUltimately, however, the machine was a means to an end, not the end itself. Rather than a device, the actual mission of the OLPC project was: … to empower the world's poorest children through education. Yet, as described by in their materials, the computer was intended to play a key role in this: With access to this type of tool [the computer], children are engaged in their own education, and learn, share, and create together. They become connected to each other, to the world and to a brighter future. Hence, making a suitable computer suitable to that purpose and the conditions where it would be used, at a price point that would enable broad distribution, was a key part of the project. The Underlying Belief System of the OLPC ProjectSince they are key to the thinking behind the OLPC project, I believe if fair to frame my discussion around the following four questions: Will giving computers to kids in the developing world improve their education? Will having a thus better-educated youth help bring a society out of poverty? Can that educational improvement be accomplished by giving the computers to the kids, with no special training for teachers? Should this be attempted on a global scale without any advance field trials or pilot studies? From the perspective of the OLPC project, the answer to every one of these questions is an unequivocal “yes”. In fact, as we shall see, any suggestion to the contrary is typically answered by condescension and/or mockery. The answers appear to be viewed as self-evident and not worth even questioning. Those who have not subscribed to this doctrine might call such a viewpoint hubris. What staggers me is how the project got so far without the basic assumptions being more broadly questioned, much less such questions being seriously addressed by the proponents. How did seemingly otherwise people commit to the project, through their labour or financial investment, given the apparently naïve and utopian approach that it took? Does the desire to do good cloud judgment that much? Are we that dazzled by a cool technology or big hairy audacious goal? Or by a charismatic personality? To explain my concern, and what this artifact represents to me, let me just touch on the four assumptions on which the project was founded. Will giving computers to kids in the developing world improve education? The literature on this question is, at best, mixed. What is clear is that one cannot make any assumption that such improvements will occur, regardless of whether one is talking about the developing world or suburban USA. For example, in January 2011, The World Bank published the following study: Can Computers Help Students Learn? From Evidence to Policy, January 2011, Number 4, The World Bank. A public-private partnership in Colombia, called Computers for Education, was created in 2002 to increase the availability of computers in public schools for use in education. Since starting, the program has installed more than 73, 000 computers in over 6, 300 public schools in more than 1, 000 municipalities. By 2008, over 2 million students and 83, 000 teachers had taken part. This document reports on a two-year study to determine the impact of the program on student performance. Students in schools that received the computers and teacher training did not do measurably better on tests than students in the control group. Nor was there a positive effect on other measures of learning. Researchers did not find any difference in test scores when they looked at specific components of math and language studies, such as algebra and geometry, and grammar and paraphrase ability in Spanish. But report also notes that results of such studies are mixed: Studies on the relationship between using computers in the classroom and improved test scores in developing countries give mixed results: A review of Israel’s Tomorrow-98 program in the mid-1990s, which put computers in schools across the country, did not find any impact on math and Hebrew language scores. But in India, a study of a computer-assisted learning program showed a significant positive impact on math scores. One thing researchers agree on, more work is needed in this field. Before moving on, a search of the literature will show that these results are consistent with those that were available in the literature at the time that the project was started. The point that I am making is not that the OLPC project could not be made to work; rather, that it was wrong to assume that it would do so without spending at least as much time designing the process to bring that about, as was expended designing the computer itself. Risk is fine, and something that can be mitigated. But diving in under the assumption that it would just work is not calculated risk, it is gambling - with other people’s lives, education and money. Will a better educated population help bring a society out of poverty? I am largely going to punt on this question. The fact is, I would be hard pressed to argue against education. But let us grant that improving education in the developing world is a good thing. The appropriate question is: is the approach of the OLPC project a reasonable or responsible way to disburse the limited resources that are available to address the educational challenges of the developing world? At the very least, I would suggest that this is a topic worthy of debate. An a priori assumption that giving computers is the right solution is akin to the, “If you build it they will come” approach seen in the movie, Field of Dreams. The problem here is that this is not a movie. There are real lives and futures that are at stake here – lives of those who cannot afford to see the movie, much less have precious resources spent on projects that are not well thought through. Can that improvement be accomplished by just giving the computers to the kids without training teachers? Remarkably, the OLPC Project’s answer is an explicit, “Yes”. In a TED talk filmed in December 2007, the founder of the OLPC initiative, Nicholas Negroponte states: “When people tell me, you know, who’s going to teach the teachers to teach the kids, I say to myself, “What planet do you come from? ” Okay, there’s not a person in this room [the TED Conference], I don’t care how techy you are, there’s not a person in this room that doesn’t give their laptop or cell phone to a kid to help them debug it. Okay, we all need help, even those of us who are very seasoned. ”Let us leave aside the naïvete of this statement stemming from the lack of distinction between ability to use applications and devices versus the ability to create and shape them. A failure of logic remains in that those unseasoned kids are part of “us”, as in “we all need help”. Where do the kids go for help? To other kids? What if they don’t know? Often they won’t. After all, the question may well have to do with a concept in calculus, rather than how to use the computer. What then? No answer is offered. Rather, those who dare raise the serious and legitimate concerns regarding teacher preparation are mockingly dismissed as coming from another planet! Well, perhaps they are. But in that case, there should at least be some debate as to who lives on which planet. Is it the people raising the question or the one dismissing the concern that lives in the real world of responsible thought and action? Can this all be accomplished without any advance field trials? Should one just immediately commit to international deployment of the program? As recently as September 2009, Negroponte took part in a panel discussion where he spoke on this matter. He states: I'd like you to imagine that I told you \"I have a technology that is going to change the quality of life. \" And then I tell you \"Really the right thing to do is to set up a pilot project to test my technology. And then the second thing to do is, once the pilot has been running for some period of time, is to go and measure very carefully the benefits of that technology. \"And then I am to tell you that what we are going to is very scientifically evaluate this technology, with control groups - giving it to some, giving it to others. And this all is very reasonable until I tell you the technology is electricity. And you say \"Wait, you don't have to do that!\"But you don't have to do that with laptops and learning either. And the fact that somebody in the room would say the impact is unclear is to me amazing - unbelievably amazing. There's not a person in this room who hasn't bought a laptop for their child, if they could afford it. And you don't know somebody who hasn't done it, if they can afford it. So there's only one question on the table and that's, “How to afford it? ” That's the only question. There is no other question - it's just the economics. And so, when One Laptop Per Child started, I didn't have the picture quite as clear as that, but we did focus on trying to get the price down. We did focus on those things. Unfortunately, Negroponte demonstrates his lack of understanding of both the history of electricity and education in this example. His historical mistake is this: yes, it was pretty obvious that electricity could bring many benefits to society. But what happened when Edison did exactly what Negroponte advocates? He almost lost his company due to his complete (but mistaken) conviction that DC, rather the AC was the correct technology to pursue. As with electricity, yes, it is rather obvious that education could bring significant benefits to the developing world. But in order to avoid making the same kind of expensive mistake that Edison did, perhaps one might want to do one’s best to make sure that the chosen technology is the AC, rather than DC, of education. A little more research, and a little less hubris might have put the investments in Edison and the OLPC to much better use. But the larger question is this: in what way is it responsible for the wealthy western world to advocate an untested and expensive (in every sense) technological solution on the poorest nations in the world? If history has taught us anything, it has taught us that just because our intentions are good, the same is not necessarily true for consequences of our actions. Later in his presentation, Negroponte states: … our problems are swimming against very naïve views of education. With this, I have to agree. It is just whose views on education are naïve, and how can such views emerge from MIT, no less, much less pass with so little critical scrutiny by the public, the press, participants, and funders? In an interview with Paul Marks, published in the New Scientist in December 2008, we see the how the techno-centric aspect of the project plays into the ostensible human centric purpose of the project. Negroponte’s retort regarding some of the initial skepticism that the project provoked was this: “When we first said we could build a laptop for $100 it was viewed as unrealistic and so 'anti-market' and so 'anti' the current laptops which at the time were around $1000 each, \" Negroponte said. \"It was viewed as pure bravado - but look what happened: the netbook market has developed in our wake. \" The project's demands for cheaper components such as keyboards, and processors nudged the industry into finding ways to cut costs, he says. \"What started off as a revolution became a culture. \"Surprise, yes, computers get smaller, faster, and cheaper over the course of time, and yes, one can even grant that the OLPC project may have accelerated that inevitable move. And, I have already stated my admiration and respect for the quality of the technology that was developed. But in the context of the overall objectives of the project, the best that one can say is, “Congratulations on meeting a milestone. ” However, by the same token, one might also legitimately question if starting with the hardware was not an instance of putting the cart before the horse. Yes, it is obviously necessary to have portable computers in the first place, before one can introduce them into the classroom, home, and donate them to children in the developing world. But it is also the case that small portable computers were already in existence and at the time that the project was initiated. While a factor of ten more expensive than the eventual target price, they were both available and adequate to support limited preliminary testing of the underlying premises of the project in an affordable manner. That is, before launching into a major - albeit well-intentioned – hardware development project, it may have been prudent to have tested the underlying premises of its motivation. Here we have to return to the raison d’être of the initiative: … to empower the world's poorest children through educationHence, the extent to which this is achieved from a given investment must be the primary metric of success, as well as the driving force of the project. Yet, that is clearly not what happened. Driven by a blind Edisonian belief in their un-tested premise, the project’s investments were overwhelmingly on the side of technology rather than pedagogy. Perhaps the nature and extent of the naïve (but well-meaning) utopian dream underlying the project is captured in the last part of the interview, above: Negroponte believes that empowering children and their parents with the educational resources offered by computers and the Internet will lead to informed decisions that improve democracy. Indeed, it has led to some gentle ribbing between himself and his brother: John Negroponte - currently deputy secretary of state in the outgoing Bush administration and the first ever director of national intelligence at the National Security Agency. \"I often joke with John that he can bring democracy his way - and I'll bring it mine, \" he says. Apparently providing inexpensive laptops to children in the developing world is not only going to raise educational standards, eradicate poverty, it is also going to bring democracy! All that, with no mention of the numerous poor non-democratic countries that have literacy levels equal to or higher than the USA (Cuba might be one reasonable example). The words naïve technological-utopianism come to mind. I began by admitting that I was conflicted in terms of this project. From the purely technological perspective, there is much to admire in the project’s accomplishments. Sadly, that was not the project’s primary objective. What appears to be missing throughout is an inability to distinguish between the technology and the purpose to which is was intended to serve. My concern in this regard is reflected in a paper by Warschauer &amp; Ames(2010). The analysis reveals that provision of individual laptops is a utopian vision for the children in the poorest countries, whose educational and social futures could be more effectively improved if the same investments were instead made on more sustainable and proven interventions. Middle- and high-income countries may have a stronger rationale for providing individual laptops to children, but will still want to eschew OLPC’s technocentric vision. In summary, OLPC represents the latest in a long line of technologically utopian development schemes that have unsuccessfully attempted to solve complex social problems with overly simplistic solutions. There is a delicate relationship between technology and society, culture, ethics, and values. What this case study reflects is the fact that technologies are not neutral. They never are. Hence, technological initiatives must be accompanied by appropriate social, cultural and ethical considerations – especially in projects such as this where the technologies are being introduced into particularly vulnerable societies. That did not happen here, The fact that this project got the support that it did, and has gone as far as it has, given the way it was approached, is why this reminder – in the form of this device – is included in the collection. And if anyone ever wonders why I am so vocal about the need for public discourse around technology, one need look no further than the OLPC project."
- }
-] \ No newline at end of file
diff --git a/src/scraping/buxton/json/incomplete.json b/src/scraping/buxton/json/incomplete.json
deleted file mode 100644
index 4b05a2a86..000000000
--- a/src/scraping/buxton/json/incomplete.json
+++ /dev/null
@@ -1,468 +0,0 @@
-[
- {
- "filename": "3DMag.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "3DPlus.docx",
- "year": "ERR__YEAR__: outer match was captured.",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "3DSpace.docx",
- "year": "ERR__YEAR__: outer match was captured.",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "3Dconnexion_SpaceNavigator.docx",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "3MErgo.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "ADB2.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "AWrock.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "Abaton.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "Active.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "AlphaSmart_Pro.docx",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured."
- },
- {
- "filename": "Apple_ADB_Mouse.docx",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured."
- },
- {
- "filename": "Apple_Mac_Portable-Katy’s MacBook Air-2.docx",
- "year": "__ERR__YEAR__TRANSFORM__: NaN cannot be parsed to a numeric value.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Apple_Mac_Portable-Katy’s MacBook Air.docx",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Apple_Scroll_Mouse.docx",
- "year": "__ERR__YEAR__TRANSFORM__: NaN cannot be parsed to a numeric value.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "Apple_iPhone.docx",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Brailler.docx",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Brewster_Stereoscope.docx",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "CasioTC500.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "Citizen_LC_909.docx",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "Citizen_LC_913.docx",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured."
- },
- {
- "filename": "Citizen_LCl_914.docx",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured."
- },
- {
- "filename": "CoolPix.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Cross.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Dymo_MK-6.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Emotiv.docx",
- "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Explorer.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Falcon.docx",
- "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Freeboard.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match was captured.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "FujitsuPalm.docx",
- "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "FujitsuTouch.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "GRiD1550-Katy’s MacBook Air-2.docx",
- "year": "__ERR__YEAR__TRANSFORM__: NaN cannot be parsed to a numeric value.",
- "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "GRiD1550-Katy’s MacBook Air.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "GRiD1550.docx",
- "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Genius_Ring_Mouse.docx",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "HTC_Touch.docx",
- "year": "__ERR__YEAR__TRANSFORM__: NaN cannot be parsed to a numeric value.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured."
- },
- {
- "filename": "Helios-Klimax.docx",
- "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Honeywell_T86.docx",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "IBMTrack.docx",
- "year": "ERR__YEAR__: outer match was captured.",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "IBM_Convertable-Katy’s MacBook Air-2.docx",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "IBM_Convertable-Katy’s MacBook Air.docx",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "IBM_Convertable.docx",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "IBM_PS2_Mouse.docx",
- "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "IBM_Simon.docx",
- "year": "__ERR__YEAR__TRANSFORM__: NaN cannot be parsed to a numeric value."
- },
- {
- "filename": "IDEO.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Joyboard.docx",
- "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Kensington_SB_TB-Mouse.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "Leatherman_Tread.docx",
- "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "M1.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured."
- },
- {
- "filename": "MS-1_Stereoscope.docx",
- "year": "__ERR__YEAR__TRANSFORM__: NaN cannot be parsed to a numeric value.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured."
- },
- {
- "filename": "MWB_Braille_Writer.docx",
- "company": "ERR__COMPANY__: outer match wasn't captured.",
- "year": "__ERR__YEAR__TRANSFORM__: NaN cannot be parsed to a numeric value.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured."
- },
- {
- "filename": "MaltronLH.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured."
- },
- {
- "filename": "Marine_Band_Harmonica.docx",
- "company": "ERR__COMPANY__: outer match was captured.",
- "year": "ERR__YEAR__: outer match was captured.",
- "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Matrox.docx",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured."
- },
- {
- "filename": "Metaphor_Kbd.docx",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Metaphor_Mouse.docx",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Motorola_DynaTAC.docx",
- "year": "__ERR__YEAR__TRANSFORM__: NaN cannot be parsed to a numeric value.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "NewO.docx",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "Newton120.docx",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Nikon_Coolpix-100.docx",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "Numonics_Mgr_Mouse.docx",
- "company": "ERR__COMPANY__: outer match was captured.",
- "year": "ERR__YEAR__: outer match was captured.",
- "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "PadMouse.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "PowerTrack.docx",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "ProAgio.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "Pulsar_time_Computer.docx",
- "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured."
- },
- {
- "filename": "Ring.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "SafeType_Kbd.docx",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured."
- },
- {
- "filename": "Samsung_SPH-A500.docx",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "SurfMouse.docx",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured."
- },
- {
- "filename": "TPARCtab.docx",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "Thumbelina.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured."
- },
- {
- "filename": "adecm.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "eMate.docx",
- "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "gravis.docx",
- "year": "ERR__YEAR__: outer match was captured.",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "iGesture.docx",
- "year": "__ERR__YEAR__TRANSFORM__: NaN cannot be parsed to a numeric value.",
- "primaryKey": "ERR__PRIMARYKEY__: outer match was captured.",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "iGrip.docx",
- "shortDescription": "ERR__SHORTDESCRIPTION__: outer match was captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- },
- {
- "filename": "iLiad.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "degreesOfFreedom": "ERR__DEGREESOFFREEDOM__: outer match wasn't captured."
- },
- {
- "filename": "round.docx",
- "secondaryKey": "ERR__SECONDARYKEY__: outer match wasn't captured.",
- "originalPrice": "ERR__ORIGINALPRICE__: outer match wasn't captured.",
- "dimensions": "ERR__DIMENSIONS__: outer match wasn't captured.",
- "longDescription": "ERR__LONGDESCRIPTION__: outer match was captured."
- }
-] \ No newline at end of file
diff --git a/src/scraping/buxton/node_scraper.ts b/src/scraping/buxton/node_scraper.ts
deleted file mode 100644
index ab6c9dcb2..000000000
--- a/src/scraping/buxton/node_scraper.ts
+++ /dev/null
@@ -1,256 +0,0 @@
-import { readdirSync, writeFile, existsSync, mkdirSync } from "fs";
-import * as path from "path";
-import { red, cyan, yellow, green } from "colors";
-import { Opt } from "../../new_fields/Doc";
-const StreamZip = require('node-stream-zip');
-
-export interface DeviceDocument {
- title: string;
- shortDescription: string;
- longDescription: string;
- company: string;
- year: number;
- originalPrice: number;
- degreesOfFreedom: number;
- dimensions: string;
- primaryKey: string;
- secondaryKey: string;
-}
-
-interface AnalysisResult {
- device?: DeviceDocument;
- errors?: any;
-}
-
-type Converter<T> = (raw: string) => { transformed?: T, error?: string };
-
-interface Processor<T> {
- exp: RegExp;
- matchIndex?: number;
- transformer?: Converter<T>;
-}
-
-const RegexMap = new Map<keyof DeviceDocument, Processor<any>>([
- ["title", {
- exp: /contact\s+(.*)Short Description:/
- }],
- ["company", {
- exp: /Company:\s+([^\|]*)\s+\|/,
- transformer: (raw: string) => ({ transformed: raw.replace(/\./g, "") })
- }],
- ["year", {
- exp: /Year:\s+([^\|]*)\s+\|/,
- transformer: numberValue
- }],
- ["primaryKey", {
- exp: /Primary:\s+(.*)(Secondary|Additional):/,
- transformer: collectUniqueTokens
- }],
- ["secondaryKey", {
- exp: /(Secondary|Additional):\s+([^\{\}]*)Links/,
- transformer: collectUniqueTokens,
- matchIndex: 2
- }],
- ["originalPrice", {
- exp: /Original Price \(USD\)\:\s+\$([0-9\.]+)/,
- transformer: numberValue
- }],
- ["degreesOfFreedom", {
- exp: /Degrees of Freedom:\s+([0-9]+)/,
- transformer: numberValue
- }],
- ["dimensions", {
- exp: /Dimensions\s+\(L x W x H\):\s+([0-9\.]+\s+x\s+[0-9\.]+\s+x\s+[0-9\.]+\s\([A-Za-z]+\))/,
- transformer: (raw: string) => {
- const [length, width, group] = raw.split(" x ");
- const [height, unit] = group.split(" ");
- return {
- transformed: {
- length: Number(length),
- width: Number(width),
- height: Number(height),
- unit: unit.replace(/[\(\)]+/g, "")
- }
- };
- }
- }],
- ["shortDescription", {
- exp: /Short Description:\s+(.*)Bill Buxton[’']s Notes/,
- transformer: correctSentences
- }],
- ["longDescription", {
- exp: /Bill Buxton[’']s Notes(.*)Device Details/,
- transformer: correctSentences
- }],
-]);
-
-function numberValue(raw: string) {
- const transformed = Number(raw);
- if (isNaN(transformed)) {
- return { error: `${transformed} cannot be parsed to a numeric value.` };
- }
- return { transformed };
-}
-
-function collectUniqueTokens(raw: string) {
- return { transformed: Array.from(new Set(raw.replace(/,|\s+and\s+/g, " ").split(/\s+/).map(token => token.toLowerCase().trim()))).map(capitalize).sort() };
-}
-
-function correctSentences(raw: string) {
- raw = raw.replace(/\./g, ". ").replace(/\:/g, ": ").replace(/\,/g, ", ").replace(/\?/g, "? ").trimRight();
- raw = raw.replace(/\s{2,}/g, " ");
- return { transformed: raw };
-}
-
-const outDir = path.resolve(__dirname, "json");
-const successOut = "buxton.json";
-const failOut = "incomplete.json";
-const deviceKeys = Array.from(RegexMap.keys());
-
-function printEntries(zip: any) {
- const { entriesCount } = zip;
- console.log(`Recognized ${entriesCount} entr${entriesCount === 1 ? "y" : "ies"}.`);
- for (const entry of Object.values<any>(zip.entries())) {
- const desc = entry.isDirectory ? 'directory' : `${entry.size} bytes`;
- console.log(`${entry.name}: ${desc}`);
- }
-}
-
-async function wordToPlainText(pathToDocument: string): Promise<string> {
- const zip = new StreamZip({ file: pathToDocument, storeEntries: true });
- const contents = await new Promise<string>((resolve, reject) => {
- zip.on('ready', () => {
- let body = "";
- zip.stream("word/document.xml", (error: any, stream: any) => {
- if (error) {
- reject(error);
- }
- stream.on('data', (chunk: any) => body += chunk.toString());
- stream.on('end', () => {
- resolve(body);
- zip.close();
- });
- });
- });
- });
- let body = "";
- const components = contents.toString().split('<w:t');
- for (const component of components) {
- const tags = component.split('>');
- const content = tags[1].replace(/<.*$/, "");
- body += content;
- }
- return body;
-}
-
-function tryGetValidCapture(matches: RegExpExecArray | null, matchIndex: number): Opt<string> {
- let captured: string;
- if (!matches || !(captured = matches[matchIndex])) {
- return undefined;
- }
- const lower = captured.toLowerCase();
- if (/to come/.test(lower)) {
- return undefined;
- }
- if (lower.includes("xxx")) {
- return undefined;
- }
- if (!captured.toLowerCase().replace(/[….\s]+/g, "").length) {
- return undefined;
- }
- return captured;
-}
-
-function capitalize(word: string): string {
- const clean = word.trim();
- if (!clean.length) {
- return word;
- }
- return word.charAt(0).toUpperCase() + word.slice(1);
-}
-
-function analyze(path: string, body: string): AnalysisResult {
- const device: any = {};
-
- const segments = path.split("/");
- const filename = segments[segments.length - 1].replace("Bill_Notes_", "");
-
- const errors: any = { filename };
-
- for (const key of deviceKeys) {
- const { exp, transformer, matchIndex } = RegexMap.get(key)!;
- const matches = exp.exec(body);
-
- let captured = tryGetValidCapture(matches, matchIndex ?? 1);
- if (!captured) {
- errors[key] = `ERR__${key.toUpperCase()}__: outer match ${matches === null ? "wasn't" : "was"} captured.`;
- continue;
- }
-
- captured = captured.replace(/\s{2,}/g, " ");
- if (transformer) {
- const { error, transformed } = transformer(captured);
- if (error) {
- errors[key] = `__ERR__${key.toUpperCase()}__TRANSFORM__: ${error}`;
- continue;
- }
- captured = transformed;
- }
-
- device[key] = captured;
- }
-
- const errorKeys = Object.keys(errors);
- if (errorKeys.length > 1) {
- console.log(red(`\n@ ${cyan(filename.toUpperCase())}...`));
- errorKeys.forEach(key => key !== "filename" && console.log(red(errors[key])));
- return { errors };
- }
-
- return { device };
-}
-
-async function parseFiles(): Promise<DeviceDocument[]> {
- const sourceDirectory = path.resolve(`${__dirname}/source`);
- const candidates = readdirSync(sourceDirectory).filter(file => file.endsWith(".doc") || file.endsWith(".docx")).map(file => `${sourceDirectory}/${file}`);
- const imported = await Promise.all(candidates.map(async path => ({ path, body: await wordToPlainText(path) })));
- // const imported = [{ path: candidates[10], body: await extract(candidates[10]) }];
- const data = imported.map(({ path, body }) => analyze(path, body));
- const masterDevices: DeviceDocument[] = [];
- const masterErrors: any[] = [];
- data.forEach(({ device, errors }) => {
- if (device) {
- masterDevices.push(device);
- } else {
- masterErrors.push(errors);
- }
- });
- const total = candidates.length;
- if (masterDevices.length + masterErrors.length !== total) {
- throw new Error(`Encountered a ${masterDevices.length} to ${masterErrors.length} mismatch in device / error split!`);
- }
- console.log();
- await writeOutputFile(successOut, masterDevices, total, true);
- await writeOutputFile(failOut, masterErrors, total, false);
- console.log();
-
- return masterDevices;
-}
-
-async function writeOutputFile(relativePath: string, data: any[], total: number, success: boolean) {
- console.log(yellow(`Encountered ${data.length} ${success ? "valid" : "invalid"} documents out of ${total} candidates. Writing ${relativePath}...`));
- return new Promise<void>((resolve, reject) => {
- const destination = path.resolve(outDir, relativePath);
- const contents = JSON.stringify(data, undefined, 4);
- writeFile(destination, contents, err => err ? reject(err) : resolve());
- });
-}
-
-export async function main() {
- if (!existsSync(outDir)) {
- mkdirSync(outDir);
- }
- return parseFiles();
-}
-
-main(); \ No newline at end of file
diff --git a/src/server/ApiManagers/UtilManager.ts b/src/server/ApiManagers/UtilManager.ts
index dbf274e93..4cb57a4e7 100644
--- a/src/server/ApiManagers/UtilManager.ts
+++ b/src/server/ApiManagers/UtilManager.ts
@@ -1,10 +1,9 @@
import ApiManager, { Registration } from "./ApiManager";
import { Method } from "../RouteManager";
import { exec } from 'child_process';
-import { command_line } from "../ActionUtilities";
import RouteSubscriber from "../RouteSubscriber";
import { red } from "colors";
-import { main } from "../../scraping/buxton/node_scraper";
+import executeImport from "../../scraping/buxton/final/BuxtonImporter";
export default class UtilManager extends ApiManager {
@@ -43,26 +42,7 @@ export default class UtilManager extends ApiManager {
register({
method: Method.GET,
subscription: "/buxton",
- secureHandler: async ({ res }) => {
- const cwd = './src/scraping/buxton';
-
- const onResolved = (stdout: string) => { console.log(stdout); res.redirect("/"); };
- const onRejected = (err: any) => { console.error(err.message); res.send(err); };
- const tryPython3 = (reason: any) => {
- console.log("Initial scraper failed for the following reason:");
- console.log(red(reason.Error));
- console.log("Falling back to python3...");
- return command_line('python3 scraper.py', cwd).then(onResolved, onRejected);
- };
-
- return command_line('python scraper.py', cwd).then(onResolved, tryPython3);
- },
- });
-
- register({
- method: Method.GET,
- subscription: "/newBuxton",
- secureHandler: async ({ res }) => res.send(await main())
+ secureHandler: async ({ res }) => res.send(await executeImport())
});
register({
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 27c4bf854..26d6423e9 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -162,6 +162,11 @@ export namespace DashUploadUtils {
type: string;
}
+ export interface ImageResizer {
+ resizer?: sharp.Sharp;
+ suffix: SizeSuffix;
+ }
+
/**
* Based on the url's classification as local or remote, gleans
* as much information as possible about the specified image
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index db20d10f2..efee42f63 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -58,8 +58,8 @@ export class CurrentUserUtils {
{ title: "todo item", icon: "check", ignoreClick: true, drag: 'getCopy(this.dragFactory, true)', dragFactory: notes[notes.length - 1] },
{ title: "web page", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", {_width: 300, _height: 300, title: "New Webpage" })' },
{ title: "cat image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 200, title: "an image of a cat" })' },
+ { title: "buxton", icon: "cloud-upload-alt", ignoreClick: true, drag: "Docs.Create.Buxton()" },
{ title: "webcam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { width: 400, height: 400, title: "a test cam" })' },
- { title: "buxton", icon: "faObjectGroup", ignoreClick: true, drag: "Docs.Create.Buxton()" },
{ title: "record", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` },
{ title: "clickable button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, title: "Button" })' },
{ title: "presentation", icon: "tv", click: 'openOnRight(Doc.UserDoc().curPresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().curPresentation = getCopy(this.dragFactory,true)`, dragFactory: emptyPresentation },