/*
 *
 * K2HR3 Web Application
 *
 * Copyright 2017 Yahoo Japan Corporation.
 *
 * K2HR3 is K2hdkc based Resource and Roles and policy Rules, gathers
 * common management information for the cloud.
 * K2HR3 can dynamically manage information as "who", "what", "operate".
 * These are stored as roles, resources, policies in K2hdkc, and the
 * client system can dynamically read and modify these information.
 *
 * For the full copyright and license information, please view
 * the license file that was distributed with this source code.
 *
 * AUTHOR:   Takeshi Nakatani
 * CREATE:   Tue Aug 15 2017
 * REVISION:
 *
 */

import React						from 'react';
import ReactDOM						from 'react-dom';									// eslint-disable-line no-unused-vars
import PropTypes					from 'prop-types';

import Paper						from '@mui/material/Paper';							// For contents wrap
import Box							from '@mui/material/Box';

// Styles
import { r3Container }				from './r3styles';									// But nothing for r3Container now.

// Parts
import R3AppBar						from './r3appbar';									// AppBar
import R3MainTree					from './r3maintree';								// Main TreeView
import R3Toolbar					from './r3toolbar';									// Toolbar
import R3MsgBox						from './r3msgbox';									// Message Box
import R3AboutDialog				from './r3aboutdialog';								// About Dialog
import R3AccountDialog				from './r3accountdialog';							// Account Dialog
import R3SigninDialog				from './r3signincreddialog';						// SignIn by Credential Dialog
import R3Progress					from './r3progress';								// Progress

// For contents
import R3Resource					from './r3resource';
import R3Role						from './r3role';
import R3Policy						from './r3policy';
import R3Service					from './r3service';

// For context
import { R3CommonContext }			from './r3commoncontext';

// Utilities
import R3Provider					from '../util/r3provider';
import R3Message					from '../util/r3message';
import { localTenantPrefix }		from '../util/r3define';
import { clientTypes }				from '../util/r3device';
import { r3LicensesJsonString }		from '../util/r3licenses';
import { r3VersionString }			from '../util/r3version';
import { resourceType, roleType, policyType, serviceType, errorType, warningType, infoType, signinUnknownType, signinUnscopedToken, signinCredential } from '../util/r3types';		// eslint-disable-line no-unused-vars
import { r3DeepCompare, r3IsEmptyStringObject, r3CompareString, r3CompareCaseString, r3IsEmptyString, r3IsEmptyEntity, r3IsEmptyEntityObject, r3ConvertFromJSON, r3IsSafeTypedEntity, r3DeepClone } from '../util/r3util';

//
// Container Class
//
export default class R3Container extends React.Component
{
	static propTypes = {
		title:			PropTypes.string
	};

	static defaultProps = {
		title:			'K2HR3'
	};

	// State for not render
	r3provider			= new R3Provider(this.cbProgressControl);	// [NOTE] signin must not be specified here.
	contentUpdating		= false;
	progressDisplayFunc	= null;										// Callback from R3Progress for registering its method function.

	// Licenses
	licensesObj			= r3ConvertFromJSON(r3LicensesJsonString);

	// for dispatching contents
	static dispUnique	= 0;

	// State
	//
	// [NOTE] type/service in selected
	// 
	// If selected.type is ROLE or POLICY or RESOURCE and selected.service
	// is empty, it means that "ROLE/POLICY/RESOURCE" top or the pass under
	// those is selected.
	// If selected.type is SERVICE, it means SERVICE top or "service name"
	// under SERVICE top is selected.
	// On this case, selected.service is set empty or "service name".
	// If selected.service is empty, it means that SERVICE top is selected.
	// If selected.service is not empty, "service name" under SERVICE top is
	// selected.
	// If selected.service is not empty and selected.type is ROLE or POLICY
	// or RESOURCE, it means that ROLE/POLICY/RESOURCE under "service name"
	// (under SERVICE) is selected.
	// At this case, when selected.path is not empty,
	// "SERVICE > service name > ROLE/POLICY/RESOURCE > path" is selected.
	// 
	// <<DETAIL EXAMPLE>>
	// [type]				[service]		[path]		[selected item]
	// ROLE/POLICY/RESOURCE	empty			empty/path	ROLE/POLICY/RESOURCE top or path under it
	// SERVICE				empty			empty		SERVICE top
	// SERVICE				service name	empty		"SERVICE > service name"
	// ROLE/POLICY/RESOURCE	service name	empty		"SERVICE > service name > ROLE/POLICY/RESOURCE"
	// ROLE/POLICY/RESOURCE	service name	path		"SERVICE > service name > ROLE/POLICY/RESOURCE > path"
	//
	state = {
		selected: {
			tenant:				null,
			type:				null,
			service:			null,
			path:				null
		},
		tenants:				[],
		mainTree:				this.r3provider.getEmptyTreeList(true),
		mainTreeEndock:			!clientTypes.isMobile,
		mainTreeDocked:			(clientTypes.isMobile || clientTypes.isTablet ? false : true),
		mainTreeOpen:			false,
		service:				{},
		role:					{},
		policy:					{},
		resource:				{},
		toolbarData:			this.r3provider.getPathDetailInfo(null, null, false, false, null, null),
		message:				new R3Message(this.r3provider.getR3TextRes().iNotSelectTenant),
		aboutDialogOpen:		false,
		licensePackage:			null,
		licenseType:			null,
		licenseText:			null,
		r3VersionText:			null,
		accountDialogOpen:		false,
		username:				this.r3provider.getR3Context().getSafeUserName(),
		unscopedtoken:			this.r3provider.getR3Context().getSafeUnscopedToken(),
		useLocalTenant:			this.r3provider.getR3Context().useLocalTenant(),
		signinDialogOpen:		false
	};

	constructor(props)
	{
		super(props);

		// Binding(do not define handlers as arrow functions for performance)
		this.handleIsContentUpdating		= this.handleIsContentUpdating.bind(this);

		this.handleTenantChange				= this.handleTenantChange.bind(this);
		this.handleLocalTenantCreate		= this.handleLocalTenantCreate.bind(this);
		this.handleLocalTenantChange		= this.handleLocalTenantChange.bind(this);
		this.handleLocalTenantDelete		= this.handleLocalTenantDelete.bind(this);
		this.handleTypeChange				= this.handleTypeChange.bind(this);
		this.handleListItemChange			= this.handleListItemChange.bind(this);
		this.handleNameItemInServiceChange	= this.handleNameItemInServiceChange.bind(this);
		this.handleTypeInServiceChange		= this.handleTypeInServiceChange.bind(this);
		this.handleListItemInServiceChange	= this.handleListItemInServiceChange.bind(this);

		this.handleTreeDetach				= this.handleTreeDetach.bind(this);
		this.handleTreeOpen					= this.handleTreeOpen.bind(this);
		this.handleTreePopupClose			= this.handleTreePopupClose.bind(this);
		this.handleTreeDocking				= this.handleTreeDocking.bind(this);

		this.handleMoveToUpPath				= this.handleMoveToUpPath.bind(this);
		this.handleCreatePath				= this.handleCreatePath.bind(this);
		this.handleCheckConflictPath		= this.handleCheckConflictPath.bind(this);
		this.handleCheckConflictServiceName	= this.handleCheckConflictServiceName.bind(this);
		this.handleDeletePath				= this.handleDeletePath.bind(this);
		this.handleCreateService			= this.handleCreateService.bind(this);
		this.handleCreateServiceTenant		= this.handleCreateServiceTenant.bind(this);
		this.handleDeleteService			= this.handleDeleteService.bind(this);

		this.handleContentUpdating			= this.handleContentUpdating.bind(this);
		this.handleSave						= this.handleSave.bind(this);
		this.handleServiceSave				= this.handleServiceSave.bind(this);

		this.handleSign						= this.handleSign.bind(this);
		this.handleAbout					= this.handleAbout.bind(this);
		this.handAboutDialogClose			= this.handAboutDialogClose.bind(this);
		this.handleAccount					= this.handleAccount.bind(this);
		this.handAccountDialogClose			= this.handAccountDialogClose.bind(this);
		this.handSignInDialogClose			= this.handSignInDialogClose.bind(this);
		this.cbProgressControl				= this.cbProgressControl.bind(this);		// For progress callback from provider
		this.cbRefRegister					= this.cbRefRegister.bind(this);			// For registering callback from progress

		// styles
		this.sxClasses						= r3Container(props.theme);
	}

	componentDidMount()
	{
		// Initialize tenant
		this.r3provider.getTenantList(false, this.state.useLocalTenant, (error, resobj) => this.cbTenantList(error, resobj));
	}

	//
	// Re-Initialize by sign in/out
	//
	componentReinitialize(isSignIn, username, unscopedtoken)
	{
		// Re-Initialize provider object
		this.r3provider = new R3Provider(this.cbProgressControl, isSignIn, username, unscopedtoken);

		// Re-Initialize & Update state
		this.updateState({
			selected: {
				tenant:				null,
				type:				null,
				service:			null,
				path:				null
			},
			tenants:				[],
			mainTree:				this.r3provider.getEmptyTreeList(true),
			mainTreeEndock:			!clientTypes.isMobile,
			mainTreeDocked:			(clientTypes.isMobile || clientTypes.isTablet ? false : true),
			mainTreeOpen:			false,
			service:				{},
			role:					{},
			policy:					{},
			resource:				{},
			toolbarData:			this.r3provider.getPathDetailInfo(null, null, false, false, null, null),
			aboutDialogOpen:		false,
			licensePackage:			null,
			licenseType:			null,
			licenseText:			null,
			r3VersionText:			null,
			accountDialogOpen:		false,
			username:				username,
			unscopedtoken:			unscopedtoken,
			signinDialogOpen:		false
		});

		if(isSignIn){
			// Re-Initialize tenant list
			this.r3provider.getTenantList(true, this.state.useLocalTenant, (error, resobj) => this.cbTenantList(error, resobj));

			// All parts updates
			this.updateStateAllParts(this.state.selected.tenant, this.state.selected.service, this.state.selected.type, this.state.selected.path, false, (isSignIn ? (this.r3provider.getR3TextRes().iSignined + this.r3provider.getR3TextRes().iNotSignin) : (this.r3provider.getR3TextRes().iSignouted + this.r3provider.getR3TextRes().iNotSignin)));
		}
	}

	//
	// Utility for updating State
	//
	updateState(newState, message = null, type = infoType)
	{
		if(r3IsEmptyEntity(newState)){
			newState = {};
		}
		if(!this.r3provider.getR3Context().isLogin()){
			var	r3providerMsg = this.r3provider.getR3Context().getErrorMsg();
			if(!r3IsEmptyString(r3providerMsg)){
				newState.message = new R3Message(r3providerMsg, errorType);
			}else{
				newState.message = new R3Message(this.r3provider.getR3TextRes().iNotSignin);
			}
		}else if(r3IsEmptyString(message)){
			if(undefined === newState.selected){
				if(undefined === this.state.selected || r3IsEmptyStringObject(this.state.selected.tenant, 'name')){
					newState.message = new R3Message(this.r3provider.getR3TextRes().iNotSelectTenant);
				}else if(null === this.state.selected.type || null === this.state.selected.path){
					newState.message = new R3Message(this.r3provider.getR3TextRes().iNotSelectPath);
				}else{
					newState.message = new R3Message();
				}
			}else if(null === newState.selected.tenant){
				newState.message = new R3Message(this.r3provider.getR3TextRes().iNotSelectTenant);
			}else if(null === newState.selected.type || null === newState.selected.path){
				newState.message = new R3Message(this.r3provider.getR3TextRes().iNotSelectPath);
			}else{
				newState.message = new R3Message();
			}
		}else{
			newState.message = new R3Message(message, type);
		}
		this.setState(newState);
	}

	//
	// Callback for provider
	//
	cbTenantList(error, tenants)
	{
		if(null !== error){
			this.updateState(null, this.r3provider.getR3TextRes().eCommunication + error.message, errorType);
		}else{
			this.updateState({
				tenants:	tenants
			}, ((!r3IsSafeTypedEntity(tenants, 'array') || 0 === tenants.length) ? this.r3provider.getR3TextRes().iNotHaveAnyTenant : null));
		}
	}

	//
	// Update MainTree List
	//
	updateMainTreeList(selectedTenant, selectedService, selectedType, selectedPath, callback)
	{
		console.info('CALL : updateMainTreeList');

		let	_callback	= callback;
		let	_tenant		= selectedTenant;
		let	_service	= selectedService;
		let	_type		= selectedType;
		let	_path		= selectedPath;

		this.cbProgressControl(true);										// collectively display progress

		this.r3provider.getAllTreeList(_tenant, (error, resobj) =>
		{
			// always error is null
			if(null !== error){
				this.cbProgressControl(false);								// collectively undisplay progress
				_callback(error, null);
				return;
			}

			// set seleted flag
			this.r3provider.selectTreeList(resobj, _service, _type, _path);

			this.cbProgressControl(false);									// collectively undisplay progress
			_callback(null, resobj);
		});
	}

