jspaint/src/sessions.js

588 lines
20 KiB
JavaScript
Raw Normal View History

2014-10-17 22:51:51 +00:00
(() => {
2018-02-09 07:26:14 +00:00
const log = (...args)=> {
2019-12-14 22:49:23 +00:00
window.console && console.log(...args);
2014-10-18 06:34:44 +00:00
};
2018-02-09 07:26:14 +00:00
let localStorageAvailable = false;
try {
localStorage._available = true;
localStorageAvailable = localStorage._available;
delete localStorage._available;
// eslint-disable-next-line no-empty
} catch (e) {}
// @TODO: keep other data in addition to the image data
// such as the file_name and other state
// (maybe even whether it's considered saved? idk about that)
// I could have the image in one storage slot and the state in another
2018-02-09 07:26:14 +00:00
const match_threshold = 1; // 1 is just enough for a workaround for Brave browser's farbling: https://github.com/1j01/jspaint/issues/184
const canvas_has_any_apparent_image_data = ()=>
main_canvas.ctx.getImageData(0, 0, main_canvas.width, main_canvas.height).data.some((v)=> v > match_threshold);
2019-10-24 23:50:35 +00:00
let $recovery_window;
2019-10-24 23:50:35 +00:00
function show_recovery_window(no_longer_blank) {
$recovery_window && $recovery_window.close();
const $w = $recovery_window = $DialogWindow();
$w.on("close", ()=> {
2019-10-24 23:50:35 +00:00
$recovery_window = null;
});
2019-10-24 23:50:35 +00:00
$w.title("Recover Document");
let backup_impossible = false;
try{window.localStorage}catch(e){backup_impossible = true;}
2019-10-24 23:50:35 +00:00
$w.$main.append($(`
<h1>Woah!</h1>
<p>Your browser may have cleared the canvas due to memory usage.</p>
2019-10-24 23:50:35 +00:00
<p>Undo to recover the document, and remember to save with <b>File > Save</b>!</p>
${
backup_impossible ?
"<p><b>Note:</b> No automatic backup is possible unless you enable Cookies in your browser.</p>"
: (
no_longer_blank ?
`<p>
<b>Note:</b> normally a backup is saved automatically,<br>
but autosave is paused while this dialog is open<br>
2019-10-24 23:50:35 +00:00
to avoid overwriting the (singular) backup.
</p>
<p>
(See <b>File &gt; Manage Storage</b> to view backups.)
</p>`
: ""
)
}
}
`));
const $undo = $w.$Button("Undo", ()=> {
undo();
});
const $redo = $w.$Button("Redo", ()=> {
redo();
});
const update_buttons_disabled = ()=> {
2019-10-24 23:50:35 +00:00
$undo.attr("disabled", undos.length < 1);
$redo.attr("disabled", redos.length < 1);
};
$G.on("session-update.session-hook", update_buttons_disabled);
2019-10-24 23:50:35 +00:00
update_buttons_disabled();
$w.$Button(localize("Close"), ()=> {
$w.close();
});
$w.center();
2021-04-01 19:07:46 +00:00
$w.find("button:enabled").focus();
}
let last_undos_length = undos.length;
2019-10-24 23:50:35 +00:00
function handle_data_loss() {
const window_is_open = $recovery_window && !$recovery_window.closed;
let save_paused = false;
2019-10-24 23:50:35 +00:00
if (!canvas_has_any_apparent_image_data()) {
if (!window_is_open) {
show_recovery_window();
}
2019-10-25 00:12:56 +00:00
save_paused = true;
2019-10-24 23:50:35 +00:00
} else if (window_is_open) {
if (undos.length > last_undos_length) {
show_recovery_window(true);
}
2019-10-25 00:12:56 +00:00
save_paused = true;
2019-10-24 23:50:35 +00:00
}
last_undos_length = undos.length;
2019-10-25 00:12:56 +00:00
return save_paused;
2019-10-24 23:50:35 +00:00
}
2019-10-29 18:29:08 +00:00
class LocalSession {
constructor(session_id) {
this.id = session_id;
const lsid = `image#${session_id}`;
2019-12-14 22:49:23 +00:00
log(`Local storage ID: ${lsid}`);
2019-10-29 18:29:08 +00:00
// save image to storage
this.save_image_to_storage_immediately = () => {
const save_paused = handle_data_loss();
2019-10-29 18:29:08 +00:00
if (save_paused) {
return;
}
log(`Saving image to storage: ${lsid}`);
storage.set(lsid, main_canvas.toDataURL("image/png"), err => {
2019-10-29 18:29:08 +00:00
if (err) {
if (err.quotaExceeded) {
storage_quota_exceeded();
}
else {
// e.g. localStorage is disabled
// (or there's some other error?)
2020-01-05 22:27:51 +00:00
// @TODO: show warning with "Don't tell me again" type option
2019-10-29 18:29:08 +00:00
}
}
});
};
this.save_image_to_storage_soon = debounce(this.save_image_to_storage_immediately, 100);
storage.get(lsid, (err, uri) => {
2019-10-29 18:29:08 +00:00
if (err) {
if (localStorageAvailable) {
2021-02-11 18:53:45 +00:00
show_error_message("Failed to retrieve image from local storage.", err);
2019-10-29 18:29:08 +00:00
}
else {
2020-01-05 22:27:51 +00:00
// @TODO: DRY with storage manager message
2019-10-29 18:29:08 +00:00
show_error_message("Please enable local storage in your browser's settings for local backup. It may be called Cookies, Storage, or Site Data.");
2018-02-09 07:26:14 +00:00
}
}
2019-10-29 18:29:08 +00:00
else if (uri) {
load_image_from_uri(uri).then((info) => {
open_from_image_info(info, null, null, true, true);
}, (error) => {
show_error_message("Failed to open image from local storage.", error);
2019-10-29 18:29:08 +00:00
});
}
else {
// no uri so lets save the blank canvas
this.save_image_to_storage_soon();
2019-10-29 18:29:08 +00:00
}
2018-02-09 07:26:14 +00:00
});
$G.on("session-update.session-hook", this.save_image_to_storage_soon);
2018-02-09 07:26:14 +00:00
}
2019-10-29 18:29:08 +00:00
end() {
// Skip debounce and save immediately
this.save_image_to_storage_soon.cancel();
this.save_image_to_storage_immediately();
2019-10-29 18:29:08 +00:00
// Remove session-related hooks
$G.off(".session-hook");
}
}
2018-02-09 07:26:14 +00:00
2019-12-14 22:49:23 +00:00
// The user ID is not persistent
// A person can enter a session multiple times,
2019-12-14 22:49:23 +00:00
// and is always given a new user ID
let user_id;
2017-10-30 19:37:46 +00:00
// @TODO: I could make the color persistent, though.
// You could still have multiple cursors and they would just be the same color.
2015-02-23 18:44:58 +00:00
// There could also be an option to change your color
2018-02-09 07:26:14 +00:00
// The data in this object is stored in the server when you enter a session
// It is (supposed to be) removed when you leave
const user = {
2014-10-18 06:34:44 +00:00
// Cursor status
cursor: {
// cursor position in canvas coordinates
x: 0, y: 0,
// whether the user is elsewhere, such as in another tab
2014-10-18 06:34:44 +00:00
away: true,
},
// Currently selected tool (@TODO)
2020-12-05 22:33:21 +00:00
tool: localize("Pencil"),
// Color components
2014-10-18 06:34:44 +00:00
hue: ~~(Math.random() * 360),
saturation: ~~(Math.random() * 50) + 50,
lightness: ~~(Math.random() * 40) + 50,
2014-10-18 06:34:44 +00:00
};
2018-02-09 07:26:14 +00:00
// The main cursor color
user.color = `hsla(${user.hue}, ${user.saturation}%, ${user.lightness}%, 1)`;
// Unused
user.color_transparent = `hsla(${user.hue}, ${user.saturation}%, ${user.lightness}%, 0.5)`;
2020-01-05 22:27:51 +00:00
// (@TODO) The color (that may be) used in the toolbar indicating to other users it is selected by this user
user.color_desaturated = `hsla(${user.hue}, ${~~(user.saturation*0.4)}%, ${user.lightness}%, 0.8)`;
2018-02-09 07:26:14 +00:00
// The image used for other people's cursors
const cursor_image = new Image();
cursor_image.src = "images/cursors/default.png";
2018-02-09 07:26:14 +00:00
2019-12-15 03:24:52 +00:00
class MultiUserSession {
2019-10-29 18:29:08 +00:00
constructor(session_id) {
2019-10-29 20:43:18 +00:00
this.id = session_id;
2019-12-16 18:22:09 +00:00
this._fb_listeners = [];
file_name = `[Loading ${this.id}]`;
2014-10-17 22:51:51 +00:00
update_title();
const on_firebase_loaded = () => {
file_name = `[${this.id}]`;
2019-10-29 18:29:08 +00:00
update_title();
2019-10-29 20:43:18 +00:00
this.start();
2019-10-29 18:29:08 +00:00
};
2019-12-15 03:24:52 +00:00
if (!MultiUserSession.fb_root) {
2019-10-29 18:29:08 +00:00
$.getScript("lib/firebase.js")
.done(() => {
const config = {
2019-10-29 18:29:08 +00:00
apiKey: "AIzaSyBgau8Vu9ZE8u_j0rp-Lc044gYTX5O3X9k",
authDomain: "jspaint.firebaseapp.com",
databaseURL: "https://jspaint.firebaseio.com",
projectId: "firebase-jspaint",
storageBucket: "",
messagingSenderId: "63395010995"
};
firebase.initializeApp(config);
2019-12-15 03:24:52 +00:00
MultiUserSession.fb_root = firebase.database().ref("/");
2019-10-29 18:29:08 +00:00
on_firebase_loaded();
})
.fail(() => {
2019-10-29 18:29:08 +00:00
show_error_message("Failed to load Firebase; the document will not load, and changes will not be saved.");
file_name = `[Failed to load ${this.id}]`;
2019-10-29 18:29:08 +00:00
update_title();
2014-10-18 06:34:44 +00:00
});
2019-10-29 18:29:08 +00:00
}
else {
on_firebase_loaded();
}
}
start() {
2020-01-05 22:27:51 +00:00
// @TODO: how do you actually detect if it's failing???
showMessageBox({
messageHTML: `
<p>The document may not load. Changes may not save.</p>
<p>Multiuser sessions are public. There is no security.</p>
`
});
// "<p>The document may not load. Changes may not save. If it does save, it's public. There is no security.</p>"// +
// "<p>I haven't found a way to detect Firebase quota limits being exceeded, " +
// "so for now I'm showing this message regardless of whether it's working.</p>" +
// "<p>If you're interested in using multiuser mode, please thumbs-up " +
// "<a href='https://github.com/1j01/jspaint/issues/68'>this issue</a> to show interest, and/or subscribe for updates.</p>"
2019-12-16 18:22:09 +00:00
2019-10-29 18:29:08 +00:00
// Wrap the Firebase API because they don't
// provide a great way to clean up event listeners
const _fb_on = (fb, event_type, callback, error_callback) => {
2019-10-29 20:43:18 +00:00
this._fb_listeners.push({ fb, event_type, callback, error_callback });
2019-10-29 18:29:08 +00:00
fb.on(event_type, callback, error_callback);
};
// Get Firebase references
2019-12-15 03:24:52 +00:00
this.fb = MultiUserSession.fb_root.child(this.id);
2019-10-29 20:43:18 +00:00
this.fb_data = this.fb.child("data");
this.fb_users = this.fb.child("users");
2019-10-29 18:29:08 +00:00
if (user_id) {
2019-10-29 20:43:18 +00:00
this.fb_user = this.fb_users.child(user_id);
}
2019-10-29 18:29:08 +00:00
else {
2019-10-29 20:43:18 +00:00
this.fb_user = this.fb_users.push();
user_id = this.fb_user.key;
2014-10-18 00:50:01 +00:00
}
2019-10-29 18:29:08 +00:00
// Remove the user from the session when they disconnect
2019-10-29 20:43:18 +00:00
this.fb_user.onDisconnect().remove();
2019-10-29 18:29:08 +00:00
// Make the user present in the session
2019-10-29 20:43:18 +00:00
this.fb_user.set(user);
2019-10-29 18:29:08 +00:00
// @TODO: Execute the above two lines when .info/connected
// For each existing and new user
2019-10-29 20:43:18 +00:00
_fb_on(this.fb_users, "child_added", snap => {
2019-10-29 18:29:08 +00:00
// Is this you?
if (snap.key === user_id) {
// You already have a cursor.
return;
}
// Get the Firebase reference for this user
const fb_other_user = snap.ref;
2019-10-29 18:29:08 +00:00
// Get the user object stored on the server
let other_user = snap.val();
2019-10-29 18:29:08 +00:00
// @TODO: display other cursor types?
// @TODO: display pointer button state?
// @TODO: display selections
const cursor_canvas = make_canvas(32, 32);
2019-10-29 18:29:08 +00:00
// Make the cursor element
const $cursor = $(cursor_canvas).addClass("user-cursor").appendTo($app);
2019-10-29 18:29:08 +00:00
$cursor.css({
display: "none",
position: "absolute",
left: 0,
top: 0,
opacity: 0,
zIndex: 5, // @#: z-index
2019-10-29 18:29:08 +00:00
pointerEvents: "none",
transition: "opacity 0.5s",
});
// When the cursor data changes
_fb_on(fb_other_user, "value", snap => {
2019-10-29 18:29:08 +00:00
other_user = snap.val();
// If the user has left
if (other_user == null) {
// Remove the cursor element
$cursor.remove();
}
2019-10-29 18:29:08 +00:00
else {
// Draw the cursor
const draw_cursor = () => {
2019-10-29 18:29:08 +00:00
cursor_canvas.width = cursor_image.width;
cursor_canvas.height = cursor_image.height;
2021-06-19 23:58:47 +00:00
const cursor_ctx = cursor_canvas.ctx;
cursor_ctx.fillStyle = other_user.color;
cursor_ctx.fillRect(0, 0, cursor_canvas.width, cursor_canvas.height);
cursor_ctx.globalCompositeOperation = "multiply";
cursor_ctx.drawImage(cursor_image, 0, 0);
cursor_ctx.globalCompositeOperation = "destination-atop";
cursor_ctx.drawImage(cursor_image, 0, 0);
2019-10-29 18:29:08 +00:00
};
if (cursor_image.complete) {
draw_cursor();
}
else {
$(cursor_image).one("load", draw_cursor);
}
// Update the cursor element
const canvas_rect = canvas_bounding_client_rect;
2019-10-29 18:29:08 +00:00
$cursor.css({
display: "block",
position: "absolute",
left: canvas_rect.left + magnification * other_user.cursor.x,
top: canvas_rect.top + magnification * other_user.cursor.y,
opacity: 1 - other_user.cursor.away,
});
}
2019-10-29 18:29:08 +00:00
});
2014-10-18 06:34:44 +00:00
});
let previous_uri;
// let pointer_operations = []; // the multiplayer syncing stuff is a can of worms, so this is disabled
this.write_canvas_to_database_immediately = () => {
const save_paused = handle_data_loss();
2019-10-29 18:29:08 +00:00
if (save_paused) {
return;
}
// Sync the data from this client to the server (one-way)
const uri = main_canvas.toDataURL();
2019-10-29 18:29:08 +00:00
if (previous_uri !== uri) {
// log("clear pointer operations to set data", pointer_operations);
// pointer_operations = [];
2019-12-14 22:49:23 +00:00
log("Write canvas data to Firebase");
2019-10-29 20:43:18 +00:00
this.fb_data.set(uri);
2019-10-29 18:29:08 +00:00
previous_uri = uri;
}
else {
2019-12-14 22:49:23 +00:00
log("(Don't write canvas data to Firebase; it hasn't changed)");
2019-10-29 18:29:08 +00:00
}
};
this.write_canvas_to_database_soon = debounce(this.write_canvas_to_database_immediately, 100);
let ignore_session_update = false;
$G.on("session-update.session-hook", ()=> {
if (ignore_session_update) {
2019-12-14 22:49:23 +00:00
log("(Ignore session-update from Sync Session undoable)");
return;
}
this.write_canvas_to_database_soon();
});
2021-06-19 23:58:47 +00:00
// Any time we change or receive the image data
2019-10-29 20:43:18 +00:00
_fb_on(this.fb_data, "value", snap => {
log("Firebase data update");
const uri = snap.val();
2019-10-29 18:29:08 +00:00
if (uri == null) {
// If there's no value at the data location, this is a new session
// Sync the current data to it
this.write_canvas_to_database_soon();
2019-10-29 18:29:08 +00:00
}
else {
previous_uri = uri;
// Load the new image data
const img = new Image();
img.onload = () => {
2019-10-29 21:00:31 +00:00
// Cancel any in-progress pointer operations
// if (pointer_operations.length) {
// $G.triggerHandler("pointerup", "cancel");
// }
const test_canvas = make_canvas(img);
const image_data_remote = test_canvas.ctx.getImageData(0, 0, test_canvas.width, test_canvas.height);
const image_data_local = main_ctx.getImageData(0, 0, main_canvas.width, main_canvas.height);
2019-12-21 16:37:54 +00:00
if (!image_data_match(image_data_remote, image_data_local, 5)) {
ignore_session_update = true;
2019-12-16 06:10:09 +00:00
undoable({
name: "Sync Session",
icon: get_help_folder_icon("p_database.png"),
}, ()=> {
// Write the image data to the canvas
main_ctx.copy(img);
$canvas_area.trigger("resize");
});
ignore_session_update = false;
}
// (transparency = has_any_transparency(main_ctx); here would not be ideal
2019-10-29 21:00:31 +00:00
// Perhaps a better way of syncing transparency
// and other options will be established)
/*
2019-10-29 21:00:31 +00:00
// Playback recorded in-progress pointer operations
2019-12-14 22:49:23 +00:00
log("Playback", pointer_operations);
Convert some for loops to for-of $ lebab --replace src/ --transform for-of src/extra-tools.js: 34: warning Unable to transform for loop (for-of) 89: warning Unable to transform for loop (for-of) src/functions.js: 291: warning Unable to transform for loop (for-of) 796: warning Unable to transform for loop (for-of) 1095: warning Unable to transform for loop (for-of) 1123: warning Unable to transform for loop (for-of) 1128: warning Unable to transform for loop (for-of) 1138: warning Unable to transform for loop (for-of) 1190: warning Unable to transform for loop (for-of) 1221: warning Unable to transform for loop (for-of) 1222: warning Unable to transform for loop (for-of) 1245: warning Unable to transform for loop (for-of) 1249: warning Unable to transform for loop (for-of) src/image-manipulation.js: 48: warning Unable to transform for loop (for-of) 74: warning Unable to transform for loop (for-of) 337: warning Unable to transform for loop (for-of) 558: warning Unable to transform for loop (for-of) 604: warning Unable to transform for loop (for-of) 672: warning Index variable used in for-loop body (for-of) 675: warning Unable to transform for loop (for-of) 831: warning Unable to transform for loop (for-of) 848: warning Unable to transform for loop (for-of) 877: warning Unable to transform for loop (for-of) src/OnCanvasSelection.js: 132: warning Unable to transform for loop (for-of) 176: warning Unable to transform for loop (for-of) src/OnCanvasTextBox.js: 134: warning Unable to transform for loop (for-of) src/simulate-random-gestures.js: 94: warning Unable to transform for loop (for-of) 111: warning Unable to transform for loop (for-of) src/storage.js: 22: warning Unable to transform for loop (for-of) src/tool-options.js: 40: warning Unable to transform for loop (for-of) 58: warning Unable to transform for loop (for-of) src/tools.js: 75: warning Unable to transform for loop (for-of) 251: warning Unable to transform for loop (for-of) 389: warning Unable to transform for loop (for-of) 393: warning Unable to transform for loop (for-of) 508: warning Unable to transform for loop (for-of)
2019-10-29 20:53:16 +00:00
2019-10-29 21:00:31 +00:00
for (const e of pointer_operations) {
// Trigger the event at each place it is listened for
$canvas.triggerHandler(e, ["synthetic"]);
$G.triggerHandler(e, ["synthetic"]);
}
*/
2019-10-29 21:00:31 +00:00
};
2019-10-29 18:29:08 +00:00
img.src = uri;
}
}, error => {
2019-10-29 18:29:08 +00:00
show_error_message("Failed to retrieve data from Firebase. The document will not load, and changes will not be saved.", error);
file_name = `[Failed to load ${this.id}]`;
2019-10-29 18:29:08 +00:00
update_title();
2014-10-18 06:34:44 +00:00
});
2019-10-29 18:29:08 +00:00
// Update the cursor status
$G.on("pointermove.session-hook", e => {
2019-10-30 00:48:51 +00:00
const m = to_canvas_coords(e);
2019-10-29 20:43:18 +00:00
this.fb_user.child("cursor").update({
2019-10-29 18:29:08 +00:00
x: m.x,
y: m.y,
away: false,
});
});
2019-12-18 05:42:19 +00:00
$G.on("blur.session-hook", ()=> {
2019-10-29 20:43:18 +00:00
this.fb_user.child("cursor").update({
2019-10-29 18:29:08 +00:00
away: true,
});
});
// @FIXME: the cursor can come back from "away" via a pointer event
// while the window is blurred and stay there when the user goes away
// maybe replace "away" with a timestamp of activity and then
// clients can decide whether a given cursor should be visible
/*
const debug_event = (e, synthetic) => {
// const label = synthetic ? "(synthetic)" : "(normal)";
// window.console && console.debug && console.debug(e.type, label);
};
$canvas_area.on("pointerdown.session-hook", "*", (e, synthetic) => {
debug_event(e, synthetic);
if(synthetic){ return; }
pointer_operations = [e];
const pointermove = (e, synthetic) => {
debug_event(e, synthetic);
if(synthetic){ return; }
pointer_operations.push(e);
};
$G.on("pointermove.session-hook", pointermove);
$G.one("pointerup.session-hook", (e, synthetic) => {
debug_event(e, synthetic);
if(synthetic){ return; }
$G.off("pointermove.session-hook", pointermove);
});
}
});
*/
2019-10-29 18:29:08 +00:00
}
end() {
// Skip debounce and save immediately
this.write_canvas_to_database_soon.cancel();
this.write_canvas_to_database_immediately();
2019-10-29 18:29:08 +00:00
// Remove session-related hooks
$G.off(".session-hook");
// $canvas_area.off("pointerdown.session-hook");
2019-10-29 18:29:08 +00:00
// Remove collected Firebase event listeners
2019-12-18 05:42:19 +00:00
this._fb_listeners.forEach(({ fb, event_type, callback/*, error_callback*/ }) => {
2019-12-14 22:49:23 +00:00
log(`Remove listener for ${fb.path.toString()} .on ${event_type}`);
2019-10-29 18:29:08 +00:00
fb.off(event_type, callback);
});
2019-10-29 20:43:18 +00:00
this._fb_listeners.length = 0;
2019-10-29 18:29:08 +00:00
// Remove the user from the session
2019-10-29 20:43:18 +00:00
this.fb_user.remove();
2019-10-29 18:29:08 +00:00
// Remove any cursor elements
$app.find(".user-cursor").remove();
// Reset to "untitled"
reset_file();
}
}
2018-02-09 07:26:14 +00:00
// Handle the starting, switching, and ending of sessions from the location.hash
2018-02-09 07:26:14 +00:00
let current_session;
const end_current_session = () => {
if(current_session){
2019-12-14 22:49:23 +00:00
log("Ending current session");
current_session.end();
current_session = null;
2014-10-18 00:50:01 +00:00
}
};
const generate_session_id = () => (Math.random()*(2 ** 32)).toString(16).replace(".", "");
2019-12-18 05:42:19 +00:00
const update_session_from_location_hash = () => {
const session_match = location.hash.match(/^#?(?:.*,)?(session|local):(.*)$/i);
const load_from_url_match = location.hash.match(/^#?(?:.*,)?(load):(.*)$/i);
2018-01-10 03:42:38 +00:00
if(session_match){
const local = session_match[1].toLowerCase() === "local";
const session_id = session_match[2];
if(session_id === ""){
2019-12-14 22:49:23 +00:00
log("Invalid session ID; session ID cannot be empty");
end_current_session();
}else if(!local && session_id.match(/[./[\]#$]/)){
2019-12-14 22:49:23 +00:00
log("Session ID is not a valid Firebase location; it cannot contain any of ./[]#$");
end_current_session();
}else if(!session_id.match(/[-0-9A-Za-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02af\u1d00-\u1d25\u1d62-\u1d65\u1d6b-\u1d77\u1d79-\u1d9a\u1e00-\u1eff\u2090-\u2094\u2184-\u2184\u2488-\u2490\u271d-\u271d\u2c60-\u2c7c\u2c7e-\u2c7f\ua722-\ua76f\ua771-\ua787\ua78b-\ua78c\ua7fb-\ua7ff\ufb00-\ufb06]+/)){
2019-12-14 22:49:23 +00:00
log("Invalid session ID; it must consist of 'alphanumeric-esque' characters");
end_current_session();
}else if(
current_session && current_session.id === session_id &&
local === (current_session instanceof LocalSession)
){
log("Hash changed but the session ID and session type are the same");
2015-02-23 18:44:58 +00:00
}else{
// @TODO: Ask if you want to save before starting a new session
end_current_session();
if(local){
2019-12-15 03:24:52 +00:00
log(`Starting a new LocalSession, ID: ${session_id}`);
2015-02-23 18:44:58 +00:00
current_session = new LocalSession(session_id);
}else{
2019-12-15 03:24:52 +00:00
log(`Starting a new MultiUserSession, ID: ${session_id}`);
current_session = new MultiUserSession(session_id);
2015-02-23 18:44:58 +00:00
}
2014-10-18 00:50:01 +00:00
}
}else if(load_from_url_match){
const url = decodeURIComponent(load_from_url_match[2]);
2018-02-09 07:26:14 +00:00
2021-02-12 16:12:42 +00:00
const uris = get_uris(url);
2019-09-17 20:31:49 +00:00
if (uris.length === 0) {
show_error_message("Invalid URL to load (after #load: in the address bar). It must include a protocol (https:// or http://)");
return;
}
log("Switching to new session from #load: URL (to #local: URL with session ID)");
// Note: could use into_existing_session=false on open_from_image_info instead of creating the new session beforehand
end_current_session();
change_url_param("local", generate_session_id());
load_image_from_uri(uri).then((info) => {
open_from_image_info(info, null, null, true, true);
}, show_resource_load_error_message);
2014-10-18 00:50:01 +00:00
}else{
2019-12-14 22:49:23 +00:00
log("No session ID in hash");
const old_hash = location.hash;
end_current_session();
change_url_param("local", generate_session_id(), {replace_history_state: true});
2019-12-14 22:49:23 +00:00
log("After replaceState:", location.hash);
if (old_hash === location.hash) {
// e.g. on Wayback Machine
show_error_message("Autosave is disabled. Failed to update URL to start session.");
} else {
update_session_from_location_hash();
}
2014-10-18 00:50:01 +00:00
}
2018-01-19 06:57:39 +00:00
};
2018-02-09 07:26:14 +00:00
2020-05-10 02:40:05 +00:00
$G.on("hashchange popstate change-url-params", e => {
2018-02-02 07:21:23 +00:00
log(e.type, location.hash);
2018-01-19 06:57:39 +00:00
update_session_from_location_hash();
});
2019-12-15 03:24:52 +00:00
log("Initializing with location hash:", location.hash);
2018-01-19 06:57:39 +00:00
update_session_from_location_hash();
2018-02-09 07:26:14 +00:00
window.new_local_session = () => {
end_current_session();
log("Changing URL to start new session...");
change_url_param("local", generate_session_id());
};
// @TODO: Session GUI
2019-12-14 22:49:23 +00:00
// @TODO: Indicate when the session ID is invalid
2015-02-23 17:46:57 +00:00
// @TODO: Indicate when the session switches
2018-02-02 07:21:23 +00:00
// @TODO: Indicate when there is no session!
2018-02-09 07:26:14 +00:00
// Probably in app.js so as to handle the possibility of sessions.js failing to load.
2014-10-17 22:51:51 +00:00
})();