4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | Web Term
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/history.jsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { OutputIndicator } from './indicators';
3 | import * as el from './history.styles';
4 |
5 | export function OutputValue({ output }) {
6 | if (Array.isArray(output)) {
7 | return (
8 |
9 | {output.map((item, i) => (
10 |
11 |
12 |
13 | ))}
14 |
15 | );
16 | } else if (typeof output === 'string' || output === null) {
17 | return output;
18 | } else if (output instanceof Error || output instanceof URL) {
19 | return output.toString();
20 | } else {
21 | throw new Error(
22 | `we have not implemented rendering for this type of output yet: ${typeof output}`,
23 | );
24 | }
25 | }
26 |
27 | export function History({ home, history }) {
28 | return (
29 | {
31 | if (ref) ref.scrollTop = ref.scrollHeight;
32 | }}
33 | >
34 |
35 | {history.map(result => (
36 |
41 | ))}
42 |
43 |
44 | );
45 | }
46 |
47 | export function CommandResult({ home, result }) {
48 | let isHome = home.origin === result.url.origin;
49 | return (
50 |
51 |
52 |
53 | {result.command ? (
54 |
55 | {result.command.name}{' '}
56 | {result.command.args.join(' ')}
57 |
58 | ) : (
59 | result.input
60 | )}
61 |
62 |
63 | {isHome ? '~' : ''}
64 | {result.url.pathname}
65 |
66 | {result.output ? (
67 |
68 |
69 |
70 |
71 | ) : null}
72 |
73 | );
74 | }
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WebTerm
2 |
3 | In his original blog post, [Reimagining the browser as a Network OS][network-os], Paul Frazee outlines the idea of a terminal for the web, and more importantly, what an OS built on top of the p2p web might look like. You can view his [original pull request here][original-pr]. This app is a personal attempt at continuing his work.
4 |
5 | [network-os]: https://pfrazee.hashbase.io/blog/reimagining-the-browser-as-a-network-os
6 | [original-pr]: https://github.com/beakerbrowser/beaker/pull/589
7 |
8 | ## Ideas & Goals
9 |
10 | Although this project is heavily inspired by Paul's PR, it is a ground up rewrite, and it's still very early on in its development. Some bits are still missing. Ultimately I have a few personal goals with this experiment:
11 |
12 | * **It's just a Web App** – The original PR was based on a `term://` protocol built into Beaker itself. Although I'm not ruling out the possibility of this being built into Beaker eventually, most of the current features don't _require_ this to be built into the browser. I want to see how far we can get in userland with nothing but Beaker's core APIs.
13 | * **It's just Dat** - In this experiment, your file system is built on top of dat. Consiquentially, this experiment should take full advantage of everything dat has to offer. You should be able to:
14 | 1. Easily sync your workspace between multiple devices.
15 | 2. Quickly undo changes to your filesystem (never fear `rm -rf` again).
16 | 3. Easily collaborate with multiple authors on projects (w/ multiwritter)
17 | * **User Friendliness** – The terminal is notoriously scary for new developers. What if we changed that? What if commands and their APIs were discoverable? What if documentation was consistantly available? How can we maintain the power of the terminal while simultaneously removing some of its footguns?
18 | * **Multi Language** – One important piece that I would like to explore is the ability to write commands with Web Assembly. Although the current wasm API is limited I would like to provide first-class support in the command spec. That way if you want to write your commands in Rust or something similar and run them in the browser, you can.
19 |
20 | ## Contributing
21 |
22 | The current project is so young that I'm not really looking for contributors ATM, but nevertheless I would love to hear your ideas. Feel free to [post an issue][] or message me [on twitter][]. I also stream this project's development live [on twitch][].
23 |
24 | [on twitter]: https://twitter.com/webdesserts
25 | [on twitch]: https://www.twitch.tv/webdesserts
26 | [post an issue]: https://github.com/webdesserts/webterm/issues
27 |
--------------------------------------------------------------------------------
/src/prompt.jsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import { InputIndicator } from './indicators';
5 |
6 | const CenteredContent = styled.div`
7 | margin: 0 auto;
8 | max-width: 600px;
9 | width: 100%;
10 | display: grid;
11 | grid-template-columns: 24px 1fr;
12 | grid-gap: 8px 0;
13 | padding: 16px 16px 16px 24px;
14 | align-items: baseline;
15 | `;
16 |
17 | const Location = styled.div`
18 | grid-column: span 2;
19 | color: var(--light-grey);
20 | `;
21 |
22 | const Input = styled.input.attrs({ type: 'text' })`
23 | background-color: transparent;
24 | border: none;
25 | color: var(--white);
26 | font: inherit;
27 | border-bottom: 1px solid var(--light-grey);
28 | padding: 4px 8px;
29 | &:focus {
30 | outline: none;
31 | border-color: var(--magenta);
32 | }
33 | `;
34 |
35 | export class Prompt extends React.Component {
36 | static propTypes = {
37 | onSubmit: PropTypes.func,
38 | history: PropTypes.array,
39 | url: PropTypes.instanceOf(URL),
40 | };
41 |
42 | static defaultProps = {
43 | onSubmit: () => {},
44 | };
45 |
46 | state = {
47 | value: '',
48 | historyIndex: null,
49 | };
50 |
51 | componentWillReceiveProps(nextProps) {
52 | this.setState({ historyIndex: nextProps.history.length });
53 | }
54 |
55 | onKeyDown = event => {
56 | let { history, onSubmit } = this.props;
57 | let { value, historyIndex: currentHistoryIndex } = this.state;
58 |
59 | if (event.key === 'Enter') {
60 | onSubmit(value);
61 | this.setState({ value: '' });
62 | } else if (event.key === 'ArrowUp') {
63 | let historyIndex = currentHistoryIndex - 1;
64 | let command = history[historyIndex];
65 | if (command) {
66 | this.setState({
67 | historyIndex,
68 | value: command.input,
69 | });
70 | }
71 | } else if (event.key === 'ArrowDown') {
72 | let historyIndex = currentHistoryIndex + 1;
73 | let command = history[historyIndex];
74 | if (command) {
75 | this.setState({
76 | historyIndex,
77 | value: command.input,
78 | });
79 | }
80 | }
81 | };
82 |
83 | onChange = event => {
84 | let { value } = event.target;
85 | this.setState({ value });
86 | };
87 |
88 | render() {
89 | let { isHome, url } = this.props;
90 | let { value } = this.state;
91 | return (
92 |
93 |
94 |
95 | {isHome ? '~' : ''}
96 | {url.pathname}
97 |
98 |
99 |
106 |
107 |
108 | );
109 | }
110 | }
111 |
--------------------------------------------------------------------------------