https://ant.design/components/transfer/
Hey all! I was just wondering is it possible to implement two separate functions on the transfer buttons. For example, I want to run add function when the user clicks transfer to the right and I want to add remove function when the user clicks transfer button to the left.
From the documentation all I could see was both the buttons just trigger onChange function and I dont want that.
The API of Transfer component uses only one function to change the data, but you can call different functions inside onChange depending on the direction:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Transfer } from "antd";
const mockData = [];
for (let i = 0; i < 20; i++) {
mockData.push({
key: i.toString(),
title: `content${i + 1}`,
description: `description of content${i + 1}`
});
}
const initialTargetKeys = mockData
.filter((item) => +item.key > 10)
.map((item) => item.key);
const App = () => {
const [targetKeys, setTargetKeys] = useState(initialTargetKeys);
const [selectedKeys, setSelectedKeys] = useState([]);
const handleAdd = (nextTargetKeys, moveKeys) => {
console.log("add");
setTargetKeys(nextTargetKeys);
};
const handleDelete = (nextTargetKeys, moveKeys) => {
console.log("delete");
setTargetKeys(nextTargetKeys);
};
const onChange = (nextTargetKeys, direction, moveKeys) => {
if (direction === "left") {
handleDelete(nextTargetKeys, moveKeys);
} else {
handleAdd(nextTargetKeys, moveKeys);
}
};
const onSelectChange = (sourceSelectedKeys, targetSelectedKeys) => {
setSelectedKeys([...sourceSelectedKeys, ...targetSelectedKeys]);
};
return (
<Transfer
dataSource={mockData}
titles={["Source", "Target"]}
targetKeys={targetKeys}
selectedKeys={selectedKeys}
onChange={onChange}
onSelectChange={onSelectChange}
render={(item) => item.title}
/>
);
};
ReactDOM.render(<App />, document.getElementById("container"));
Related
`Below mentioned is my code for intersection observer
useIntersectionObserver
/* eslint-disable #typescript-eslint/no-shadow */
import { RefObject, useEffect, useState } from 'react';
export default function useIntersectionObserver(
elementRef: RefObject<Element>,
{ threshold = 0, root = null, rootMargin = '0%' }
) {
const [entry, setEntry] = useState<IntersectionObserverEntry>();
const callBackFunction = ([entry]: IntersectionObserverEntry[]): void => {
setEntry(entry);
};
useEffect(() => {
const node = elementRef?.current; // DOM Ref
const isIOSupported = !!window.IntersectionObserver;
if (!isIOSupported || !node) return;
const observerOptions = { threshold, root, rootMargin };
const observer = new IntersectionObserver(
callBackFunction,
observerOptions
);
observer.observe(node);
return () => observer.disconnect();`your text`
}, [elementRef, JSON.stringify(threshold), root, rootMargin]);
return entry;
}
Below is the code mentioned where I am calling useInterSectionObserver hook
const scrollDivElementRef = useRef<null | HTMLDivElement>(null);
const chatDivRef = useRef<null | HTMLDivElement>(null);
const entry = useIntersectionObserver(scrollDivElementRef, {});
const isVisible = !!entry?.isIntersecting;
Here scrollDivElementRef is the ref of div which we are observing for the intersection.It is basically our sentinal element.
Below mentioned is the useEffect hook which is going to perform some action when isVisible will become true.
Below mentioned is a code in react-native webview ,basically we are opening our react web app inside ios app , but our intersection observer is not able to detect changes . We have implemented intersection observer for infinite loading of messages. Every time user will scroll upwards , we will get "isVisible" as true and we will make an API call to fetch more messages.
useEffect(() => {
if (isVisible) {
dispatch(
getPostsForChannel(inputChannelId, true, channelMessages[0].timestamp)
);
}
}, [isVisible]);
<View style={styles.container}>
<WebView
style={{ marginTop: 40 }}
//TODO: Update the url
source={{ uri: 'http://192.168.1.2:3000' }}
onLoad={(syntheticEvent) => {
const { nativeEvent } = syntheticEvent;
}}
javaScriptEnabled={true}
onError={(e) => {
const { nativeEvent } = e;
setUrl(HOME_URL);
}}
onHttpError={() => {
setUrl(HOME_URL);
}}
onMessage={onMessage}
limitsNavigationsToAppBoundDomains={true}
// injectedJavaScript="window.octNativeApp=true"
injectedJavaScriptBeforeContentLoaded={initializeNativeApp}
scalesPageToFit={false}
setBuiltInZoomControls={false}
useWebView2
allowsInlineMediaPlayback={true}
mediaPlaybackRequiresUserAction={false}
></WebView>
</View>`
It will be really very helpful , if you people can help me with this problem.
Thanks & Regards
Mohit
I tried giving the height & width to the webview but it is also not working.Tried almost every piece of advise available in other platforms but not able to fic this`
I'm attempting to use playwright to automate an electron js application, but I can't seem to find any relevant information. To automate a simple programme, I used playwright:- https://playwright.dev/docs/api/class-electron and https://www.electronjs.org/docs/latest/tutorial/quick-start. However, I am unable to obtain the elements (buttons, dropdowns, and so on) in the electron application. Any reference or documentation that will deeply guide me to automate desktop application using playwright.
I got mine to work using their intro guide
for me since the installer installs additional components, i had to build and install, then supply the path to the exe
in my package.json i have.
"playwright": "^1.25.0",
"#playwright/test": "^1.25.0",
"eslint-plugin-playwright": "^0.10.0",
I created this class to help me have a cleaner code.
import { _electron as electron, ElectronApplication, Page } from 'playwright';
class ElectronAppController {
static electronApp: ElectronApplication;
static window1: Page;
static window2: Page;
static window3: Page;
static async launchApp() {
ElectronAppController.electronApp = await electron.launch({
executablePath: 'C:\\pathTo\\app.exe',
});
ElectronAppController.electronApp.on('window', async (page) => {
ElectronAppController.assignWindows(page);
});
const mywindows: Page[] =
await ElectronAppController.electronApp.windows();
for (
let index = 0, l = mywindows.length;
index < l;
index += 1
) {
ElectronAppController.assignWindows(
mywindows[index]
);
}
}
private static assignWindows(page: Page) {
const myurl = path.basename(page.url());
if (myurl === 'window1.html') {
ElectronAppController.window1= page;
}
if (myurl === 'window2.html') {
ElectronAppController.window2= page;
}
if (myurl === 'window3.html') {
ElectronAppController.window3= page;
}
return true;
}
}
the test file name should be [name].spec.ts, don't forget to import
test.describe('First Window Tests', async () => {
test.beforeAll(async () => {
await ElectronAppController.launchApp();
});
test('Check if first window opened', didLaunchApp);
test('name of the test', async () => {
// test body
// this will allow you to record a test very useful, but sometimes it has some problems check note bellow
await ElectronAppController.window1.pause;
});
test.afterAll(async () => {
await ElectronAppController.electronApp.close();
});
});
here is a didLaunchApp just as a simple test
const didLaunchApp = async () => {
const isVisible: boolean = await ElectronAppController.electronApp.evaluate(
async ({ BrowserWindow }) => {
const mainWindow = BrowserWindow.getAllWindows()[0];
const getState = () => mainWindow.isVisible();
return new Promise((resolve) => {
if (mainWindow.isVisible()) {
resolve(getState());
} else {
mainWindow.once('ready-to-show', () => {
setTimeout(() => resolve(getState()), 0);
});
}
});
}
);
await expect(isVisible).toBeTruthy();
};
you can record tests but sometimes that might make some problems if you have some popups or other effects on hovering over an element,
you can read more about selectors here
I'm just finishing a series of e2e tests using the same as you, Electron with React. What you don't see? Does it at least load the application?
Share the code of one test and how you launch using .launch method.
Wanted to check how many instances are running and control the number of instances running in one exe electron bundle. Let us say I wanted to allow only three instances running for the one exe bundle. I am not able to do this.
Current Behavior:
Only one and remaining can block. Or open for any number of instances. We need to control only three instances running, not more than that.
Example:
const { app } = require('electron')
let myWindow = null
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
// Someone tried to run a second instance, we should focus our window.
if (myWindow) {
if (myWindow.isMinimized()) myWindow.restore()
myWindow.focus()
}
})
// Create myWindow, load the rest of the app, etc...
app.on('ready', () => {
})
}
You can try with the following code to know how many windows have been opened.
const count = BrowserWindow.getAllWindows().length;
To check visible windows, you can try the following code
let count = BrowserWindow.getAllWindows()
.filter(b => {
return b.isVisible()
}).length
Once you get the number of instances, based upon the condition for number of instance, ie. if it is more than 3, you can quit using app.quit().
You can make each instance write to a file (increment a counter for example) when the instance starts and when it exits. (decrement the counter). You should check that file to see if the maximum number of instances are running
import { app } from "electron";
import path from "path";
import fs from "fs";
const MAX_APP_INSTANCES = 3;
const INSTANCE_COUNT_FILE_PATH = path.join(
app.getPath("userData"),
"numOfInstances"
);
// utils to read/write number of instances to a file
const instanceCountFileExists = () => fs.existsSync(INSTANCE_COUNT_FILE_PATH);
const readInstanceCountFile = () =>
parseInt(fs.readFileSync(INSTANCE_COUNT_FILE_PATH, "utf-8"));
const writeInstanceCountFile = (value) =>
fs.writeFileSync(INSTANCE_COUNT_FILE_PATH, value);
const incInstanceCountFile = () => {
const value = readInstanceCountFile() + 1;
writeInstanceCountFile(value.toString());
};
const decInstanceCountFile = () => {
const value = readInstanceCountFile() - 1;
writeInstanceCountFile(value.toString());
};
// logic needed to only allow a certain number of instances to be active
if (instanceCountFileExists() && readInstanceCountFile() >= MAX_APP_INSTANCES) {
app.quit();
} else {
if (!instanceCountFileExists()) {
writeInstanceCountFile("1");
} else {
incInstanceCountFile();
}
app.on("quit", () => decInstanceCountFile());
}
Note: this is solution is somewhat hacky. For example, the quit event is not guaranteed to fire when the Electron app exits
I'm working on a JHipster application that I'm trying to get functioning in Electron. I have Golden Layout for window/pane management and cross-pane communication. I am having several problems with the combination of technologies, including:
I can't pop out more than one pane at the same time into their own Electron windows. I instead get an Uncaught Error: Can't create config, layout not yet initialised error in the console.
Two thirds of the panes don't display anything when popped out into Electron windows, and I'm not sure what the reason is. Any ideas or suggestions for this? One example of content is a leaflet map, another is a "PowerPoint preview" that is really just divs that mock the appearance of slides.
I haven't made it this far yet, but I assume that I will have trouble communicating between popped-out Electron windows when I get more than one open. Right now, the panes communicate between each other using Golden Layout's glEventHub emissions. I have an avenue to explore when I cross that bridge, namely Electron ipcRenderer.
Some borrowed code is here (most of it I can't share because it's company confidential):
electron.js:
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
const path = require('path');
const isDev = require('electron-is-dev');
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({width: 900, height: 680});
mainWindow.loadURL(isDev ? 'http://localhost:9000' : `file://${path.join(__dirname, '../build/index.html')}`);
if (isDev) {
// Open the DevTools.
//BrowserWindow.addDevToolsExtension('<location to your react chrome extension>');
mainWindow.webContents.openDevTools();
}
mainWindow.on('closed', () => mainWindow = null);
}
app.on('ready', createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (mainWindow === null) {
createWindow();
}
});
goldenLayoutComponent.tsx, a patch for Golden Layout:
import React from "react";
import ReactDOM from "react-dom";
// import "./goldenLayout-dependencies";
import GoldenLayout from "golden-layout";
import "golden-layout/src/css/goldenlayout-base.css";
import "golden-layout/src/css/goldenlayout-dark-theme.css";
import $ from "jquery";
interface IGoldenLayoutProps {
htmlAttrs: {},
config: any,
registerComponents: Function
}
interface IGoldenLayoutState {
renderPanels: Set<any>
}
interface IContainerRef {
current: any
}
export class GoldenLayoutComponent extends React.Component <IGoldenLayoutProps, IGoldenLayoutState> {
goldenLayoutInstance = undefined;
state = {
renderPanels: new Set<any>()
};
containerRef: IContainerRef = React.createRef();
render() {
const panels = Array.from(this.state.renderPanels || []);
return (
<div ref={this.containerRef as any} {...this.props.htmlAttrs}>
{panels.map((panel, index) => {
return ReactDOM.createPortal(
panel._getReactComponent(),
panel._container.getElement()[0]
);
})}
</div>
);
}
componentRender(reactComponentHandler) {
this.setState(state => {
const newRenderPanels = new Set(state.renderPanels);
newRenderPanels.add(reactComponentHandler);
return { renderPanels: newRenderPanels };
});
}
componentDestroy(reactComponentHandler) {
this.setState(state => {
const newRenderPanels = new Set(state.renderPanels);
newRenderPanels.delete(reactComponentHandler);
return { renderPanels: newRenderPanels };
});
}
componentDidMount() {
this.goldenLayoutInstance = new GoldenLayout(
this.props.config || {},
this.containerRef.current
);
if (this.props.registerComponents instanceof Function)
this.props.registerComponents(this.goldenLayoutInstance);
this.goldenLayoutInstance.reactContainer = this;
this.goldenLayoutInstance.init();
}
}
// Patching internal GoldenLayout.__lm.utils.ReactComponentHandler:
const ReactComponentHandler = GoldenLayout["__lm"].utils.ReactComponentHandler;
class ReactComponentHandlerPatched extends ReactComponentHandler {
_container: any;
_reactClass: any;
_render() {
const reactContainer = this._container.layoutManager.reactContainer; // Instance of GoldenLayoutComponent class
if (reactContainer && reactContainer.componentRender)
reactContainer.componentRender(this);
}
_destroy() {
// ReactDOM.unmountComponentAtNode( this._container.getElement()[ 0 ] );
this._container.off("open", this._render, this);
this._container.off("destroy", this._destroy, this);
}
_getReactComponent() {
// the following method is absolute copy of the original, provided to prevent depenency on window.React
const defaultProps = {
glEventHub: this._container.layoutManager.eventHub,
glContainer: this._container
};
const props = $.extend(defaultProps, this._container._config.props);
return React.createElement(this._reactClass, props);
}
}
GoldenLayout["__lm"].utils.ReactComponentHandler = ReactComponentHandlerPatched;
Any help or insight into these issues would be appreciated. Thanks in advance!
If you are still looking for a solutions, 1 and 2 I have solved, if you want to see my solution you could see in this repository.
But it was basically this:
1: The window that popups has a different path than the main window, so I just had to put a try catch in my requires, and you have to set
nativeWindowOpen = true
when creating the Browser window.
2: Solves it's self after 1 I think
I'm trying test custom angular components. I want check the components class state. For this a have two options:
Get the component in the scope
Use web components xtag
The problems:
My scope is always empty - I debug in the console I'm using karma to run the tests in the browser.
xtag returns an HtmlElement class so I can't access my properties(cast in dart?)
The codes:
library vader.panel_spec;
import '../_specs.dart';
import "package:vader/components/vader_component.dart";
main(){
describe('Tests for Panel UI',(){
TestBed _;
Scope _scope;
Element ngAppElement;
beforeEach(async(inject((TestBed tb, Scope scope, VmTurnZone zone, TemplateCache cache) {
_ = tb;
_scope = scope;
addToTemplateCache(cache, 'packages/vader/components/panel.html');
addToTemplateCache(cache, 'packages/vader/components/window/window.html');
})));
beforeEachModule((Module module) {
ngAppElement = new DivElement()..attributes['ng-app'] = '';
module
..install(new VaderComponentModule());
module..bind(Node, toValue: ngAppElement);
document.body.append(ngAppElement);
});
afterEach(() {
ngAppElement.remove();
ngAppElement = null;
});
compile(html) {
ngAppElement.setInnerHtml(html, treeSanitizer: new NullTreeSanitizer());
_.compile(ngAppElement);
return ngAppElement.firstChild;
}
it("should collapse when clicked", async((){
Element panel = compile("<v-panel>CollapsedItem</v-panel>");
microLeap();
_.rootScope.apply();
Element contentPanel = panel.querySelector('.content-panel');
expect(contentPanel.classes.contains('collapse'), isFalse);
_.triggerEvent(panel.querySelector('#collapse-icon'), 'click');
_.rootScope.apply();
window.console.debug(_.rootScope.context);
var comp = panel.xtag;
expect(comp.doCollapse, isTrue);
}));
});
}
library ng_specs;
import 'dart:html' hide Animation;
import 'package:angular/angular.dart';
import 'package:angular/mock/module.dart';
import 'package:guinness/guinness_html.dart' as gns;
export 'dart:html' hide Animation;
export 'package:unittest/unittest.dart' hide expect;
export 'package:guinness/guinness_html.dart';
export 'package:mock/mock.dart';
export 'package:di/di.dart';
export 'package:di/dynamic_injector.dart';
export 'package:angular/angular.dart';
export 'package:angular/application.dart';
export 'package:angular/introspection.dart';
export 'package:angular/core/annotation.dart';
export 'package:angular/core/registry.dart';
export 'package:angular/core/module_internal.dart';
export 'package:angular/core_dom/module_internal.dart';
export 'package:angular/core/parser/parser.dart';
export 'package:angular/core/parser/lexer.dart';
export 'package:angular/directive/module.dart';
export 'package:angular/formatter/module.dart';
export 'package:angular/routing/module.dart';
export 'package:angular/animate/module.dart';
export 'package:angular/mock/module.dart';
export 'package:perf_api/perf_api.dart';
es(String html) {
var div = new DivElement();
div.setInnerHtml(html, treeSanitizer: new NullTreeSanitizer());
return new List.from(div.nodes);
}
e(String html) => es(html).first;
Expect expect(actual, [matcher]) {
final expect = new Expect(actual);
if (matcher != null) {
expect.to(matcher);
}
return expect;
}
class Expect extends gns.Expect {
Expect(actual) : super(actual);
NotExpect get not => new NotExpect(actual);
toBeValid() => _expect(actual.valid && !actual.invalid, true,
reason: 'Form is not valid');
toBePristine() => _expect(actual.pristine && !actual.dirty, true,
reason: 'Form is dirty');
get _expect => gns.guinness.matchers.expect;
}
class NotExpect extends gns.NotExpect {
NotExpect(actual) : super(actual);
toBeValid() => _expect(actual.valid && !actual.invalid, false,
reason: 'Form is valid');
toBePristine() => _expect(actual.pristine && !actual.dirty, false,
reason: 'Form is pristine');
get _expect => gns.guinness.matchers.expect;
}
_injectify(fn) {
// The function does two things:
// First: if the it() passed a function, we wrap it in
// the "sync" FunctionComposition.
// Second: when we are calling the FunctionComposition,
// we inject "inject" into the middle of the
// composition.
if (fn is! FunctionComposition) fn = sync(fn);
return fn.outer(inject(fn.inner));
}
// Replace guinness syntax elements to inject dependencies.
beforeEachModule(fn) => gns.beforeEach(module(fn), priority:1);
beforeEach(fn) => gns.beforeEach(_injectify(fn));
afterEach(fn) => gns.afterEach(_injectify(fn));
it(name, fn) => gns.it(name, _injectify(fn));
iit(name, fn) => gns.iit(name, _injectify(fn));
_removeNgBinding(node) {
if (node is Element) {
node = node.clone(true) as Element;
node.classes.remove('ng-binding');
node.querySelectorAll(".ng-binding").forEach((Element e) {
e.classes.remove('ng-binding');
});
return node;
}
return node;
}
/**
* It adds an html template into the TemplateCache.
*/
void addToTemplateCache(TemplateCache cache, String path) {
HttpRequest request = new HttpRequest();
request.open("GET", path, async : false);
request.send();
cache.put(path, new HttpResponse(200, request.responseText));
}
main() {
gns.beforeEach(setUpInjector, priority:3);
gns.afterEach(tearDownInjector);
gns.guinnessEnableHtmlMatchers();
gns.guinness.matchers.config.preprocessHtml = _removeNgBinding;
}
Use ngInjector(panel).get(PanelComponent) or ngDirectives(panel)[0]. See the documentation here.