	//
	// Update Service Data
	//
	updateServiceData(selectedTenant, selectedService, treeList, callback)
	{
		console.info('CALL : updateServiceData');

		let	_callback	= callback;
		let	_tenant		= selectedTenant;
		let	_service	= selectedService;

		if(	r3IsEmptyStringObject(_tenant, 'name')	||
			r3IsEmptyString(_service)				)
		{
			// update state
			_callback(null, {});
			return;
		}

		this.cbProgressControl(true);										// collectively display progress

		// check owner
		if(!this.r3provider.checkServiceOwnerInTreeList(treeList, _service)){
			this.cbProgressControl(false);									// collectively undisplay progress

			// tenant is not service owner, then service data is empty
			_callback(null, {});
			return;
		}

		this.r3provider.getServiceData(_tenant, _service, (error, resobj) =>
		{
			this.cbProgressControl(false);									// collectively undisplay progress

			if(null !== error){
				console.info(error.message);
				_callback(error, null);
				return;
			}
			if(null === resobj){
				console.error('Could not get role data for tenant(' + _tenant + '), service(' + _service + ')');
				resobj = {};
			}

			_callback(null, resobj);
		});
	}

	//
	// Update Role Data
	//
	updateRoleData(selectedTenant, selectedService, selectedType, selectedPath, callback)
	{
		console.info('CALL : updateRoleData');

		let	_callback	= callback;
		let	_tenant		= selectedTenant;
		let	_service	= selectedService;
		let	_type		= selectedType;
		let	_path		= selectedPath;

		if(	r3IsEmptyStringObject(_tenant, 'name')	||
			!r3CompareString(_type, roleType)		||
			r3IsEmptyString(_path)					)
		{
			// update state
			_callback(null, {});
			return;
		}

		this.r3provider.getRoleData(_tenant, _service, _path, false, (error, resobj) =>
		{
			if(null !== error){
				console.info(error.message);
				_callback(error, null);
				return;
			}
			if(null === resobj){
				console.error('Could not get role data for tenant(' + _tenant + '), path(' + _path + ')');
				resobj = {};
			}

			_callback(null, resobj);
		});
	}

	//
	// Update Policy Data
	//
	updatePolicyData(selectedTenant, selectedService, selectedType, selectedPath, callback)
	{
		console.info('CALL : updatePolicyData');
		let	_callback	= callback;
		let	_tenant		= selectedTenant;
		let	_service	= selectedService;
		let	_type		= selectedType;
		let	_path		= selectedPath;

		if(	r3IsEmptyStringObject(_tenant, 'name')	||
			!r3CompareString(_type, policyType)		||
			r3IsEmptyString(_path)					)
		{
			// update state
			_callback(null, {});
			return;
		}

		this.r3provider.getPolicyData(_tenant, _service, _path, (error, resobj) =>
		{
			if(null !== error){
				console.info(error.message);
				_callback(error, null);
				return;
			}
			if(null === resobj){
				console.error('Could not get policy data for tenant(' + _tenant + '), path(' + _path + ')');
				resobj = {};
			}

			_callback(null, resobj);
		});
	}

	//
	// Update Resource Data
	//
	updateResourceData(selectedTenant, selectedService, selectedType, selectedPath, callback)
	{
		console.info('CALL : updateResourceData');

		let	_callback	= callback;
		let	_tenant		= selectedTenant;
		let	_service	= selectedService;
		let	_type		= selectedType;
		let	_path		= selectedPath;

		if(	r3IsEmptyStringObject(_tenant, 'name')	||
			!r3CompareString(_type, resourceType)	||
			r3IsEmptyString(_path)					)
		{
			// update state
			_callback(null, {});
			return;
		}

		this.r3provider.getResourceData(_tenant, _service, _path, false, (error, resobj) =>
		{
			if(null !== error){
				console.info(error.message);
				_callback(error, null);
				return;
			}
			if(null === resobj){
				console.error('Could not get resource data for tenant(' + _tenant + '), path(' + _path + ')');
				resobj = {};
			}

			_callback(null, resobj);
		});
	}

	updateStateAllParts(tenant, service, type, path, openMainTree, message = null, msgType = infoType)
	{
		// new state data
		let	newState = {
			selected: {
				tenant:			tenant,
				service:		service,
				type:			type,
				path:			path
			},
			mainTreeOpen:		(this.state.mainTreeDocked ? false : openMainTree),
		};
		let	_tenant		= tenant;
		let	_service	= service;
		let	_type		= type;
		let	_path		= path;
		let	_message	= message;
		let	_msgType	= msgType;

		this.cbProgressControl(true);										// collectively display progress

		this.updateMainTreeList(_tenant, _service, _type, _path, (error, mainTree) =>
		{
			if(null !== error){
				this.cbProgressControl(false);								// collectively undisplay progress
				console.info(error.message);
				return;
			}
			newState.mainTree		= mainTree;
			newState.toolbarData	= this.r3provider.getPathDetailInfo(_tenant, _service, this.r3provider.checkServiceOwnerInTreeList(newState.mainTree, _service), this.r3provider.checkServiceTenantInTreeList(newState.mainTree, _service), _type, _path);

			this.updateServiceData(_tenant, _service, mainTree, (error, serviceData) =>
			{
				if(null !== error){
					this.cbProgressControl(false);							// collectively undisplay progress
					console.info(error.message);
					return;
				}
				newState.service= serviceData;

				this.updateRoleData(_tenant, _service, _type, _path, (error, role) =>
				{
					if(null !== error){
						this.cbProgressControl(false);						// collectively undisplay progress
						console.info(error.message);
						this.updateState(null, error.message, errorType);
						return;
					}
					newState.role = role;

					this.updatePolicyData(_tenant, _service, _type, _path, (error, policy) =>
					{
						if(null !== error){
							this.cbProgressControl(false);					// collectively undisplay progress
							console.info(error.message);
							this.updateState(null, error.message, errorType);
							return;
						}
						newState.policy = policy;

						this.updateResourceData(_tenant, _service, _type, _path, (error, resource) =>
						{
							this.cbProgressControl(false);					// collectively undisplay progress

							if(null !== error){
								console.info(error.message);
								this.updateState(null, error.message, errorType);
								return;
							}
							newState.resource = resource;

							// update all
							this.updateState(newState, _message, _msgType);
						});
					});
				});
			});
		});
	}

