blockbench/js/copy_paste.js
JannisX11 cd44f3be21 Ready for first beta [ci-build]
Fix pasting a group does not select it
Wrapping non-renamed elements (cubes and meshes) into a group no longer names group after them
2024-04-18 20:17:49 +02:00

485 lines
14 KiB
JavaScript

const Clipbench = {
elements: [],
types: {
text: 'text',
display_slot: 'display_slot',
keyframe: 'keyframe',
animation: 'animation',
face: 'face',
mesh_selection: 'mesh_selection',
texture: 'texture',
layer: 'layer',
outliner: 'outliner',
texture_selection: 'texture_selection',
image: 'image',
},
type_icons: {
face: 'aspect_ratio',
mesh_selection: 'fa-gem',
outliner: 'fas.fa-cube',
},
getCopyType(mode, check) {
// mode: 1 = copy, 2 = paste
let p = Prop.active_panel;
let text;
if (!check) {
text = getFocusedTextInput() && window.getSelection()+'';
}
if (text) {
return Clipbench.types.text;
}
if (Painter.selection.canvas && Toolbox.selected.id == 'copy_paste_tool') {
return Clipbench.types.texture_selection;
}
if (Modes.display) {
return Clipbench.types.display_slot
}
if (Animator.open && Prop.active_panel == 'animations') {
return Clipbench.types.animation
}
if (Animator.open && Timeline.animators.length && (Timeline.selected.length || mode === 2) && ['keyframe', 'timeline', 'preview'].includes(p)) {
return Clipbench.types.keyframe
}
if (Modes.edit && p == 'preview' && Mesh.selected[0] && Mesh.selected[0].getSelectedVertices().length && (mode !== 2 || Clipbench.vertices)) {
return Clipbench.types.mesh_selection;
}
if (mode == 2 && Modes.edit && Format.meshes && Clipbench.last_copied == 'mesh_selection' && (p == 'preview' || p == 'outliner')) {
return Clipbench.types.mesh_selection;
}
if ((p == 'uv' || p == 'preview') && Modes.edit) {
return Clipbench.types.face;
}
if (p == 'textures' && (Texture.selected || mode === 2)) {
return Clipbench.types.texture;
}
if (p == 'layers' && Texture.selected && Texture.selected.selected_layer) {
return Clipbench.types.layer;
}
if (p == 'outliner' && Modes.edit) {
return Clipbench.types.outliner;
}
if (!Project) {
return Clipbench.types.image;
}
},
async getPasteType() {
let p = Prop.active_panel;
if (getFocusedTextInput()) {
return Clipbench.types.text;
}
if (!Project) {
return Clipbench.types.image;
}
if (Painter.selection.canvas && Toolbox.selected.id == 'copy_paste_tool') {
return Clipbench.types.texture_selection;
}
if (Modes.display) {
return Clipbench.types.display_slot
}
if (Animator.open && Prop.active_panel == 'animations') {
return Clipbench.types.animation
}
if (Animator.open && Timeline.animators.length && ['keyframe', 'timeline', 'preview'].includes(p)) {
return Clipbench.types.keyframe
}
if (Modes.edit && p == 'preview') {
let options = [];
if (Clipbench.elements.length || Clipbench.group) {
options.push(Clipbench.types.outliner);
}
if (Mesh.selected[0] && Mesh.selected[0].getSelectedVertices().length && Clipbench.vertices) {
options.push(Clipbench.types.mesh_selection);
}
if (UVEditor.getMappableElements().length && UVEditor.clipboard?.length) {
options.push(Clipbench.types.face);
}
if (options.length > 1 && options.includes(settings.preview_paste_behavior.value)) {
return settings.preview_paste_behavior.value;
} else if (options.length > 1) {
return await new Promise((resolve, reject) => {
new Menu(options.map(option => {
return {
id: option,
name: tl(`menu.paste.${option}`),
icon: Clipbench.type_icons[option],
click() {
resolve(option);
}
}
})).show('mouse');
})
} else {
return options[0]
}
}
if (p == 'uv' && Modes.edit && UVEditor.clipboard?.length) {
return Clipbench.types.face;
}
if (p == 'textures') {
return Clipbench.types.texture;
}
if (p == 'layers' && Texture.selected && Texture.selected.selected_layer) {
return Clipbench.types.layer;
}
if (p == 'outliner' && Modes.edit) {
return Clipbench.types.outliner;
}
},
copy(event, cut) {
let match = SharedActions.run('copy', event, cut);
if (match) return;
let copy_type = Clipbench.getCopyType(1);
Clipbench.last_copied = copy_type;
switch (copy_type) {
case 'text':
Clipbench.setText(window.getSelection()+'');
break;
case 'display_slot':
DisplayMode.copy();
break;
case 'animation':
Clipbench.setAnimation();
break;
case 'keyframe':
if (Timeline.selected.length) {
Clipbench.setKeyframes();
if (cut) {
BarItems.delete.trigger();
}
}
break;
case 'face':
UVEditor.copy(event);
if (Prop.active_panel == 'uv') {
Clipbench.group = undefined;
Clipbench.elements = [];
}
break;
case 'mesh_selection':
UVEditor.copy(event);
Clipbench.setMeshSelection(Mesh.selected[0], event);
break;
case 'texture':
Clipbench.setTexture(Texture.selected);
if (cut) {
BarItems.delete.trigger();
}
break;
}
if (copy_type == 'outliner' || (copy_type == 'face' && Prop.active_panel == 'preview')) {
Clipbench.setElements();
Clipbench.setGroup();
if (Group.selected) {
Clipbench.setGroup(Group.selected);
} else {
Clipbench.setElements(selected);
}
if (cut) {
BarItems.delete.trigger();
}
if (Prop.active_panel == 'outliner') {
UVEditor.clipboard = []
}
}
},
async paste(event) {
let match = SharedActions.run('paste', event);
if (match) return;
switch (await Clipbench.getPasteType()) {
case 'text':
Clipbench.setText(window.getSelection()+'');
break;
case 'texture_selection':
UVEditor.addPastingOverlay();
break;
case 'display_slot':
DisplayMode.paste();
break;
case 'animation':
Clipbench.pasteAnimation();
break;
case 'keyframe':
Clipbench.pasteKeyframes()
break;
case 'face':
UVEditor.paste(event);
break;
case 'mesh_selection':
Clipbench.pasteMeshSelection();
break;
case 'texture':
Clipbench.pasteTextures();
break;
case 'outliner':
Clipbench.pasteOutliner(event);
break;
case 'image':
Clipbench.pasteImage(event);
break;
}
},
setGroup(group) {
if (!group) {
Clipbench.group = undefined
return;
}
Clipbench.group = group.getSaveCopy()
if (isApp) {
clipboard.writeHTML(JSON.stringify({type: 'group', content: Clipbench.group}))
}
},
setElements(arr) {
if (!arr) {
Clipbench.elements = []
return;
}
arr.forEach(function(element) {
Clipbench.elements.push(element.getSaveCopy())
})
if (isApp) {
clipboard.writeHTML(JSON.stringify({type: 'elements', content: Clipbench.elements}))
}
},
setText(text) {
if (isApp) {
clipboard.writeText(text)
} else if (navigator.clipboard) {
navigator.clipboard.writeText(text);
} else {
document.execCommand('copy')
}
},
setMeshSelection(mesh) {
this.vertices = {};
this.faces = {};
mesh.getSelectedVertices().forEach(vkey => {
this.vertices[vkey] = mesh.vertices[vkey].slice();
})
for (let fkey in mesh.faces) {
let face = mesh.faces[fkey];
if (face.isSelected(fkey)) {
this.faces[fkey] = new MeshFace(null, face);
}
}
},
pasteMeshSelection() {
let elements = Mesh.selected.slice();
Undo.initEdit({elements});
let new_mesh;
if (!elements.length) {
new_mesh = new Mesh({name: 'pasted', vertices: []});
elements.push(new_mesh);
}
let selection_mode_before = BarItems.selection_mode.value;
BarItems.selection_mode.change('vertex');
elements.forEach(mesh => {
let old_vertices = Object.keys(this.vertices);
let vertices_positions = old_vertices.map(vkey => this.vertices[vkey]);
let new_vertices = mesh.addVertices(...vertices_positions);
for (let old_fkey in this.faces) {
let old_face = this.faces[old_fkey];
let new_face = new MeshFace(mesh, old_face);
Property.resetUniqueValues(MeshFace, new_face);
let new_face_vertices = new_face.vertices.map(old_vkey => {
let new_vkey = new_vertices[old_vertices.indexOf(old_vkey)];
new_face.uv[new_vkey] = new_face.uv[old_vkey];
delete new_face.uv[old_vkey];
return new_vkey;
})
new_face.vertices.replace(new_face_vertices);
mesh.addFaces(new_face);
}
mesh.getSelectedVertices(true).replace(new_vertices);
})
if (new_mesh) {
new_mesh.init().select();
}
// Update vertex selection to appropriate selection mode
BarItems.selection_mode.change(selection_mode_before);
Undo.finishEdit('Paste mesh selection');
Canvas.updateView({elements: Mesh.selected, selection: true})
},
pasteOutliner(event) {
Undo.initEdit({outliner: true, elements: [], selection: true});
//Group
var target = 'root'
if (Group.selected) {
target = Group.selected
Group.selected.isOpen = true
} else if (selected[0]) {
target = selected[0]
}
selected.length = 0
if (isApp) {
var raw = clipboard.readHTML()
try {
var data = JSON.parse(raw)
if (data.type === 'elements' && data.content) {
Clipbench.group = undefined;
Clipbench.elements = data.content;
} else if (data.type === 'group' && data.content) {
Clipbench.group = data.content;
Clipbench.elements = [];
}
} catch (err) {}
}
if (Clipbench.group) {
function iterate(obj, parent) {
if (obj.children) {
let copy = new Group(obj).addTo(parent).init();
copy._original_name = copy.name;
copy.createUniqueName();
Property.resetUniqueValues(Group, copy);
if (obj.children && obj.children.length) {
obj.children.forEach((child) => {
iterate(child, copy)
})
}
return copy;
} else if (OutlinerElement.isTypePermitted(obj.type)) {
var copy = OutlinerElement.fromSave(obj).addTo(parent).selectLow();
copy.createUniqueName();
Property.resetUniqueValues(copy.constructor, copy);
copy.preview_controller.updateTransform(copy);
return copy;
}
}
let copy = iterate(Clipbench.group, target);
copy.select();
} else if (Clipbench.elements && Clipbench.elements.length) {
let elements = [];
Clipbench.elements.forEach(function(obj) {
if (!OutlinerElement.isTypePermitted(obj.type)) return;
var copy = OutlinerElement.fromSave(obj).addTo(target).selectLow();
copy.createUniqueName();
Property.resetUniqueValues(copy.constructor, copy);
elements.push(copy);
})
Canvas.updateView({elements});
}
//Rotate Cubes
if (!Format.rotate_cubes) {
elements.forEach(cube => {
if (cube instanceof Cube == false) return;
cube.rotation.V3_set(0, 0, 0)
})
Canvas.updateView({elements, element_aspects: {transform: true}});
}
//Canvas Limit
if (Format.cube_size_limiter && !settings.deactivate_size_limit.value) {
elements.forEach(s => {
if (s instanceof Cube) {
//Push elements into 3x3 block box
Format.cube_size_limiter.move(s);
}
})
Canvas.updateView({elements, element_aspects: {transform: true, geometry: true}});
}
//Rotation Limit
if (Format.rotation_limit && Format.rotate_cubes) {
elements.forEach(cube => {
if (cube instanceof Cube == false) return;
if (!cube.rotation.allEqual(0)) {
var axis = getAxisNumber(cube.rotationAxis()) || 0;
var cube_rotation = Format.rotation_snap ? Math.round(cube.rotation[axis]/22.5)*22.5 : cube.rotation[axis];
var angle = limitNumber( cube_rotation, -45, 45 );
cube.rotation.V3_set(0, 0, 0);
cube.rotation[axis] = angle;
}
})
Canvas.updateView({elements, element_aspects: {transform: true}});
}
Undo.finishEdit('Paste Elements', {outliner: true, elements: selected, selection: true});
},
pasteImage() {
function loadFromDataUrl(dataUrl) {
if (!dataUrl || dataUrl.length < 32) return;
Codecs.image.load(dataUrl);
}
if (isApp) {
var image = clipboard.readImage().toDataURL();
loadFromDataUrl(image);
} else {
navigator.clipboard.read().then(content => {
if (content && content[0] && content[0].types.includes('image/png')) {
content[0].getType('image/png').then(blob => {
let url = URL.createObjectURL(blob);
loadFromDataUrl(url);
})
}
}).catch(() => {})
}
}
}
BARS.defineActions(function() {
new Action('copy', {
icon: 'fa-copy',
category: 'edit',
work_in_dialog: true,
condition: () => Clipbench.getCopyType(1, true) || SharedActions.condition('copy'),
keybind: new Keybind({key: 'c', ctrl: true, shift: null}),
click(event) {
Clipbench.copy(event)
}
})
new Action('cut', {
icon: 'fa-cut',
category: 'edit',
work_in_dialog: true,
condition: () => Clipbench.getCopyType(1, true) || SharedActions.condition('copy'),
keybind: new Keybind({key: 'x', ctrl: true, shift: null}),
click(event) {
Clipbench.copy(event, true)
}
})
let paste = new Action('paste', {
icon: 'fa-clipboard',
category: 'edit',
work_in_dialog: true,
condition: () => Clipbench.getCopyType(2, true) || SharedActions.condition('paste'),
keybind: new Keybind({key: 'v', ctrl: true, shift: null}),
click(event) {
Clipbench.paste(event)
}
})
paste.addSubKeybind('outliner', 'menu.paste.outliner', null, event => {
Clipbench.pasteOutliner(event);
});
paste.addSubKeybind('face', 'menu.paste.face', null, event => {
UVEditor.paste(event);
});
paste.addSubKeybind('mesh_selection', 'menu.paste.mesh_selection', null, event => {
Clipbench.pasteMeshSelection();
});
paste.addSubKeybind('texture', 'data.texture', null, event => {
Clipbench.pasteTextures();
});
paste.addSubKeybind('image', 'format.image', null, event => {
Clipbench.pasteImage(event);
});
paste.addSubKeybind('animation', 'menu.animation', null, event => {
Clipbench.pasteAnimation();
});
paste.addSubKeybind('keyframe', 'menu.keyframe', null, event => {
Clipbench.pasteKeyframes();
});
paste.addSubKeybind('display_slot', 'category.display', null, event => {
DisplayMode.paste();
});
})