I'm trying to embed a YouTube video into my Windows 10 Universal App. I know there is ways to do it that goes against the terms on YouTube, but is there a way to do this that doesn't go against them?
I've tried the following code, and I was able to get a YouTube player up. But the video doesn't load.
Under Initialize
string html = #"<style> body{margin:0; padding:0;} iframe{width:100%;height:480px;}#media screen and (max-width:300px) { iframe{width:100%;height:180px;}} </style><iframe style=""padding:0px;margin-bottom:-20px;"" src=""https://www.youtube.com/embed/OLE5oAZanA4" + #" ? rel=0"" frameborder=""0"" allowfullscreen></iframe>";
videoView.NavigateToString(html);
UI code
<WebView Name="videoView" HorizontalAlignment="Left" Height="297" Margin="466,150,0,0" Grid.Row="1" VerticalAlignment="Top" Width="441"/>
For anyone that does use MyToolkit (which goes against YT terms). Is the views still being tracked when you use this method?
try this
StringBuilder stringbuild = new StringBuilder();
stringbuild.Append("<!DOCTYPE html>");
stringbuild.Append("<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\">");
stringbuild.Append("<head> <meta charset=\"utf-8\"/> <title></title> </head>");
stringbuild.Append("<body>");
stringbuild.Append(" <style> iframe {border: none;}");
stringbuild.Append(" html, body {margin: 0px; padding: 0px; border: 0px; width: 100%; height: 100%; overflow:hidden;} </style>");
stringbuild.Append(" <div id=\"player\" style=\"width:200px; height:400px;\"></div>");
stringbuild.Append("<script>");
stringbuild.Append("var tag = document.createElement('script');");
stringbuild.Append("tag.src = \"https://www.youtube.com/iframe_api\";");
stringbuild.Append("var firstScriptTag = document.getElementsByTagName('script')[0];");
stringbuild.Append(" firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);");
stringbuild.Append(" function onYouTubeIframeAPIReady() {window.external.notify(\"YoutubePlayerLoadCompleted\"); }");
stringbuild.Append(" var width; var height;");
stringbuild.Append(" function setwidth(incoming) { width = incoming; document.getElementById(\"player\").style.width = incoming+ 'px'; }");
stringbuild.Append("function setheight(incoming) { height = incoming; document.getElementById(\"player\").style.height = incoming +'px'; }");
stringbuild.Append("var player;");
stringbuild.Append(" function loadplayer(incomming) { player = new YT.Player('player', { height: height, width: width, playerVars: { 'fs': 1 }, videoId: incomming, events: { 'onReady': onPlayerReady, 'onStateChange': onPlayerStateChange } }); var element = document.getElementById('videoframe'); }");
stringbuild.Append("function onPlayerReady(event) { }");
stringbuild.Append("function onPlayerStateChange(event) {}");
stringbuild.Append("function play() { if (!player){} else { try { player.playVideo();} catch(err){window.external.notify(err.message);} }}");
stringbuild.Append(" function pause() { player.pauseVideo(); }");
stringbuild.Append("</script> </body> </html>");
string ts = stringbuild.ToString();
webview.NavigateToString(ts);
webview.ScriptNotify += async delegate (object sender1, NotifyEventArgs e1)
{
var jsScriptValue = e1.Value;
if (jsScriptValue.ToString().Equals("YoutubePlayerLoadCompleted"))
{
await webview.InvokeScriptAsync("setwidth", new string[] {"500" });
await webview.InvokeScriptAsync("setheight", new string[] {"400" });
await webview.InvokeScriptAsync("loadplayer", new string[] { "_P9lGTiiXW0" });
}
};
Related
I want to show 30 days in Day View Scheduler with Horizontal Scrollbar. Currently, Horizontal Scrollbar is available only for Timeline View but I want it for Day View as well as Month View.
For Timeline View with Horizontal Scrollbar code:
scheduler.createTimelineView({
name: "timeline",
x_unit: "minute",
x_date: "%H:%i",
x_step: 30,
x_size: 24*7,
x_start: 16,
x_length: 48,
y_unit: sections,
y_property: "section_id",
render: "bar",
scrollable: true,
column_width: 70,
scroll_position:new Date(2018, 0, 15) });
Please share your ideas and Sample links
Thanks in Advance
Try using Custom View. You can remove the default Day view and display your own instead, with the number of days you want to display. This can be done like this:
First in scheduler.config.header set tab "thirty_days" instead of "day":
scheduler.config.header = [
"thirty_days",
"week",
"month",
"date",
"prev",
"today",
"next"
];
The label for the view is set as in:
scheduler.locale.labels.thirty_days_tab = "Days";
Next, set the start date of the viewing interval, as well as viewing templates. It's better to create the custom view in the onTemplatesReady event handler function so that your custom view templates are ready before the scheduler is initialized:
scheduler.attachEvent("onTemplatesReady", () => {
scheduler.date.thirty_days_start = function(date) {
const ndate = new Date(date.valueOf());
ndate.setDate(Math.floor(date.getDate()/10)*10+1);
return this.date_part(ndate);
}
scheduler.date.add_thirty_days = function(date,inc) {
return scheduler.date.add(date,inc*30,"day");
}
const format = scheduler.date.date_to_str(scheduler.config.month_day);
scheduler.templates.thirty_days_date = scheduler.templates.week_date;
scheduler.templates.thirty_days_scale_date = function(date) {
return format(date);
};
});
To add horizontal scrolling to the view, you can place the scheduler inside the scrollable element and give the scheduler the width required to display all columns. You'll need to hide a default navigation panel of the scheduler and create a custom one with HTML, so it would have a correct width and won't be affected by scrolling:
scheduler.xy.nav_height = 0;
scheduler.attachEvent("onSchedulerReady", function () {
const navBar = scheduler.$container.querySelector(".dhx_cal_navline").cloneNode(true);
navBar.style.width = "100%";
document.querySelector(".custom-scheduler-header").appendChild(navBar);
document.querySelectorAll(".custom-scheduler-header .dhx_cal_tab").forEach(function (tab) {
tab.onclick = function () {
const name = tab.getAttribute("name");
const view = name.substr(0, name.length - 4);
scheduler.setCurrentView(null, view);
};
});
document.querySelector(".custom-scheduler-header .dhx_cal_prev_button").onclick = function () {
const state = scheduler.getState();
scheduler.setCurrentView(scheduler.date.add(state.date, -1, state.mode));
};
document.querySelector(".custom-scheduler-header .dhx_cal_next_button").onclick = function () {
const state = scheduler.getState();
scheduler.setCurrentView(scheduler.date.add(state.date, 1, state.mode));
};
document.querySelector(".custom-scheduler-header .dhx_cal_today_button").onclick = function () {
scheduler.setCurrentView(new Date());
};
scheduler.attachEvent("onBeforeViewChange", (oldView, oldDate, newView, newDate) => {
const innerContainer = document.getElementById("scheduler_here");
if (newView === "thirty_days") {
innerContainer.style.width = "3000px";
} else {
innerContainer.style.width = "100%";
}
return true;
});
scheduler.attachEvent("onViewChange", function (view, date) {
const dateLabel = document.querySelector(".custom-scheduler-header .dhx_cal_date");
const state = scheduler.getState();
dateLabel.innerHTML = scheduler.templates[view + "_date"](state.min_date, state.max_date);
document.querySelectorAll(".custom-scheduler-header .dhx_cal_tab").forEach(function(tab) {
tab.classList.remove("active");
});
const activeTab = document.querySelector(".custom-scheduler-header ." + view + "_tab");
if (activeTab) {
activeTab.classList.add("active");
}
});
});
Styles that you will need:
.custom-scheduler-header .dhx_cal_navline{
display: block !important;
height: 60px !important;
}
.custom-scheduler-header .dhx_cal_navline.dhx_cal_navline_flex{
display: flex !important;
}
.dhx-scheduler {
height: 100vh;
width: 100vw;
position: relative;
overflow: hidden;
background-color: #fff;
font-family: Roboto, Arial;
font-size: 14px;
}
.dhx_cal_container .dhx_cal_navline {
display: none;
}
Please see an example: https://snippet.dhtmlx.com/znd7ffiv
You may need to fix the hour scale so that it remains visible when scrolling horizontally on the calendar. I did not implement this in the example, I think that this can be done in the same way as for the navigation panel. If you need, write to me and I will send an update in a few working days.
As for the "Month" view, the approach is the same as for the "Day" view.
I have problem with Rogue Engine Ui.
I have a clickable div and I want to do something if clicked.
Here is my javascript class in RogueEngine :
start() {
this.initializeUi();
}
async initializeUi(){
const htmlPath = RE.getStaticPath("ui.html");
const gameUi = await (await fetch(htmlPath)).text();
RE.Runtime.uiContainer.innerHTML = gameUi;
this.button = document.getElementById("turbine-btn");
this.button.onclick = () => this.onButtonClick();
}
onButtonClick() {
this.button.style.display = "none";
RE.Debug.log("Hooorah!!!!");
}
And here is my HTML file in the root of the "Static" folder:
<div id="turbine-btn" class="clickable">dfafdsafsdfsd</div>
<style>
.clickable {
cursor: pointer;
text-decoration: underline;
}
</style>
When I click, nothing happens. Does anyone know what's the problem with this?
I recently started learning and building an app for my school for remote online classes with WebRTC and PeerJs on my school's server (VPS). So far I am able to set up 1 to 1 peer connection but having a hard time pausing and resuming stream transmission.
I am seeking some help on how to pause and resume video and audio stream for self and remote user, while on active connection. When I execute localStream.getVideoTracks()[0].enabled = false, it only disable video for me (not for a remote user).
As some have suggested replaceTrack API, but I am not unable to find tutorials about it as I am new to it.
My code (Thanks to Link) looks like below:
var url = new URL(window.location.href)
var disableStreamInBeginning = url.searchParams.get("disableStreamInBeginning")
var passwordProtectedRoom = url.searchParams.get("passwordProtectedRoom")
var muteAllInBeginning = url.searchParams.get("muteAllInBeginning")
var isVideoCall = url.searchParams.get("isVideoCall")
var singleOrConference = url.searchParams.get("singleOrConference")
const conferenceView = document.getElementById('conference')
const loader = document.getElementById('loader')
const localVideoView = document.getElementById('local-video')
const remoteVideoView = document.getElementById('remote-video')
const remoteVideoDiv = document.getElementById('remote-video-div')
if(typeof disableStreamInBeginning !== 'undefined' && disableStreamInBeginning == 'true'){
var disbaleSelfStream = true
} else {
var disbaleSelfStream = false
}
if(typeof passwordProtectedRoom !== 'undefined' && passwordProtectedRoom == 'true'){
var passwordProtected = true
} else {
var passwordProtected = false
}
if(typeof muteAllInBeginning !== 'undefined' && muteAllInBeginning == 'true'){
var muteAll = true
} else {
var muteAll = false
}
if(typeof isVideoCall !== 'undefined' && isVideoCall == 'true'){
var videoCall = true
} else {
var videoCall = false
}
if(typeof singleOrConference !== 'undefined' && singleOrConference == 'conference'){
var isConference = true
conferenceView.style.display = 'block'
} else {
var isConference = false
localVideoView.style.opacity = 0
remoteVideoView.style.opacity = 0
remoteVideoDiv.style.opacity = 0
}
var selectedCamera = 'user'
let localStream;
const socket = io('/');
localVideoView.muted = true;
const peers = {};
const peer = new Peer(undefined, {
host: '/',
port: '443',
path: '/myapp',
secure: true
})
// Handelling incoming call connection
peer.on("call", async (call) => {
let stream = null;
try {
stream = await navigator.mediaDevices.getUserMedia(
{
video: {
facingMode: selectedCamera
},
audio: true
});
call.answer(stream);
call.on("stream", (remoteVideoStream) => {
addVideoStream(remoteVideoView, remoteVideoStream);
});
} catch (err) {
console.log('peer.on("call": ' + err);
};
});
// On new user connected
socket.on("user-connected", async (userId) => {
connectDataToNewUser(userId);
try {
stream = await navigator.mediaDevices.getUserMedia(
{
audio: true,
video: true,
})
}
catch (err) {
console.log('socket.on("user-connected": ' + err);
};
connectMediaToNewUser(userId, stream);
});
// Show own Video on own device screen
(async () => {
try {
localStream= await navigator.mediaDevices.getUserMedia(
{
video: {
facingMode: selectedCamera
},
audio: true
});
addVideoStream(localVideoView, localStream);
} catch (err) {
console.log('(async () =>: ' + err);
}
})();
peer.on("open", (id) => {
socket.emit("join-room", ROOM_ID, id);
});
peer.on("error", (err) => {
console.log('peer.on("error": ' + err);
})
socket.on("user-disconnected", (userId) => {
if (peers[userId]) {
peers[userId].close();
}
});
// Set up event listener for an "another user" data connection established event
peer.on("connection", (conn) => {
conn.on("data", (data) => {
console.log('Received data ' + data);
});
// Set up event listener for connection conn established event
conn.on("open", () => {
conn.send('Hello!');
});
});
// Initiate a Data call (Messages) to user
const connectDataToNewUser = (userId) => {
let conn = peer.connect(userId);
conn.on("data", (data) => {
console.log('Received data: ' + data);
});
conn.on("open", () => {
conn.send('hi!');
});
};
// Initiate a Media call (Audio/Video) to user
const connectMediaToNewUser = (userId, stream) => {
const call = peer.call(userId, stream);
call.on("stream", (userVideoStream) => {
addVideoStream(remoteVideoView, userVideoStream);
});
call.on("close", () => {
remoteVideoView.remove();
});
call.on("error", (error) => {
console.log('connectMediaToNewUser' + error);
});
peers[userId] = call;
};
const addVideoStream = (video, stream) => {
video.srcObject = stream;
video.addEventListener("loadedmetadata", () => {
if(disbaleSelfStream){
systemStream.getVideoTracks()[0].enabled = false
systemStream.getAudioTracks()[0].enabled = false
} else {
loader.style.opacity = 0
video.style.opacity = 1
video.play()
remoteVideoDiv.style.opacity = 0
}
});
};
Server Side Code:
const express = require('express')
const app = express()
const httpPort = process.env.PORT || 80
const httpsPort = 443
const { ExpressPeerServer } = require('peer')
const path = require('path')
const http = require('http')
const https = require('https')
const fs = require('fs')
// Certificate & credentials
const privateKey = fs.readFileSync(path.join(__dirname, 'certs', 'key.pem'))
const certificate = fs.readFileSync(path.join(__dirname, 'certs', 'cert.pem'))
const credentials = {
key: privateKey,
cert: certificate
}
const httpsServer = https.createServer(credentials, app).listen(httpsPort, () => { console.log('Peer Server listening to port ' + httpsPort) })
const peerServer = ExpressPeerServer(httpsServer, {
debug: true,
path: '/myapp'
})
app.use(peerServer)
const io = require('socket.io')(httpsServer, {
forceNew: true,
transports: ["polling"],
})
const { v4: uuidV4 } = require('uuid')
app.set('view engine', 'ejs')
app.use(express.static('public'))
app.get('/', (req, res) => {
res.redirect(`/${uuidV4()}`)
})
app.get('/:room', (req, res) => {
res.render('room', { roomId: req.params.room })
})
io.on('connection', (socket) => {
socket.on('join-room', (roomId, userId) => {
socket.join(roomId)
socket.broadcast.to(roomId).emit('user-connected', userId)
socket.on('disconnect', () => {
socket.broadcast.to(roomId).emit('user-disconnected', userId)
})
socket.on('text-message', message => {
socket.broadcast.to(roomId).emit('text-message-received', message)
})
socket.on('system-stream-updated', remoteUserId => {
socket.broadcast.to(roomId).emit('new-remote-stream', remoteUserId)
})
})
})
And room.ejs (if needed)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script>
const ROOM_ID = "<%= roomId %>"
</script>
<script src="peer.min.js" defer></script>
<script src="/socket.io/socket.io.js" defer></script>
<script src="client.js" defer></script>
<title>Interface</title>
<style type="text/css">
html, body {
padding: 0;
margin: 0;
}
.container, .local-video {
position: absolute;
width: 100%;
height: 100%;
object-fit: cover;
}
.remote-video-div {
position: absolute;
max-width: 30%;
width: 30%;
margin: 16px;
}
.remote-video {
max-width: 100%;
width: 100%;
margin-bottom: -5px;
}
.video-inset {
outline: unset;
visibility: hidden;
position: relative;
margin:0;
padding:0;
}
.background-black {
background-color: #000000 !important;
}
.display-none {
display: none;
}
.loader {
margin: 250px auto;
border: 7px solid #9e9c9c;
border-radius: 50%;
border-top: 7px solid #ffffff;
width: 40px;
height: 40px;
-webkit-animation: spin 2s linear infinite; /* Safari */
animation: spin 2s linear infinite;
/* Safari */
#-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
#keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
<link rel="icon" type="image/ico" href="favicon.ico"/>
</head>
<body>
<div class="local-video-div background-black">
<video class="local-video" autoplay></video>
</div>
<div class="container background-black display-none" id="loader">
<div class="loader"></div>
</div>
<div class="remote-video-div background-black">
<video class="remote-video" autoplay onclick="remoteVideoClick()"></video>
</div>
<div class="container background-black display-none" id="conference">
</div>
</body>
</html>
Thanks in advance.
I ran into the same problem and was looking for a solution. It's funny because your question itself solved my issue! I did this ...
<button
onClick={() =>
(stream.getVideoTracks()[0].enabled =
!stream.getVideoTracks()[0].enabled)
}
>
By the way as you might have noticed (from the "onClick") that I am doing this on react. In your case, however, I think you should get the video stream from the device first and set it into a variable. Then call the user with that stream variable and when you need it, you can stop and start the streaming by doing stream.getVideoTracks()[0].enabled = !stream.getVideoTracks()[0].enabled. But, make sure you have one source of truth for the stream and always reference it from there. I did this in react and it's working for me. Hope that it does for you too!
I have a function in Cypress support/index.js that is meant to get the dimensions of the cy.document outerWidth and outerHeight, then return them for future use in a test. My problem is that when the test runs and the values are compared with others the assertion says the values are NaN. I checked by console logging the value at the point of the assertion and it was empty, so I must be doing something wrong, I'm just not sure what. My function is below, any help gratefully received, thanks.
function getViewport() {
var viewport = {}
cy.document().then((doc) => {
let width = Cypress.$(doc).outerWidth()
let height = Cypress.$(doc).outerHeight()
viewport['bottom'] = height
viewport['height'] = height
viewport['left'] = 0
viewport['right'] = width
viewport['top'] = 0
viewport['width'] = width
viewport['x'] = 0
viewport['y'] = 0
}).then(() => {
return viewport
})
return viewport
}
The code that calls getViewport() is
export const getRect = (obj) => {
var rect
if (obj == 'viewport') {
rect = getViewport()
} else {
rect = getElement(obj)
if (Cypress.config('parseLayoutToInt')) { rect = parseAllToInt(rect) }
}
return rect
}
And that is called by a custom command, where subject is prevSubject and the element is the string "viewport"
Cypress.Commands.add('isInside', { prevSubject: true }, (subject, element, expected) => {
var minuend, subtrahend, diff
minuend = getRect(element)
subtrahend = getRect(subject)
diff = getRectDiff(minuend, subtrahend, expected);
expect(diff).to.deep.equal(expected);
})
Like #NoriSte said, the cy commands are asynchronous thus you can't mix them with sync code.
What you want to do is something like:
function getViewport() {
return cy.document().then( doc => {
rect = /* do something synchronous */
return rect;
});
}
Anyway, to answer the original question (in the title), there's a couple of patterns I use to store a value for later use in cypress:
wrap next commands in the then callback:
cy.document().then( doc => {
return doc.documentElement.getBoundingClientRect();
}).then( viewportRect => {
cy.doSomething(viewportRect);
cy.doSomethingElse();
});
cache to a variable and access the cached value from inside an enqueued command:
let viewportRect;
cy.document().then( doc => {
return doc.documentElement.getBoundingClientRect();
}).then( rect => viewportRect = rect );
cy.doSomething();
// this is important -- you need to access the `viewportRect`
// asynchronously, else it will be undefined at the time of access
// because it's itself assigned asynchronously in the first command'd callback
cy.then(() => {
doSomething(viewportRect);
});
Ad the actual problem in your question (if I understood it correctly), I've made a solution you can learn from:
const getRect = (selector) => {
if (selector == 'viewport') {
return cy.document().then( doc => {
return doc.documentElement.getBoundingClientRect();
});
} else if ( typeof selector === 'string' ) {
return cy.get(selector).then( $elem => {
return $elem[0].getBoundingClientRect();
});
// assume DOM elem
} else {
return cy.wrap(selector).then( elem => {
return Cypress.$(elem)[0].getBoundingClientRect();
});
}
};
const isInside = (containerRect, childRect) => {
if ( !containerRect || !childRect ) return false;
return (
childRect.top >= containerRect.top &&
childRect.bottom <= containerRect.bottom &&
childRect.left >= containerRect.left &&
childRect.right <= containerRect.right
);
};
Cypress.Commands.add('isInside', { prevSubject: true }, (child, container, expected) => {
return getRect(child).then( childRect => {
getRect(container).then( containerRect => {
expect(isInside(containerRect, childRect)).to.equal(expected);
});
});
});
describe('test', () => {
it('test', () => {
cy.document().then( doc => {
doc.body.innerHTML = `
<div class="one"></div>
<div class="two"></div>
<style>
.one, .two {
position: absolute;
}
.one {
background: rgba(255,0,0,0.3);
width: 400px;
height: 400px;
}
.two {
background: rgba(0,0,255,0.3);
width: 200px;
height: 200px;
}
</style>
`;
});
cy.get('.two').isInside('.one', true);
cy.get('.one').isInside('.two', false);
});
it('test2', () => {
cy.document().then( doc => {
doc.body.innerHTML = `
<div class="one"></div>
<div class="two"></div>
<style>
body, html { margin: 0; padding: 0 }
.one, .two {
position: absolute;
}
.one {
background: rgba(255,0,0,0.3);
width: 400px;
height: 400px;
}
.two {
background: rgba(0,0,255,0.3);
width: 200px;
height: 200px;
left: 300px;
}
</style>
`;
});
cy.get('.two').isInside('.one', false);
cy.get('.one').isInside('.two', false);
});
it('test3', () => {
cy.document().then( doc => {
doc.body.innerHTML = `
<div class="one"></div>
<style>
body, html { margin: 0; padding: 0 }
.one {
position: absolute;
background: rgba(255,0,0,0.3);
width: 400px;
height: 400px;
left: -100px;
}
</style>
`;
});
cy.get('.one').isInside('viewport', false);
});
});
Why there is a synchronous return in your getViewport function? I'm speaking about the last return viewport
function getViewport() {
var viewport = {}
cy.document().then((doc) => {
...
})
return viewport // <-- ?????
}
doing so, all the cy.document().then((doc) etc. code is useless.
I don't know if this is the problem, but I can't run your code locally because it misses a lot of functions. Could you share a "working” GitHub repo to make some more tests?
I ran into this problem as well, and opted for a solution with async/await:
function getDocument() {
return new Promise(resolve => {
cy.document().then(d => {
console.log('deeee', d);
resolve(d);
});
});
}
describe('Stuff', () => {
it('Sees the toasty character', async () => {
const document = await getDocument();
// Your test code here
});
});
Even though Cypress commands aren't really promises, you can create your own promise, and resolve it when ready. Then await that promise in your test code.
Hope it helps!
I am cloning a selected object on a canvas in Fabric.js using a simple function.
function duplicateObj() {
var obj = canvas.getActiveObject();
var clone = fabric.util.object.clone(obj);
clone.set({left: 100,top: 100});
canvas.add(clone);
}
That works absolutely fine. Now if I work with the object and the clone is not required anymore and I select and delete it, both objects, the clone and the original object are deleted. The delete function is:
function deleteObj() {
var obj = canvas.getActiveObject();
canvas.fxRemove(obj);
}
The objects are the same. Is there are way to clone objects and make the clone independent of the of the original? I tried this:
function duplicateObj() {
var obj = canvas.getActiveObject();
var clone = fabric.util.object.clone(obj);
clone.initialize();
$.extend(clone, obj);
fabric.util.object.extend(clone, obj);
clone.set({left: 100,top: 100});
canvas.add(clone);
}
It works, however the objects are the same again and if I only use initialize I am ending up with an object that has now properties set.
here is the solution
var object = fabric.util.object.clone(canvas.getActiveObject());
object.set("top", object.top+5);
object.set("left", object.left+5);
canvas.add(object);
This worked very well for me, and the cloned object is totally unlinked from the original:
var object = canvas.getActiveObject();
object.clone(function(clone) {
canvas.add(clone.set({
left: object.left + 10,
top: object.top + 10
}));
});
And you can do it to clone all selected objects:
var activeObjects = canvas.getActiveObjects();
if (activeObjects) {
activeObjects.forEach(function(object) {
object.clone(function(clone) {
canvas.add(clone.set({
left: object.left + 10,
top: object.top + 10
}));
})
});
}
I hope it can help you!
I was having a similar issue where actions on the clone would affect the original object. I opted to just serialize the object and deserialize it into a new object:
var copyData = canvas.getActiveObject().toObject();
fabric.util.enlivenObjects([copyData], function(objects) {
objects.forEach(function(o) {
o.set('top', o.top + 15);
o.set('left', o.left + 15);
canvas.add(o);
});
canvas.renderAll();
});
for fabricjs 2.0
$(".copy").on("click", function () {
var activeObject = canvas.getActiveObject();
activeObject.clone(function (cloned) {
canvas.discardActiveObject();
cloned.set({
top: cloned.top + 20,
evented: true
});
if (cloned.type === 'activeSelection') {
// active selection needs a reference to the canvas.
cloned.canvas = canvas;
cloned.forEachObject(function (obj) {
canvas.add(obj);
});
cloned.setCoords();
} else {
canvas.add(cloned);
}
canvas.setActiveObject(cloned);
canvas.requestRenderAll();
});
});
Here is my implementation of cloning selected object or group.
https://jsfiddle.net/milanhlinak/rxtjm7w0/1/
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="lib/jquery-3.1.1.min.js"></script>
<script type="text/javascript" src="lib/fabric.min.js"></script>
</head>
<body>
<button onclick="cloneSelected()">Clone selected</button>
<canvas id="canvas" style="border: 1px solid #cccccc"></canvas>
<script>
var canvas = new fabric.Canvas('canvas', {
width: 500,
height: 500,
});
canvas.add(new fabric.Rect({
left: 100,
top: 100,
width: 50,
height: 50,
fill: '#faa'
}));
canvas.add(new fabric.Circle({
left: 300,
top: 300,
radius: 25,
fill: '#afa'
}));
function cloneSelected() {
console.log('cloneSelected');
var activeObject = canvas.getActiveObject();
var activeGroup = canvas.getActiveGroup();
if (activeObject) {
console.log('object selected');
var clonedObject = null;
var json = activeObject.toJSON();
if (json.type == 'rect') {
clonedObject = new fabric.Rect(json);
} else if (json.type == 'circle') {
clonedObject = new fabric.Circle(json);
} else {
console.log('unknown object type: ' + json.type);
return;
}
var oldLeft = clonedObject.getLeft();
var oldTop = clonedObject.getTop();
clonedObject.setLeft(oldLeft + 10);
clonedObject.setTop(oldTop + 10);
var boundingRect = clonedObject.getBoundingRect(true);
if (boundingRect.left + boundingRect.width > canvas.getWidth()) {
clonedObject.setLeft(oldLeft);
}
if (boundingRect.top + boundingRect.height > canvas.getHeight()) {
clonedObject.setTop(oldTop);
}
canvas.add(clonedObject);
canvas.setActiveObject(clonedObject);
canvas.renderAll();
console.log('selected object cloned');
} else if (activeGroup) {
console.log('group selected');
canvas.discardActiveGroup();
var clonedObjects = [];
activeGroup.getObjects().forEach(function (object) {
var clonedObject = null;
var json = object.toJSON();
if (json.type == 'rect') {
clonedObject = new fabric.Rect(json);
} else if (json.type === 'circle') {
clonedObject = new fabric.Circle(json);
} else {
console.log('unknown object type: ' + json.type);
return;
}
clonedObject.setCoords();
canvas.add(clonedObject);
clonedObject.set('active', true);
clonedObjects.push(clonedObject);
});
var group = new fabric.Group(clonedObjects.reverse(), {
canvas: canvas
});
group.addWithUpdate(null);
var oldLeft = group.getLeft();
var oldTop = group.getTop();
group.setLeft(oldLeft + 10);
group.setTop(oldTop + 10);
var boundingRect = group.getBoundingRect(true);
if (boundingRect.left + boundingRect.width > canvas.getWidth()) {
group.setLeft(oldLeft);
}
if (boundingRect.top + boundingRect.height > canvas.getHeight()) {
group.setTop(oldTop);
}
group.setCoords();
canvas.setActiveGroup(group);
group.saveCoords();
canvas.renderAll();
console.log('selected objects cloned');
} else {
console.log('no object selected');
}
}
</script>
</body>
</html>
You can use
var obj = canvas.getActiveObject();
obj.clone(function(c) {
canvas.add(c.set({ left: 100, top: 100, angle: -15 }));
});
Here you can see it working:
http://fabricjs.com/opacity_mouse_move/
I wanted a clone to be able to reset an item to the originally saved state...like others I found objects pass on variables to the clone and vice versa...
What i did was create a var to hold the clone for later recall like this:
var o;
var object = canvas.getActiveObject().clone(
function(obj){
o = obj;
}
);
one caveat if you set id's and dont make them unique during cloning process it makes the whole thing go wild...unless you delete the original first..
Check the demo for Copy and Paste here:
http://fabricjs.com/copypaste
Here is the code to copy/paste or clone the selected object.
function Clone() {
Copy();
Paste()
}
function Copy() {
// clone what are you copying since you
// may want copy and paste on different moment.
// and you do not want the changes happened
// later to reflect on the copy.
canvas.getActiveObject().clone(function(cloned) {
_clipboard = cloned;
});
}
function Paste() {
// clone again, so you can do multiple copies.
_clipboard.clone(function(clonedObj) {
canvas.discardActiveObject();
clonedObj.set({
left: clonedObj.left + 10,
top: clonedObj.top + 10,
evented: true,
});
if (clonedObj.type === 'activeSelection') {
// active selection needs a reference to the canvas.
clonedObj.canvas = canvas;
clonedObj.forEachObject(function(obj) {
canvas.add(obj);
});
// this should solve the unselectability
clonedObj.setCoords();
} else {
canvas.add(clonedObj);
}
_clipboard.top += 10;
_clipboard.left += 10;
canvas.setActiveObject(clonedObj);
canvas.requestRenderAll();
});
}