aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/chat-app.js10
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/files-app.js13
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/group-page.js13
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/transport.js18
4 files changed, 37 insertions, 17 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/chat-app.js b/packages/meshbay-hub/src/meshbay_hub/static/chat-app.js
index 4714674..0a7ef26 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/chat-app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/chat-app.js
@@ -204,7 +204,8 @@ function ChatImage({ filename, entries, transportRef, gekRef }) {
// root is read-only, or the one drive that was writable is unplugged — and the
// paperclip says so rather than producing a refusal from the node.
function ChatPanel({ transportRef, username, entries, gekRef, onRefreshIndex,
- onPreview, attachRoot = '', onActivity, status }) {
+ onPreview, attachRoot = '', attachDir = '',
+ onActivity, status }) {
const [messages, setMessages] = useState([]);
const [hasMore, setHasMore] = useState(false);
const [loadingOlder, setLoadingOlder] = useState(false);
@@ -484,7 +485,10 @@ function ChatPanel({ transportRef, username, entries, gekRef, onRefreshIndex,
try {
// Two people sending IMG_1234.jpg both succeed; the node picks a free name
// and the message has to point at the one it chose.
- const ack = await transport.uploadFile(file, { root: attachRoot });
+ // `attachDir` is the folder the operator chose in Settings; `attachRoot`
+ // is the fallback for a group where they have not chosen one yet.
+ const ack = await transport.uploadFile(
+ file, { root: attachRoot, dir: attachDir || undefined });
const storedAs = (ack && ack.stored_as) || file.name;
await new Promise(r => setTimeout(r, 2500));
if (onRefreshIndex) await onRefreshIndex();
@@ -506,7 +510,7 @@ function ChatPanel({ transportRef, username, entries, gekRef, onRefreshIndex,
} finally {
setAttaching(false);
}
- }, [username, onRefreshIndex, jumpToBottom, attachRoot]);
+ }, [username, onRefreshIndex, jumpToBottom, attachRoot, attachDir]);
const onKeyDown = useCallback((e) => {
if (e.key === 'Enter' && !e.shiftKey) {
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/files-app.js b/packages/meshbay-hub/src/meshbay_hub/static/files-app.js
index a6a3ca4..9ca3aae 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/files-app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/files-app.js
@@ -75,12 +75,12 @@ function FilesPanel({
e.target.value = '';
const transport = transportRef.current;
if (!files.length || !transport || !transport.connected) return;
- // The root being browsed is the destination. A group can have several
- // writable roots, so leaving the node to pick one means a file uploaded
- // from a folder the operator is looking at lands in a different one —
- // which is only noticed much later, if at all.
- const uploadRoot = currentPath ? currentPath.split('/')[0] : '';
- if (!uploadRoot) return;
+ // The folder on screen is the destination — not its root, and not a
+ // subdirectory of the node's invention. Somebody dropping a file into the
+ // folder they are looking at expects it to be in that folder.
+ const uploadDir = currentPath;
+ if (!uploadDir) return;
+ const uploadRoot = uploadDir.split('/')[0];
setError('');
for (const file of files) {
@@ -92,6 +92,7 @@ function FilesPanel({
onProgress: (sent) => onProgress(sent, file.size),
signal,
root: uploadRoot,
+ dir: uploadDir,
});
// The node re-indexes on a filesystem event, so there is nothing to
// wait on but the clock. Refreshing here means the file appears in
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
index 513792b..a1e6411 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
@@ -590,7 +590,16 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
[nodeRoots]);
const legacyNode = nodeRoots.length > 0
&& nodeRoots.every((r) => r.writable === undefined);
- const attachRoot = writableRoots.length ? writableRoots[0].name
+ // The operator's chosen attachment folder wins where there is one — that is
+ // what the Chat settings pane is for. Its root has to be writable and
+ // present, or the choice is stale (they made it read-only, or ejected the
+ // drive) and the fallback is better than a refusal at send time.
+ const chatDirRoot = chatDirectory ? chatDirectory.split('/')[0] : '';
+ const chatDirUsable = Boolean(
+ chatDirRoot && writableRoots.some((r) => r.name === chatDirRoot));
+ const attachDir = chatDirUsable ? chatDirectory : '';
+ const attachRoot = chatDirUsable ? chatDirRoot
+ : writableRoots.length ? writableRoots[0].name
: (legacyNode && memberUpload
? (nodeRoots.find((r) => r.upload) || nodeRoots[0]).name
: '');
@@ -655,7 +664,7 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
groupId, transportRef, gekRef, status, username,
entries, availableEntries, nodeDirs, nodeRoots,
setEntries, setNodeDirs, setNodeRoots, applyIndex,
- isNodeAdmin, operatorPaired, attachRoot, userId, setError, onPreview,
+ isNodeAdmin, operatorPaired, attachRoot, attachDir, userId, setError, onPreview,
onRefreshIndex: refreshIndex, onActivity: touchActivity,
// Plural everywhere: Videos and Music read a list now, and Photos always
// did. The scalar `videoRoot`/`audioRoot` shapes survive only on the wire,
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
index 32f8539..3acfd20 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
@@ -1778,13 +1778,18 @@ class MeshBayTransport {
* free one rather than replacing anything. The ack says which, and that is what
* this returns.
*
- * `root` names which shared directory to upload into — a name, never a path;
- * the node picks the destination inside it. Since a group can have several
- * writable roots, leaving it out is a guess, and the node's fallback ("the
- * first writable one") exists only for MNP 1.0 clients, which had exactly one
- * destination. Every caller here browses a root and knows which one it is.
+ * `dir` names the folder to upload into, as a virtual path
+ * (`Media/Films/1999`) — where the sender is actually looking. The node
+ * resolves it against the group's own roots, which refuses `..`, absolute
+ * segments and anything escaping its root; it is a place among the group's
+ * folders, never a path on the operator's filesystem.
+ *
+ * `root` is the older, coarser form: the root's name and nothing below it.
+ * Kept because a node that predates `dir` reads it, and because Chat has no
+ * folder on screen to name. Omitting both leaves the node to pick, which it
+ * only does for a client old enough to have had one destination.
*/
- async uploadFile(file, { chunkSize, onProgress, signal, root } = {}) {
+ async uploadFile(file, { chunkSize, onProgress, signal, root, dir } = {}) {
// The same file twice at once would confuse the node, which keys its own
// upload state by name — and would race for the same destination.
if (this._uploaders.has(file.name)) {
@@ -1836,6 +1841,7 @@ class MeshBayTransport {
total_chunks: total,
data: buf,
...(root ? { root } : {}),
+ ...(dir ? { dir } : {}),
});
}
while (acked < total) {