Spaces:
Paused
Paused
/** | |
* Copyright (C) 2024 Puter Technologies Inc. | |
* | |
* This file is part of Puter. | |
* | |
* Puter is free software: you can redistribute it and/or modify | |
* it under the terms of the GNU Affero General Public License as published | |
* by the Free Software Foundation, either version 3 of the License, or | |
* (at your option) any later version. | |
* | |
* This program is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* GNU Affero General Public License for more details. | |
* | |
* You should have received a copy of the GNU Affero General Public License | |
* along with this program. If not, see <https://www.gnu.org/licenses/>. | |
*/ | |
import UIWindowPublishWebsite from './UIWindowPublishWebsite.js'; | |
import UIWindowItemProperties from './UIWindowItemProperties.js'; | |
import UIWindowGetCopyLink from './UIWindowGetCopyLink.js'; | |
import UIWindowSaveAccount from './UIWindowSaveAccount.js'; | |
import UIPopover from './UIPopover.js'; | |
import UIWindowEmailConfirmationRequired from './UIWindowEmailConfirmationRequired.js'; | |
import UIContextMenu from './UIContextMenu.js' | |
import UIAlert from './UIAlert.js' | |
import path from "../lib/path.js" | |
function UIItem(options){ | |
const matching_appendto_count = $(options.appendTo).length; | |
if(matching_appendto_count > 1){ | |
$(options.appendTo).each(function(){ | |
const opts = options; | |
opts.appendTo = this; | |
UIItem(opts); | |
}) | |
return; | |
}else if(matching_appendto_count === 0){ | |
return; | |
} | |
const item_id = global_element_id++; | |
let last_mousedown_ts = 999999999999999; | |
let rename_cancelled = false; | |
// set options defaults | |
options.disabled = options.disabled ?? false; | |
options.is_dir = options.is_dir ?? false; | |
options.is_selected = options.is_selected ?? false; | |
options.is_shared = options.is_shared ?? false; | |
options.is_shortcut = options.is_shortcut ?? 0; | |
options.is_trash = options.is_trash ?? false; | |
options.metadata = options.metadata ?? ''; | |
options.multiselectable = options.multiselectable ?? true; | |
options.shortcut_to = options.shortcut_to ?? ''; | |
options.shortcut_to_path = options.shortcut_to_path ?? ''; | |
options.immutable = (options.immutable === false || options.immutable === 0 || options.immutable === undefined ? 0 : 1); | |
options.sort_container_after_append = (options.sort_container_after_append !== undefined ? options.sort_container_after_append : false); | |
const is_shared_with_me = (options.path !== '/'+window.user.username && !options.path.startsWith('/'+window.user.username+'/')); | |
let website_url = determine_website_url(options.path); | |
// do a quick check to see if the target parent has any file type restrictions | |
const appendto_allowed_file_types = $(options.appendTo).attr('data-allowed_file_types') | |
if(!window.check_fsentry_against_allowed_file_types_string({is_dir: options.is_dir, name:options.name, type:options.type}, appendto_allowed_file_types)) | |
options.disabled = true; | |
// -------------------------------------------------------- | |
// HTML for Item | |
// -------------------------------------------------------- | |
let h = ''; | |
h += `<div id="item-${item_id}" | |
class="item${options.is_selected ? ' item-selected':''} ${options.disabled ? 'item-disabled':''}" | |
data-id="${item_id}" | |
data-name="${html_encode(options.name)}" | |
data-metadata="${html_encode(options.metadata)}" | |
data-uid="${options.uid}" | |
data-is_dir="${options.is_dir ? 1 : 0}" | |
data-is_trash="${options.is_trash ? 1 : 0}" | |
data-has_website="${options.has_website ? 1 : 0 }" | |
data-website_url = "${website_url ? html_encode(website_url) : ''}" | |
data-immutable="${options.immutable}" | |
data-is_shortcut = "${options.is_shortcut}" | |
data-shortcut_to = "${html_encode(options.shortcut_to)}" | |
data-shortcut_to_path = "${html_encode(options.shortcut_to_path)}" | |
data-sortable = "${options.sortable ?? 'true'}" | |
data-sort_by = "${html_encode(options.sort_by) ?? 'name'}" | |
data-size = "${options.size ?? ''}" | |
data-type = "${html_encode(options.type) ?? ''}" | |
data-modified = "${options.modified ?? ''}" | |
data-associated_app_name = "${html_encode(options.associated_app_name) ?? ''}" | |
data-path="${html_encode(options.path)}">`; | |
// spinner | |
h += `<div class="item-spinner">`; | |
h += `</div>`; | |
// modified | |
h += `<div class="item-attr item-attr--modified">`; | |
h += `<span>${options.modified === 0 ? '-' : timeago.format(options.modified*1000)}</span>`; | |
h += `</div>`; | |
// size | |
h += `<div class="item-attr item-attr--size">`; | |
h += `<span>${options.size ? byte_format(options.size) : '-'}</span>`; | |
h += `</div>`; | |
// type | |
h += `<div class="item-attr item-attr--type">`; | |
if(options.is_dir) | |
h += `<span>Folder</span>`; | |
else | |
h += `<span>${options.type ? html_encode(options.type) : '-'}</span>`; | |
h += `</div>`; | |
// icon | |
h += `<div class="item-icon">`; | |
h += `<img src="${html_encode(options.icon.image)}" class="item-icon-${options.icon.type}" data-item-id="${item_id}">`; | |
h += `</div>`; | |
// badges | |
h += `<div class="item-badges">`; | |
// website badge | |
h += `<img class="item-badge item-has-website-badge long-hover" | |
style="${options.has_website ? 'display:block;' : ''}" | |
src="${html_encode(window.icons['world.svg'])}" | |
data-item-id="${item_id}" | |
>`; | |
// link badge | |
h += `<img class="item-badge item-has-website-url-badge" | |
style="${website_url ? 'display:block;' : ''}" | |
src="${html_encode(window.icons['link.svg'])}" | |
data-item-id="${item_id}" | |
>`; | |
// shared badge | |
h += `<img class="item-badge item-badge-has-permission" | |
style="display: ${ is_shared_with_me ? 'block' : 'none'}; | |
background-color: #ffffff; | |
padding: 2px;" src="${html_encode(window.icons['shared.svg'])}" | |
data-item-id="${item_id}" | |
title="A user has shared this item with you.">`; | |
// owner-shared badge | |
h += `<img class="item-badge item-is-shared" | |
style="background-color: #ffffff; padding: 2px; ${!is_shared_with_me && options.is_shared ? 'display:block;' : ''}" | |
src="${html_encode(window.icons['owner-shared.svg'])}" | |
data-item-id="${item_id}" | |
data-item-uid="${options.uid}" | |
data-item-path="${html_encode(options.path)}" | |
title="You have shared this item with at least one other user." | |
>`; | |
// shortcut badge | |
h += `<img class="item-badge item-shortcut" | |
style="background-color: #ffffff; padding: 2px; ${options.is_shortcut !== 0 ? 'display:block;' : ''}" | |
src="${html_encode(window.icons['shortcut.svg'])}" | |
data-item-id="${item_id}" | |
title="Shortcut" | |
>`; | |
h += `</div>`; | |
// name | |
h += `<span class="item-name" data-item-id="${item_id}" title="${html_encode(options.name)}">${html_encode(truncate_filename(options.name, TRUNCATE_LENGTH)).replaceAll(' ', ' ')}</span>` | |
// name editor | |
h += `<textarea class="item-name-editor hide-scrollbar" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off" data-gramm_editor="false">${html_encode(options.name)}</textarea>` | |
h += `</div>`; | |
// append to options.appendTo | |
$(options.appendTo).append(h); | |
// updte item_container | |
const item_container = $(options.appendTo).closest('.item-container'); | |
show_or_hide_empty_folder_message(item_container); | |
// get all the elements needed | |
const el_item = document.getElementById(`item-${item_id}`); | |
const el_item_name = document.querySelector(`#item-${item_id} > .item-name`); | |
const el_item_icon = document.querySelector(`#item-${item_id} .item-icon`); | |
const el_item_name_editor = document.querySelector(`#item-${item_id} > .item-name-editor`); | |
const is_trashed = $(el_item).attr('data-path').startsWith(trash_path + '/'); | |
// update parent window's explorer item count if applicable | |
if(options.appendTo !== undefined){ | |
let el_window = options.appendTo; | |
if(!$(el_window).hasClass('.window')) | |
el_window = $(el_window).closest('.window'); | |
update_explorer_footer_item_count(el_window); | |
} | |
// -------------------------------------------------------- | |
// Dragster | |
// allow dragging of local files on this window, if it's is_dir | |
// -------------------------------------------------------- | |
if(options.is_dir){ | |
$(el_item).dragster({ | |
enter: function () { | |
$(el_item).not('.item-disabled').addClass('item-selected'); | |
}, | |
leave: function () { | |
$(el_item).removeClass('item-selected'); | |
}, | |
drop: function (dragsterEvent, event) { | |
const e = event.originalEvent; | |
$(el_item).removeClass('item-selected'); | |
// if files were dropped... | |
if(e.dataTransfer?.items?.length > 0){ | |
upload_items( e.dataTransfer.items, $(el_item).attr('data-path')) | |
} | |
e.stopPropagation(); | |
e.preventDefault(); | |
return false; | |
} | |
}); | |
} | |
// -------------------------------------------------------- | |
// Draggable | |
// -------------------------------------------------------- | |
let longer_hover_timeout; | |
let last_window_dragged_over; | |
$(el_item).draggable({ | |
appendTo: "body", | |
helper: "clone", | |
revert: "invalid", | |
//containment: "document", | |
zIndex: 10000, | |
scroll:false, | |
distance: 5, | |
revertDuration: 100, | |
start: function(event, ui) { | |
// select this item and its helper | |
$(el_item).addClass('item-selected'); | |
$('.ui-draggable-dragging').addClass('item-selected'); | |
//clone other selected items | |
$(el_item) | |
.siblings('.item-selected') | |
.clone() | |
.addClass('item-selected-clone') | |
.css('position', 'absolute') | |
.appendTo('body') | |
.hide(); | |
// Bring item and clones to front | |
$('.item-selected-clone, .ui-draggable-dragging').css('z-index', 99999); | |
// count badge | |
const item_count = $('.item-selected-clone').length; | |
if(item_count > 0){ | |
$('body').append(`<span class="draggable-count-badge">${item_count + 1}</span>`); | |
} | |
// Disable all droppable UIItems that are not a dir/app to avoid accidental cancellation | |
// on Items that are not droppables. In general if an item is dropped on another, if the | |
// target is not a dir, the source needs to be dropped on the target's container. | |
$(`.item[data-is_dir="0"][data-associated_app_name=""]:not(.item-selected)`).droppable('disable'); | |
// Disable pointer events on all app iframes. This is needed because as soon as | |
// a dragging event enters the iframe the event is delegated to iframe which makes the item | |
// stuck at the edge of the iframe not allowing us to move items freely across the screen | |
$('.window-app-iframe').css('pointer-events', 'none') | |
// reset longer hover timeout and last window dragged over | |
longer_hover_timeout = null; | |
last_window_dragged_over = null; | |
}, | |
drag: function(event, ui) { | |
// Only show drag helpers if the item has been moved more than 5px | |
if( Math.abs(ui.originalPosition.top - ui.offset.top) > 5 | |
|| | |
Math.abs(ui.originalPosition.left - ui.offset.left) > 5 ){ | |
$('.ui-draggable-dragging').show(); | |
$('.item-selected-clone').show(); | |
$('.draggable-count-badge').show(); | |
} | |
const other_selected_items = $('.item-selected-clone'); | |
const item_count = other_selected_items.length + 1; | |
// Move count badge with mouse | |
$('.draggable-count-badge').css({ | |
top: event.pageY, | |
left: event.pageX + 10, | |
}) | |
// Move other selected items | |
for(let i=0; i < item_count - 1; i++){ | |
$(other_selected_items[i]).css({ | |
'left': ui.position.left + 3 * (i+1), | |
'top': ui.position.top + 3 * (i+1), | |
'z-index': 999 - (i), | |
'opacity': 0.5 - i*0.1, | |
}) | |
} | |
// remove all item-container active borders | |
$('.item-container').removeClass('item-container-active'); | |
// if item has changed container, remove timeout for window focus and reset last target | |
if(longer_hover_timeout && last_window_dragged_over !== window.mouseover_window){ | |
clearTimeout(longer_hover_timeout); | |
longer_hover_timeout = null; | |
last_window_dragged_over = window.mouseover_window; | |
} | |
// if item hover for more than 1.2s, focus the window | |
if(!longer_hover_timeout){ | |
longer_hover_timeout = setTimeout(() => { | |
$(last_window_dragged_over).focusWindow(); | |
}, 1200); | |
} | |
// Highlight item container to help user see more clearly where the item is going to be dropped | |
if($(window.mouseover_item_container).closest('.window').is(window.mouseover_window) && | |
// do not highlight if the target is the same as the item being moved | |
$(el_item).attr('data-path') !== $(window.mouseover_item_container).attr('data-path') && | |
// do not highlight if item is being moved to where it already is | |
$(el_item).attr('data-path') !== $(window.mouseover_item_container).attr('data-path')){ | |
// highlight item container | |
$(window.mouseover_item_container).addClass('item-container-active'); | |
} | |
// send drag event to iframe if mouse is inside iframe | |
if(mouseover_window){ | |
const $app_iframe = $(mouseover_window).find('.window-app-iframe'); | |
if(!$(mouseover_window).hasClass('window-disabled') && $app_iframe.length > 0){ | |
var rect = $app_iframe.get(0).getBoundingClientRect(); | |
// if mouse is inside iframe, send drag message to iframe | |
if(mouseX > rect.left && mouseX < rect.right && mouseY > rect.top && mouseY < rect.bottom){ | |
$app_iframe.get(0).contentWindow.postMessage({msg: "drag", x: (mouseX - rect.left), y: (mouseY - rect.top)}, '*'); | |
} | |
} | |
} | |
}, | |
stop: function(event, ui){ | |
$('.item-selected-clone').remove(); | |
$('.draggable-count-badge').remove(); | |
// re-enable all droppable UIItems that are not a dir | |
$(`.item[data-is_dir='0']:not(.item-selected)`).droppable('enable'); | |
// remove active item-container border highlights | |
$('.item-container').removeClass('item-container-active'); | |
// reset longer hover timeout and last window dragged over | |
clearTimeout(longer_hover_timeout); | |
last_window_dragged_over = null; | |
} | |
}); | |
// -------------------------------------------------------- | |
// Droppable | |
// -------------------------------------------------------- | |
$(el_item).droppable({ | |
accept: '.item', | |
// 'pointer' is very important because of active window tracking is based on the position of cursor. | |
tolerance: 'pointer', | |
drop: async function( event, ui ) { | |
// Check if hovering over an item that is VISIBILE | |
if($(event.target).closest('.window').attr('data-id') !== $(mouseover_window).attr('data-id')) | |
return; | |
// If ctrl is pressed and source is Trashed, cancel whole operation | |
if(event.ctrlKey && path.dirname($(ui.draggable).attr('data-path')) === window.trash_path) | |
return; | |
const items_to_move = [] | |
// First item | |
items_to_move.push(ui.draggable); | |
// All subsequent items | |
const cloned_items = document.getElementsByClassName('item-selected-clone'); | |
for(let i =0; i<cloned_items.length; i++){ | |
const source_item = document.getElementById('item-' + $(cloned_items[i]).attr('data-id')); | |
if(source_item !== null) | |
items_to_move.push(source_item); | |
} | |
// -------------------------------------------------------- | |
// If dropped on an app, open the app with the dropped | |
// items as argument | |
//-------------------------------------------------------- | |
if(options.associated_app_name){ | |
// an array that hold the items to sign | |
const items_to_open = []; | |
// prepare items to sign | |
for(let i=0; i < items_to_move.length; i++){ | |
items_to_open.push({ | |
name: $(items_to_move[i]).attr('data-name'), | |
uid: $(items_to_move[i]).attr('data-uid'), | |
action: 'write', | |
path: $(items_to_move[i]).attr('data-path') | |
}); | |
} | |
// open each item | |
for (let i = 0; i < items_to_open.length; i++) { | |
const item = items_to_open[i]; | |
launch_app({ | |
name: options.associated_app_name, | |
file_path: item.path, | |
// app_obj: open_item_meta.suggested_apps[0], | |
window_title: item.name, | |
file_uid: item.uid, | |
file_signature: item, | |
}); | |
} | |
// deselect dragged item | |
for(let i=0; i < items_to_move.length; i++) | |
$(items_to_move[i]).removeClass('item-selected'); | |
} | |
//-------------------------------------------------------- | |
// If dropped on a directory, move items to that directory | |
//-------------------------------------------------------- | |
else{ | |
// If ctrl key is down, copy items. Except if target or source is Trash | |
if(event.ctrlKey){ | |
if(options.is_dir && $(el_item).attr('data-path') !== window.trash_path ) | |
copy_items(items_to_move, $(el_item).attr('data-path')) | |
else if(!options.is_dir) | |
copy_items(items_to_move, path.dirname($(el_item).attr('data-path'))); | |
} | |
// If alt key is down, create shortcut items | |
else if(event.altKey && window.feature_flags.create_shortcut){ | |
items_to_move.forEach((item_to_move) => { | |
create_shortcut( | |
path.basename($(item_to_move).attr('data-path')), | |
$(item_to_move).attr('data-is_dir') === '1', | |
options.is_dir ? $(el_item).attr('data-path') : path.dirname($(el_item).attr('data-path')), | |
null, | |
$(item_to_move).attr('data-shortcut_to') === '' ? $(item_to_move).attr('data-uid') : $(item_to_move).attr('data-shortcut_to'), | |
$(item_to_move).attr('data-shortcut_to_path') === '' ? $(item_to_move).attr('data-path') : $(item_to_move).attr('data-shortcut_to_path'), | |
); | |
}); | |
} | |
// Otherwise, move items | |
else if(options.is_dir){ | |
move_items(items_to_move, $(el_item).attr('data-shortcut_to_path') !== '' ? $(el_item).attr('data-shortcut_to_path') : $(el_item).attr('data-path')); | |
} | |
} | |
// Re-enable droppable on all 'item-container's | |
$('.item-container').droppable('enable') | |
return false; | |
}, | |
over: function(event, ui){ | |
// Check hovering over an item that is VISIBILE | |
const $event_parent_win = $(event.target).closest('.window') | |
if( $event_parent_win.length > 0 && $event_parent_win.attr('data-id') !== $(mouseover_window).attr('data-id')) | |
return; | |
// Don't do anything if the dragged item is NOT a UIItem | |
if(!$(ui.draggable).hasClass('item')) | |
return; | |
// If this is a directory or an app, and an item was dragged over it, highlight it. | |
if(options.is_dir || options.associated_app_name){ | |
$(el_item).addClass('item-selected'); | |
$('.ui-draggable-dragging .item-name, .item-selected-clone .item-name').css('opacity', 0.1) | |
// remove all item-container active borders | |
$('.item-container').addClass('item-container-transparent-border') | |
} | |
// Disable all window bodies | |
$('.item-container').droppable( 'disable' ) | |
}, | |
out: function(event, ui){ | |
// Don't do anything if the dragged item is NOT a UIItem | |
if(!$(ui.draggable).hasClass('item')) | |
return; | |
// Unselect directory/app if item is dragged out | |
if(options.is_dir || options.associated_app_name){ | |
$(el_item).removeClass('item-selected'); | |
$('.ui-draggable-dragging .item-name, .item-selected-clone .item-name').css('opacity', 'initial') | |
$('.item-container').removeClass('item-container-transparent-border') | |
} | |
$('.item-container').droppable( 'enable' ) | |
} | |
}); | |
// -------------------------------------------------------- | |
// Double Click/Single Tap on Item | |
// -------------------------------------------------------- | |
if(isMobile.phone || isMobile.tablet){ | |
$(el_item).on('click', async function (e) { | |
// if item is disabled, do not allow any action | |
if($(el_item).hasClass('item-disabled')) | |
return false; | |
if($(e.target).hasClass('item-name-editor')) | |
return false; | |
open_item({ | |
item: el_item, | |
maximized: true, | |
}); | |
}); | |
}else{ | |
$(el_item).on('dblclick', async function (e) { | |
// if item is disabled, do not allow any action | |
if($(el_item).hasClass('item-disabled')) | |
return false; | |
if($(e.target).hasClass('item-name-editor')) | |
return false; | |
open_item({ | |
item: el_item, | |
new_window: e.metaKey || e.ctrlKey, | |
}); | |
}); | |
} | |
// -------------------------------------------------------- | |
// Mousedown | |
// -------------------------------------------------------- | |
$(el_item).on('mousedown', function (e) { | |
// if item is disabled, do not allow any action | |
if($(el_item).hasClass('item-disabled')) | |
return false; | |
// if link badge is clicked, don't continue | |
if($(e.target).hasClass('item-has-website-url-badge')) | |
return false; | |
const $el_parent_window = $(el_item).closest('.window'); | |
// first see if this is a ContextMenu call on multiple items | |
if(e.which === 3 && $(el_item).hasClass('item-selected') && $(el_item).siblings('.item-selected').length > 0){ | |
$(".context-menu").remove(); | |
return false; | |
} | |
// unselect other items if neither CTRL nor Command key are held | |
// or | |
// if parent is not multiselectable | |
if((!e.ctrlKey && !e.metaKey && !$(this).hasClass('item-selected')) || ($el_parent_window.length>0 && $el_parent_window.attr('data-multiselectable') !== 'true')){ | |
$(this).closest('.item-container').find('.item-selected').removeClass('item-selected'); | |
} | |
if((e.ctrlKey || e.metaKey) && $(this).hasClass('item-selected')){ | |
$(this).removeClass('item-selected') | |
} | |
else{ | |
$(this).addClass('item-selected') | |
} | |
update_explorer_footer_selected_items_count($el_parent_window) | |
}); | |
// -------------------------------------------------------- | |
// Click | |
// -------------------------------------------------------- | |
$(el_item).on('click', function (e) { | |
// if item is disabled, do not allow any action | |
if($(el_item).hasClass('item-disabled')) | |
return false; | |
skip_a_rename_click = false; | |
const $el_parent_window = $(el_item).closest('.window'); | |
// do not unselect other items if: | |
// CTRL/Command key is pressed or clicking an item that is already selected | |
if(!e.ctrlKey && !e.metaKey){ | |
$(this).closest('.item-container').find('.item-selected').not(this).removeClass('item-selected'); | |
update_explorer_footer_selected_items_count($el_parent_window) | |
} | |
//---------------------------------------------------------------- | |
// On an OpenFileDialog? | |
//---------------------------------------------------------------- | |
if($el_parent_window.attr('data-is_openFileDialog') === 'true'){ | |
if(!options.is_dir) | |
$el_parent_window.find('.openfiledialog-open-btn').removeClass('disabled'); | |
else | |
$el_parent_window.find('.openfiledialog-open-btn').addClass('disabled'); | |
} | |
//---------------------------------------------------------------- | |
// On a SaveFileDialog? | |
//---------------------------------------------------------------- | |
if($el_parent_window.attr('data-is_saveFileDialog') === 'true' && !options.is_dir){ | |
$el_parent_window.find('.savefiledialog-filename').val($(el_item).attr('data-name')); | |
$el_parent_window.find('.savefiledialog-save-btn').removeClass('disabled'); | |
} | |
}); | |
$(document).on('click', function(e){ | |
if(!$(e.target).hasClass('item') && !$(e.target).hasClass('item-name') && !$(e.target).hasClass('item-icon')){ | |
skip_a_rename_click = true; | |
} | |
if($(e.target).parents('.item').data('id') !== item_id){ | |
skip_a_rename_click = true; | |
} | |
}) | |
// -------------------------------------------------------- | |
// Rename | |
// -------------------------------------------------------- | |
function rename(){ | |
if(rename_cancelled){ | |
rename_cancelled = false; | |
return; | |
} | |
const old_name = $(el_item).attr('data-name'); | |
const old_path = $(el_item).attr('data-path'); | |
const new_name = $(el_item_name_editor).val(); | |
// Don't send a rename request if: | |
// the new name is the same as the old one, | |
// or it's empty, | |
// or editable was not even active at all | |
if(old_name === new_name || !new_name || new_name === '.' || new_name === '..' || !$(el_item_name_editor).hasClass('item-name-editor-active')){ | |
if(new_name === '.'){ | |
UIAlert(`The name "." is not allowed, because it is a reserved name. Please choose another name.`); | |
} | |
else if(new_name === '..'){ | |
UIAlert(`The name ".." is not allowed, because it is a reserved name. Please choose another name.`) | |
} | |
$(el_item_name).html(truncate_filename(options.name, TRUNCATE_LENGTH).replaceAll(' ', ' ')); | |
$(el_item_name).show(); | |
$(el_item_name_editor).val($(el_item).attr('data-name')); | |
$(el_item_name_editor).hide(); | |
return; | |
} | |
// deactivate item name editable | |
$(el_item_name_editor).removeClass('item-name-editor-active'); | |
// Perform rename request | |
puter.fs.rename({ | |
uid: options.uid === 'null' ? null : options.uid, | |
new_name: new_name, | |
excludeSocketID: window.socket.id, | |
success: async (fsentry)=>{ | |
// Has the extension changed? in that case update options.sugggested_apps | |
const old_extension = path.extname(old_name); | |
const new_extension = path.extname(new_name); | |
if(old_extension !== new_extension){ | |
suggest_apps_for_fsentry({ | |
uid: options.uid, | |
onSuccess: function(suggested_apps){ | |
options.suggested_apps = suggested_apps; | |
} | |
}); | |
} | |
// Set new item name | |
$(`.item[data-uid='${$(el_item).attr('data-uid')}'] .item-name`).html(html_encode(truncate_filename(new_name, TRUNCATE_LENGTH)).replaceAll(' ', ' ')); | |
$(el_item_name).show(); | |
// Hide item name editor | |
$(el_item_name_editor).hide(); | |
// Set new icon | |
const new_icon = (options.is_dir ? window.icons['folder.svg'] : (await item_icon(fsentry)).image); | |
$(el_item_icon).find('.item-icon-icon').attr('src', new_icon); | |
// Set new data-name | |
options.name = new_name; | |
$(el_item).attr('data-name', html_encode(new_name)); | |
$(`.item[data-uid='${$(el_item).attr('data-uid')}']`).attr('data-name', html_encode(new_name)); | |
$(`.window-${options.uid}`).attr('data-name', html_encode(new_name)); | |
// Set new title attribute | |
$(`.item[data-uid='${$(el_item).attr('data-uid')}']`).attr('title', html_encode(new_name)); | |
$(`.window-${options.uid}`).attr('title', html_encode(new_name)); | |
// Set new value for item-name-editor | |
$(`.item[data-uid='${$(el_item).attr('data-uid')}'] .item-name-editor`).val(html_encode(new_name)); | |
$(`.item[data-uid='${$(el_item).attr('data-uid')}'] .item-name`).attr('title', html_encode(new_name)); | |
// Set new data-path | |
options.path = path.join( path.dirname(options.path), options.name); | |
const new_path = options.path; | |
$(el_item).attr('data-path', new_path); | |
$(`.item[data-uid='${$(el_item).attr('data-uid')}']`).attr('data-path', new_path); | |
$(`.window-${options.uid}`).attr('data-path', new_path); | |
// Update all elements that have matching paths | |
$(`[data-path="${html_encode(old_path)}" i]`).each(function(){ | |
$(this).attr('data-path', new_path) | |
if($(this).hasClass('window-navbar-path-dirname')) | |
$(this).text(new_name); | |
}); | |
// Update the paths of all elements whose paths start with old_path | |
$(`[data-path^="${html_encode(old_path) + '/'}"]`).each(function(){ | |
const new_el_path = _.replace($(this).attr('data-path'), old_path + '/', new_path+'/'); | |
$(this).attr('data-path', new_el_path); | |
}); | |
// Update the 'Sites Cache' | |
if($(el_item).attr('data-has_website') === '1') | |
await update_sites_cache(); | |
// Update website_url | |
website_url = determine_website_url(new_path); | |
$(el_item).attr('data-website_url', website_url); | |
// Update all exact-matching windows | |
$(`.window-${options.uid}`).each(function(){ | |
update_window_path(this, options.path); | |
}) | |
// Set new name for corresponding open windows | |
$(`.window-${options.uid} .window-head-title`).text(new_name); | |
// Re-sort all matching item containers | |
$(`.item[data-uid='${$(el_item).attr('data-uid')}']`).parent('.item-container').each(function(){ | |
sort_items(this, $(el_item).closest('.item-container').attr('data-sort_by'), $(el_item).closest('.item-container').attr('data-sort_order')); | |
}) | |
}, | |
error: function (err){ | |
// reset to old name | |
$(el_item_name).text(truncate_filename(options.name, TRUNCATE_LENGTH)); | |
$(el_item_name).show(); | |
// hide item name editor | |
$(el_item_name_editor).hide(); | |
$(el_item_name_editor).val(html_encode($(el_item).attr('data-name'))); | |
//show error | |
if(err.message){ | |
UIAlert(err.message) | |
} | |
}, | |
}); | |
} | |
// -------------------------------------------------------- | |
// Rename if enter pressed on Item Name Editor | |
// -------------------------------------------------------- | |
$(el_item_name_editor).on('keypress',function(e) { | |
// If name editor is not active don't continue | |
if(!$(el_item_name_editor).is(":visible")) | |
return; | |
// Enter key = rename | |
if(e.which === 13) { | |
e.stopPropagation(); | |
e.preventDefault(); | |
$(el_item_name_editor).blur(); | |
$(el_item).addClass('item-selected'); | |
last_enter_pressed_to_rename_ts = Date.now(); | |
update_explorer_footer_selected_items_count($(el_item).closest('.item-container')); | |
return false; | |
} | |
}) | |
// -------------------------------------------------------- | |
// Cancel and undo if escape pressed on Item Name Editor | |
// -------------------------------------------------------- | |
$(el_item_name_editor).on('keyup',function(e) { | |
if(!$(el_item_name_editor).is(":visible")) | |
return; | |
// Escape = undo rename | |
else if(e.which === 27){ | |
e.stopPropagation(); | |
e.preventDefault(); | |
rename_cancelled = true; | |
$(el_item_name_editor).hide(); | |
$(el_item_name_editor).val(options.name); | |
$(el_item_name).show(); | |
} | |
}); | |
$(el_item_name_editor).on('focusout',function(e) { | |
e.stopPropagation(); | |
e.preventDefault(); | |
rename(); | |
}); | |
/************************************************ | |
* Takes care of 'click to edit item name' | |
************************************************/ | |
let skip_a_rename_click = true; | |
$(el_item_name).on('click', function(e){ | |
if( !skip_a_rename_click && e.which !== 3 && $(el_item_name).parent('.item-selected').length > 0){ | |
last_mousedown_ts = Date.now(); | |
setTimeout(() => { | |
if(!skip_a_rename_click && (Date.now() - last_mousedown_ts) > 400){ | |
if (!e.ctrlKey && !e.metaKey) | |
activate_item_name_editor(el_item) | |
last_mousedown_ts = 0 | |
}else{ | |
last_mousedown_ts = Date.now() + 500; | |
skip_a_rename_click= false; | |
} | |
}, 500); | |
} | |
skip_a_rename_click = false; | |
}) | |
$(el_item_name).on('dblclick', function(e){ | |
skip_a_rename_click = true; | |
}) | |
// -------------------------------------------------------- | |
// ContextMenu | |
// -------------------------------------------------------- | |
$(el_item).bind("contextmenu taphold", async function (event) { | |
// if item is disabled, do not allow any action | |
if($(el_item).hasClass('item-disabled')) | |
return false; | |
// if on website link badge, don't continue | |
if($(event.target).hasClass('item-has-website-url-badge')) | |
return false; | |
// dimiss taphold on regular devices | |
if(event.type==='taphold' && !isMobile.phone && !isMobile.tablet) | |
return; | |
// if editing item name, preserve native context menu | |
if(event.target === el_item_name_editor) | |
return; | |
// if ctrl is pressed don't open ctxmenu, ctrl is for drag and copy | |
if(event.ctrlKey) | |
return false; | |
event.preventDefault(); | |
let menu_items; | |
const $selected_items = $(el_item).closest('.item-container').find('.item-selected').not(el_item).addBack(); | |
// ------------------------------------------------------- | |
// Multiple items selected | |
// ------------------------------------------------------- | |
if($selected_items.length > 1){ | |
const are_trashed = $selected_items.attr('data-path').startsWith(trash_path + '/'); | |
menu_items = [] | |
// ------------------------------------------- | |
// Restore | |
// ------------------------------------------- | |
if(are_trashed){ | |
menu_items.push({ | |
html: "Restore", | |
onClick: function(){ | |
$selected_items.each(function() { | |
const ell = this; | |
let metadata = $(ell).attr('data-metadata') === '' ? {} : JSON.parse($(ell).attr('data-metadata')) | |
move_items([ell], path.dirname(metadata.original_path)); | |
}) | |
} | |
}); | |
// ------------------------------------------- | |
// - | |
// ------------------------------------------- | |
menu_items.push('-'); | |
} | |
if(!are_trashed){ | |
// ------------------------------------------- | |
// Donwload | |
// ------------------------------------------- | |
menu_items.push({ | |
html: 'Download', | |
onClick: async function(){ | |
let items = []; | |
for (let index = 0; index < $selected_items.length; index++) { | |
items.push($selected_items[index]); | |
} | |
zipItems(items, path.dirname($(el_item).attr('data-path')), true); | |
} | |
}); | |
// ------------------------------------------- | |
// Zip | |
// ------------------------------------------- | |
menu_items.push({ | |
html: 'Zip', | |
onClick: async function(){ | |
let items = []; | |
for (let index = 0; index < $selected_items.length; index++) { | |
items.push($selected_items[index]); | |
} | |
zipItems(items, path.dirname($(el_item).attr('data-path')), false); | |
} | |
}); | |
// ------------------------------------------- | |
// - | |
// ------------------------------------------- | |
menu_items.push('-'); | |
} | |
// ------------------------------------------- | |
// Cut | |
// ------------------------------------------- | |
menu_items.push({ | |
html: "Cut", | |
onClick: function(){ | |
window.clipboard_op= 'move'; | |
window.clipboard = []; | |
$selected_items.each(function() { | |
const ell = this; | |
window.clipboard.push($(ell).attr('data-path')); | |
}) | |
} | |
}); | |
// ------------------------------------------- | |
// Copy | |
// ------------------------------------------- | |
if(!are_trashed){ | |
menu_items.push({ | |
html: "Copy", | |
onClick: function(){ | |
window.clipboard_op= 'copy'; | |
window.clipboard = []; | |
$selected_items.each(function() { | |
const ell = this; | |
window.clipboard.push({path: $(ell).attr('data-path')}); | |
}) | |
} | |
}); | |
} | |
// ------------------------------------------- | |
// - | |
// ------------------------------------------- | |
menu_items.push('-'); | |
// ------------------------------------------- | |
// Delete Permanently | |
// ------------------------------------------- | |
if(are_trashed){ | |
menu_items.push({ | |
html: 'Delete Permanently', | |
onClick: async function(){ | |
const alert_resp = await UIAlert({ | |
message: `Are you sure you want to permanently delete these items?`, | |
buttons:[ | |
{ | |
label: 'Delete', | |
type: 'primary', | |
}, | |
{ | |
label: 'Cancel' | |
}, | |
] | |
}) | |
if((alert_resp) === 'Delete'){ | |
for (let index = 0; index < $selected_items.length; index++) { | |
const element = $selected_items[index]; | |
await delete_item(element); | |
} | |
const trash = await puter.fs.stat(trash_path); | |
// update other clients | |
if(window.socket){ | |
window.socket.emit('trash.is_empty', {is_empty: trash.is_empty}); | |
} | |
if(trash.is_empty){ | |
$(`.item[data-path="${html_encode(trash_path)}" i], .item[data-shortcut_to_path="${trash_path}" i]`).find('.item-icon > img').attr('src', window.icons['trash.svg']); | |
$(`.window[data-path="${html_encode(trash_path)}"]`).find('.window-head-icon').attr('src', window.icons['trash.svg']); | |
} | |
} | |
} | |
}); | |
} | |
// ------------------------------------------- | |
// Create Shortcut | |
// ------------------------------------------- | |
if(!are_trashed && window.feature_flags.create_shortcut){ | |
menu_items.push({ | |
html: 'Create Shortcut', | |
onClick: async function(){ | |
$selected_items.each(function() { | |
let base_dir = path.dirname($(this).attr('data-path')); | |
// Trash on Desktop is a special case | |
if($(this).attr('data-path') && $(this).closest('.item-container').attr('data-path') === window.desktop_path){ | |
base_dir = window.desktop_path; | |
} | |
// create shortcut | |
create_shortcut( | |
path.basename($(this).attr('data-path')), | |
$(this).attr('data-is_dir') === '1', | |
base_dir, | |
$(this).closest('.item-container'), | |
$(this).attr('data-shortcut_to') === '' ? $(this).attr('data-uid') : $(this).attr('data-shortcut_to'), | |
$(this).attr('data-shortcut_to_path') === '' ? $(this).attr('data-path') : $(this).attr('data-shortcut_to_path'), | |
); | |
}) | |
} | |
}); | |
} | |
// ------------------------------------------- | |
// Delete | |
// ------------------------------------------- | |
if(!are_trashed){ | |
menu_items.push({ | |
html: 'Delete', | |
onClick: async function(){ | |
move_items($selected_items, trash_path); | |
} | |
}); | |
} | |
} | |
// ------------------------------------------------------- | |
// One item selected | |
// ------------------------------------------------------- | |
else{ | |
const is_trash = $(el_item).attr('data-path') === trash_path || $(el_item).attr('data-shortcut_to_path') === trash_path; | |
menu_items = []; | |
// ------------------------------------------- | |
// Open | |
// ------------------------------------------- | |
if(!is_trashed){ | |
menu_items.push({ | |
html: 'Open', | |
onClick: function(){ | |
open_item({item: el_item}); | |
} | |
}); | |
// ------------------------------------------- | |
// - | |
// ------------------------------------------- | |
if(options.associated_app_name || is_trash) | |
menu_items.push('-'); | |
} | |
// ------------------------------------------- | |
// Open With | |
// ------------------------------------------- | |
if(!is_trashed && !is_trash && (options.associated_app_name === null || options.associated_app_name === undefined)){ | |
let items = []; | |
if(!options.suggested_apps || options.suggested_apps.length === 0){ | |
// try to find suitable apps | |
const suitable_apps = await suggest_apps_for_fsentry({ | |
uid: options.uid, | |
path: options.path, | |
}); | |
if(suitable_apps && suitable_apps.length > 0){ | |
options.suggested_apps = suitable_apps; | |
} | |
} | |
if(options.suggested_apps && options.suggested_apps.length > 0){ | |
for (let index = 0; index < options.suggested_apps.length; index++) { | |
const suggested_app = options.suggested_apps[index]; | |
if ( ! suggested_app ) { | |
console.warn(`suggested_app is null`, options.suggested_apps, index); | |
continue; | |
} | |
items.push({ | |
html: suggested_app.title, | |
icon: `<img src="${html_encode(suggested_app.icon ?? window.icons['app.svg'])}" style="width:16px; height: 16px; margin-bottom: -4px;">`, | |
onClick: async function(){ | |
launch_app({ | |
name: suggested_app.name, | |
file_path: $(el_item).attr('data-path'), | |
window_title: $(el_item).attr('data-name'), | |
file_uid: $(el_item).attr('data-uid'), | |
}); | |
} | |
}) | |
} | |
}else{ | |
items.push({ | |
html: 'No suitable apps found', | |
disabled: true, | |
}); | |
} | |
// add all suitable apps | |
menu_items.push({ | |
html: 'Open With', | |
items: items, | |
}); | |
// ------------------------------------------- | |
// -- separator -- | |
// ------------------------------------------- | |
menu_items.push('-'); | |
} | |
// ------------------------------------------- | |
// Open in New Window | |
// (only if the item is on a window) | |
// ------------------------------------------- | |
if($(el_item).closest('.window-body').length > 0 && options.is_dir){ | |
menu_items.push({ | |
html: 'Open in New Window', | |
onClick: function(){ | |
if(options.is_dir){ | |
open_item({item: el_item, new_window: true}) | |
} | |
} | |
}); | |
// ------------------------------------------- | |
// -- separator -- | |
// ------------------------------------------- | |
if(!is_trash && !is_trashed && options.is_dir) | |
menu_items.push('-'); | |
} | |
// ------------------------------------------- | |
// Publish As Website | |
// ------------------------------------------- | |
if(!is_trashed && !is_trash && options.is_dir){ | |
menu_items.push({ | |
html: 'Publish As Website', | |
disabled: !options.is_dir, | |
onClick: async function () { | |
if(window.require_email_verification_to_publish_website){ | |
if(window.user.is_temp && | |
!await UIWindowSaveAccount({ | |
send_confirmation_code: true, | |
message: 'Please create an account to proceed.', | |
window_options: { | |
backdrop: true, | |
close_on_backdrop_click: false, | |
} | |
})) | |
return; | |
else if(!window.user.email_confirmed && !await UIWindowEmailConfirmationRequired()) | |
return; | |
} | |
UIWindowPublishWebsite(options.uid, $(el_item).attr('data-name'), $(el_item).attr('data-path')); | |
} | |
}); | |
} | |
// ------------------------------------------- | |
// Deploy As App | |
// ------------------------------------------- | |
if(!is_trashed && !is_trash && options.is_dir){ | |
menu_items.push({ | |
html: 'Deploy As App', | |
disabled: !options.is_dir, | |
onClick: async function () { | |
launch_app({ | |
name: 'dev-center', | |
file_path: $(el_item).attr('data-path'), | |
file_uid: $(el_item).attr('data-uid'), | |
params: { | |
source_path: options.path, | |
} | |
}) | |
} | |
}); | |
menu_items.push('-'); | |
} | |
// ------------------------------------------- | |
// Empty Trash | |
// ------------------------------------------- | |
if(is_trash){ | |
menu_items.push({ | |
html: 'Empty Trash', | |
onClick: async function(){ | |
empty_trash(); | |
} | |
}); | |
} | |
// ------------------------------------------- | |
// Donwload | |
// ------------------------------------------- | |
if(!is_trash && !is_trashed && (options.associated_app_name === null || options.associated_app_name === undefined)){ | |
menu_items.push({ | |
html: 'Download', | |
disabled: options.is_dir && !window.feature_flags.download_directory, | |
onClick: async function(){ | |
if(options.is_dir) | |
zipItems(el_item, path.dirname($(el_item).attr('data-path')), true); | |
else | |
trigger_download([options.path]); | |
} | |
}); | |
} | |
// ------------------------------------------- | |
// Get Copy Link | |
// ------------------------------------------- | |
if(!is_trashed && !is_trash && (options.associated_app_name === null || options.associated_app_name === undefined)){ | |
menu_items.push({ | |
html: 'Get Copy Link', | |
onClick: async function(){ | |
if(window.user.is_temp && | |
!await UIWindowSaveAccount({ | |
message: 'Please create an account to proceed.', | |
send_confirmation_code: true, | |
window_options: { | |
backdrop: true, | |
close_on_backdrop_click: false, | |
} | |
})) | |
return; | |
else if(!window.user.email_confirmed && !await UIWindowEmailConfirmationRequired()) | |
return; | |
UIWindowGetCopyLink({ | |
name: $(el_item).attr('data-name'), | |
uid: $(el_item).attr('data-uid'), | |
path: $(el_item).attr('data-path'), | |
is_dir: options.is_dir, | |
}); | |
} | |
}); | |
} | |
// ------------------------------------------- | |
// Zip | |
// ------------------------------------------- | |
if(!is_trash && !is_trashed && !$(el_item).attr('data-path').endsWith('.zip')){ | |
menu_items.push({ | |
html: "Zip", | |
onClick: function(){ | |
zipItems(el_item, path.dirname($(el_item).attr('data-path')), false); | |
} | |
}) | |
} | |
// ------------------------------------------- | |
// Unzip | |
// ------------------------------------------- | |
if(!is_trash && !is_trashed && $(el_item).attr('data-path').endsWith('.zip')){ | |
menu_items.push({ | |
html: "Unzip", | |
onClick: async function(){ | |
const zip = new JSZip(); | |
let filPath = $(el_item).attr('data-path'); | |
let file = puter.fs.read($(el_item).attr('data-path')); | |
zip.loadAsync(file).then(async function (zip) { | |
const rootdir = await puter.fs.mkdir(path.dirname(filPath) + '/' + path.basename(filPath, '.zip'), {dedupeName: true}); | |
Object.keys(zip.files).forEach(async function (filename) { | |
if(filename.endsWith('/')) | |
await puter.fs.mkdir(rootdir.path +'/' + filename, {createMissingParents: true}); | |
zip.files[filename].async('blob').then(async function (fileData) { | |
await puter.fs.write(rootdir.path +'/' + filename, fileData); | |
}).catch(function (e) { | |
// UIAlert(e.message); | |
}) | |
}) | |
}).catch(function (e) { | |
// UIAlert(e.message); | |
}) | |
} | |
}) | |
} | |
// ------------------------------------------- | |
// Restore | |
// ------------------------------------------- | |
if(is_trashed){ | |
menu_items.push({ | |
html: 'Restore', | |
onClick: async function(){ | |
let metadata = $(el_item).attr('data-metadata') === '' ? {} : JSON.parse($(el_item).attr('data-metadata')) | |
move_items([el_item], path.dirname(metadata.original_path)); | |
} | |
}); | |
} | |
// ------------------------------------------- | |
// - | |
// ------------------------------------------- | |
if(!is_trash && (options.associated_app_name === null || options.associated_app_name === undefined)) | |
menu_items.push('-'); | |
// ------------------------------------------- | |
// Cut | |
// ------------------------------------------- | |
if($(el_item).attr('data-immutable') === '0'){ | |
menu_items.push({ | |
html: "Cut", | |
onClick: function(){ | |
window.clipboard_op= 'move'; | |
window.clipboard= [options.path]; | |
} | |
}); | |
} | |
// ------------------------------------------- | |
// Copy | |
// ------------------------------------------- | |
if(!is_trashed && !is_trash){ | |
menu_items.push({ | |
html: "Copy", | |
onClick: function(){ | |
window.clipboard_op= 'copy'; | |
window.clipboard= [{path: options.path}]; | |
} | |
}); | |
} | |
// ------------------------------------------- | |
// Paste Into Folder | |
// ------------------------------------------- | |
if($(el_item).attr('data-is_dir') === '1' && !is_trashed && !is_trash){ | |
menu_items.push({ | |
html: "Paste Into Folder", | |
disabled: clipboard.length > 0 ? false : true, | |
onClick: function(){ | |
if(clipboard_op === 'copy') | |
copy_clipboard_items($(el_item).attr('data-path'), null); | |
else if(clipboard_op === 'move') | |
move_clipboard_items(null, $(el_item).attr('data-path')) | |
} | |
}) | |
} | |
// ------------------------------------------- | |
// - | |
// ------------------------------------------- | |
if($(el_item).attr('data-immutable') === '0' && !is_trash){ | |
menu_items.push('-') | |
} | |
// ------------------------------------------- | |
// Create Shortcut | |
// ------------------------------------------- | |
if(!is_trashed && window.feature_flags.create_shortcut){ | |
menu_items.push({ | |
html: 'Create Shortcut', | |
onClick: async function(){ | |
let base_dir = path.dirname($(el_item).attr('data-path')); | |
// Trash on Desktop is a special case | |
if($(el_item).attr('data-path') && $(el_item).closest('.item-container').attr('data-path') === window.desktop_path){ | |
base_dir = window.desktop_path; | |
} | |
create_shortcut( | |
path.basename($(el_item).attr('data-path')), | |
options.is_dir, | |
base_dir, | |
options.appendTo, | |
options.shortcut_to === '' ? options.uid : options.shortcut_to, | |
options.shortcut_to_path === '' ? options.path : options.shortcut_to_path, | |
); | |
} | |
}); | |
} | |
// ------------------------------------------- | |
// Delete | |
// ------------------------------------------- | |
if($(el_item).attr('data-immutable') === '0' && !is_trashed){ | |
menu_items.push({ | |
html: 'Delete', | |
onClick: async function(){ | |
move_items([el_item], trash_path); | |
} | |
}); | |
} | |
// ------------------------------------------- | |
// Delete Permanently | |
// ------------------------------------------- | |
if(is_trashed){ | |
menu_items.push({ | |
html: 'Delete Permanently', | |
onClick: async function(){ | |
const alert_resp = await UIAlert({ | |
message: `Are you sure you want to permanently delete this item?`, | |
buttons:[ | |
{ | |
label: 'Delete', | |
type: 'primary', | |
}, | |
{ | |
label: 'Cancel' | |
}, | |
] | |
}) | |
if((alert_resp) === 'Delete'){ | |
await delete_item(el_item); | |
// check if trash is empty | |
const trash = await puter.fs.stat(trash_path); | |
// update other clients | |
if(window.socket){ | |
window.socket.emit('trash.is_empty', {is_empty: trash.is_empty}); | |
} | |
// update this client | |
if(trash.is_empty){ | |
$(`.item[data-path="${html_encode(trash_path)}" i], .item[data-shortcut_to_path="${html_encode(trash_path)}" i]`).find('.item-icon > img').attr('src', window.icons['trash.svg']); | |
$(`.window[data-path="${trash_path}"]`).find('.window-head-icon').attr('src', window.icons['trash.svg']); | |
} | |
} | |
} | |
}); | |
} | |
// ------------------------------------------- | |
// Rename | |
// ------------------------------------------- | |
if($(el_item).attr('data-immutable') === '0' && !is_trashed && !is_trash){ | |
menu_items.push({ | |
html: "Rename", | |
onClick: function(){ | |
activate_item_name_editor(el_item) | |
} | |
}); | |
} | |
// ------------------------------------------- | |
// - | |
// ------------------------------------------- | |
menu_items.push('-'); | |
// ------------------------------------------- | |
// Properties | |
// ------------------------------------------- | |
menu_items.push({ | |
html: "Properties", | |
onClick: function(){ | |
let window_height = 500; | |
let window_width = 450; | |
let left = $(el_item).position().left + $(el_item).width(); | |
left = left > (window.innerWidth - window_width)? (window.innerWidth - window_width) : left; | |
let top = $(el_item).position().top + $(el_item).height(); | |
top = top > (window.innerHeight - (window_height + window.taskbar_height + window.toolbar_height))? (window.innerHeight - (window_height + window.taskbar_height + window.toolbar_height)) : top; | |
UIWindowItemProperties( | |
$(el_item).attr('data-name'), | |
$(el_item).attr('data-path'), | |
$(el_item).attr('data-uid'), | |
left, | |
top, | |
window_width, | |
window_height, | |
); | |
} | |
}); | |
} | |
// Create ContextMenu | |
UIContextMenu({ | |
parent_element: ($(options.appendTo).hasClass('desktop') ? undefined : options.appendTo), | |
items: menu_items | |
}); | |
return false | |
}) | |
// -------------------------------------------------------- | |
// Resize Item Name Editor on every keystroke | |
// -------------------------------------------------------- | |
$(el_item_name_editor).on('input keypress focus', function(){ | |
const val = $(el_item_name_editor).val(); | |
$('.item-name-shadow').html(html_encode(val).replaceAll(' ', ' ')); | |
if(val !== ''){ | |
const w = $('.item-name-shadow').width(); | |
const h = $('.item-name-shadow').height(); | |
$(el_item_name_editor).width(w + 4) | |
$(el_item_name_editor).height(h + 2) | |
} | |
}) | |
if(options.sort_container_after_append){ | |
sort_items(options.appendTo, $(el_item).closest('.item-container').attr('data-sort_by'), $(el_item).closest('.item-container').attr('data-sort_order')); | |
} | |
if(options.editable){ | |
activate_item_name_editor(el_item) | |
} | |
} | |
// Create item-name-shadow | |
// This element has the exact styling as item name editor and allows us | |
// to measure the width and height of the item name editor and automatically | |
// resize it to fit the text. | |
$('body').append(`<span class="item-name-shadow"></span>`); | |
$(document).on('click', '.item-has-website-url-badge', async function(e){ | |
e.stopPropagation(); | |
e.preventDefault(); | |
const website_url = $(this).closest('.item').attr('data-website_url'); | |
if(website_url){ | |
window.open(website_url, '_blank'); | |
} | |
return false; | |
}) | |
$(document).on('mousedown', '.item-has-website-url-badge', async function(e){ | |
console.log('mousedown') | |
e.stopPropagation(); | |
e.preventDefault(); | |
return false; | |
}) | |
$(document).on('contextmenu', '.item-has-website-url-badge', async function(e){ | |
e.stopPropagation(); | |
e.preventDefault(); | |
// close other context menus | |
const $ctxmenus = $(".context-menu"); | |
$ctxmenus.fadeOut(200, function(){ | |
$ctxmenus.remove(); | |
}); | |
UIContextMenu({ | |
parent_element: this, | |
items: [ | |
// Open | |
{ | |
html: `Open in New Tab <img src="${window.icons['launch.svg']}" style="width:10px; height:10px; margin-left: 5px;">` , | |
html_active: `Open in New Tab <img src="${window.icons['launch-white.svg']}" style="width:10px; height:10px; margin-left: 5px;">` , | |
onClick: function(){ | |
const website_url = $(e.target).closest('.item').attr('data-website_url'); | |
if(website_url){ | |
window.open(website_url, '_blank'); | |
} | |
} | |
}, | |
// Copy Link | |
{ | |
html: 'Copy Link', | |
onClick: async function(){ | |
const website_url = $(e.target).closest('.item').attr('data-website_url'); | |
if(website_url){ | |
await copy_to_clipboard(website_url); | |
} | |
} | |
}, | |
] | |
}); | |
return false; | |
}) | |
$(document).on('click', '.item-has-website-badge', async function(e){ | |
puter.fs.stat({ | |
uid: $(this).closest('.item').attr('data-uid'), | |
returnSubdomains: true, | |
returnPermissions: false, | |
returnVersions: false, | |
success: function (fsentry){ | |
if(fsentry.subdomains) | |
window.open(fsentry.subdomains[0].address, '_blank'); | |
} | |
}) | |
}) | |
$(document).on('long-hover', '.item-has-website-badge', function(e){ | |
puter.fs.stat({ | |
uid: $(this).closest('.item').attr('data-uid'), | |
returnSubdomains: true, | |
returnPermissions: false, | |
returnVersions: false, | |
success: function (fsentry){ | |
var box = e.target.getBoundingClientRect(); | |
var body = document.body; | |
var docEl = document.documentElement; | |
var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop; | |
var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft; | |
var clientTop = docEl.clientTop || body.clientTop || 0; | |
var clientLeft = docEl.clientLeft || body.clientLeft || 0; | |
var top = box.top + scrollTop - clientTop; | |
var left = box.left + scrollLeft - clientLeft; | |
if(fsentry.subdomains){ | |
let h = `<div class="allow-user-select website-badge-popover-content">`; | |
h += `<div class="website-badge-popover-title">Associated website${ fsentry.subdomains.length > 1 ? 's':''}</div>`; | |
fsentry.subdomains.forEach(subdomain => { | |
h += ` | |
<a class="website-badge-popover-link" href="${subdomain.address}" style="font-size:13px;" target="_blank">${subdomain.address.replace('https://', '')}</a> | |
<br>`; | |
}); | |
h += `</div>`; | |
// close other website popovers | |
$('.website-badge-popover-content').closest('.popover').remove(); | |
// show a UIPopover with the website | |
UIPopover({ | |
target: e.target, | |
content:h, | |
snapToElement: e.target, | |
parent_element: e.target, | |
top: top - 30, | |
left: left + 20, | |
}) | |
} | |
} | |
}) | |
}) | |
$(document).on('click', '.website-badge-popover-link', function(e){ | |
// remove the parent popover | |
$(e.target).closest('.popover').remove(); | |
}) | |
// removes item(s) | |
$.fn.removeItems = async function(options) { | |
options = options || {}; | |
$(this).each(async function() { | |
const parent_container = $(this).closest('.item-container'); | |
$(this).remove(); | |
show_or_hide_empty_folder_message(parent_container); | |
}); | |
return this; | |
} | |
window.activate_item_name_editor= function(el_item){ | |
// files in trash cannot be renamed, the user should be notified with an Alert. | |
if($(el_item).attr('data-immutable') !== '0'){ | |
return; | |
} | |
// files in trash cannot be renamed, user should be notified with an Alert. | |
else if(path.dirname($(el_item).attr('data-path')) === window.trash_path){ | |
UIAlert(`This item can't be renamed because it's in the trash. To rename this item, first drag it out of the Trash.`) | |
return; | |
} | |
const el_item_name = $(el_item).find('.item-name'); | |
const el_item_name_editor = $(el_item).find('.item-name-editor').get(0); | |
$(el_item_name).hide(); | |
$(el_item_name_editor).show(); | |
$(el_item_name_editor).focus(); | |
$(el_item_name_editor).addClass('item-name-editor-active'); | |
// select all text before extension | |
const item_name = $(el_item).attr('data-name'); | |
const is_dir = parseInt($(el_item).attr('data-is_dir')); | |
const extname = path.extname('/'+item_name); | |
if(extname !== '' && !is_dir) | |
el_item_name_editor.setSelectionRange(0, item_name.length - extname.length) | |
else | |
$(el_item_name_editor).select(); | |
} | |
export default UIItem; | |