	//
	// Handle Check updating
	//
	handleIsContentUpdating()
	{
		return this.contentUpdating;
	}

	//
	// Handle Tenant in MainTree Change
	//
	handleTenantChange(tenant)
	{
		let	type	= (r3DeepCompare(tenant, this.state.selected.tenant) ? this.state.selected.type		: null);
		let	service	= (r3DeepCompare(tenant, this.state.selected.tenant) ? this.state.selected.service	: null);
		let	path	= (r3DeepCompare(tenant, this.state.selected.tenant) ? this.state.selected.path		: null);

		this.updateStateAllParts(tenant, service, type, path, false, this.r3provider.getR3TextRes().iSucceedChangeTenant);
	}

	//
	// Update Tenant List Callback for provider
	//
	// [NOTE]
	// This CallBack is for creating/updating a Local Tenant.
	//
	cbUpdateChangeTenant(error, tenants, tenantname)
	{
		if(null !== error){
			this.updateState(null, this.r3provider.getR3TextRes().eCommunication + error.message, errorType);
		}else{
			// update tenant list
			this.updateState({
				tenants:	r3DeepClone(tenants)
			}, ((!r3IsSafeTypedEntity(tenants, 'array') || 0 === tenants.length) ? this.r3provider.getR3TextRes().iNotHaveAnyTenant : null));

			// change selected tenant
			let	_selectedTenant = null;
			if(!r3IsEmptyString(tenantname)){
				let	_tenantName;
				if(0 === tenantname.indexOf(localTenantPrefix)){
					_tenantName = tenantname;
				}else{
					_tenantName = localTenantPrefix + tenantname;
				}
				tenants.map( (item) => {
					if(!r3IsEmptyString(item.name) && item.name == _tenantName){
						// found
						_selectedTenant = r3DeepClone(item);
					}
				});
			}
			this.updateStateAllParts(_selectedTenant, null, null, null, false, this.r3provider.getR3TextRes().iSucceedChangeTenant);
		}
	}

	//
	// Handle Create Local Tenant in MainTree
	//
	//	name			: Local Tenant name(prefix with local@) to create
	//	display			: Display name allowed empty
	//	description		: Description name allowed empty
	//	users			: User name list included self name
	//
	handleLocalTenantCreate(name, display, description, users)
	{
		console.info('CALL : handleLocalTenantCreate');

		let	_name		= name;
		let	_display	= display;
		let	_description= description;
		let	_users		= r3DeepClone(users);

		this.cbProgressControl(true);										// collectively display progress

		this.r3provider.createLocalTenant(_name, _display, _description, _users, (error) =>
		{
			if(null !== error){
				this.cbProgressControl(false);								// collectively undisplay progress
				this.updateState(null, this.r3provider.getR3TextRes().eLocalTenantCreate + error.message, errorType);
				return;
			}

			//
			// update tenant list and change to new local tenant
			//
			this.r3provider.getTenantList(true, this.state.useLocalTenant, (error, resobj) => this.cbUpdateChangeTenant(error, resobj, _name));

			this.cbProgressControl(false);									// collectively undisplay progress
		});
	}

	//
	// Handle Change Local Tenant in MainTree
	//
	//	name			: Local Tenant name(prefix with local@) to change
	//	id				: Local Tenant ID
	//	display			: Display name allowed empty
	//	description		: Description name allowed empty
	//	users			: User name list
	//
	handleLocalTenantChange(name, id, display, description, users)
	{
		console.info('CALL : handleLocalTenantChange');

		let	_name		= name;
		let	_id			= id;
		let	_display	= display;
		let	_description= description;
		let	_users		= r3DeepClone(users);

		this.cbProgressControl(true);										// collectively display progress

		this.r3provider.updateLocalTenant(_name, _id, _display, _description, _users, (error) =>
		{
			if(null !== error){
				this.cbProgressControl(false);								// collectively undisplay progress
				this.updateState(null, this.r3provider.getR3TextRes().eLocalTenantUpdate + error.message, errorType);
				return;
			}

			//
			// update tenant list and change to new local tenant
			//
			this.r3provider.getTenantList(true, this.state.useLocalTenant, (error, resobj) => this.cbUpdateChangeTenant(error, resobj, _name));

			this.cbProgressControl(false);									// collectively undisplay progress
		});
	}

	//
	// Handle Delete Local Tenant in MainTree
	//
	//	name			: Local Tenant name(prefix with local@) to delete
	//	id				: Local Tenant id
	//
	handleLocalTenantDelete(name, id)
	{
		console.info('CALL : handleLocalTenantDelete');

		let	_name		= name;
		let	_id			= id;

		this.cbProgressControl(true);										// collectively display progress

		this.r3provider.deleteLocalTenant(_name, _id, (error) =>
		{
			if(null !== error){
				this.cbProgressControl(false);								// collectively undisplay progress
				this.updateState(null, this.r3provider.getR3TextRes().eLocalTenantDelete + error.message, errorType);
				return;
			}

			//
			// update tenant list and change to new local tenant
			//
			this.r3provider.getTenantList(true, this.state.useLocalTenant, (error, resobj) => this.cbUpdateChangeTenant(error, resobj, null));

			this.cbProgressControl(false);									// collectively undisplay progress
		});
	}

	//
	// Handle Top level type in MainTree Change
	//
	handleTypeChange(newType)
	{
		let	type	= r3IsEmptyStringObject(this.state.selected.tenant, 'name') ? null : newType;

		this.updateStateAllParts(this.state.selected.tenant, null, type, null, true, (null === type ? this.r3provider.getR3TextRes().iNotSelectTenant : this.r3provider.getR3TextRes().iSucceedChangeType));
	}

	//
	// Handle ROLE/POLICY/RESOURCE(not under SERVICE) Item Change
	//
	handleListItemChange(type, path)
	{
		// this handler is called when "tenant > ROLE/POLICY/RESOURCE/ > item" is selected.
		// it means service name is empty(not selected).
		//
		this.updateStateAllParts(this.state.selected.tenant, null, type, path, false, this.r3provider.getR3TextRes().iSucceedChangePath);
	}

