Re-implement "Create Parent Dialog" without using React for 7.0 (#5381)
* Fixed an issue where the window size did not match the content * Replaced the semi-transparent progress bar with a spinner * Fixed a problem that allowed triggering a search while one was already in progress * Reduced code complexity
This commit is contained in:
parent
36e0c6469c
commit
c9efc33d9c
8 changed files with 75 additions and 178 deletions
|
@ -1,91 +0,0 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2020 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
https://digitalscholar.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import React, { memo, useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cx from 'classnames';
|
||||
|
||||
function CreateParent({ loading, item, toggleAccept }) {
|
||||
// With React 18, this is required for the window's dialog to be properly sized
|
||||
const ref = useRef();
|
||||
useEffect(() => {
|
||||
// Wait for Fluent to inject translated strings before resizing the dialog (fixes #5365).
|
||||
const observer = new MutationObserver(() => window.sizeToContent());
|
||||
observer.observe(ref.current, { childList: true, subtree: true });
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
// When the input has/does not have characters toggle the accept button on the dialog
|
||||
const handleInput = (e) => {
|
||||
if (e.target.value.trim() !== '') {
|
||||
toggleAccept(true);
|
||||
}
|
||||
else {
|
||||
toggleAccept(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="create-parent-container" ref={ ref }>
|
||||
<div className="title">
|
||||
{ item.attachmentFilename }
|
||||
</div>
|
||||
<p className="intro" data-l10n-id="create-parent-intro"/>
|
||||
<div className="body">
|
||||
<input
|
||||
id="parent-item-identifier"
|
||||
size="50"
|
||||
autoFocus={true}
|
||||
disabled={loading}
|
||||
onChange={handleInput}
|
||||
/>
|
||||
<div
|
||||
mode="undetermined"
|
||||
className={cx('downloadProgress', { hidden: !loading })}
|
||||
>
|
||||
<div className="progress-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
CreateParent.propTypes = {
|
||||
loading: PropTypes.bool,
|
||||
item: PropTypes.object,
|
||||
toggleAccept: PropTypes.func
|
||||
};
|
||||
|
||||
Zotero.CreateParent = memo(CreateParent);
|
||||
|
||||
|
||||
Zotero.CreateParent.render = (root, props) => {
|
||||
root.render(<CreateParent { ...props } />);
|
||||
};
|
|
@ -23,68 +23,73 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
/* global Zotero_Lookup: false */
|
||||
|
||||
import ReactDOM from "react-dom";
|
||||
const ZoteroCreateParentDialog = { // eslint-disable-line no-unused-vars
|
||||
init() {
|
||||
this.io = window.arguments[0];
|
||||
|
||||
var io;
|
||||
let createParent;
|
||||
let root;
|
||||
this.inputEl = document.getElementById('parent-item-identifier');
|
||||
this.progressEl = document.getElementById('progress');
|
||||
this.acceptBtnEl = document.querySelector('dialog').getButton("accept");
|
||||
this.manualEntryBtnEl = document.querySelector('dialog').getButton("extra2");
|
||||
|
||||
function toggleAccept(enabled) {
|
||||
document.querySelector('dialog').getButton("accept").disabled = !enabled;
|
||||
}
|
||||
// Set font size from pref
|
||||
Zotero.UIProperties.registerRoot(
|
||||
document.getElementById('zotero-create-parent-container')
|
||||
);
|
||||
|
||||
function doLoad() {
|
||||
// Set font size from pref
|
||||
let sbc = document.getElementById('zotero-create-parent-container');
|
||||
Zotero.UIProperties.registerRoot(sbc);
|
||||
this.inputEl.addEventListener('input', this.handleInput.bind(this));
|
||||
document.addEventListener('dialogaccept', this.handleAcceptClick.bind(this));
|
||||
document.addEventListener('dialogextra2', this.handleManualEntry.bind(this));
|
||||
|
||||
io = window.arguments[0];
|
||||
document.getElementById('title').textContent = this.io.dataIn.item.attachmentFilename;
|
||||
this.inputEl.focus();
|
||||
},
|
||||
|
||||
createParent = document.getElementById('create-parent');
|
||||
root = ReactDOM.createRoot(createParent);
|
||||
Zotero.CreateParent.render(root, {
|
||||
loading: false,
|
||||
item: io.dataIn.item,
|
||||
toggleAccept
|
||||
});
|
||||
async performLookup() {
|
||||
let newItems = await Zotero_Lookup.addItemsFromIdentifier(
|
||||
this.inputEl,
|
||||
this.io.dataIn.item,
|
||||
this.handleStatusChange.bind(this)
|
||||
);
|
||||
|
||||
document.addEventListener('dialogaccept', (event) => {
|
||||
doAccept();
|
||||
event.preventDefault();
|
||||
});
|
||||
document.addEventListener('dialogextra2', doManualEntry);
|
||||
}
|
||||
|
||||
function doUnload() {
|
||||
root.unmount();
|
||||
}
|
||||
|
||||
async function doAccept() {
|
||||
let textBox = document.getElementById('parent-item-identifier');
|
||||
let childItem = io.dataIn.item;
|
||||
let newItems = await Zotero_Lookup.addItemsFromIdentifier(
|
||||
textBox,
|
||||
childItem,
|
||||
(on) => {
|
||||
// Render react again with correct loading value
|
||||
Zotero.CreateParent.render(root, {
|
||||
loading: on,
|
||||
item: childItem,
|
||||
toggleAccept
|
||||
});
|
||||
// If we successfully created a parent, return it
|
||||
if (newItems.length) {
|
||||
this.io.dataOut = { parent: newItems[0] };
|
||||
window.close();
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
// If we successfully created a parent, return it
|
||||
if (newItems.length) {
|
||||
io.dataOut = { parent: newItems[0] };
|
||||
handleInput(event) {
|
||||
const input = event.target.value.trim();
|
||||
this.acceptBtnEl.disabled = input === '';
|
||||
},
|
||||
|
||||
handleStatusChange(isLookingUp) {
|
||||
this.inputEl.disabled = isLookingUp;
|
||||
this.acceptBtnEl.disabled = isLookingUp;
|
||||
this.manualEntryBtnEl.disabled = isLookingUp;
|
||||
if (isLookingUp) {
|
||||
this.progressEl.setAttribute("status", "animate");
|
||||
}
|
||||
else {
|
||||
this.progressEl.removeAttribute("status");
|
||||
}
|
||||
},
|
||||
|
||||
handleAcceptClick(ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
if (this.inputEl.value.trim() === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.performLookup();
|
||||
},
|
||||
|
||||
handleManualEntry() {
|
||||
this.io.dataOut = { parent: false };
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
|
||||
function doManualEntry() {
|
||||
io.dataOut = { parent: false };
|
||||
window.close();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
title="&zotero.createParent.title;"
|
||||
drawintitlebar-platforms="mac"
|
||||
onload="doLoad();"
|
||||
onunload="doUnload();">
|
||||
onload="ZoteroCreateParentDialog.init()"
|
||||
>
|
||||
<dialog
|
||||
id="zotero-parent-dialog"
|
||||
orient="vertical"
|
||||
|
@ -31,12 +31,16 @@
|
|||
<script src="editMenuOverlay.js"/>
|
||||
<script src="lookup.js"/>
|
||||
<script src="createParentDialog.js"/>
|
||||
<script src="components/createParent/createParent.js"/>
|
||||
|
||||
<html:link rel="localization" href="zotero.ftl"/>
|
||||
|
||||
<vbox id="zotero-create-parent-container" flex="1">
|
||||
<html:div id="create-parent" />
|
||||
<vbox id="zotero-create-parent-container">
|
||||
<html:h1 id="title" class="title" />
|
||||
<html:label for="parent-item-identifier" class="intro" id="intro" data-l10n-id="create-parent-intro"/>
|
||||
<html:div class="body">
|
||||
<html:input type="text" id="parent-item-identifier" aria-labelledby="intro" />
|
||||
<image id="progress" class="zotero-spinner-16" />
|
||||
</html:div>
|
||||
</vbox>
|
||||
</dialog>
|
||||
</window>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.create-parent-container {
|
||||
#zotero-create-parent-container {
|
||||
.title {
|
||||
font-size: 1.4em;
|
||||
font-weight: 700;
|
||||
|
@ -7,39 +7,18 @@
|
|||
}
|
||||
|
||||
.intro {
|
||||
// Text is added asynchonously, so set a height for dialog auto-sizing
|
||||
min-height: 1em;
|
||||
margin-bottom: .5em;
|
||||
max-width: 500px; // Avoid expanding the dialog too wide; instead, in some languages, the intro text will wrap.
|
||||
max-width: 550px; // Avoid expanding the dialog too wide; instead, in some languages, the intro text will wrap.
|
||||
}
|
||||
|
||||
.body {
|
||||
position: relative;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-bottom: 1em;
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.downloadProgress {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: -1em;
|
||||
transform: translateY(-100%);
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.create-parent-container {
|
||||
#zotero-create-parent-container {
|
||||
// This matches the margin on the buttons in the dialog box, which are platform dependent
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.create-parent-container {
|
||||
#zotero-create-parent-container {
|
||||
// This matches the margin on the buttons in the dialog box, which are platform dependent
|
||||
padding-left: 8px;
|
||||
padding-right: 6px;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.create-parent-container {
|
||||
#zotero-create-parent-container {
|
||||
// This matches the margin on the buttons in the dialog box, which are platform dependent
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
|
|
@ -1731,7 +1731,7 @@ describe("ZoteroPane", function() {
|
|||
let parent;
|
||||
let dialogPromise = waitForDialog(async (win) => {
|
||||
parent = await createDataObject('item', { title: 'Book Title' });
|
||||
win.io.dataOut = { parent };
|
||||
win.arguments[0].dataOut = { parent };
|
||||
win.close();
|
||||
}, false, 'chrome://zotero/content/createParentDialog.xhtml');
|
||||
let createParentPromise = zp.createParentItemsFromSelected();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue