Skip to content

Commit 2b901db

Browse files
committed
ANDROID: file manager webui (wip)
1 parent 40dcbd9 commit 2b901db

File tree

2 files changed

+122
-39
lines changed

2 files changed

+122
-39
lines changed

src/platform/android/app/src/main/java/net/sourceforge/smallbasic/WebServer.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
public abstract class WebServer {
3535
private static final int BUFFER_SIZE = 32768;
3636
private static final int SEND_SIZE = BUFFER_SIZE / 4;
37+
private static final String UTF_8 = "utf-8";
3738

3839
public WebServer() {
3940
super();
@@ -67,7 +68,7 @@ public void run() {
6768
private Map<String, Collection<String>> getParameters(String url) throws IOException {
6869
Map<String, Collection<String>> result = new HashMap<>();
6970
try {
70-
String[] args = URLDecoder.decode(url, "utf-8").split("[?]");
71+
String[] args = URLDecoder.decode(url, UTF_8).split("[?]");
7172
if (args.length == 2) {
7273
for (String arg : args[1].split("&")) {
7374
String[] field = arg.split("=");
@@ -113,7 +114,7 @@ private Map<String, String> getPostData(InputStream inputStream, final String li
113114
int eq = nextField.indexOf("=");
114115
if (eq != -1) {
115116
String key = nextField.substring(0, eq);
116-
String value = URLDecoder.decode(nextField.substring(eq + 1), "utf-8");
117+
String value = URLDecoder.decode(nextField.substring(eq + 1), UTF_8);
117118
result.put(key, value);
118119
}
119120
}
@@ -144,10 +145,22 @@ private Response handleDownload(Map<String, Collection<String>> parameters) thro
144145
return new Response(new ByteArrayInputStream(outputStream.toByteArray()), outputStream.size());
145146
}
146147

148+
/**
149+
* Handler for failed token validation
150+
*/
151+
private Response handleError(String error) throws IOException {
152+
JsonBuilder out = new JsonBuilder();
153+
out.append('{');
154+
out.append("error", error, false);
155+
out.append('}');
156+
return new Response(out.getBytes());
157+
}
158+
147159
/**
148160
* Handler for files API
149161
*/
150162
private Response handleFileList() throws IOException {
163+
log("sending file list");
151164
JsonBuilder builder = new JsonBuilder();
152165
builder.append('[');
153166
long id = 0;
@@ -252,6 +265,7 @@ private void runServer(final int socketNum, final String token) throws IOExcepti
252265
handlePost(socket, postData, fields[1]);
253266
} else {
254267
log("Invalid token");
268+
handleError("invalid token").send(socket);
255269
}
256270
} else {
257271
log("Invalid request");
@@ -328,7 +342,7 @@ void append(String field, long value, boolean nextField) {
328342
}
329343

330344
byte[] getBytes() throws UnsupportedEncodingException {
331-
return json.toString().getBytes("utf-8");
345+
return json.toString().getBytes(UTF_8);
332346
}
333347
}
334348

@@ -344,6 +358,16 @@ public Response(InputStream inputStream, long length) {
344358
this.length = length;
345359
}
346360

361+
public Response(String data) throws IOException {
362+
this.inputStream = new ByteArrayInputStream(data.getBytes(UTF_8));
363+
this.length = data.length();
364+
}
365+
366+
public Response(byte[] bytes) {
367+
this.inputStream = new ByteArrayInputStream(bytes);
368+
this.length = bytes.length;
369+
}
370+
347371
InputStream getInputStream() {
348372
return inputStream;
349373
}

src/platform/android/webui/src/App.js

Lines changed: 95 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
AppBar,
88
Box,
99
Button,
10+
CssBaseline,
1011
Link,
1112
TextField,
1213
Toolbar,
@@ -58,11 +59,19 @@ const columns = [{
5859
},
5960
}];
6061

62+
function getFetchHeader(body) {
63+
return {
64+
method: 'POST',
65+
headers: {'Content-Type': 'application/text;charset=utf-8'},
66+
body: body
67+
};
68+
}
69+
6170
function GridToolbarDownload(props) {
6271
let color = useTheme().palette.primary.main;
6372
let args = "";
6473
let join = "";
65-
console.log(props.rows);
74+
6675
props.selections.forEach(i => {
6776
args += join + "f=" + encodeURIComponent(props.rows[i].fileName);
6877
join = "&";
@@ -92,8 +101,91 @@ function AppToolbar(props) {
92101

93102
function FileList(props) {
94103
const [selectionModel, setSelectionModel] = useState([]);
104+
105+
return (
106+
<DataGrid rows={props.rows}
107+
columns={columns}
108+
pageSize={5}
109+
components={{Toolbar: AppToolbar}}
110+
componentsProps={{toolbar: {selections: selectionModel, rows: props.rows}}}
111+
onSelectionModelChange={(model) => setSelectionModel(model)}
112+
selectionModel={selectionModel}
113+
rowsPerPageOptions={[5]}
114+
checkboxSelection
115+
disableSelectionOnClick/>
116+
);
117+
}
118+
119+
function TokenInput(props) {
120+
const [token, setToken] = useState("");
121+
const [error, setError] = useState(false);
122+
123+
const onClick = () => {
124+
const validate = async () => {
125+
let response = await fetch('/api/files', getFetchHeader("token=" + token));
126+
let data = await response.json();
127+
if (data.error) {
128+
setError(true);
129+
} else {
130+
props.setRows(data);
131+
props.setToken(token);
132+
}
133+
};
134+
if (token) {
135+
validate().catch(console.error);
136+
}
137+
};
138+
139+
const onChange = (event) => {
140+
setToken(event.target.value);
141+
if (!event.target.value) {
142+
setError(false);
143+
}
144+
};
145+
146+
const onKeyPress = (event) => {
147+
if (event.key === 'Enter') {
148+
onClick();
149+
}
150+
};
151+
152+
const help = "Enter the access token displayed on the SmallBASIC [About] screen.";
153+
let helperText = error ? "Invalid token. " + help : help;
154+
155+
return (
156+
<Box sx={{display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%'}}>
157+
<Box sx={{display: 'flex', flexDirection: 'column', marginBottom: '10em'}}>
158+
<Box sx={{marginBottom: '1em'}}>
159+
<TextField sx={{width: '20em'}}
160+
error={error}
161+
value={token}
162+
onChange={onChange}
163+
onKeyPress={onKeyPress}
164+
helperText={helperText}
165+
label="Enter your access token"/>
166+
</Box>
167+
<Box>
168+
<Button onClick={onClick} variant="contained">Submit</Button>
169+
</Box>
170+
</Box>
171+
</Box>
172+
);
173+
}
174+
175+
export default function App() {
176+
const [token, setToken] = useState(null);
177+
const [rows, setRows] = useState([]);
178+
179+
let content;
180+
if (token) {
181+
content = <FileList rows={rows}/>;
182+
} else {
183+
content = <TokenInput setRows={setRows} setToken={setToken} token={token}/>;
184+
}
185+
95186
return (
96-
<Box sx={{flexGrow: 1}}>
187+
<Box>
188+
<CssBaseline/>
97189
<AppBar position="static">
98190
<Toolbar>
99191
<Typography variant="h6" component="div" sx={{flexGrow: 1}}>
@@ -109,41 +201,8 @@ function FileList(props) {
109201
</Toolbar>
110202
</AppBar>
111203
<Box sx={{height: 'calc(100vh - 5.5em)', width: '100%'}}>
112-
<DataGrid rows={props.rows}
113-
columns={columns}
114-
pageSize={5}
115-
components={{Toolbar: AppToolbar}}
116-
componentsProps={{toolbar: {selections: selectionModel, rows: props.rows}}}
117-
onSelectionModelChange={(model) => setSelectionModel(model)}
118-
selectionModel={selectionModel}
119-
rowsPerPageOptions={[5]}
120-
checkboxSelection
121-
disableSelectionOnClick/>
204+
{content}
122205
</Box>
123206
</Box>
124-
125-
);
126-
}
127-
128-
export default function App() {
129-
const [token, setToken] = useState("token=ABC123");
130-
const [rows, setRows] = useState([]);
131-
132-
useEffect(() => {
133-
const getFiles = async () => {
134-
let response = await fetch('/api/files', {
135-
method: 'POST',
136-
headers: {
137-
'Content-Type': 'application/text;charset=utf-8'
138-
},
139-
body: token
140-
});
141-
setRows(await response.json());
142-
};
143-
getFiles().catch(console.error);
144-
}, [token, setRows]);
145-
146-
return (
147-
<FileList rows={rows}/>
148207
);
149208
}

0 commit comments

Comments
 (0)