Skip to content

Commit

Permalink
Merge pull request #18 from ngwese/feature/file
Browse files Browse the repository at this point in the history
file handling improvements
  • Loading branch information
ngwese authored Mar 4, 2018
2 parents 9d1aab6 + ac552b4 commit f3695bc
Show file tree
Hide file tree
Showing 31 changed files with 1,744 additions and 192 deletions.
12 changes: 12 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Chrome",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceRoot}/app/src"
}
]
}
5 changes: 4 additions & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"classname": "^0.0.0",
"immutable": "^3.8.2",
"parse-filepath": "^1.0.2",
"react": "^16.2.0",
"react-ace": "^5.9.0",
"react-dom": "^16.2.0",
"react-modal": "^3.2.1",
"react-redux": "^5.0.6",
"react-scripts": "1.0.17",
"react-split-pane": "^0.1.74",
Expand All @@ -23,7 +26,7 @@
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"proxy": "http://nnnn.local:5000",
"proxy": "http://localhost:5000",
"devDependencies": {
"redux-devtools": "^3.4.1"
}
Expand Down
48 changes: 39 additions & 9 deletions app/src/api.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import rest from 'rest';
import mime from 'rest/interceptor/mime';
import parsePath from 'parse-filepath';

const API_ROOT = '/api/v1'

