/* SpacedeckSections This module contains functions dealing with Space Sections UI. */ var SpacedeckSections = { data: { MAX_COLUMNS: 20, redo_stack: [], undo_stack: [], opened_dialog: "none", // which property panel to display color_options_picker: false, advanced_properties: false, embed_code_html: "", active_tool: "pointer", lightbox_artifact: {}, snap_ruler_y: -1000, snap_ruler_x: -1000, minimap_width: 100, minimap_height: 200, minimap_scale: 10, scroll_left: 0, scroll_top: 0, window_width: 800, window_height: 600, bounds_margin_horiz: 0, bounds_margin_vert: 0, editing_artifact_id: null, selected_artifacts_dict: {}, first_selected_artifact: null, selection_metrics: { contains_text: false, contains_images: false, contains_audio: false, contains_vectors: false, contains_shapes: false, borders_stylable: true, count: 0, x: 0, y: 0, w: 0, h: 0, x1: 0, y1: 0, x2: 0, y2: 0, style: "display:none", vector_points: [{},{}], vector_selection: false }, selected_artifacts_json: "", zones: [], user_cursors: [], default_style: {}, // will be copied from active_style on startup active_style: { border_radius: 0, stroke: 0, font_family: "Inter", font_size: 36, line_height: 1.5, letter_spacing: 0, stroke_color: "#000000", fill_color: "#00000000", text_color: "#000000", background_color: "#ffffff", padding: 0, padding_horz: 0, padding_vert: 0, padding_top: 0, padding_left: 0, padding_right: 0, padding_bottom: 0, margin: 0, margin_horz: 0, margin_vert: 0, margin_top: 0, margin_left: 0, margin_right: 0, margin_bottom: 0, brightness: 100, contrast: 100, opacity: 100, saturation: 100, blur: 0, hue: 0, columns: 1, column_width: 900, row_height: 0, gutter: 0, }, color_picker_target: "fill_color", color_picker_saturation: 255, color_picker_value: 255, color_picker_hue: 127, color_picker_opacity: 255, swatches: [ {id:0, hex:"#4a2f7e"}, {id:1, hex:"#9b59b6"}, {id:2, hex:"#3498db"}, {id:3, hex:"#2ecc71"}, {id:4, hex:"#f1c40f"}, {id:5, hex:"#e67e22"}, {id:6, hex:"#d55c4b"}, {id:7, hex:"#6f4021"}, {id:8, hex:"#ffffff"}, {id:9, hex:"#95a5a6"}, {id:10, hex:"#252525"}, {id:11, hex:"rgba(0,0,0,0)"}, ], swatches_text: [ {id:1, hex:"#9b59b6"}, {id:2, hex:"#3498db"}, {id:3, hex:"#2ecc71"}, {id:4, hex:"#f1c40f"}, {id:5, hex:"#e67e22"}, {id:6, hex:"#d55c4b"}, {id:8, hex:"#ffffff"}, {id:10, hex:"#252525"}, ], fonts: [ "Inter", "Courier" ], detected_text_formats: {}, active_text_format_name: "Paragraph", image_search_results: [], video_search_results: [], audio_search_results: [], generic_search_query: "", media_search_target: "google", search_loading: false, viewport_zoom: 1, viewport_zoom_percent: 100, bounds_zoom: 1, current_zone_idx: -1, margin_mode: "global", padding_mode: "global", delete_artifact: "unconfirmed", color_mode: "palette", background_mode: "image", layout_mode: "layout", follow_mode: true, space_background_uploading: false, toolbar_props_x: 0, toolbar_props_y: 0, toolbar_props_in: false, toolbar_artifacts_x: "-1000px", toolbar_artifacts_y: "-1000px", toolbar_artifacts_in: true }, methods: { setup_section_module: function() { // defaults ----------------------------------------------------------------------------- this.default_style = _.clone(this.active_style); // keybindings -------------------------------------------------------------------------- Mousetrap.bind('del', function(evt) { this.if_editable(function() {this.delete_selected_artifacts(evt);}) }.bind(this)); Mousetrap.bind('backspace', function(evt) { this.if_editable(function() {this.delete_selected_artifacts(evt);}) }.bind(this)); Mousetrap.bind(['command+d', 'ctrl+d' ], function(evt) { evt.preventDefault(); evt.stopPropagation(); this.if_editable(function() {this.duplicate_selected_artifacts();}) }.bind(this)); Mousetrap.bind(['command+z', 'ctrl+z' ], function(evt) { this.if_editable(function() {this.undo();}) }.bind(this)); Mousetrap.bind(['command+shift+z','ctrl+shift+z'], function(evt) { this.if_editable(function() {this.redo();}) }.bind(this)); Mousetrap.bind(['command+a', 'ctrl+a' ], function(evt) { this.if_editable(function() {this.select_all_artifacts();}) }.bind(this)); Mousetrap.bind(['command+e', 'ctrl+e' ], function(evt) { this.if_editable(function() {this.toggle_full_width();}) }.bind(this)); Mousetrap.bind(['command+=', 'ctrl+=' ], function(evt) { evt.preventDefault(); evt.stopPropagation(); this.zoom_in(); }.bind(this)); Mousetrap.bind(['command+-', 'ctrl+-' ], function(evt) { evt.preventDefault(); evt.stopPropagation(); this.zoom_out(); }.bind(this)); Mousetrap.bind('+', function(evt) { evt.preventDefault(); evt.stopPropagation(); this.zoom_in(); }.bind(this)); Mousetrap.bind('-', function(evt) { evt.preventDefault(); evt.stopPropagation(); this.zoom_out(); }.bind(this)); Mousetrap.bind('up', function(evt) { this.nudge_selected_artifacts(0,-1,evt);}.bind(this)); Mousetrap.bind('down', function(evt) { this.nudge_selected_artifacts(0,1,evt);}.bind(this)); Mousetrap.bind('left', function(evt) { this.nudge_selected_artifacts(-1,0,evt);}.bind(this)); Mousetrap.bind('right', function(evt) { this.nudge_selected_artifacts(1,0,evt);}.bind(this)); Mousetrap.bind('shift+up', function(evt) { this.if_editable(function() {this.nudge_selected_artifacts(0,-10,evt);}) }.bind(this)); Mousetrap.bind('shift+down', function(evt) { this.if_editable(function() {this.nudge_selected_artifacts(0,10,evt);}) }.bind(this)); Mousetrap.bind('shift+left', function(evt) { this.if_editable(function() {this.nudge_selected_artifacts(-10,0,evt);}) }.bind(this)); Mousetrap.bind('shift+right', function(evt) { this.if_editable(function() {this.nudge_selected_artifacts(10,0,evt);}) }.bind(this)); Mousetrap.bind('space', function(evt) { this.activate_pan_tool(evt); }.bind(this)); $(document).bind("beforecopy", this.handle_onbeforecopy.bind(this)); $(window).bind("beforeunload", this.handle_onunload.bind(this)); $(window).bind("resize", this.handle_window_resize.bind(this)); }, setup_watches: function() { this.$watch('active_style.stroke', function (value, mutation) { this.set_artifact_style_prop("stroke",parseInt(this.active_style.stroke)); }.bind(this)); this.$watch('active_style.border_radius', function (value, mutation) { this.set_artifact_style_prop("border_radius",parseInt(this.active_style.border_radius)); }.bind(this)); this.$watch('active_style.padding', function (value, mutation) { this.active_style.padding_horz = this.active_style.padding; this.active_style.padding_vert = this.active_style.padding; }.bind(this)); this.$watch('active_style.padding_horz', function (value, mutation) { this.active_style.padding_left = this.active_style.padding_horz; this.active_style.padding_right = this.active_style.padding_horz; }.bind(this)); this.$watch('active_style.padding_vert', function (value, mutation) { this.active_style.padding_top = this.active_style.padding_vert; this.active_style.padding_bottom = this.active_style.padding_vert; }.bind(this)); this.$watch('active_style.padding_top', function (value, mutation) { this.set_artifact_style_prop("padding_top",parseInt(this.active_style.padding_top)); }.bind(this)); this.$watch('active_style.padding_bottom', function (value, mutation) { this.set_artifact_style_prop("padding_bottom",parseInt(this.active_style.padding_bottom)); }.bind(this)); this.$watch('active_style.padding_left', function (value, mutation) { this.set_artifact_style_prop("padding_left",parseInt(this.active_style.padding_left)); }.bind(this)); this.$watch('active_style.padding_right', function (value, mutation) { this.set_artifact_style_prop("padding_right",parseInt(this.active_style.padding_right)); }.bind(this)); this.$watch('active_style.margin', function (value, mutation) { this.active_style.margin_horz = this.active_style.margin; this.active_style.margin_vert = this.active_style.margin; }.bind(this)); this.$watch('active_style.margin_horz', function (value, mutation) { this.active_style.margin_left = this.active_style.margin_horz; this.active_style.margin_right = this.active_style.margin_horz; }.bind(this)); this.$watch('active_style.margin_vert', function (value, mutation) { this.active_style.margin_top = this.active_style.margin_vert; this.active_style.margin_bottom = this.active_style.margin_vert; }.bind(this)); this.$watch('active_style.margin_top', function (value, mutation) { this.set_artifact_style_prop("margin_top",parseInt(this.active_style.margin_top)); }.bind(this)); this.$watch('active_style.margin_bottom', function (value, mutation) { this.set_artifact_style_prop("margin_bottom",parseInt(this.active_style.margin_bottom)); }.bind(this)); this.$watch('active_style.margin_left', function (value, mutation) { this.set_artifact_style_prop("margin_left",parseInt(this.active_style.margin_left)); }.bind(this)); this.$watch('active_style.margin_right', function (value, mutation) { this.set_artifact_style_prop("margin_right",parseInt(this.active_style.margin_right)); }.bind(this)); this.$watch('active_style.stroke_color', function (value, mutation) { this.set_artifact_style_prop("stroke_color",this.active_style.stroke_color); var rgba = hex_to_rgba(this.active_style.stroke_color); var hsv = rgb_to_hsv(rgba.r, rgba.g, rgba.b); this.active_style.stroke_color_hsv = hsv; }.bind(this)); this.$watch('active_style.fill_color', function (value, mutation) { this.set_artifact_style_prop("fill_color",this.active_style.fill_color); var rgba = hex_to_rgba(this.active_style.fill_color); var hsv = rgb_to_hsv(rgba.r, rgba.g, rgba.b); this.active_style.fill_color_hsv = hsv; }.bind(this)); this.$watch('active_style.text_color', function (value, mutation) { this.set_artifact_style_prop("text_color",this.active_style.text_color); this.apply_formatting(null,"forecolor",this.active_style.text_color); var rgba = hex_to_rgba(this.active_style.text_color); var hsv = rgb_to_hsv(rgba.r, rgba.g, rgba.b); this.active_style.text_color_hsv = hsv; }.bind(this)); this.$watch('active_style.font_size', function (value, mutation) { this.apply_formatting(null,"preciseFontSize",this.active_style.font_size+"px"); }.bind(this)); this.$watch('active_style.line_height', function (value, mutation) { this.apply_formatting(null,"lineHeight",this.active_style.line_height+"em"); }.bind(this)); this.$watch('active_style.letter_spacing', function (value, mutation) { this.apply_formatting(null,"letterSpacing",this.active_style.letter_spacing+"px"); }.bind(this)); // color picker this.$watch('color_picker_hue', function (value, mutation) { this.apply_color_picker(); }.bind(this)); this.$watch('color_picker_value', function (value, mutation) { this.apply_color_picker(); }.bind(this)); this.$watch('color_picker_saturation', function (value, mutation) { this.apply_color_picker(); }.bind(this)); this.$watch('color_picker_opacity', function (value, mutation) { this.apply_color_picker(); }.bind(this)); // filters this.$watch('active_style.brightness', function (value, mutation) { this.set_artifact_style_prop("brightness",parseInt(this.active_style.brightness)); }.bind(this)); this.$watch('active_style.blur', function (value, mutation) { this.set_artifact_style_prop("blur",parseInt(this.active_style.blur)); }.bind(this)); this.$watch('active_style.contrast', function (value, mutation) { this.set_artifact_style_prop("contrast",parseInt(this.active_style.contrast)); }.bind(this)); this.$watch('active_style.saturation', function (value, mutation) { this.set_artifact_style_prop("saturation",parseInt(this.active_style.saturation)); }.bind(this)); this.$watch('active_style.hue', function (value, mutation) { this.set_artifact_style_prop("hue",parseInt(this.active_style.hue)); }.bind(this)); this.$watch('active_style.opacity', function (value, mutation) { this.set_artifact_style_prop("opacity",parseInt(this.active_style.opacity)); }.bind(this)); this.throttled_save_active_space = _.throttle(function(){ save_space(this.active_space); }.bind(this), 2000); // canvas this.$watch('active_style.background_color', function (value, mutation) { if (this.active_style.background_color != this.active_space.background_color) { this.$set("active_space.background_color",this.active_style.background_color); this.throttled_save_active_space(); } var rgba = hex_to_rgba(this.active_style.background_color); var hsv = rgb_to_hsv(rgba.r, rgba.g, rgba.b); this.active_style.background_color_hsv = hsv; }.bind(this)); }, if_editable: function(fn) { // call given closure if space is editable // used by key bindings if (this.active_space_role!="viewer") fn.bind(this)(); }, background_image_style: function(images) { if (!images) return null; if (isNaN(images.length)) images = [images]; for (var i=0; i0) { return "background-image: url("+images[i]+")"; } } }, space_thumbnail_style: function(space) { if (space.avatar_thumb_uri && space.avatar_thumb_uri.length>0) { return "background-image:url('"+space.avatar_thumb_uri+"')"; } if (space.space_type == "folder") return ""; return "background-image:url('/api/spaces/"+space._id+"/png')"; }, reset_artifact_filters: function() { this.active_style.brightness = this.default_style.brightness; this.active_style.contrast = this.default_style.contrast; this.active_style.opacity = this.default_style.opacity; this.active_style.saturation = this.default_style.saturation; this.active_style.blur = this.default_style.blur; this.active_style.hue = this.default_style.hue; }, increase_columns: function() { if (this.active_style.columns1) this.active_style.columns--; }, extract_properties_from_selection: function() { // stop extract->apply feedback loop this.skip_formatting = true; var arts = this.selected_artifacts(); window.setTimeout(function() { this.skip_formatting = false; }.bind(this),10); if (!arts.length) return; if (arts.length == 1) { var a = arts[0]; var props = [ "stroke", "border_radius", "letter_spacing", "stroke_color", "fill_color", "text_color" ]; for (var i=0; i=1) { this.selection_metrics.contains_text = true; if (notes.length==1) { var a = notes[0]; var dom = $("
"+a.description+"
")[0]; var el = dom.firstChild; do { if (el && el.style) { if (el.style.fontSize) this.active_style.font_size = parseInt(el.style.fontSize); if (el.style.fontFamily) this.active_style.font_family = el.style.fontFamily; if (el.style.letterSpacing) this.active_style.letter_spacing = parseInt(el.style.letterSpacing); if (el.style.lineHeight) this.active_style.line_height = parseFloat(el.style.lineHeight); if (el.style.color) this.active_style.text_color = el.style.color; } } while (el && (el = dom.nextSibling)); } } if (arts.length == 1) { this.extract_color_picker_from_selection(); } var images = _.filter(arts, function(a) { return a.mime.match("image") }); if (images.length>=1) { this.selection_metrics.contains_images = true; } var audio = _.filter(arts, function(a) { return a.mime.match("audio") }); if (audio.length>=1) { this.selection_metrics.contains_audio = true; } var embeds = _.filter(arts, function(a) { return a.mime.match("embed") }); if (embeds.length>=1) { this.selection_metrics.contains_embeds = true; } var embeds = _.filter(arts, function(a) { return a.mime=="x-spacedeck/vector" }); if (embeds.length>=1) { this.selection_metrics.contains_vectors = true; } var embeds = _.filter(arts, function(a) { return a.mime=="x-spacedeck/shape"; }); if (embeds.length>=1) { this.selection_metrics.contains_shapes = true; } var sm = this.selection_metrics; this.selection_metrics.borders_stylable = !(sm.contains_vectors||sm.contains_shapes); }, increase_letter_spacing: function(evt) { this.active_style.letter_spacing++; }, decrease_letter_spacing: function(evt) { this.active_style.letter_spacing--; }, apply_font: function(evt, font) { this.apply_formatting(evt,'fontName',font); this.active_style.font_family = font; }, toggle_advanced_properties: function() { this.advanced_properties = !this.advanced_properties; }, open_dialog: function(id, evt) { if (evt) { evt.stopPropagation(); evt.preventDefault(); } this.active_tool = "pointer"; if (this.opened_dialog == id) { this.opened_dialog = "none"; return; } if (_.contains(["mobile","shapes","zones"],id)) { this.deselect(); } this.opened_dialog=id; if (id.match("color") || id.match("background")) { this.color_picker_target = id.replace("color-","")+"_color"; this.color_mode = "palette"; this.extract_color_picker_from_selection(); } if (_.contains(["audio","video","image","search"],id)) { if ($("#"+id+" input")[0]) { $("#"+id+" input")[0].focus(); } } if (this.opened_dialog == "background") { this.color_picker_target = "background_color"; this.background_mode='color'; } if (this.opened_dialog == "info") { this.access_settings_space = this.active_space; this.access_settings_memberships = this.active_space_memberships; this.editors_section = "list"; if (this.active_space_is_readonly || this.embedded) { this.space_info_section = "info"; } else if (this.active_space_role == "admin") { this.space_info_section = "access"; } } }, toggle_color_options: function() { this.color_options_picker = !this.color_options_picker; }, close_lightbox: function() { this.lightbox_artifact = {}; this.close_modal(); }, /* --------------- artifact manipulation ------------------------- */ prepare_clipboard: function() { if ('ontouchstart' in window) return; // don't do this on touch devices this.selected_artifacts_json = JSON.stringify(this.selected_artifacts()); //$("#space-clipboard > textarea")[0].blur(); this.prepare_clipboard_step2(); }, prepare_clipboard_step2: function() { if ('ontouchstart' in window) return; // don't do this on touch devices setTimeout(function() { if (!$("#space-clipboard > textarea").length) return; // not ready yet $("#space-clipboard > textarea")[0].focus(); $("#space-clipboard > textarea")[0].select(); },100); }, handle_section_keydown: function(evt) { if (evt.keyCode == 67 && (evt.ctrlKey || evt.metaKey)) { // c key this.prepare_clipboard(); this.prepare_clipboard_step2(); } return true; }, handle_onbeforecopy: function(evt) { if (this.editing_artifact_id) return; var focused_tag = evt.target.nodeName.toLowerCase(); if (focused_tag != "body") return; this.prepare_clipboard_step2(); window.setTimeout(function() { if (!$("#space-clipboard > textarea").length) return; // not ready yet $("#space-clipboard > textarea")[0].blur(); },10); }, handle_onunload: function(evt) { if (!window.artifact_save_queue) return; var changes = Object.keys(window.artifact_save_queue).length; if (changes>0) { var message = "There are "+changes+" changes that are still being saved. Discard them?"; evt.returnValue = message; return message; } window._spacedeck_location_change = true; }, handle_window_resize: function(evt) { this.adjust_bounds_zoom(); }, handle_scroll: function(evt) { if (this.active_view!="space") return; if (!$("#space").length) return; el = $("#space")[0]; this.scroll_left = el.scrollLeft/this.viewport_zoom; this.scroll_top = el.scrollTop/this.viewport_zoom; this.window_width = window.innerWidth/this.viewport_zoom; this.window_height = window.innerHeight/this.viewport_zoom; this.resize_minimap(); // follow presenter mode: send viewport rectangle to viewers if (this.logged_in && this.present_mode) { if (this.active_space_role!="viewer") { this.presenter_send_viewport(); } } }, presenter_send_viewport: function() { name = this.user.nickname || this.user.email; var msg = { action: "viewport", x: this.scroll_left, y: this.scroll_top, w: this.window_width, h: this.window_height, zoom: this.viewport_zoom, name: name, id: this.user._id }; var packed = JSON.stringify(msg); if (packed==this._old_viewport_msg) return; this._old_viewport_msg = packed; if (this.present_mode && this.active_space_role!="viewer") this.websocket_send(msg); }, presenter_send_media_action: function(artifact_id,type,cmd,time) { name = this.user.nickname || this.user.email; var msg = { action: "media", artifact_id: artifact_id, type: type, command: cmd, time: time, name: name, id: this.user._id }; if (this.present_mode && this.active_space_role!="viewer") this.websocket_send(msg); }, resize_minimap: function() { if (!this.active_space) return; this.minimap_scale = this.active_space.width/100.0; }, handle_minimap_mouseup: function(evt) { this.minimap_mouse_state = "idle"; }, handle_minimap_mousemove: function(evt) { if (this.minimap_mouse_state=="pressed") { this.handle_minimap_mousedown(evt); } }, handle_minimap_mousedown: function(evt) { if (!$("#space").length) return; this.minimap_mouse_state = "pressed"; el = $("#space")[0]; evt = fixup_touches(evt); var ofs = $(evt.target).offset(); var x = evt.pageX - ofs.left; var y = evt.pageY - ofs.top; el.scrollLeft = (x-this.window_width/(this.minimap_scale*2))*this.minimap_scale*this.viewport_zoom; el.scrollTop = (y-this.window_height/(this.minimap_scale*2))*this.minimap_scale*this.viewport_zoom; this.handle_scroll(); }, handle_user_cursor_update: function(msg) { // console.log("handle cursor", msg); var now = new Date().getTime(); msg.t = now; var existing = false; for (var i=0; i5000) { u.x=-10000; } } } if (!existing) { this.user_cursors.push(_.clone(msg)); } }, handle_presenter_viewport_update: function(msg) { this.zoom_to_rect({ x1: msg.x, y1: msg.y, x2: msg.x+msg.w, y2: msg.y+msg.h }); }, handle_presenter_media_update: function(msg) { if(this.follow_mode) { if (msg.type=="audio") { var sel="#artifact-"+msg.artifact_id+" .audio"; try { $(sel)[0].dispatchEvent(new Event("remote_"+msg.command)); console.log("event dispatched"); } catch (e) { } } if (msg.type=="video") { var sel="#artifact-"+msg.artifact_id+" .video"; try { $(sel)[0].dispatchEvent(new Event("remote_"+msg.command)); console.log("event dispatched"); } catch (e) { } } } else { console.log("ignore media update, muted"); } }, may_select: function(a) { if (!a) return false; if (!this.active_space) return false; if (this.active_space_role=="viewer" || (a.locked && this.active_space_role!="admin")) { return false; } if (this.active_space.editors_locking && !this.logged_in && this.guest_nickname!=a.editor_name) { return false; } return true; }, select: function(evt, a) { if (!this.may_select(a)) return; if (evt && !evt.shiftKey && this.is_selected(a)) return; // already selected if (!evt || !evt.shiftKey) { this.deselect(); } if (evt && evt.shiftKey) { if (this.selected_artifacts_dict[a._id]) { delete this.selected_artifacts_dict[a._id]; } else { this.selected_artifacts_dict[a._id] = true; } } else { this.selected_artifacts_dict[a._id] = true; } this.update_board_artifact_viewmodel(a); this.extract_properties_from_selection(); this.update_selection_metrics(); this.prepare_clipboard(); this.show_toolbar_props(); }, select_all_artifacts: function(evt) { this.deselect(); for (var i=0; i"+a.description+"").text(); return txt || ""; }, deselect: function(hard) { if (window._sd_fader_moving) { window._sd_fader_moving = false; // signal from fader directive return; } this.hide_toolbar_props(); document.getSelection().removeAllRanges(); blur(); //this.prepare_clipboard(); this.prepare_clipboard_step2(); this.discover_zones(); var prev_selected = this.selected_artifacts(); this.selected_artifacts_dict = {}; // nuke empty notes for (var i=0; i=window.innerHeight-300) { pp.y = pp2.y-100; } if (pp.x<0) { pp.x=0; } if (pp.y<0) { pp.y=0; } // FIXME make sure that menus fit in window this.toolbar_props_x = pp.x+"px"; this.toolbar_props_y = pp.y+"px"; //this.hide_toolbar_artifacts(); } this.selection_metrics.x1 = sr.x1; this.selection_metrics.x2 = sr.x2; this.selection_metrics.y1 = sr.y1; this.selection_metrics.y2 = sr.y2; this.selection_metrics.x = sr.x; this.selection_metrics.y = sr.y; this.selection_metrics.w = sr.w; this.selection_metrics.h = sr.h; this.selection_metrics.style = sr.style; if (!arts) arts = this.selected_artifacts(); this.first_selected_artifact = arts[0]; this.selection_metrics.count=arts.length; this.selection_metrics.scribble_selection = false; if (arts.length == 1 && arts[0].mime == "x-spacedeck/vector") { if (arts[0].shape == "scribble") { this.selection_metrics.scribble_selection = true; } this.selection_metrics.vector_points = arts[0].control_points; this.selection_metrics.vector_selection = true; } else { this.selection_metrics.vector_points = [{},{}]; this.selection_metrics.vector_selection = false; } this.selection_metrics.has_link=false; this.insert_link_url=""; if (arts.length == 1 && arts[0].meta && arts[0].meta.link_uri && arts[0].meta.link_uri.length>0) { this.selection_metrics.has_link=true; this.insert_link_url = arts[0].meta.link_uri; } }, begin_transaction: function() { this.transaction_running = true; if (!this.undo_stack.length || this.undo_stack[this.undo_stack.length-1].action!="empty") { this.undo_stack.push({action:"empty"}); } else { //console.log("undo slot is already empty."); } this.redo_stack = []; this.artifacts_before_transaction = this.active_space_artifacts.map(function(a) { return _.cloneDeep(a); }); }, fixup_space_size: function() { if (!this.active_space) return; this.active_space.width =Math.max(this.active_space.width, window.innerWidth); this.active_space.height=Math.max(this.active_space.height, window.innerHeight); }, end_transaction: function() { this.transaction_running = false; this.throttled_process_artifact_save_queue(); if (!this.active_space) return; var er = this.enclosing_rect(this.active_space_artifacts); if (!er) return; // resize space this.active_space.width =Math.max((parseInt(er.x2/window.innerWidth)+2)*window.innerWidth, window.innerWidth); this.active_space.height=Math.max((parseInt(er.y2/window.innerHeight)+2)*window.innerHeight, window.innerHeight); console.log("bounds: ",this.active_space.width,this.active_space.height); if (this._last_bounds_width != this.active_space.width || this._last_bounds_height != this.active_space.height) { this._last_bounds_width = this.active_space.width; this._last_bounds_height = this.active_space.height; save_space(this.active_space); } this.resize_minimap(); this.discover_zones(); }, find_artifact_before_transaction: function(needle) { return this.find_artifact_in_array(this.artifacts_before_transaction, needle); }, find_artifact_in_array: function(haystack, needle) { var res = _.find(haystack, function(a) { return (needle._id && (a._id == needle._id)); }); return res; }, unsaved_transactions: function() { if (!window.artifact_save_queue) return 0; return Object.keys(window.artifact_save_queue).length; }, process_artifact_save_queue: function() { if (!window.artifact_save_queue) { return; } if (this.transaction_running) { console.log("not saving, transaction still in progress."); return; } var ids = Object.keys(window.artifact_save_queue); for (var i=0; i0 || dy>0) { this.go_to_next_zone(); return; } if (dx<0 || dy<0) { this.go_to_previous_zone(); return; } } if (!this.selected_artifacts().length) { if (!$("#space").length) return; var el = $("#space")[0]; el.scrollLeft+=dx*100; el.scrollTop +=dy*100; return; } if (this.active_space_is_readonly) return; if (event) { event.preventDefault(); event.stopPropagation(); } this.begin_transaction(); this.update_selected_artifacts(function(a) { return { x: a.x+dx, y: a.y+dy }; }); }, /* -------------------------------------------------------------------- */ highest_z: function() { var z = _.max(this.active_space_artifacts.map(function(a){return a.z||0})); if (z<0) z=0; if (z>999) z=999; return z; }, find_place_for_item: function(width, height) { var arts = this.active_space_artifacts; var tw = window.innerWidth; var th = window.innerHeight; var el = $("#space")[0]; if (!el) return {x:0,y:0,z:1}; // FIXME var wrap = $(".wrapper"); var wx = parseInt(wrap.css("margin-left")); var wy = parseInt(wrap.css("margin-top")); var x = parseInt((el.scrollLeft + tw/2)/this.viewport_zoom - width/2 - wx/this.viewport_zoom); var y = parseInt((el.scrollTop + th/2)/this.viewport_zoom - height/2 - wy/this.viewport_zoom); /* if (this.opened_dialog!="none") { // we have less visible space if a dialog is obscuring sight y/=2; } */ var z = this.highest_z()+1; if (arts.length==0) return {x:x,y:y}; x += parseInt(Math.random()*20)-10; y += parseInt(Math.random()*20)-10; return {x:x,y:y,z:z}; }, save_audio_edit: function(a) { // just a helper to be called from view model this.opened_dialog = "none"; this.update_board_artifact_viewmodel(a); save_artifact(a); }, save_artifact: function(a, on_success) { // helper to be called from view model if (this.guest_nickname) { a.editor_name = this.guest_nickname; } this.update_board_artifact_viewmodel(a); save_artifact(a, on_success); }, add_artifact: function (space, item_type, url, evt) { this.active_tool = "pointer"; this.mouse_state = "idle"; //this.hide_toolbar_artifacts(); if (!url && (item_type == 'image' || item_type == 'video' || item_type == 'embed')) { url = prompt("URL?"); if (!url || !url.length) return; } //this.opened_dialog = "none"; var w=300,h=200; var z=this.highest_z()+1; // TODO: find solution for legacy types mimes = { "text": "text/html", "note": "text/html", "image": "image/jpg", "video": "video/mp4" }; var new_item = { mime: mimes[item_type], description: "", payload_uri: url, payload_thumbnail_medium_uri: url || null, payload_thumbnail_web_uri: url || null, space_id: space._id, order: this.active_space_artifacts.length+1, valign: "middle", align: "center" //fill_color: "#f8f8f8" }; if (mimes[item_type] == "text/html") { new_item.padding_left = 10; new_item.padding_top = 10; new_item.padding_right = 10; new_item.padding_bottom = 10; new_item.fill_color = "rgba(255,255,255,1)"; new_item.description = "

Text

"; } if (evt) { var point = this.cursor_point_to_space(evt); point.x-=100; point.y-=100; } else { var point = this.find_place_for_item(w,h); z = point.z; } new_item.x = parseInt(point.x); new_item.y = parseInt(point.y); new_item.z = z; new_item.w = w; new_item.h = h; if (this.guest_nickname) { new_item.editor_name = this.guest_nickname; } // console.log("new artifact", new_item); save_artifact(new_item, function(saved_item) { // console.log("saved artifact", saved_item); this.update_board_artifact_viewmodel(saved_item); this.active_space_artifacts.push(saved_item); if (!url) { this.select(null, saved_item); } if (item_type.match("text")) { this.editing_artifact_id = saved_item._id; window.setTimeout(function() { // FIXME: replace hack var el = $("#artifact-"+saved_item._id+" .text-editing"); focus_contenteditable(el[0], false); },400); } }.bind(this)); }, go_to_first_zone: function() { this.discover_zones(); if (!this.zones.length) return; this.zoom_to_zone(this.zones[0]); }, go_to_previous_zone: function() { this.discover_zones(); if (!this.zones.length) return; var prev_idx = (this.current_zone_idx-1); if (prev_idx<0) prev_idx = this.zones.length-1; this.current_zone_idx = prev_idx; this.zoom_to_zone(this.zones[this.current_zone_idx]); }, go_to_next_zone: function() { this.discover_zones(); if (!this.zones.length) return; var next_idx = ((this.current_zone_idx+1) % this.zones.length); this.current_zone_idx = next_idx; this.zoom_to_zone(this.zones[this.current_zone_idx]); }, sort_zone_up: function(z) { var idx = this.zones.indexOf(z); if (idx<1) return; var new_zones = _.flatten([this.zones.slice(0,idx-1),[z],this.zones[idx-1],this.zones.slice(idx+1,this.zones.length)]); for (var i=0; i=this.zones.length) return; var new_zones = _.flatten([this.zones.slice(0,idx),this.zones[idx+1],[z],this.zones.slice(idx+2,this.zones.length)]); for (var i=0; i1 && !skip_deselect) { if (!confirm("Delete "+ids.length+" items?")) return; } for (var i=0; i"+a.description+"")[0]; var el = dom.firstChild; do { // clear nested styles first if (el && el.childNodes) { for (var j=0; j=1) { if (sa[0].meta && sa[0].meta.link_uri) { def = sa[0].meta.link_uri; } } var insert_link_url = prompt("URL:",def); this.update_selected_artifacts(function(a) { var update = {link_uri: insert_link_url}; if (a.payload_uri && a.payload_uri.match("webgrabber")) { var enc_uri = encodeURIComponent(btoa(insert_link_url)); var thumb_uri = ENV.apiEndpoint + "/api/webgrabber/"+enc_uri; update.payload_uri = thumb_uri; update.payload_thumbnail_web_uri = thumb_uri; update.payload_thumbnail_medium_uri = thumb_uri; update.payload_thumbnail_big_uri = thumb_uri; } return update; }); this.opened_dialog = "none"; }, clone_artifact: function(a,dx,dy,on_success) { var copy = _.cloneDeep(a); delete copy["$index"]; delete copy["_id"]; if (dx) copy.x += dx; if (dy) copy.y += dy; copy.order = this.active_space_artifacts.length+1; if (this.guest_nickname) { copy.editor_name = this.guest_nickname; } copy.space_id = this.active_space._id; save_artifact(copy, function(saved) { this.update_board_artifact_viewmodel(saved); this.active_space_artifacts.push(saved); if (on_success) { on_success(saved); } else { this.select(null,saved); } }.bind(this)); return copy; }, toggle_lock_of_selected_artifacts: function() { this.update_selected_artifacts(function(a) { return {locked: !a.locked}; }, true); }, duplicate_selected_artifacts: function() { var arts = this.selected_artifacts(); for (var i=0; i/g)) { // crappy heuristic if this is actually HTML pastedText = pastedText.replace(/\n/g,"
"); } this.insert_embedded_artifact(pastedText); }, insert_embedded_artifact: function(text) { var space = this.active_space; if (!space) return; if (text[0]=='[' || text[0]=='{') { // might be json try { parsed = JSON.parse(text); if (text[0]=='{') parsed = [parsed]; this.deselect(); for (var i=0; i"), title: "", space_id: space._id }; var w = 400; var h = 300; var point = this.find_place_for_item(w,h); new_item.x = point.x; new_item.y = point.y; new_item.w = w; new_item.h = h; new_item.z = point.z; if (this.guest_nickname) { new_item.editor_name = this.guest_nickname; } save_artifact(new_item, function(saved_item) { this.update_board_artifact_viewmodel(saved_item); this.active_space_artifacts.push(saved_item); }.bind(this)); }, create_artifact_via_embed_url: function(url) { this.close_modal(); var point = this.find_place_for_item(200,200); var z = this.highest_z()+1; var a = { space_id: this.active_space._id, mime: "image/png", description: url, state: "uploading", x: point.x, y: point.y, w: 200, h: 200, z: z, order: this.active_space_artifacts.length } var metadata = parse_link(url) if (!metadata) { return; } if (metadata.type == "unknown") { var enc_uri = encodeURIComponent(btoa(url)); a.meta = { link_uri: url } if (this.guest_nickname) { a.editor_name = this.guest_nickname; } // step 1: create placeholder save_artifact(a, function(saved_a) { this.update_board_artifact_viewmodel(saved_a); this.active_space_artifacts.push(saved_a); var thumb_uri = ENV.apiEndpoint + "/api/webgrabber/"+enc_uri; // step 2: push payload_uri for processing saved_a.state = "idle"; saved_a.payload_uri = thumb_uri; saved_a.payload_thumbnail_web_uri = thumb_uri; saved_a.payload_thumbnail_medium_uri = thumb_uri; saved_a.payload_thumbnail_big_uri = thumb_uri; save_artifact(saved_a, function(saved_a2) { this.update_board_artifact_viewmodel(saved_a); }.bind(this)); }.bind(this)); return; } var w = metadata.thumbnail_width || 200; var h = metadata.thumbnail_height || 200; if (w<200) w = 200; if (h<200) h = 200; if (metadata.provider_name == "soundcloud") { w = 500; h = 150; } a = _.extend(a, { mime: "oembed/"+metadata.type+"-"+metadata.provider_name, description: metadata.url || url, //payload_uri: metadata.url || url, payload_thumbnail_medium_uri: metadata.thumbnail_url, payload_thumbnail_web_uri: metadata.thumbnail_url, state: "idle", title: metadata.title, link_uri: metadata.url || url, x: point.x - w/2, y: point.y - h/2, w: w, h: h }); if (this.guest_nickname) { a.editor_name = this.guest_nickname; } save_artifact(a, function(saved_a) { this.update_board_artifact_viewmodel(saved_a); this.active_space_artifacts.push(saved_a); }.bind(this)); }, create_artifact_via_payload_url: function(type, url) { this.add_artifact(this.active_space, type, url, null); }, handle_touch_select_background_image: function() { $('#background-uploader').click(); }, handle_insert_image_url: function(url) { if (!url || !url.length) { $("#image_file_upload").click(); // redirect to file upload return; } this.create_artifact_via_payload_url("image", url); this.insert_image_url = ""; // this.opened_dialog = "none"; }, handle_insert_video_url: function(url) { if (!url.length) { $("#video_file_upload").click(); // redirect to file upload return; } var object = parse_link(url); if (object) { this.create_artifact_via_embed_url(url); } else { this.create_artifact_via_payload_url("video", url); } this.insert_video_url = ""; //this.opened_dialog = "none"; }, handle_insert_audio_url: function(url) { if (!url.length) { $("#audio_file_upload").click(); // redirect to file upload return; } var object = parse_link(url); if (object) { this.create_artifact_via_embed_url(url); } else { this.create_artifact_via_payload_url("audio", url); } this.insert_audio_url = ""; }, handle_generic_file_upload: function(evt) { var files = evt.target.files; this.opened_dialog = "none"; if (files && files.length) { console.log("file: ",files[0]); for (var i=0; ieff_w) { // horizontal centering this.bounds_margin_horiz = (window.innerWidth-eff_w)/2; } else { this.bounds_margin_horiz = 0; } if (window.innerHeight-80>eff_h) { // horizontal centering this.bounds_margin_vert = (window.innerHeight-eff_h)/2-80; } else { this.bounds_margin_vert = 0; } }, zoom_to_original: function() { var old_zoom = this.viewport_zoom; this.viewport_zoom = 1; this.viewport_zoom_percent = parseInt(this.viewport_zoom*100); this.adjust_bounds_zoom(); this.zoom_adjust_scroll(this.viewport_zoom/old_zoom); }, zoom_to_fit: function() { var er = this.enclosing_rect(this.active_space_artifacts); if (!er) return; var pad = 200; er.x1-=pad; er.y1-=pad-100; er.x2+=pad; er.y2+=pad+100; this.zoom_to_rect(er, 1); }, zoom_to_zone: function(z) { if (!$("#space").length) return; var er = this.enclosing_rect([z]); var el = $("#space")[0]; var cur_r = { x1: el.scrollLeft/this.viewport_zoom, y1: el.scrollTop/this.viewport_zoom, x2: (el.scrollLeft+window.innerWidth)/this.viewport_zoom, y2: (el.scrollTop+window.innerHeight)/this.viewport_zoom }; var pad = 10; er.x1-=pad; er.y1-=pad; er.x2+=pad; er.y2+=pad; if (!this.animation_running) { this.animation_running = true; this.animate_zoom_to_rect(er, 200, cur_r); this.current_zone_idx = this.zones.indexOf(z); } }, zoom_to_rect: function(er, max_zoom) { if (!$("#space").length) return; var el = $("#space")[0]; var w = er.x2-er.x1; var h = er.y2-er.y1; if (w>h) { this.viewport_zoom = window.innerWidth/w; if (window.innerHeight < h*this.viewport_zoom) { this.viewport_zoom = window.innerHeight/h; } } else { this.viewport_zoom = window.innerHeight/h; if (window.innerWidth < w*this.viewport_zoom) { this.viewport_zoom = window.innerWidth/w; } } if (max_zoom) { if (this.viewport_zoom>max_zoom) this.viewport_zoom = max_zoom; } if (this.viewport_zoom<0.05) this.viewport_zoom = 0.05; this.viewport_zoom_percent = parseInt(this.viewport_zoom*100); this.adjust_bounds_zoom(); if (!el) return; var animate = function() { el.scrollTop = (er.y1+(h/2))*this.viewport_zoom-window.innerHeight/2; el.scrollLeft = (er.x1+(w/2))*this.viewport_zoom-window.innerWidth/2; this.handle_scroll(); }.bind(this); if ("requestAnimationFrame" in window) { window.requestAnimationFrame(animate); } else { animate(); } }, animate_zoom_to_rect: function(target_r, duration, cur_r, elapsed) { if (!$("#space").length) return; var el = $("#space")[0]; var anim_res = 20; if (!elapsed) elapsed = 0; if (duration>elapsed) { window.setTimeout(function() { this.animate_zoom_to_rect(target_r, duration, cur_r, elapsed+anim_res); }.bind(this), anim_res); /*var dx = (el.scrollLeft-ncx)/anim_res; var dy = (el.scrollLeft-ncy)/anim_res; el.scrollLeft += dx; el.scrollTop += dy;*/ // interpolate var dx1 = ((target_r.x1-cur_r.x1)/duration)*elapsed; var dx2 = ((target_r.x2-cur_r.x2)/duration)*elapsed; var dy1 = ((target_r.y1-cur_r.y1)/duration)*elapsed; var dy2 = ((target_r.y2-cur_r.y2)/duration)*elapsed; var step_r = { x1: cur_r.x1+dx1, x2: cur_r.x2+dx2, y1: cur_r.y1+dy1, y2: cur_r.y2+dy2 }; /*console.log("cur_r: ",cur_r); console.log("target_r: ",target_r); console.log("step_r: ",step_r);*/ this.zoom_to_rect(step_r); } else { // done this.zoom_to_rect(target_r); this.animation_running = false; } }, zoom_to_point: function(p,amount) { var el = $("#space")[0]; var sx = el.scrollLeft/this.viewport_zoom; var sy = el.scrollTop/this.viewport_zoom; var ww = window.innerWidth/(this.viewport_zoom); var wh = window.innerHeight/(this.viewport_zoom); var oxx = (p.x-(sx+ww/2))*amount; var oyy = (p.y-(sy+wh/2))*amount; var ox = -oxx; var oy = -oyy; var r = { x1: p.x-(ww/2)*amount + ox, y1: p.y-(wh/2)*amount + oy, x2: p.x+(ww/2)*amount + ox, y2: p.y+(wh/2)*amount + oy }; this.zoom_to_rect(r,2); }, throttled_zoom_to_point: _.throttle(function(p,a){ this.zoom_to_point(p,a); }, 50), zoom_to_cursor: function(evt,amount) { var point = this.cursor_point_to_space(evt); this.throttled_zoom_to_point.bind(this)(point,amount); }, zoom_adjust_scroll: function(f) { var adjust_scroll = function() { if (!$("#space").length) return; if (!this.active_space || !this.active_space_loaded) return; var el = $("#space")[0]; var eff_w = this.active_space.width*this.viewport_zoom; var eff_h = this.active_space.height*this.viewport_zoom; var sx = el.scrollLeft; var sy = el.scrollTop; var cx = window.innerWidth/2; var cy = window.innerHeight/2; var ncx = f*(sx+cx)-cx; var ncy = f*(sy+cy)-cy; if (eff_w=2) { this.viewport_zoom = 2; } this.viewport_zoom_percent = parseInt(this.viewport_zoom*100); this.adjust_bounds_zoom(); this.zoom_adjust_scroll(this.viewport_zoom/old_zoom); }, zoom_out: function() { if (!this.viewport_zoom) this.viewport_zoom = 1; var old_zoom = this.viewport_zoom; this.viewport_zoom /= 1.5; if (this.viewport_zoom<0.05) { this.viewport_zoom = 0.05; } this.viewport_zoom_percent = parseInt(this.viewport_zoom*100); this.adjust_bounds_zoom(); this.zoom_adjust_scroll(this.viewport_zoom/old_zoom); }, activate_pan_tool: function(evt) { if (evt) { evt.stopPropagation(); evt.preventDefault(); } this.active_tool = "pan"; if (this.stop_pan_timeout) { window.clearTimeout(this.stop_pan_timeout); } // pan exit hack this.stop_pan_timeout = window.setTimeout(function () { if (this.active_tool == "pan") { this.active_tool = "pointer"; } }.bind(this),500); }, approve_pdf_upload: function(evt,approve_pdf_upload, mode){ this.close_modal(); if(mode == "classic"){ this.create_artifact_via_upload(evt, this.pending_pdf_file, false); } if(mode == "grid") { this.global_spinner = true; save_pdf_file(this.active_space, this.dropped_point, this.pending_pdf_file, approve_pdf_upload, function(createdArtifacts){ this.global_spinner = false; _.each(createdArtifacts, function(new_artifact){ this.update_board_artifact_viewmodel(new_artifact); this.active_space_artifacts.push(new_artifact) }.bind(this)); }.bind(this), function(xhr) { this.global_spinner = false; alert("Error PDF ("+xhr.status+")"); }.bind(this)); } }, handle_data_drop: function(evt) { if (this.active_space_role=="viewer") { return false; } var json = evt.dataTransfer.getData('application/json'); var dest_section = this.active_space; var files = evt.dataTransfer.files; if (files && files.length) { for (var i=0; i1)); } } } else { var json = evt.dataTransfer.getData('application/json'); if (json) { var parsed = JSON.parse(json); delete parsed._id; parsed.space_id = this.active_space._id; var w = 300; var h = 200; if (parsed.board && parsed.w && parsed.h) { w = parsed.w; h = parsed.h; } var point = this.cursor_point_to_space(evt); point.x-=w/2; point.y-=h/2; parsed.board = { x: point.x, y: point.y, w: w, h: h, z: 20 }; if (this.guest_nickname) { parsed.editor_name = this.guest_nickname; } save_artifact(parsed, function(saved_a) { this.update_board_artifact_viewmodel(saved_a); this.active_space_artifacts.push(saved_a); }.bind(this)); return; } var html = evt.dataTransfer.getData('text/html'); if (html) { var rx = /src="([^"]+)"/g; var m = rx.exec(html); if (m) { this.add_artifact(this.active_space, "image", m[1], evt); } } } }, clear_search_results: function() { this.image_search_results = []; this.audio_search_results = []; this.video_search_results = []; }, download_selected_artifacts: function() { var arts = this.selected_artifacts(); if (arts.length!=1) return; if (arts[0].payload_uri) { try { window.open(arts[0].payload_uri); } catch (e) { // could not open window } } } } }