	// Handle Service name under "SERVICE" Change
	handleNameItemInServiceChange(servicename)
	{
		//
		// Type is serviceType("service")
		//
		let	message = this.r3provider.checkServiceOwnerInTreeList(this.state.mainTree, servicename) ? this.r3provider.getR3TextRes().iSucceedChangeServiceOwner : this.r3provider.getR3TextRes().iSucceedChangeServiceName;

		this.updateStateAllParts(this.state.selected.tenant, servicename, serviceType, null, true, message);
	}

	// Handle Type(ROLE/POLICY/RESOURCE) under "SERVICE > Service name" Change
	handleTypeInServiceChange(servicename, type_in_service)
	{
		this.updateStateAllParts(this.state.selected.tenant, servicename, type_in_service, null, true, this.r3provider.getR3TextRes().iSucceedChangeServiceType);
	}

	// Handle Item under "SERVICE > Service name > Type(ROLE/POLICY/RESOURCE)" Change
	handleListItemInServiceChange(servicename, type_in_service, path)
	{
		this.updateStateAllParts(this.state.selected.tenant, servicename, type_in_service, path, false, this.r3provider.getR3TextRes().iSucceedChangeServicePath);
	}

	//
	// Handle MainTree open
	//
	handleTreeOpen()
	{
		this.updateState({
			mainTreeDocked:		false,
			mainTreeOpen:		true
		});
	}

	//
	// Handle MainTree popup close
	//
	handleTreePopupClose()
	{
		this.updateState({
			mainTreeOpen:		false
		});
	}

	//
	// Handle MainTree docking status
	//
	handleTreeDetach()
	{
		this.handleTreeDocking(false);
	}

	//
	// Handle MainTree docking status
	//
	handleTreeDocking(isDocking)
	{
		this.updateState({
			mainTreeDocked:		isDocking,
			mainTreeOpen:		false
		});
	}

	//
	// Handle Move path
	//
	handleMoveToUpPath()
	{
		//
		// Check for not move up type
		//
		// no tenant												||
		// unknown type												||
		// SERVICE top(type & empty service)						||
		// ROLE/POLICY/RESOURCE top(type & empty service or path)	||
		//
		if(	r3IsEmptyStringObject(this.state.selected.tenant, 'name') ||
			(!r3CompareCaseString(serviceType, this.state.selected.type) && !r3CompareCaseString(roleType, this.state.selected.type) && !r3CompareCaseString(policyType, this.state.selected.type) && !r3CompareCaseString(resourceType, this.state.selected.type)) ||
			(r3CompareCaseString(serviceType, this.state.selected.type) && r3IsEmptyString(this.state.selected.service)) ||
			((r3CompareCaseString(roleType, this.state.selected.type) || r3CompareCaseString(policyType, this.state.selected.type) || r3CompareCaseString(resourceType, this.state.selected.type)) && (r3IsEmptyString(this.state.selected.service) && r3IsEmptyString(this.state.selected.path))) )
		{
			console.error('Current path is not existed.');
			return;
		}

		// make new type/service/path
		let	_newType;
		let	_newServiceName;
		let	_newPath;
		let	_message;
		let	separatorPos;
		if(r3CompareCaseString(serviceType, this.state.selected.type) && !r3IsEmptyString(this.state.selected.service)){
			// now "SERVICE > service name", move to SERVICE top
			_newType		= serviceType;
			_newServiceName	= null;
			_newPath		= null;
			_message		= this.r3provider.getR3TextRes().iSucceedMoveService;

		}else if((r3CompareCaseString(roleType, this.state.selected.type) || r3CompareCaseString(policyType, this.state.selected.type) || r3CompareCaseString(resourceType, this.state.selected.type)) && !r3IsEmptyString(this.state.selected.service)){
			// now "SERVICE > service name > ROLE/POLICY/RESOURCE..."
			if(r3IsEmptyString(this.state.selected.path)){
				// now "SERVICE > service name > ROLE/POLICY/RESOURCE", move to "SERVICE > service name"
				_newType		= serviceType;
				_newServiceName	= this.state.selected.service;
				_newPath		= null;
				_message		= this.r3provider.getR3TextRes().iSucceedMoveServiceName;

			}else{
				// now "SERVICE > service name > ROLE/POLICY/RESOURCE > path"
				separatorPos = this.state.selected.path.lastIndexOf('/');
				if(-1 === separatorPos){
					// now "SERVICE > service name > ROLE/POLICY/RESOURCE > path", move to "SERVICE > service name > ROLE/POLICY/RESOURCE"
					_newType		= this.state.selected.type;
					_newServiceName	= this.state.selected.service;
					_newPath		= null;
					_message		= this.r3provider.getR3TextRes().iSucceedMoveType;

				}else{
					// now "SERVICE > service name > ROLE/POLICY/RESOURCE > path/path...", move to upper path
					_newType		= this.state.selected.type;
					_newServiceName	= this.state.selected.service;
					_newPath		= this.state.selected.path.substr(0, separatorPos);
					_message		= this.r3provider.getR3TextRes().iSucceedChangePath;
				}
			}
		}else{
			// now "ROLE/POLICY/RESOURCE > path"
			separatorPos = this.state.selected.path.lastIndexOf('/');
			if(-1 === separatorPos){
				// now "ROLE/POLICY/RESOURCE > path", move to "ROLE/POLICY/RESOURCE"
				_newType		= this.state.selected.type;
				_newServiceName	= null;
				_newPath		= null;
				_message		= this.r3provider.getR3TextRes().iSucceedMoveType;

			}else{
				// now "ROLE/POLICY/RESOURCE > path/path...", move to move to upper path
				_newType		= this.state.selected.type;
				_newServiceName	= null;
				_newPath		= this.state.selected.path.substr(0, separatorPos);
				_message		= this.r3provider.getR3TextRes().iSucceedChangePath;
			}
		}

		// update all
		this.updateStateAllParts(this.state.selected.tenant, _newServiceName, _newType, _newPath, false, _message);
	}

	//
	// Handle Create Path
	//
	handleCreatePath(newPath, newAllPath)
	{
		console.info('CALL CREATE PATH : ' + newPath + '(' + newAllPath + ')');

		let	_path = newAllPath.trim();

		this.r3provider.createEmptyData(this.state.selected.tenant, this.state.selected.type, _path, (error) =>
		{
			if(null !== error){
				this.updateState(null, this.r3provider.getR3TextRes().eCommunication + error.message, errorType);
				return;
			}

			// update all
			this.updateStateAllParts(this.state.selected.tenant, null, this.state.selected.type, _path, false, this.r3provider.getR3TextRes().iSucceedCreatePath);
		});
	}

	//
	// Handle Check Confrict Path
	//
	handleCheckConflictPath(newCreatePath)
	{
		if(r3IsEmptyString(this.state.selected.type) || r3CompareCaseString(serviceType, this.state.selected.type) || !r3IsEmptyString(this.state.selected.service)){
			//
			// if type is ignore(not select any or not service type or under service), this function returns true.
			// it means do not create new path.
			//
			return true;
		}

		for(let cnt = 0; cnt < this.state.mainTree.length; ++cnt){
			if(this.state.selected.type === this.state.mainTree[cnt].path){
				return this.findCheckConflictPath(this.state.mainTree[cnt].children, newCreatePath, true);
			}
		}
		return false;
	}

	//
	// Handle Check Confrict Service Name
	//
	handleCheckConflictServiceName(newServiceName)
	{
		if(!r3CompareCaseString(serviceType, this.state.selected.type) || !r3IsEmptyString(this.state.selected.service)){
			//
			// if type is ignore(not select service), this function returns true.
			// it means do not create new service name.
			//
			return true;
		}
		for(let cnt = 0; cnt < this.state.mainTree.length; ++cnt){
			if(r3CompareCaseString(serviceType, this.state.mainTree[cnt].path)){
				return this.findCheckConflictPath(this.state.mainTree[cnt].children, newServiceName, false);
			}
		}
		return false;
	}

	//
	// Utility : find same path under items
	//
	findCheckConflictPath(items, itemPath, isCheckNest)
	{
		if(!r3IsSafeTypedEntity(items, 'array') || r3IsEmptyString(itemPath)){
			return false;
		}
		for(let cnt = 0; cnt < items.length; ++cnt){
			if(itemPath === items[cnt].path){
				return true;
			}
			if(isCheckNest && this.findCheckConflictPath(items[cnt].children, itemPath)){
				return true;
			}
		}
		return false;
	}

	//
	// Handle Delete path
	//
	handleDeletePath()
	{
		console.info('CALL DELETE PATH');

		if(r3IsEmptyEntity(this.state.selected.path)){
			return;
		}
		this.r3provider.removeData(this.state.selected.tenant, this.state.selected.type, this.state.selected.path, (error) =>
		{
			if(null !== error){
				this.updateState(null, this.r3provider.getR3TextRes().eCommunication + error.message, errorType);
				return;
			}

			// update all
			this.updateStateAllParts(this.state.selected.tenant, this.state.selected.service, this.state.selected.type, null, false, this.r3provider.getR3TextRes().iSucceedDeletePath);
		});
	}

	handleDeleteService(isServiceOwner, isServiceTenant)
	{
		console.info('CALL DELETE SERVICE');

		if(	r3IsEmptyString(this.state.selected.service)	||
			!r3IsSafeTypedEntity(isServiceTenant, 'boolean')||
			!r3IsSafeTypedEntity(isServiceOwner, 'boolean')	)
		{
			return;
		}
		let	_isServiceOwner	= isServiceOwner;
		let	_isServiceTenant= isServiceTenant;

		this.r3provider.removeService(this.state.selected.tenant, this.state.selected.service, isServiceTenant, (error) =>
		{
			if(null !== error){
				this.updateState(null, this.r3provider.getR3TextRes().eCommunication + error.message, errorType);
				return;
			}

			let	_servicename;
			let	_type;
			if(!_isServiceTenant){
				// Case : remove service
				_servicename		= null;
				_type				= serviceType;
			}else{
				// Case : remove service tenant
				if(_isServiceOwner){
					_servicename	= this.state.selected.service;
					_type			= serviceType;
				}else{
					_servicename	= null;
					_type			= serviceType;
				}
			}

			// update all
			this.updateStateAllParts(this.state.selected.tenant, _servicename, _type, null, false, (_isServiceTenant ? this.r3provider.getR3TextRes().iSucceedDeleteServiceTenant : this.r3provider.getR3TextRes().iSucceedDeleteService));
		});
	}

	//
	// Handle Updating flag
	//
	handleContentUpdating(isUpdating)
	{
		if(r3IsSafeTypedEntity(isUpdating, 'boolean')){
			this.contentUpdating = isUpdating;
		}
	}

	//
	// Handle Save
	//
	handleSave(data)
	{
		console.info('CALL SAVE DATA : ' + JSON.stringify(data));

		this.r3provider.updateData(this.state.selected.tenant, this.state.selected.type, this.state.selected.path, data, (error) =>
		{
			// set off updating flag
			//
			// [NOTE]
			// We need to chane status of updating because it can not be rolled back when this function is called.
			//
			this.handleContentUpdating(false);

			if(null !== error){
				this.updateState(null, this.r3provider.getR3TextRes().eCommunication + error.message, errorType);
				return;
			}

			// update all
			this.updateStateAllParts(this.state.selected.tenant, this.state.selected.service, this.state.selected.type, this.state.selected.path, false, this.r3provider.getR3TextRes().iSucceedUpdate);
		});
	}

	//
	// Handle Save for Service
	//
	handleServiceSave(data)
	{
		console.info('CALL SERVICE SAVE DATA : ' + JSON.stringify(data));

		this.r3provider.updateServiceData(this.state.selected.tenant, this.state.selected.service, data.tenant, true, data.verify, (error) =>
		{
			// set off updating flag
			//
			// [NOTE]
			// We need to chane status of updating because it can not be rolled back when this function is called.
			//
			this.handleContentUpdating(false);

			if(null !== error){
				this.updateState(null, this.r3provider.getR3TextRes().eCommunication + error.message, errorType);
				return;
			}

			// update all
			this.updateStateAllParts(this.state.selected.tenant, this.state.selected.service, this.state.selected.type, this.state.selected.path, false, this.r3provider.getR3TextRes().iSucceedUpdate);
		});
	}

	//
	// Handle Create for Service
	//
	handleCreateService(newServiceName, newVerify)
	{
		console.info('CALL CREATE SERVICE : Service Name=' + JSON.stringify(newServiceName) + ', Verify=' + JSON.stringify(newVerify));

		let	_newServiceName = newServiceName;
		let	_newVerify		= newVerify;

		this.r3provider.createInitializedService(this.state.selected.tenant, _newServiceName, _newVerify, (error) =>
		{
			if(null !== error){
				this.updateState(null, this.r3provider.getR3TextRes().eCommunication + error.message, errorType);
				return;
			}

			// update all
			this.updateStateAllParts(this.state.selected.tenant, _newServiceName, serviceType, null, false, this.r3provider.getR3TextRes().iSucceedCreateService);
		});
	}