function api_path(p) {
function apiPath(p) {
return API_ROOT + p;
}

// TODO: once API is stateless move this to be a method
export function siblingScriptResourceForName(name, siblingResource) {
let resourceBase = apiPath('/scripts/');
if (siblingResource) {
// FIXME: this assumes siblingResource is absolute and lacks an authority
resourceBase = parsePath(siblingResource).dirname + '/';
}
return resourceBase + encodeURI(name);
}

// TODO: switch all this to just use fetch and remove 'rest'
// TODO: switch from snake to camel case
class API {
Expand All @@ -17,15 +28,15 @@ class API {
list_scripts(cb) {
const request = {
method: 'GET',
path: api_path('/scripts'),
path: apiPath('/scripts'),
};
this.client(request).then(cb);
}

read_script(url, cb) {
this.client(url).then(cb);
readScript(resource, cb) {
fetch(resource).then(cb)
}

// https://stackoverflow.com/questions/40284338/react-fetch-delete-and-put-requests
write_script(resource, code, cb) {
const formData = new FormData();
Expand All @@ -35,7 +46,26 @@ class API {
body: formData,
}).then(cb)
}


deleteScript(resource, cb) {
fetch(resource, {
method: 'DELETE'
}).then(cb)
}

renameScript(resource, name, cb) {
const formData = new FormData();
formData.append('name', name)
fetch(resource, {
method: 'PATCH',
body: formData,
}).then(cb)
}

createFolder() {
console.log("api.createFolder() not implemented")
}

list_repl_endpoints(cb) {
fetch('/repl-endpoints.json').then((response) => {
// TODO: parse url and insert hostname if not specified
Expand All @@ -46,12 +76,12 @@ class API {
resourceForScript(name, path) {
// TODO: would be good to clean up and normalize urls
// TODO: implement path (subdir) support
return api_path(name);
return apiPath(name);
}

fileFromResource(resource) {
// FIXME: this totally breaks the encapsulation of script resources and returns what matron would see as the script path
let prefix = api_path('/scripts/')
// MAINT: this totally breaks the encapsulation of script resources and returns what matron would see as the script path
let prefix = apiPath('/scripts/')
return resource.split(prefix)[1];
}
}
Expand Down
24 changes: 24 additions & 0 deletions app/src/api.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { siblingScriptResourceForName } from './api';

it('no sibling returns something', () => {
let r = siblingScriptResourceForName('untitled.lua');
expect(r).toEqual('/api/v1/scripts/untitled.lua');
})

it('should escape filename', () => {
let r = siblingScriptResourceForName('one two.lua');
expect(r).toEqual('/api/v1/scripts/one%20two.lua');
})

it('root sibling should match no sibling', () => {
let r = siblingScriptResourceForName('untitled.lua');
let q = siblingScriptResourceForName('untitled.lua', '/api/v1/scripts/foo.lua');
expect(q).toEqual(r);
})

it('non-root sibling works', () => {
let r = siblingScriptResourceForName('foo.lua', '/api/v1/scripts/one/two/three.lua');
expect(r).toEqual('/api/v1/scripts/one/two/foo.lua');
})

// TODO: add tests for fileFromResource
2 changes: 1 addition & 1 deletion app/src/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';

it('renders without crashing', () => {
xit('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
});
91 changes: 74 additions & 17 deletions app/src/bound-edit-activity.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,24 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import EditActivity from './edit-activity';
import { MATRON_COMPONENT } from './constants';
import { nodeForResource } from './model/listing';

import {
scriptList,
scriptRead,
scriptDirRead,
scriptSave,
scriptChange,
scriptSelect,

scriptNew,
scriptDuplicate,
scriptDelete,
scriptRename,

toolInvoke,

explorerActiveNode,
explorerToggleNode,
} from './model/script-actions';

import {
Expand All @@ -24,30 +33,55 @@ import {

const getBuffers = (scriptState) => scriptState.buffers;
const getActiveBuffer = (scriptState) => scriptState.activeBuffer;
const getListing = (scriptState) => scriptState.listing;
const getRootNodes = (scriptState) => scriptState.rootNodes;
const getExpandedNodes = (scriptState) => scriptState.expandedNodes;

const getScriptListing = createSelector(
[getBuffers, getActiveBuffer, getListing],
(buffers, activeBuffer, listing) => {
[getBuffers, getActiveBuffer, getRootNodes, getExpandedNodes],
(buffers, activeBuffer, rootNodes, expandedNodes) => {
// enrich script listing w/ modification state, etc.
return listing.toJS().map(l => {
let item = Object.assign({}, l);
item.active = l.url === activeBuffer;

let buffer = buffers.get(l.url);
if (buffer) {
item.loaded = true;
item.modified = buffer.get('modified') || false;
}

return item;
});

let enrich = (items) => {
return items.map(l => {
let item = {...l}
item.active = l.url === activeBuffer;
item.toggled = expandedNodes.has(l.url);

let buffer = buffers.get(l.url);
if (buffer) {
item.loaded = true;
item.modified = buffer.get('modified') || false;
}

if (item.children) {
item.children = enrich(item.children)
}

return item;
})
};

// MAINT: this assumes the "scripts" root is the first node in the list
// console.log("rootNode= ", rootNodes.toJS())
let scriptRoot = rootNodes.getIn([0, "children"])
if (scriptRoot) {
return enrich(scriptRoot.toJS());
}
return [];
});

const getActiveNode = createSelector(
[getActiveBuffer, getRootNodes],
(activeBuffer, rootNodes) => {
return nodeForResource(rootNodes, activeBuffer)
}
)

const mapStateToProps = (state) => {
let {activeBuffer, buffers} = state.scripts;
return {
activeBuffer,
activeBuffer,
activeNode: getActiveNode(state.scripts),
buffers,
sidebar: state.sidebar,
scriptListing: getScriptListing(state.scripts),
Expand All @@ -63,6 +97,9 @@ const mapDispatchToProps = (dispatch) => {
scriptRead: (api, resource) => {
dispatch(scriptRead(api, resource))
},
scriptDirRead: (api, resource) => {
dispatch(scriptDirRead(api, resource))
},
scriptChange: (resource, value) => {
dispatch(scriptChange(resource, value))
},
Expand Down Expand Up @@ -90,6 +127,26 @@ const mapDispatchToProps = (dispatch) => {
toolInvoke: (name) => {
dispatch(toolInvoke(name))
},

// explorer
explorerActiveNode: (node) => {
dispatch(explorerActiveNode(node))
},
explorerToggleNode: (node, toggled) => {
dispatch(explorerToggleNode(node, toggled))
},
explorerScriptNew: (sibling, value) => {
dispatch(scriptNew(sibling, value))
},
explorerScriptDuplicate: (source) => {
dispatch(scriptDuplicate(source))
},
explorerScriptDelete: (api, resource) => {
dispatch(scriptDelete(api, resource))
},
explorerScriptRename: (api, activeNode, newName, virtual) => {
dispatch(scriptRename(api, activeNode, newName, virtual))
},
}
}

Expand Down
7 changes: 7 additions & 0 deletions app/src/bound-workspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import {
replConnect,
} from './model/repl-actions';

import {
scriptList,
} from './model/script-actions';

const mapStateToProps = (state) => {
const selected = state.activity.selected;
const endpoints = state.repl.endpoints;
Expand All @@ -34,6 +38,9 @@ const mapDispatchToProps = (dispatch) => {
replConnect: (component, endpoint) => {
dispatch(replConnect(component, endpoint))
},
scriptList: (api) => {
dispatch(scriptList(api))
},
}
}

Expand Down
10 changes: 7 additions & 3 deletions app/src/constants.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export const UNTITLED_SCRIPT = 'UNTITLED_SCRIPT';
import { Set } from "immutable";

export const MATRON_COMPONENT = 'matron';
export const CRONE_COMPONENT = 'crone';
export const UNTITLED_SCRIPT = "UNTITLED_SCRIPT";

export const MATRON_COMPONENT = "matron";
export const CRONE_COMPONENT = "crone";

export const INVALID_NAME_CHARS = new Set(["/"]);
Loading

0 comments on commit f3695bc

Please sign in to comment.