GtkPopover and applet - applet

How to write code in on_applet_clicked function that show GtkPopover with example content? gtk_popover_new () and what next with this?
const Applet = imports.ui.applet;
const Util = imports.misc.util;
function MyApplet(orientation, panel_height, instance_id) {
this._init(orientation, panel_height, instance_id);
}
MyApplet.prototype = {
__proto__: Applet.IconApplet.prototype,
_init: function(orientation, panel_height, instance_id) {
Applet.IconApplet.prototype._init.call(this, orientation, panel_height, instance_id);
this.set_applet_icon_name("folder-system");
this.set_applet_tooltip(_("Click here to kill a window"));
},
on_applet_clicked: function() {
// here
}
};
function main(metadata, orientation, panel_height, instance_id) {
return new MyApplet(orientation, panel_height, instance_id);
}

You cannot use GTK+ within the Cinnamon window manager UI elements.
GTK+ is a client-side, application toolkit; it cannot be used inside a window manager.
If you want to use a menu for an applet, you'll have to use a PopupMenu instance by importing it as:
const PopupMenu = imports.ui.popupMenu;
and populate it with PopupMenuItem instances and their subclasses.

Related

View not showing up when an extension command is typed/selected

I'm creating a vscode client extension that needs to show
Shows a webview on the Primary Sidebar (already works)
Opens a view when a certain extension command is selected (it fails)
The package.json looks like this
"activationEvents": [
"onView:navCode.search",
"onLanguage:typescript",
"onCommand:navCode.start"
],
"main": "./dist/extension",
"contributes": {
"commands": [
{
"command": "navCode.start",
"title": "Show class diagram",
"category": "NavCode Diagram"
}
],
"viewsContainers": {
"activitybar": [
{
"id": "nav-code-search",
"title": "Nav Code",
"icon": "images/nav-code-logo.png"
}
]
},
"views": {
"nav-code-search": [
{
"type": "webview",
"id": "navCode.search",
"name": "Code search"
}
]
}
},
The activate method of my extension looks like these
export function activate(context: vscode.ExtensionContext) {
let refManager = new ReferenceManager();
refManager.updateWorkspaceReferences();
//Register the webview for code nav search
const provider = new SearchViewProvider(context.extensionUri, context, refManager);
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(SearchViewProvider.viewType, provider)
);
context.subscriptions.push(// Create and show a new webview
vscode.commands.registerCommand('navCode.start', () => {
const panel = vscode.window.createWebviewPanel(
'navCode', // Identifies the type of the webview. Used internally
'NavCode - Class Diagram', // Title of the panel displayed to the user
vscode.ViewColumn.One, // Editor column to show the new webview panel in.
{} // Webview options. More on these later.
);
})
);
}
The command appears correctly
But when is selected it shows this error
There is nothing else on Console or Output views, what could be causing this ?
I have searched without any success about how to register multiple views of the same extension
UPDATE
But back to the problem I believe that is realted to this method
refManager.updateWorkspaceReferences();
Because it awaits until a typescript parses all the *.ts files of the current workspace
public async updateWorkspaceReferences() {
let message: string;
let folders = vscode.workspace.workspaceFolders;
if (folders && folders.length > 0) {
message = await this.processProject(folders[0]);
}
else {
message = 'nav-code requires an open workspace to work';
}
this.log.append(message);
}
How can a have a running task, right after the extension is activated ? can I use threads ?
Here is an overview of what I ended up doing to solve the problem
Put the long-running logic inside the SearchViewProvider (not running directly inside the activate method but as a promise that notifies when done
SearchViewProvider gets notified by ReferenceManager that the long-running logic has finished using BehaviorSubject (rxjs) : referencesUpdated
The view is initialized with a default content when notified the content is refreshed.
Here is the relevant code
extension.ts
export function activate(context: vscode.ExtensionContext) {
//Register the webview for code nav search
const provider = new SearchViewProvider(context, logger);
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(SearchViewProvider.viewType, provider)
);
context.subscriptions.push(// Create and show a new webview
vscode.commands.registerCommand('navCode.start', () => {
const panel = vscode.window.createWebviewPanel(
'navCode', // Identifies the type of the webview. Used internally
'NavCode - Class Diagram', // Title of the panel displayed to the user
vscode.ViewColumn.One, // Editor column to show the new webview panel in.
{} // Webview options. More on these later.
);
})
);
}
SearchViewProvider.ts
constructor(
private context: vscode.ExtensionContext,
private logger: Logger
) {
this.extensionUri = this.context.extensionUri;
this.referenceManager = new ReferenceManager(logger);
this.referenceManager.referencesUpdated.subscribe(() => { this.refreshView() });
}
public resolveWebviewView(webview: vscode.WebviewView, context: vscode.WebviewViewResolveContext<unknown>, token: vscode.CancellationToken): void | Thenable<void> {
this.view = webview;
if (this.view) {
/** existing view logic **/
this.refreshView();
}
}
refreshView() {
if (this.view) {
this.view.webview.html = this.buildView(this.view.webview);
}
}