	//
	// Handle Create for Service Tenant
	//
	handleCreateServiceTenant(aliasRole)
	{
		console.info('CALL CREATE SERVICE/TENANT');

		let	_aliasRole = aliasRole;
		this.r3provider.createServiceTenant(this.state.selected.tenant, this.state.selected.service, _aliasRole, (error) =>
		{
			if(null !== error){
				this.updateState(null, this.r3provider.getR3TextRes().eCommunication + error.message, errorType);
				return;
			}

			// update all
			this.updateStateAllParts(this.state.selected.tenant, this.state.selected.service, serviceType, null, false, this.r3provider.getR3TextRes().iSucceedCreateServiceTenant);
		});
	}

	//
	// Handle Singin/Signout
	//
	handleSign(configName)
	{
		let	type = this.r3provider.getR3Context().getSignInType();

		if(signinUnscopedToken == type){
			//
			// Unscoped Token Login Type
			//
			let	signurl = '';

			if(this.r3provider.getR3Context().isLogin()){
				if(!r3IsEmptyString(configName)){
					console.info('Signout does not require config name, but ' + configName + ' is specified, it is ignored.');
				}
				configName	= this.r3provider.getR3Context().getSafeConfigName();
				signurl		= this.r3provider.getR3Context().getSafeSignOutUrl(configName);		// value is 'URL'

			}else{
				if(!r3IsEmptyString(configName)){
					signurl = this.r3provider.getR3Context().getSafeSignInUrl(configName).url;	// value is 'URL'

				}else if(1 <= this.r3provider.getR3Context().getSafeConfigCount(true)){
					let	signinObj = this.r3provider.getR3Context().getSafeSignInUrl();

					Object.keys(signinObj).forEach(function(_configName){
						// Get first config name
						if(r3IsEmptyString(configName)){
							configName = _configName;
						}
					});
					signurl = this.r3provider.getR3Context().getSafeSignInUrl(configName).url;	// value is 'URL'

				}else{
					console.info('Signin URL does not find.');
				}
			}
			window.location.href = signurl;

		}else if(signinCredential == type){
			//
			// Credential Login Type
			//
			if(this.r3provider.getR3Context().isLogin()){
				// reload application for sign out
				this.componentReinitialize(false);
			}else{
				// popup direct signin dialog
				this.updateState({
					signinDialogOpen:	true
				});
			}

		}else{	//signinUnknownType
			this.updateState(null, this.r3provider.getR3TextRes().eUnknownSignInType, errorType);
		}
	}

	//
	// Handle Open About Dialog
	//
	handleAbout(package_name)
	{
		if(!r3IsEmptyString(package_name) && !r3IsEmptyEntityObject(this.licensesObj, package_name)){
			// Licenses
			this.updateState({
				aboutDialogOpen:	true,
				licensePackage:		package_name,
				licenseType:		this.licensesObj[package_name].licenseType,
				licenseText:		this.licensesObj[package_name].licenseText,
				r3VersionText:		null,
				mainTreeOpen:		false
			});
		}else{
			// About K2HR3
			this.updateState({
				aboutDialogOpen:	true,
				licensePackage:		null,
				licenseType:		null,
				licenseText:		null,
				r3VersionText:		r3VersionString,
				mainTreeOpen:		false
			});
		}
	}

	//
	// Handle Close About Dialog
	//
	handAboutDialogClose(event, reason)										// eslint-disable-line no-unused-vars
	{
		this.updateState({
			aboutDialogOpen:	false,
			licensePackage:		null,
			licenseType:		null,
			licenseText:		null,
			r3VersionText:		null
		});
	}

	//
	// Handle Open Account Dialog
	//
	handleAccount()
	{
		this.updateState({
			accountDialogOpen:	true
		});
	}

	//
	// Handle Close Account Dialog
	//
	handAccountDialogClose(event, reason)									// eslint-disable-line no-unused-vars
	{
		this.updateState({
			accountDialogOpen:	false
		});
	}

	//
	// Handle Close Direct SignIn Dialog
	//
	handSignInDialogClose(event, reason, doSignIn, username, passphrase)
	{
		if(doSignIn){
			// [NOTE]
			// username is not empty when this handler is called with signin type.
			//
			let	_username = username.trim();

			// Try to sign in
			this.r3provider.getUnscopedUserToken(_username, passphrase, (error, token) =>
			{
				if(null !== error){
					this.updateState(null, this.r3provider.getR3TextRes().eCommunication + error.message, errorType);
					return;
				}
				this.componentReinitialize(true, _username, token);
			});
		}

		this.updateState({
			signinDialogOpen:	false
		});
	}

	//
	// Callback from R3Provider for Progress
	//
	cbProgressControl(isDisplay)
	{
		if(this.progressDisplayFunc){
			this.progressDisplayFunc(isDisplay);
		}
	}

	//
	// Callback from R3Progress for register object reference
	//
	cbRefRegister(func)
	{
		if(!r3IsSafeTypedEntity(func, 'function')){
			console.error('call cbRefRegister with invalid function parameter.');
			return;
		}
		this.progressDisplayFunc = func;
	}