NEXTJS fix window is not defined on import [duplicate]

In my Next.js app I can't seem to access window:
Unhandled Rejection (ReferenceError): window is not defined
componentWillMount() {
console.log('window.innerHeight', window.innerHeight);
}
̶A̶n̶o̶t̶h̶e̶r̶ ̶s̶o̶l̶u̶t̶i̶o̶n̶ ̶i̶s̶ ̶b̶y̶ ̶u̶s̶i̶n̶g̶ ̶p̶r̶o̶c̶e̶s̶s̶.̶b̶r̶o̶w̶s̶e̶r ̶ ̶t̶o̶ ̶j̶u̶s̶t̶ ̶e̶x̶e̶c̶u̶t̶e̶ ̶ ̶y̶o̶u̶r̶ ̶c̶o̶m̶m̶a̶n̶d̶ ̶d̶u̶r̶i̶n̶g̶ ̶r̶e̶n̶d̶e̶r̶i̶n̶g̶ ̶o̶n̶ ̶t̶h̶e̶ ̶c̶l̶i̶e̶n̶t̶ ̶s̶i̶d̶e̶ ̶o̶n̶l̶y̶.
But process object has been deprecated in Webpack5 and also NextJS, because it is a NodeJS variable for backend side only.
So we have to use back window object from the browser.
if (typeof window !== "undefined") {
// Client-side-only code
}
Other solution is by using react hook to replace componentDidMount:
useEffect(() => {
// Client-side-only code
})
Move the code from componentWillMount() to componentDidMount():
componentDidMount() {
console.log('window.innerHeight', window.innerHeight);
}
In Next.js, componentDidMount() is executed only on the client where window and other browser specific APIs will be available. From the Next.js wiki:
Next.js is universal, which means it executes code first server-side,
then client-side. The window object is only present client-side, so if
you absolutely need to have access to it in some React component, you
should put that code in componentDidMount. This lifecycle method will
only be executed on the client. You may also want to check if there
isn't some alternative universal library which may suit your needs.
Along the same lines, componentWillMount() will be deprecated in v17 of React, so it effectively will be potentially unsafe to use in the very near future.
If you use React Hooks you can move the code into the Effect Hook:
import * as React from "react";
export const MyComp = () => {
React.useEffect(() => {
// window is accessible here.
console.log("window.innerHeight", window.innerHeight);
}, []);
return (<div></div>)
}
The code inside useEffect is only executed on the client (in the browser), thus it has access to window.
With No SSR
https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
The error occurs because window is not yet available, while component is still mounting. You can access window object after component is mounted.
You can create a very useful hook for getting dynamic window.innerHeight or window.innerWidth
const useDeviceSize = () => {
const [width, setWidth] = useState(0)
const [height, setHeight] = useState(0)
const handleWindowResize = () => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
}
useEffect(() => {
// component is mounted and window is available
handleWindowResize();
window.addEventListener('resize', handleWindowResize);
// unsubscribe from the event on component unmount
return () => window.removeEventListener('resize', handleWindowResize);
}, []);
return [width, height]
}
export default useDeviceSize
Use case:
const [width, height] = useDeviceSize();
componentWillMount() lifecycle hook works both on server as well as client side. In your case server would not know about window or document during page serving, the suggestion is to move the code to either
Solution 1:
componentDidMount()
Or, Solution 2
In case it is something that you only want to perform in then you could write something like:
componentWillMount() {
if (typeof window !== 'undefined') {
console.log('window.innerHeight', window.innerHeight);
}
}
In the constructor of your class Component you can add
if (typeof window === 'undefined') {
global.window = {}
}
Example:
import React, { Component } from 'react'
class MyClassName extends Component {
constructor(props){
super(props)
...
if (typeof window === 'undefined') {
global.window = {}
}
}
This will avoid the error (in my case, the error would occur after I would click reload of the page).
global?.window && window.innerHeight
It's important to use the operator ?., otherwise the build command might crash.
Best solution ever
import dynamic from 'next/dynamic';
const Chart = dynamic(()=> import('react-apexcharts'), {
ssr:false,
})
A bit late but you could also consider using Dynamic Imports from next turn off SSR for that component.
You can warp the import for your component inside a dynamic function and then, use the returned value as the actual component.
import dynamic from 'next/dynamic'
const BoardDynamic = dynamic(() => import('../components/Board.tsx'), {
ssr: false,
})
<>
<BoardDynamic />
</>
I have to access the hash from the URL so I come up with this
const hash = global.window && window.location.hash;
Here's an easy-to-use workaround that I did.
const runOnClient = (func: () => any) => {
if (typeof window !== "undefined") {
if (window.document.readyState == "loading") {
window.addEventListener("load", func);
} else {
func();
}
}
};
Usage:
runOnClient(() => {
// access window as you like
})
// or async
runOnClient(async () => {
// remember to catch errors that might be raised in promises, and use the `await` keyword wherever needed
})
This is better than just typeof window !== "undefined", because if you just check that the window is not undefined, it won't work if your page was redirected to, it just works once while loading. But this workaround works even if the page was redirected to, not just once while loading.
I was facing the same problem when i was developing a web application in next.js This fixed my problem, you have to refer to refer the window object in a life cycle method or a react Hook. For example lets say i want to create a store variable with redux and in this store i want to use a windows object i can do it as follows:
let store
useEffect(()=>{
store = createStore(rootReducers, window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__())
}, [])
....
So basically, when you are working with window's object always use a hook to play around or componentDidMount() life cycle method
I wrapped the general solution (if (typeof window === 'undefined') return;) in a custom hook, that I am very pleased with. It has a similiar interface to reacts useMemo hook which I really like.
import { useEffect, useMemo, useState } from "react";
const InitialState = Symbol("initial");
/**
*
* #param clientFactory Factory function similiar to `useMemo`. However, this function is only ever called on the client and will transform any returned promises into their resolved values.
* #param deps Factory function dependencies, just like in `useMemo`.
* #param serverFactory Factory function that may be called server side. Unlike the `clientFactory` function a resulting `Promise` will not be resolved, and will continue to be returned while the `clientFactory` is pending.
*/
export function useClientSideMemo<T = any, K = T>(
clientFactory: () => T | Promise<T>,
deps: Parameters<typeof useMemo>["1"],
serverFactory?: () => K
) {
const [memoized, setMemoized] = useState<T | typeof InitialState>(
InitialState
);
useEffect(() => {
(async () => {
setMemoized(await clientFactory());
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
return typeof window === "undefined" || memoized === InitialState
? serverFactory?.()
: memoized;
}
Usage Example:
I am using it to dynamically import libaries that are not compatible with SSR in next.js, since its own dynamic import is only compatible with components.
const renderer = useClientSideMemo(
async () =>
(await import("#/components/table/renderers/HighlightTextRenderer"))
.HighlightTextRendererAlias,
[],
() => "text"
);
As you can see I even implemented a fallback factory callback, so you may provide a result when initially rendering on the server aswell. In all other aspects this hook should behave similiar to reacts useMemo hook. Open to feedback.
For such cases, Next.js has Dynamic Import.
A module that includes a library that only works in the browser, it's suggested to use Dynamic Import. Refer
Date: 06/08/2021
Check if the window object exists or not and then follow the code along with it.
function getSelectedAddress() {
if (typeof window === 'undefined') return;
// Some other logic
}
For Next.js version 12.1.0, I find that we can use process.title to determine whether we are in browser or in node side. Hope it helps!
export default function Projects(props) {
console.log({ 'process?.title': process?.title });
return (
<div></div>
);
}
1. From the terminal, I receive { 'process?.title': 'node' }
2. From Chrome devtool, I revice { 'process?.title': 'browser' }
I had this same issue when refreshing the page (caused by an import that didn't work well with SSR).
What fixed it for me was going to pages where this was occurring and forcing the import to be dynamic:
import dynamic from 'next/dynamic';
const SomeComponent = dynamic(()=>{return import('../Components/SomeComponent')}, {ssr: false});
//import SomeComponent from '../Components/SomeComponent'
Commenting out the original import and importing the component dynamically forces the client-side rendering of the component.
The dynamic import is covered in Nextjs's documentation here:
https://nextjs.org/docs/advanced-features/dynamic-import
I got to this solution by watching the youtube video here:
https://www.youtube.com/watch?v=DA0ie1RPP6g
You can define a state var and use the window event handle to handle changes like so.
const [height, setHeight] = useState();
useEffect(() => {
if (!height) setHeight(window.innerHeight - 140);
window.addEventListener("resize", () => {
setHeight(window.innerHeight - 140);
});
}, []);
You can try the below code snippet for use-cases such as - to get current pathname (CurrentUrl Path)
import { useRouter } from "next/router";
const navigator = useRouter()
console.log(navigator.pathname);
For anyone who somehow cannot use hook (for example, function component):
Use setTimeout(() => yourFunctionWithWindow()); will allow it get the window instance. Guess it just need a little more time to load.
I want to leave this approach that I found interesting for future researchers. It's using a custom hook useEventListener that can be used in so many others needs.
Note that you will need to apply a little change in the originally posted one, like I suggest here.
So it will finish like this:
import { useRef, useEffect } from 'react'
export const useEventListener = (eventName, handler, element) => {
const savedHandler = useRef()
useEffect(() => {
savedHandler.current = handler
}, [handler])
useEffect(() => {
element = !element ? window : element
const isSupported = element && element.addEventListener
if (!isSupported) return
const eventListener = (event) => savedHandler.current(event)
element.addEventListener(eventName, eventListener)
return () => {
element.removeEventListener(eventName, eventListener)
}
}, [eventName, element])
}
If it is NextJS app and inside _document.js, use below:
<script dangerouslySetInnerHTML={{
__html: `
var innerHeight = window.innerHeight;
`
}} />

Sharing code between a GTK/GJS App and a Gnome Shell Extension

I am developing a GTK application in GJS and like to reuse parts of the GTK
code inside a Gnome Shell extension. However, I did not find a way to add a
Gtk.Widget to the menu of my Gnome Shell panel icon.
I tried to use GtkClutter.Actor from clutter-gtk, but the library seems to
be out-dated and does neither work in a Wayland or X11 Gnome Shell, since it
requires Clutter 1.0 but sees 7 already loaded. When importing
imports.gi.GtkClutter in an extension, Gnome Shell yields this error:
Requiring GtkClutter, version none: Requiring namespace 'Clutter' version '1.0', but '7' is already loaded.
Here is some code to demonstrate that clutter-gtk actually works, if directly
running it via gjs; probably because I can enforce GTK 3.0 here.
gtkclutter.js:
imports.gi.versions.Gtk = '3.0' // fails if set to 4.0
const { Gtk, GLib, Clutter, GtkClutter } = imports.gi
// gtkUI returns a Gtk.Widget tree. This should be the reusable code.
function gtkUI() {
return new Gtk.Label({
label: '<span size="100000">🎉</span>',
use_markup: true,
})
}
// embedClutterActor returns a Gtk.Widget with an embedded Clutter.Actor.
function embedClutterActor(clutter_actor) {
let embed = new GtkClutter.Embed()
embed.get_stage().add_child(clutter_actor)
return embed
}
// embedGtkWidget returns a Clutter.Actor with an embedded Gtk.Widget.
function embedGtkWidget(gtk_widget) {
return new GtkClutter.Actor({ contents: gtk_widget })
}
class App {
constructor() {
this.title = 'GtkClutter'
GLib.set_prgname(this.title)
}
onActivate() { this.window.show_all() }
onStartup() { this.buildUI() }
run(ARGV=[]) {
this.app = new Gtk.Application()
this.app.connect('activate', () => this.onActivate())
this.app.connect('startup', () => this.onStartup())
this.app.run(ARGV)
}
buildUI() {
let w = this.window = new Gtk.ApplicationWindow({
application: this.app, title: this.title, icon_name: 'face-smile',
default_height: 160, default_width: 160, window_position: Gtk.WindowPosition.CENTER,
})
// Just to demonstrate that GtkClutter embedding works, we use both embeds here to create
// a Gtk.Widget from a Clutter.Actor from the actual Gtk.Widget that we want to show.
GtkClutter.init(null)
Clutter.init(null)
w.add(embedClutterActor(embedGtkWidget(gtkUI())))
// In the actual GTK App, we would just have used `w.add(gtkUI())`
// and not imported Clutter and GtkClutter at all.
}
}
new App().run(ARGV)
Here is the companion extension to the GTK app, trying (and failing)
to reuse the GTK code as contents of a GtkClutter.Actor.
extension.js:
const { Clutter, Gtk, Gio, St } = imports.gi
let GtkClutter = null // lazy import for debugging
const Main = imports.ui.main
const PanelMenu = imports.ui.panelMenu
const PopupMenu = imports.ui.popupMenu
const Me = imports.misc.extensionUtils.getCurrentExtension()
const VERSION = 'dev-version' // filled during install
const NAME = 'GtkClutterExt'
// gtkUI returns a Gtk.Widget tree. This should be the reusable code.
function gtkUI() {
return new Gtk.Button({ child: Gtk.Label({
label: `<span size="100000">🎉</span>`,
use_markup: true,
})})
}
// stUI returns an Gnome Shell widget tree that works only in Gnome Shell.
function stUI(icon_name='face-sad') {
return new St.Icon({ icon_name })
}
function statusIcon(icon_name) {
let box = new St.BoxLayout()
let icon = new St.Icon({ icon_name, style_class: 'system-status-icon emotes-icon' })
box.add_child(icon)
box.add_child(PopupMenu.arrowIcon(St.Side.BOTTOM))
return box
}
class Ext {
constructor() { this.panel_widget = null }
enable() {
log(`enabling extension ${Me.uuid}`)
try {
// Use St only for the status icon and the menu container (not the menu content).
let btn = this.panel_widget = new PanelMenu.Button(0.0, NAME, false)
let item = new PopupMenu.PopupBaseMenuItem({ reactive: false, can_focus: false })
btn.menu.addMenuItem(item)
Main.panel.addToStatusArea(NAME, btn)
try { GtkClutter = imports.gi.GtkClutter }
catch (e) { log(`failed to load clutter-gtk, err=${e.message}`) }
if (GtkClutter) {
// Using St for the status icon is OK, since it is only used by the extension.
btn.add_child(statusIcon('face-happy'))
// But for the menu, I like to reuse my GTK code from the GTK app.
// That is what GtkClutter was designed for, I believe.
item.actor.add_child(new GtkClutter.Actor({ contents: gtkUI() }))
} else {
// As fallback we show our mood regarding GtkClutter support in Gnome Shell ;)
btn.add_child(statusIcon('face-sad'))
item.actor.add_child(stUI('face-angry'))
}
} catch (e) {
log(`failed to enable ${Me.uuid}, err=${e.message}`)
}
}
disable() {
debug(`disabling extension ${Me.uuid}`)
if (this.panel_widget == null) return
this.panel_widget.destroy()
this.panel_widget = null
}
}
function init() { return new Ext() }
I know that clutter-gtk is quite dated (see https://gitlab.gnome.org/GNOME/clutter-gtk),
but I did not find a better way to lift GTK into my extension.
Questions
Does Gnome Shell provide something similar to GtkClutter.Actor that allows
extension programmers to reuse their GJS/GTK code?
Which alternative way to reuse GTK/GJS code do you see?
If GTK is such a universal and cross-platform library, why does Gnome Shell not
support it out-of-the box? (Bonus question 😉, more out of curiosity)
TL;DR You can not use GTK widgets in a GNOME Shell extension.
The toolkit used in GNOME Shell is Clutter, not GTK. Clutter is an internal library of Mutter, while GTK3 is only used in GNOME Shell for a handful of utilities.
Clutter used to be a standalone library, but is now developed specifically as a compositor toolkit for Mutter. GTK is an application toolkit, not suited for use in a compositor.
The standalone Clutter project is effectively unmaintained now, making GtkClutter pretty much the same.

SAPUI5 using Popover as a tooltip

I'm trying to use the sap.m.Popover as a "rich tooltip" for some controls. This is as per recommendation from SAP because the sap.ui.commons library is now deprecated. We have too much text we want to add to the standard string tooltip. I haven't figured out a way to use the popover directly as a tooltip, which is why I've extended the TooltipBase control to handle the popover.
I've got the popover working fine, However when I interact with my control, I get the following error:
Uncaught Error: failed to load 'myNewToolTip/controls/TooltipBaseRenderer.js' from ../controls/TooltipBaseRenderer.js: 404 - Not Found
I see from these threads that it is because the TooltipBase class is an abstract class and therefore doesn't have a renderer. However, because I'm already using the popover, I don't need to render anything. I've tried to add the TooltipBaseRenderer.js and just have an empty render class. But UI5 really doesn't like that either.
My question is what should I do, I see two options:
There is probably a simple way to use the popover as a tooltip, which I'm just too stupid to figure out (Bear in mind, I'd prefer to bind it directly in the XML view).
Figure out a way to suppress the renderer call as I don't need it.
This is my current source code for the custom control:
sap.ui.define([
"sap/m/Popover"
], function (Popover) {
"use strict";
return sap.ui.core.TooltipBase.extend("myNewToolTip.TooltipBase", {
metadata: {
properties: {
title : {}
},
events: {
"onmouseover" : {},
"onmouseout" : {}
}
},
oView: null,
setView: function(view) {
this.oView = view;
},
onmouseover : function(oEvent) {
var that = this;
if (!this.delayedCall){
this.delayedCall = setTimeout(function() {
if (!that.oPopover){
that._createQuickView();
}
}, 500);
}
},
onmouseout: function(oEvent) {
if (this.oPopover){
this.closePopover();
this.delayedCall = undefined;
}
else{
clearTimeout(this.delayedCall);
this.delayedCall = undefined;
}
},
_createQuickView: function() {
var sTitle = this.getTitle();
this.oPopover = new Popover({
title: sTitle
});
this.oPopover.openBy(this.getParent());
},
closePopover: function(){
this.oPopover.close();
this.oPopover = undefined;
}
});
});
There is no need to create a custom control just to display a popover on mouseover. As you said, there is a simpler way: Adding event delegates.
One of the events that delegates can listen to is onmouseover which can be achieved like this:
this.byId("myTargetControl").addEventDelegate({
onmouseover: function () {
// Open popover here
}
});
Demo: https://embed.plnkr.co/jAFIHK
Extending sap.ui.core.TooltipBase
If you still consider extending TooltipBase (without Popover), take a look at this example: https://embed.plnkr.co/33zFqa?show=control/MyCustomTooltip.js,preview
Keep in mind, though, that tooltips in general shouldn't contain critical information due to its lack of discoverability as Fiori Design Guideline mentions
Tooltips (...) should never contain critical information. They should also not contain redundant information.
Just as a friendly reminder :) Don't make people hover to find things.

VideoJS 5 plugin add button

I looked everywhere on the internet but I couldn't find any clear documentation or some examples to create my verySimplePlugin for videoJS 5 (Since it uses ES6).
I just want to add a button next to the big play button... Can someone help me?
Thanks...
PS: I'm using it in angularJS but I guess this can not a problem
This is how you can add download button to the end of control bar without any plugins or other complicated code:
var vjsButtonComponent = videojs.getComponent('Button');
videojs.registerComponent('DownloadButton', videojs.extend(vjsButtonComponent, {
constructor: function () {
vjsButtonComponent.apply(this, arguments);
},
handleClick: function () {
document.location = '/path/to/your/video.mp4'; //< there are many variants here so it is up to you how to get video url
},
buildCSSClass: function () {
return 'vjs-control vjs-download-button';
},
createControlTextEl: function (button) {
return $(button).html($('<span class="glyphicon glyphicon-download-alt"></span>').attr('title', 'Download'));
}
}));
videojs(
'player-id',
{fluid: true},
function () {
this.getChild('controlBar').addChild('DownloadButton', {});
}
);
I used 'glyphicon glyphicon-download-alt' icon and a title for it so it fits to the player control bar styling.
How it works:
We registering a new component called 'DownloadButton' that extends built-in 'Button' component of video.js lib
In constructor we're calling constructor of the 'Button' component (it is quite complicated for me to understand it 100% but it is similar as calling parent::__construct() in php)
buildCSSClass - set button classes ('vjs-control' is must have!)
createControlTextEl - adds content to the button (in this case - an icon and title for it)
handleClick - does something when user presses this button
After player was initialized we're adding 'DownloadButton' to 'controlBar'
Note: there also should be a way to place your button anywhere within 'controlBar' but I haven't figured out how because download button is ok in the end of the control bar
This is how I created a simple button plugin for videojs 5:
(function() {
var vsComponent = videojs.getComponent('Button');
// Create the button
videojs.SampleButton = videojs.extend(vsComponent, {
constructor: function() {
vsComponent.call(this, videojs, null);
}
});
// Set the text for the button
videojs.SampleButton.prototype.buttonText = 'Mute Icon';
// These are the defaults for this class.
videojs.SampleButton.prototype.options_ = {};
// videojs.Button uses this function to build the class name.
videojs.SampleButton.prototype.buildCSSClass = function() {
// Add our className to the returned className
return 'vjs-mute-button ' + vsComponent.prototype.buildCSSClass.call(this);
};
// videojs.Button already sets up the onclick event handler, we just need to overwrite the function
videojs.SampleButton.prototype.handleClick = function( e ) {
// Add specific click actions here.
console.log('clicked');
};
videojs.SampleButton.prototype.createEl = function(type, properties, attributes) {
return videojs.createEl('button', {}, {class: 'vjs-mute-btn'});
};
var pluginFn = function(options) {
var SampleButton = new videojs.SampleButton(this, options);
this.addChild(SampleButton);
return SampleButton;
};
videojs.plugin('sampleButton', pluginFn);
})();
You can use it this way:
var properties = { "plugins": { "muteBtn": {} } }
var player = videojs('really-cool-video', properties , function() { //do something cool here });
Or this way:
player.sampleButton()