	//
	// Renders : Contents
	//
	getContent()
	{
		// always increments(32bit cycling)
		if(R3Container.dispUnique < 0xffffffff){
			++R3Container.dispUnique;
		}else{
			R3Container.dispUnique = 0;
		}

		if(r3CompareCaseString(resourceType, this.state.selected.type)){
			if(!r3IsEmptyEntity(this.state.selected.path)){
				return (
					<Paper
						sx={ this.sxClasses.paper }
					>
						<R3Resource
							theme={ this.props.theme }
							r3provider={ this.r3provider }
							resource={ this.state.resource }
							dispUnique={ R3Container.dispUnique }
							onSave={ this.handleSave }
							onUpdate={ this.handleContentUpdating }
							isReadMode={ !r3IsEmptyString(this.state.selected.service) }
						/>
					</Paper>
				);
			}
		}else if(r3CompareCaseString(roleType, this.state.selected.type)){
			if(!r3IsEmptyEntity(this.state.selected.path)){
				return (
					<Paper
						sx={ this.sxClasses.paper }
					>
						<R3Role
							theme={ this.props.theme }
							r3provider={ this.r3provider }
							role={ this.state.role }
							dispUnique={ R3Container.dispUnique }
							onSave={ this.handleSave }
							onUpdate={ this.handleContentUpdating }
							isReadMode={ !r3IsEmptyString(this.state.selected.service) }
						/>
					</Paper>
				);
			}
		}else if(r3CompareCaseString(policyType, this.state.selected.type)){
			if(!r3IsEmptyEntity(this.state.selected.path)){
				return (
					<Paper
						sx={ this.sxClasses.paper }
					>
						<R3Policy
							theme={ this.props.theme }
							r3provider={ this.r3provider }
							policy={ this.state.policy }
							dispUnique={ R3Container.dispUnique }
							onSave={ this.handleSave }
							onUpdate={ this.handleContentUpdating }
							isReadMode={ !r3IsEmptyString(this.state.selected.service) }
						/>
					</Paper>
				);
			}
		}else if(r3CompareCaseString(serviceType, this.state.selected.type)){
			// content is displaied at only service owner
			if(this.r3provider.checkServiceOwnerInTreeList(this.state.mainTree, this.state.selected.service)){
				return (
					<Paper
						sx={ this.sxClasses.paper }
					>
						<R3Service
							theme={ this.props.theme }
							r3provider={ this.r3provider }
							tenant={ this.state.selected.tenant.name }
							service={ this.state.service }
							dispUnique={ R3Container.dispUnique }
							r3provider={ this.r3provider }
							onSave={ this.handleServiceSave }
							onUpdate={ this.handleContentUpdating }
						/>
					</Paper>
				);
			}
		}else{
			// Not selected
			return;
		}
	}

	//
	// Renders
	//
	render()
	{
		let	signinDialogMsg	= (r3CompareCaseString('http', this.r3provider.getR3Context().getSafeApiScheme()) ? this.r3provider.getR3TextRes().wDeprecateAuth : '');

		//
		// Common context for all child components
		//
		const commonContext = { r3Context: this.r3provider.getR3Context() };

		return (
			<R3CommonContext.Provider
				value={ commonContext }
			>
				<React.Fragment>
					<R3AppBar
						theme={ this.props.theme }
						r3provider={ this.r3provider }
						title={ this.props.title }
						enDock={ this.state.mainTreeEndock }
						isDocking={ this.state.mainTreeDocked }
						licensesObj={ this.licensesObj }
						onTreeDetach={ this.handleTreeDetach }
						onOpenTree={ this.handleTreeOpen }
						onCheckUpdating={ this.handleIsContentUpdating }
						onAbout={ this.handleAbout }
						onSign={ this.handleSign }
						onAccount={ this.handleAccount }
					/>
					<Box>
						<R3MainTree
							theme={ this.props.theme }
							r3provider={ this.r3provider }
							title={ this.props.title }
							enDock={ this.state.mainTreeEndock }
							isDocking={ this.state.mainTreeDocked }
							licensesObj={ this.licensesObj }
							open={ this.state.mainTreeOpen }
							editableLocalTenant={ this.state.useLocalTenant }
							userName={ this.state.username }
							tenants={ this.state.tenants }
							treeList={ this.state.mainTree }
							selectedTenant={ this.state.selected.tenant }
							selectedType={ this.state.selected.type }
							selectedService={ this.state.selected.service }
							selectedPath={ this.state.selected.path }
							onTenantChange={ this.handleTenantChange }
							onLocalTenantCreate={ this.handleLocalTenantCreate }
							onLocalTenantChange={ this.handleLocalTenantChange }
							onLocalTenantDelete={ this.handleLocalTenantDelete }
							onTypeItemChange={ this.handleTypeChange }
							onListItemChange={ this.handleListItemChange }
							onNameItemInServiceChange={ this.handleNameItemInServiceChange }
							onTypeInServiceChange={ this.handleTypeInServiceChange }
							onListItemInServiceChange={ this.handleListItemInServiceChange }
							onPopupClose={ this.handleTreePopupClose }
							onTreeDocking={ this.handleTreeDocking }
							onCheckUpdating={ this.handleIsContentUpdating }
							onAbout={ this.handleAbout }
						/>
						<Box>
							<R3Toolbar
								theme={ this.props.theme }
								r3provider={ this.r3provider }
								enDock={ this.state.mainTreeEndock }
								toolbarData={ this.state.toolbarData }
								onArrawUpward={ this.handleMoveToUpPath }
								onCreatePath={ this.handleCreatePath }
								onCheckPath={ this.handleCheckConflictPath }
								onDeletePath={ this.handleDeletePath }
								onCreateService={ this.handleCreateService }
								onCreateServiceTenant={ this.handleCreateServiceTenant }
								onCheckServiceName={ this.handleCheckConflictServiceName }
								onDeleteService={ this.handleDeleteService }
								onCheckUpdating={ this.handleIsContentUpdating }
							/>
							<R3MsgBox
								theme={ this.props.theme }
								message={ this.state.message }
							/>
							{ this.getContent() }
						</Box>
					</Box>
					<R3AboutDialog
						theme={ this.props.theme }
						r3provider={ this.r3provider }
						open={ this.state.aboutDialogOpen }
						onClose={ this.handAboutDialogClose }
						licensePackage={ this.state.licensePackage }
						licenseType={ this.state.licenseType }
						licenseText={ this.state.licenseText }
						r3VersionText={ this.state.r3VersionText }
					/>
					<R3AccountDialog
						theme={ this.props.theme }
						r3provider={ this.r3provider }
						open={ this.state.accountDialogOpen }
						onClose={ this.handAccountDialogClose }
						username={ this.state.username }
						unscopedtoken={ this.state.unscopedtoken }
					/>
					<R3SigninDialog
						theme={ this.props.theme }
						r3provider={ this.r3provider }
						open={ this.state.signinDialogOpen }
						name={ null }
						passphrase={ null }
						message={ signinDialogMsg }
						onClose={ this.handSignInDialogClose }
					/>
					<R3Progress
						theme={ this.props.theme }
						cbRefRegister={ this.cbRefRegister }
					/>
				</React.Fragment>
			</R3CommonContext.Provider>
		);
	}
}

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noexpandtab sw=4 ts=4 fdm=marker
 * vim<600: noexpandtab sw=4 ts=4
 */
