diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index ada59e578..6dc28b6da 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -97,8 +97,7 @@ jobs: run: | git clone https://github.com/informatics-isi-edu/ermrestjs.git cd ermrestjs - make dist - sudo make deploy + sudo make root-install - name: Install chaise run: | sudo mkdir -p /var/www/html/chaise diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 000000000..c0d64add5 --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,38 @@ +name: Detect version change and publish to npmjs + +on: + push: + branches: + - 'master' + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Checkout repository code + uses: actions/checkout@v4 + - name: setup node + uses: actions/setup-node@v3 + with: + node-version: '20.x' + registry-url: 'https://registry.npmjs.org' + - name: Check if version has been updated + id: version-check + uses: EndBug/version-check@v2 + - name: Publish new version + if: steps.version-check.outputs.changed == 'true' + run: | + : # these messages are duplicated in case npm publish failed + echo "Version change found in commit ${{ steps.version-check.outputs.commit }}" >> $GITHUB_STEP_SUMMARY + echo "New version: ${{ steps.version-check.outputs.version }} (${{ steps.version-check.outputs.type }})" >> $GITHUB_STEP_SUMMARY + npm ci --include=dev + npm publish + echo "Successfully published the new version! :rocket:" > $GITHUB_STEP_SUMMARY + echo "[Link to npm package](https://www.npmjs.com/package/@isrd-isi-edu/chaise)" >> $GITHUB_STEP_SUMMARY + echo "Version change found in commit ${{ steps.version-check.outputs.commit }}" >> $GITHUB_STEP_SUMMARY + echo "New version: ${{ steps.version-check.outputs.version }} (${{ steps.version-check.outputs.type }})" >> $GITHUB_STEP_SUMMARY + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} + - name: Skip publishing + if: steps.version-check.outputs.changed == 'false' + run: echo "No version change detected!" >> $GITHUB_STEP_SUMMARY diff --git a/Makefile b/Makefile index f1593733c..a9837cf92 100644 --- a/Makefile +++ b/Makefile @@ -553,6 +553,18 @@ deploy-w-config: dont_deploy_in_root .make-rsync-list-w-config $(JS_CONFIG) $(VI @rsync -avz --exclude='$(REACT_BUNDLES_FOLDERNAME)' $(DIST)/react/ $(CHAISEDIR) @rsync -avz --delete $(REACT_BUNDLES) $(CHAISEDIR) +# run dist and deploy with proper uesrs (GNU). only works with root user +.PHONY: root-install +root-install: + su $(shell stat -c "%U" Makefile) -c "make dist" + make deploy + +# run dist and deploy with proper uesrs (FreeBSD and MAC OS X). only works with root user +.PHONY: root-install-alt +root-install-alt: + su $(shell stat -f '%Su' Makefile) -c "make dist" + make deploy + # Rule to create version.txt .PHONY: gitversion gitversion: @@ -590,6 +602,8 @@ usage: @echo " updeps local update of node dependencies" @echo " update-webdriver update the protractor's webdriver" @echo " deps-test local install of dev node dependencies and update protractor's webdriver" + @echo " root-install should only be used as root. will use dist with proper user and then deploy, for GNU systems" + @echo " root-install-alt should only be used as root. will use dist with proper user and then deploy, for FreeBSD and MAC OS X" @echo " test run e2e tests" @echo " testrecordadd run data entry app add e2e tests" @echo " testrecordedit run data entry app edit e2e tests" diff --git a/docs/dev-docs/dev-guide.md b/docs/dev-docs/dev-guide.md index 114ccbb44..98c41ccd0 100644 --- a/docs/dev-docs/dev-guide.md +++ b/docs/dev-docs/dev-guide.md @@ -23,6 +23,9 @@ This is a guide for people who develop Chaise. * [Guidelines](#guidelines) * [Guidelines for promise chains](#guidelines-for-promise-chains) - [Context and provider pattern](#context-and-provider-pattern) +- [Performance](#performance) + * [Debugging](#debugging) + * [Memorization](#memorization) ## Reading Material In this section, we've included all the guides and tools that we think are useful @@ -987,3 +990,26 @@ const CounterDisplayInner = () => { export default CounterDisplay; ``` + +## Performance + +In this section, we should summarize everything related to performance. This includes how to debug performance issues and common practices to fix issues. + +### Debugging + +Before jumping into solutions, consider debugging and finding the root of the problem. + +- You should install official [React developer tools](https://react.dev/learn/react-developer-tools). With this, you can look at components and see when/why each rerenders. + - By default, the "Profiler" tab only works in development mode. To use this tab in the production mode, you need to uncomment the `'react-dom$': 'react-dom/profiling',` alias in the [app.config.js](https://github.com/informatics-isi-edu/chaise/blob/master/webpack/app.config.js) file. +- Installing in the `development` mode allows you to add break points in the code. You should also be mindful of the browser console, as React and other dependencies usually print warning/errors only in this mode. That being said, as we mentioned in [here](#development-vs-production), `development` has its downsides. + +### Memorization + +React always re-renders children when a parent component has to be re-rendered. But since we're using the provider pattern, the immediate relationship is unimportant. So, if we find any performance issues, it is probably related to redundant components rendering because of this. `memo` lets us skip re-rendering a component when its props are unchanged. You can see how we've used it [here](https://github.com/informatics-isi-edu/chaise/pull/2341/commits/29720eb277faaa6fc768a912ffcf8a8ec4776980), which significantly improved the performance of record page. + +That being said, performance-related changes applied incorrectly can even harm performance. Use `React.memo()` wisely. Don't use memoization if you can't quantify the performance gains. + +Useful links: +- https://react.dev/reference/react/memo +- https://dmitripavlutin.com/use-react-memo-wisely/ + diff --git a/docs/dev-docs/e2e-test-writing.md b/docs/dev-docs/e2e-test-writing.md index 359c6b75a..899b48566 100644 --- a/docs/dev-docs/e2e-test-writing.md +++ b/docs/dev-docs/e2e-test-writing.md @@ -40,6 +40,7 @@ profileModal.waitFor({ state: 'detached' }); Make sure `await` is consistently everywhere. It's needed for step, outside of it, and expects +7b8f4775b390d09ea8f8548425e47ac2e ``` test.describe('feature', () => { const PAGE_URL = `/recordset/#${process.env.CATALOG_ID!}/product-navbar:accommodation`; diff --git a/docs/dev-docs/e2e-test.md b/docs/dev-docs/e2e-test.md index befd0cf9f..8bb5b238c 100644 --- a/docs/dev-docs/e2e-test.md +++ b/docs/dev-docs/e2e-test.md @@ -26,11 +26,11 @@ export REMOTE_CHAISE_DIR_PATH=USERNAME@HOST:public_html/chaise These variables are used in our test framework to communicate with `ERMrest`. The following is how these variables most probably should look like: ```sh -export CHAISE_BASE_URL=https://dev.isrd.isi.edu/~chaise # No trailing `/` -export ERMREST_URL=https://dev.isrd.isi.edu/ermrest # No trailing `/` +export CHAISE_BASE_URL=https://dev.derivacloud.org/~chaise # No trailing `/` +export ERMREST_URL=https://dev.derivacloud.org/ermrest # No trailing `/` export AUTH_COOKIE="webauthn=PutYourCookieHere;" # You have to put `webauthn=` at the beginging and `;` at the end. export RESTRICTED_AUTH_COOKIE="webauthn=PutAnotherCookieHere;" # You have to put `webauthn=` at the beginging and `;` at the end. -export REMOTE_CHAISE_DIR_PATH=chirag@dev.isrd.isi.edu:public_html/chaise # No trailing `/` +export REMOTE_CHAISE_DIR_PATH=some_user_name@dev.derivacloud.org:public_html/chaise # No trailing `/` export SHARDING=false ``` @@ -44,7 +44,7 @@ You can get your cookie by querying the database, or using the following simple ## How To Run Tests ### Prerequistes -1. After setting up the environment variables, make sure that the `https://dev.isrd.isi.edu/~` directory has the public access(if not, give the folder the following permissions `chmod 755 `). +1. After setting up the environment variables, make sure that the `https://dev.derivacloud.org/~` directory has the public access(if not, give the folder the following permissions `chmod 755 `). 2. Make sure all the dependencies are installed by running the following command: @@ -64,7 +64,7 @@ You can get your cookie by querying the database, or using the following simple ``` As the name suggests this will not install dependencies. That's why you need to install all the dependencies in step 2. -4. Upload your code on the `https://dev.isrd.isi.edu/~` by the running the following command in your local chaise repository (This will upload your local code to the remote server): +4. Upload your code on the `https://dev.derivacloud.org/~` by the running the following command in your local chaise repository (This will upload your local code to the remote server): ```sh make deploy @@ -125,7 +125,7 @@ $ eval ssh-agent $ ssh-add PATH/TO/KEY # export REMOTE_CHAISE_DIR_PATH=USERNAME@HOST:public_html/chaise -$ export REMOTE_CHAISE_DIR_PATH=chirag@dev.isrd.isi.edu:public_html/chaise +$ export REMOTE_CHAISE_DIR_PATH=some_user_name@dev.derivacloud.org:public_html/chaise ``` **CI**: For CI there is no need to set `REMOTE_CHAISE_DIR_PATH` as it copies the actual file to the **chaise-config.js** in its local directory where it is running the test-suite. diff --git a/docs/dev-docs/manual-test.md b/docs/dev-docs/manual-test.md index 62d4aebd4..192bf52f3 100644 --- a/docs/dev-docs/manual-test.md +++ b/docs/dev-docs/manual-test.md @@ -22,11 +22,7 @@ ### Auto placement of tooltips - By default, the tooltips on column headers should appear on top-center - For the rightmost column if the tooltip text(comment) is too long then the tooltip should be placed on top-right -[(Right-most-column-tooltip)](https://dev.isrd.isi.edu/~dsingh/wiki-images/right-most-column-tooltip.png) - Similarly, for other columns too if the tooltip text is long and they appear on the left or right edge of the window then their tooltip should be placed on top-left or top-right respectively -([Default tooltip](https://dev.isrd.isi.edu/~dsingh/wiki-images/top-center-arrow.png), -[Long text on the leftmost column](https://dev.isrd.isi.edu/~dsingh/wiki-images/top-left-arrow.png), -[Long text on the rightmost column](https://dev.isrd.isi.edu/~dsingh/wiki-images/top-right-arrow.png)) - Make sure the tooltip does not flicker or overlap the header text if the text is too long. @@ -103,7 +99,7 @@ This means that we will only update the "total count", if we got the updated dat 1. For testing this feature, make sure that you have `debug:true` in your `chaise-config.js` to be able to look at the logs that we generate (You can also use the `network` tab in browsers to look at the actual ermrest requests). - - If you're using chrome make sure that it's showing [verbose](https://dev.isrd.isi.edu/~ashafaei/wiki-images/verbose.png) logs. + - If you're using chrome make sure that it's showing all the logs, including the "verbose" ones. More information [here](https://developer.chrome.com/docs/devtools/console/log/#browser). 2. It's better if you throttle your network speed ([chrome](https://developers.google.com/web/tools/chrome-devtools/network-performance/network-conditions)/[firefox](https://blog.nightly.mozilla.org/2016/11/07/simulate-slow-connections-with-the-network-throttling-tool/)) to simulate slower networks and make the flow-control more visible. @@ -132,16 +128,16 @@ In [ErmrestDataUtils](https://github.com/informatics-isi-edu/ErmrestDataUtils), ## Test priviledges - Verify that files uploaded by another user that you don't have permission to read, will properly create a new version of that file in hatrac. 1. Need to have 2 user accounts. One cannot be a part of any of the globus groups that we rely on to set blanket permissions (`isrd-staff`, `isrd-testers`). - - `curl -H 'cookie: webauthn=' -X PUT -H "Content-Type: application/json" -d '[]' -i "https://dev.isrd.isi.edu/hatrac/js;acl/subtree-create"` + - `curl -H 'cookie: webauthn=' -X PUT -H "Content-Type: application/json" -d '[]' -i "https://dev.derivacloud.org/hatrac/js;acl/subtree-create"` 2. User 1 creates file1 in hatrac 3. user 2 doesn't have permission to update that object in hatrac 4. user 2 tries to upload the same exact file to the same namespace in hatrac - should get 403 5. change permissions on hatrac obj to include update for user 2 - - `curl -H 'cookie: webauthn=' -X PUT -H "Content-Type: application/json" -d '[]' -i "https://dev.isrd.isi.edu/hatrac/js/chaise///;acl/update"` + - `curl -H 'cookie: webauthn=' -X PUT -H "Content-Type: application/json" -d '[]' -i "https://dev.derivacloud.org/hatrac/js/chaise///;acl/update"` 6. user 2 tries to upload same exact file to the same namespace again 7. navigate to hatrac folder and verify a new version was created - - ssh to `dev.isrd.isi.edu` + - ssh to `dev.derivacloud.org` - `cd /var/www/hatrac/js/chaise///` - `ls -al` to list all contents and file sizes diff --git a/docs/resources/viewer-annotation/ArrowLine.png b/docs/resources/viewer-annotation/ArrowLine.png new file mode 100644 index 000000000..e909c7404 Binary files /dev/null and b/docs/resources/viewer-annotation/ArrowLine.png differ diff --git a/docs/resources/viewer-annotation/Text.png b/docs/resources/viewer-annotation/Text.png new file mode 100644 index 000000000..ea5cfd627 Binary files /dev/null and b/docs/resources/viewer-annotation/Text.png differ diff --git a/docs/user-docs/logging-pre-feb-20.md b/docs/user-docs/logging-pre-feb-20.md index 5ac7e7f25..ec594a216 100644 --- a/docs/user-docs/logging-pre-feb-20.md +++ b/docs/user-docs/logging-pre-feb-20.md @@ -6,16 +6,16 @@ By providing `Deriva-Client-Context` header in ermrset requests we can log extra objects alongside the request. ERMrest will log the provided object in the `dcctx` attribute of logs. For example the following is a line from `/var/log/messages` file in dev.isrd: ``` -Jan 24 16:29:50 dev.isrd.isi.edu ermrest[4313.139635548755712]: +Jan 24 16:29:50 dev.derivacloud.org ermrest[4313.139635548755712]: { "elapsed":0.014, "req":"OSGHMz7JSySiS0Y5UOLA6w", "scheme":"https", - "host":"dev.isrd.isi.edu", + "host":"dev.derivacloud.org", "status":"304 Not Modified", "method":"GET", "path":"/ermrest/catalog/1/attributegroup/M:=isa:dataset/F5:=left(thumbnail)=(isa:file:id)/$M/F4:=left(owner)=(isa:person:name)/$M/F3:=left(gene_summary)=(vocabulary:gene_summary:id)/$M/F2:=left(status)=(isa:dataset_status:id)/$M/F1:=left(project)=(isa:project:id)/$M/release_date,id;M:=array(M:*),F5:=array(F5:*),F4:=array(F4:*),F3:=array(F3:*),F2:=array(F2:*),F1:=array(F1:*)@sort(release_date::desc::,id)?limit=26", "client":"128.9.184.94", - "referrer":"https://dev.isrd.isi.edu/~ashafaei/chaise/recordset/", + "referrer":"https://dev.derivacloud.org/chaise/recordset/", "agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", "track":"f671a1bf.559966234faaf", "dcctx":{ diff --git a/docs/user-docs/logging.md b/docs/user-docs/logging.md index 4b340e4ce..bafd383bd 100644 --- a/docs/user-docs/logging.md +++ b/docs/user-docs/logging.md @@ -31,13 +31,13 @@ By providing `Deriva-Client-Context` header in ermrset requests we can log extra "elapsed": 0.037084000000000006, "req": "Le-1bpTwSfSxddRT8Y0T5g", "scheme": "https", - "host": "dev.isrd.isi.edu", + "host": "dev.derivacloud.org", "status": "200 OK", "method": "GET", "path": "/ermrest/catalog/1/entity/T:=isa:dataset/(id)=(isa:dataset_organism:dataset_id)/M:=(organism)=(vocab:species:id)@sort(name,RID)?limit=11", "type": "application/json", "client": "128.9.180.218", - "referrer": "https://dev.isrd.isi.edu/chaise/recordset/", + "referrer": "https://dev.derivacloud.org/chaise/recordset/", "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", "track": "916d434.5aa5c5cf6b595", "dcctx": { diff --git a/help-docs/chaise/viewer-annotation.md b/help-docs/chaise/viewer-annotation.md index 73b08b4e9..1e5478be1 100644 --- a/help-docs/chaise/viewer-annotation.md +++ b/help-docs/chaise/viewer-annotation.md @@ -1,8 +1,15 @@ # How to annotate an image -* [Add a new Annotation](#add-a-new-annotation) -* [Edit/Delete an existing Annotation](#editdelete-an-existing-annotation) -* [Draw a Shape](#draw-a-shape) +- [Add a new Annotation](#add-a-new-annotation) +- [Edit/Delete an existing Annotation](#editdelete-an-existing-annotation) +- [Draw a Shape](#draw-a-shape) + * [Path](#path) + * [Rectangle](#rectangle) + * [Circle](#circle) + * [Line](#line) + * [Arrow line](#arrow-line) + * [Polygon](#polygon) + * [Text](#text) ## Add a new annotation {id=add-a-new-annotation} @@ -64,7 +71,7 @@ ## Draw a shape {id=draw-a-shape} -### Path +### Path {id=path} 1. Select the path tool (pencil icon) from the annotation tool bar. ![](https://github.com/informatics-isi-edu/chaise/raw/master/docs/resources/viewer-annotation/Path.png) @@ -75,7 +82,7 @@ 4. To add subsequent paths, repeat steps 2 & 3. -### Rectangle +### Rectangle {id=rectangle} 1. Select the rectangle tool (square icon) from the annotation tool bar. @@ -87,7 +94,7 @@ 4. To add subsequent rectangles, repeat steps 2 & 3. -### Circle +### Circle {id=circle} 1. Select the circle tool (circle icon) from the annotation tool bar. @@ -99,20 +106,28 @@ 4. To add subsequent circles, repeat steps 2 & 3. -### Line +### Line {id=line} 1. Select the line tool (line icon) from the annotation tool bar. ![](https://github.com/informatics-isi-edu/chaise/raw/master/docs/resources/viewer-annotation/Line.png) -2. Press and hold the left mouse button to draw the line on the image. Drag the mouse pointer - -to draw. As you drag the mouse, you will see the the line change its end point. +2. Press and hold the left mouse button to draw the line on the image. Drag the mouse pointer to draw. As you drag the mouse, you will see the the line change its end point. 3. Let go of the mouse button when you are done drawing. 4. To add subsequent lines, repeat steps 2 & 3. +### Arrow line {id=arrow-line} + +1. Select the arrow line tool (arrow icon) from the annotation tool bar. + ![](https://github.com/informatics-isi-edu/chaise/raw/master/docs/resources/viewer-annotation/ArrowLine.png) + +2. Press and hold the left mouse button to draw the arrow line on the image. Drag the mouse pointer to draw. As you drag the mouse, you will see the the arrow line change its end point. + +3. Let go of the mouse button when you are done drawing. + +4. To add subsequent arrow lines, repeat steps 2 & 3. -### Polygon +### Polygon {id=polygon} 1. Select the polygon tool (polygon icon) from the annotation tool bar. ![](https://github.com/informatics-isi-edu/chaise/raw/master/docs/resources/viewer-annotation/Polygon.png) @@ -123,3 +138,24 @@ to draw. As you drag the mouse, you will see the the line change its end point. 4. Let go of the mouse button when you are done placing the edge on the image. 5. To add subsequent polygons, de-select the polygon icon from the annotation toolbar and select it again. + +### Text {id=text} + + +1. Select the text tool ("A" icon) from the annotation tool bar. + ![](https://github.com/informatics-isi-edu/chaise/raw/master/docs/resources/viewer-annotation/Text.png) + +2. Upon clicking the text option, you will see the font size selector beside the option. To adjust the font size, you can, + - Click on "+" or "-" buttons. + - Or, click on the displayed number to see the list of most common font sizes and pick one of those. + - Or, Type a new number. + +3. Click anywhere on the image that you would like to add the text. + +4. Click inside the displayed textbox and write your text. + +5. The text will wrap into the next line after writing a long text. If you want your text to be displayed on the same line, You can use the knob on the right side of the textbox to resize the box. + +6. If you want to move the textbox, press and hold the left mouse button on the textbox and start moving your mouse. Releasing your left mouse button will stop this move. + +7. To add subsequent texts, repeat step 3 & 4. diff --git a/package-lock.json b/package-lock.json index 25e3e0f31..bb435aa26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@isrd-isi-edu/chaise", - "version": "0.0.14", + "version": "0.0.17", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@isrd-isi-edu/chaise", - "version": "0.0.14", + "version": "0.0.17", "license": "Apache-2.0", "dependencies": { "@babel/core": "7.15.x", @@ -7620,9 +7620,15 @@ "peer": true }, "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -8647,9 +8653,9 @@ "peer": true }, "node_modules/postcss": { - "version": "8.4.13", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.13.tgz", - "integrity": "sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", @@ -8658,10 +8664,14 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.3", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -17420,9 +17430,9 @@ "peer": true }, "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" }, "native-promise-only": { "version": "0.8.1", @@ -18207,11 +18217,11 @@ "peer": true }, "postcss": { - "version": "8.4.13", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.13.tgz", - "integrity": "sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "requires": { - "nanoid": "^3.3.3", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } diff --git a/package.json b/package.json index f16c4e2f6..0063bf0f0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@isrd-isi-edu/chaise", "description": "An adaptive User Interface for ERMrest data sources", - "version": "0.0.14", + "version": "0.0.17", "license": "Apache-2.0", "engines": { "node": ">=14.0.0", diff --git a/src/assets/scss/_button-group.scss b/src/assets/scss/_button-group.scss index 00d5c72dc..b4b286b17 100644 --- a/src/assets/scss/_button-group.scss +++ b/src/assets/scss/_button-group.scss @@ -9,6 +9,7 @@ position: relative; display: inline-flex; vertical-align: middle; // match .chaise-btn alignment given font-size hack above + align-items: baseline; // to align it to the same line > .chaise-btn { position: relative; diff --git a/src/assets/scss/_buttons.scss b/src/assets/scss/_buttons.scss index d44aa6a8c..84fa86960 100644 --- a/src/assets/scss/_buttons.scss +++ b/src/assets/scss/_buttons.scss @@ -118,6 +118,13 @@ @include helpers.chaise-btn-primary(); } + +// export menu dropdown +.export-menu.dropdown { + // make sure export button stays in the same line as other buttons + display: inline-block; +} + // export dropdown item .dropdown-item.export-menu-item, .dropdown-item.saved-query-menu-item { diff --git a/src/assets/scss/_chaise-icon.scss b/src/assets/scss/_chaise-icon.scss index 64cf8401f..792012791 100644 --- a/src/assets/scss/_chaise-icon.scss +++ b/src/assets/scss/_chaise-icon.scss @@ -30,9 +30,8 @@ } .chaise-icon.chaise-RID { - font-size: 16px; - padding-left: 3px; - padding-right: 3px; + padding-left: 5px; + padding-right: 5px; } .chaise-icon.chaise-sidebar-open::before { diff --git a/src/assets/scss/_record.scss b/src/assets/scss/_record.scss index 34c474dfc..07d56f299 100644 --- a/src/assets/scss/_record.scss +++ b/src/assets/scss/_record.scss @@ -473,4 +473,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/assets/scss/_recordset-table.scss b/src/assets/scss/_recordset-table.scss index f595f2ff6..b3828ad39 100644 --- a/src/assets/scss/_recordset-table.scss +++ b/src/assets/scss/_recordset-table.scss @@ -10,6 +10,13 @@ border-bottom: 1px solid map-get(variables.$color-map, 'table-border'); margin: 0; + // override the default bootstrap styles as we're going to apply our own styles below + --bs-table-bg: transparent; + --bs-table-accent-bg: transparent; + --bs-table-striped-bg: transparent; + --bs-table-active-bg: transparent; + --bs-table-hover-bg: transparent; + tbody { display: table-row-group; vertical-align: middle; @@ -20,13 +27,11 @@ /* table hover */ > tr:hover { - // background-color: #f7f0cf !important; - --bs-table-hover-bg: map-get(variables.$color-map, 'table-highlight-background') !important; + background-color: map-get(variables.$color-map, 'table-highlight-background') !important; /* match color from row highlight for disabled cells */ > td.disabled-cell { background-color: map-get(variables.$color-map, 'table-highlight-background') !important; - // --bs-table-hover-bg: #f7f0cf !important;; } .hover-show { @@ -157,20 +162,61 @@ } } + // Changing the padding and adding line-height for the action button to align with the rest of the columns in the table .action-btns { + /** + * the whole row min height is 30px, and buttons are 22px. so the space around the buttons must be 4px. + * if we changed the button height, the checkbox-icon-margin should be adjusted as well. + */ + $_button-height: 22px; + $_space-around-buttons: 4px; + $_checkbox-icon-margin: 2px; + text-align: center; font-size: 1.2rem; - // since we're increasing the size of icons, we should decrease the padding - padding: 5px 0 0 0; + padding: $_space-around-buttons 0 0 0; + line-height: 1.5; + + // make sure items stay in the center + .action-btns-inner-container { + display: flex; + align-content: center; + justify-content: space-around; + } + + /** + * make sure there's similar space below the buttons in the narrow case (when the content is just one line + * I needed to use three different selectors for our three different modes. + * the first one is the default mode, the second is single select, and the last one is for multi-select + */ + .chaise-btn-group, .chaise-btn.select-action-button, .chaise-checkbox { + margin-bottom: $_space-around-buttons; + } - .chaise-btn { + // make the button smaller (only visible for primary buttons. for others won't make a difference in UI and that's fine) + .chaise-checkbox, .chaise-btn { padding: 0; - margin: -3px; - height: auto; //override the default button height - min-height: 25px; // make sure the button has default height + height: $_button-height; + min-width: $_button-height; + width: $_button-height; + } + + // make sure the checkbox has the same size as others for consistent spacing. + .chaise-checkbox input { + width: $_button-height; + height: $_button-height; + & + label:before { + width: $_button-height; + height: $_button-height; + } + &:checked + label:after { + top: $_checkbox-icon-margin; + left: $_checkbox-icon-margin; + } } .chaise-view-details { + // make the view icon bigger so it looks similar size to the rest of icons font-size: 1.1em; } diff --git a/src/assets/scss/_recordset.scss b/src/assets/scss/_recordset.scss index 0f400ac2e..5f34f5231 100644 --- a/src/assets/scss/_recordset.scss +++ b/src/assets/scss/_recordset.scss @@ -50,6 +50,15 @@ margin-right: 5px; margin-bottom: 5px; + /** + * override the default behavior of button and button group + * without these the buttons would become misaligned and ellipsis logic won't work + */ + align-items: unset; + .chaise-btn { + display: inline-block; + } + .chaise-btn:focus { background-color: map-get(variables.$color-map, 'white'); } diff --git a/src/assets/scss/app.scss b/src/assets/scss/app.scss index 986bdff91..ccf915e95 100644 --- a/src/assets/scss/app.scss +++ b/src/assets/scss/app.scss @@ -475,9 +475,7 @@ html { float: right; text-align: right; - // make sure the buttons show in the same line & > * { - display: inline-block; margin-left: 5px; } @@ -676,7 +674,6 @@ html { padding-right: 5px; font-weight: 400; position: relative; - vertical-align: middle; min-height: variables.$chaise-checkbox-height; margin-bottom: 0; } @@ -685,32 +682,38 @@ html { } input { + // turn off the default browser appearance as we're showing the checkmark with :before and :after + appearance: none; + position: absolute; z-index: map-get(variables.$z-index-map, 'checkbox-input'); cursor: pointer; width: variables.$chaise-checkbox-width; height: variables.$chaise-checkbox-height; top: 0; - margin-top: 2px; &:disabled { cursor: not-allowed; - &:before, - &:checked:after { + + // we have to attach the checkmark to the label otherwise firefox won't show it. + & + label:before, + &:checked + label:after { color: map-get(variables.$color-map, 'disabled'); border-color: map-get(variables.$color-map, 'disabled'); } } - &:before, - &:checked:after { + // we have to attach the checkmark to the label otherwise firefox won't show it. + & + label:before, + &:checked + label:after { color: map-get(variables.$color-map, 'primary'); -webkit-transition: border 0.15s ease-in-out, color 0.15s ease-in-out; -o-transition: border 0.15s ease-in-out, color 0.15s ease-in-out; transition: border 0.15s ease-in-out, color 0.15s ease-in-out; } - &:before { + // we have to change the the checkmark to the label otherwise firefox won't show it. + & + label:before { content: ""; display: inline-block; position: absolute; @@ -723,14 +726,16 @@ html { background-color: map-get(variables.$color-map, 'white'); } - &:checked:after { + // we have to change the the checkmark to the label otherwise firefox won't show it. + &:checked + label:after { position: absolute; - top: -2px; - left: 2px; + top: 1px; + left: 1px; font-family: "Font Awesome 6 Free"; font-weight: 900; content: "\f00c"; // fa-solid fa-check - font-size: 1.3rem; + font-size: 1.3rem; // the visible part of icon is small, so we have to make it properly fit the box + line-height: 1; // eventhough we're making it bigger, but make sure the whole icon doesn't go beyond the limit } } } diff --git a/src/assets/scss/helpers.scss b/src/assets/scss/helpers.scss index caa24c873..33cc1b703 100644 --- a/src/assets/scss/helpers.scss +++ b/src/assets/scss/helpers.scss @@ -77,7 +77,6 @@ @mixin chaise-btn() { @include border-radius(variables.$btn-border-radius); cursor: pointer; - display: inline-block; height: variables.$btn-height; min-width: variables.$btn-height; border: variables.$btn-border-width solid; @@ -89,6 +88,14 @@ -moz-user-select: none; -ms-user-select: none; white-space: nowrap; + /** + * Fixing button spacing issue. Fix- center aligning the buttons. Adding line-height will make the children of button to + * use this property to override what bootstrap is defining. + */ + display: inline-flex; + align-items: center; + justify-content: center; + line-height: normal; } @mixin chaise-btn-primary() { diff --git a/src/components/modals/share-cite-modal.tsx b/src/components/modals/share-cite-modal.tsx index 4107ab7a7..aebf5d02c 100644 --- a/src/components/modals/share-cite-modal.tsx +++ b/src/components/modals/share-cite-modal.tsx @@ -14,11 +14,12 @@ import { LogActions } from '@isrd-isi-edu/chaise/src/models/log'; // services import { ConfigService } from '@isrd-isi-edu/chaise/src/services/config'; import { LogService } from '@isrd-isi-edu/chaise/src/services/log'; +import $log from '@isrd-isi-edu/chaise/src/services/logger'; // utils import { resolvePermalink } from '@isrd-isi-edu/chaise/src/utils/uri-utils'; import { getVersionDate, humanizeTimestamp } from '@isrd-isi-edu/chaise/src/utils/date-time-utils'; - +import { copyToClipboard } from '@isrd-isi-edu/chaise/src/utils/ui-utils'; export type ShareCiteModalProps = { /** @@ -75,6 +76,10 @@ const ShareCiteModal = ({ logStackPath, }: ShareCiteModalProps): JSX.Element => { + const DEFAULT_COPY_TOOLTIP = 'Copy link URL to clipboard.'; + const [versionLinkCopyTooltip, setVersionLinkCopyTooltip] = useState(DEFAULT_COPY_TOOLTIP); + const [liveLinkCopyTooltip, setLiveLinkCopyTooltip] = useState(DEFAULT_COPY_TOOLTIP); + const logCitationDownload = () => { LogService.logClientAction({ action: LogService.getActionString(LogActions.CITE_BIBTEXT_DOWNLOAD, logStackPath), @@ -82,25 +87,41 @@ const ShareCiteModal = ({ }, reference.defaultLogInfo); }; - const copyToClipboard = (text: string, action: string) => { + /** + * set the tooltip of the copy button + * @param isVersionLink whether this is for the version link or live link + * @param str the tooltip + */ + const setLinkCopyTooltip = (isVersionLink: boolean, str: string) => { + if (isVersionLink) { + setVersionLinkCopyTooltip(str) + } else { + setLiveLinkCopyTooltip(str); + } + } + + /** + * the callback for clicking on the copy link button + * @param isVersionLink whether this is for the version link or live link + */ + const onCopyToClipboard = (isVersionLink: boolean) => { + const action = isVersionLink ? LogActions.SHARE_VERSIONED_LINK_COPY : LogActions.SHARE_LIVE_LINK_COPY; + const text = isVersionLink ? versionLink : liveLink; + LogService.logClientAction({ action: LogService.getActionString(action, logStackPath), stack: logStack ? logStack : LogService.getStackObject() }, reference.defaultLogInfo); - // Create a dummy input to put the text string into it, select it, then copy it - // this has to be done because of HTML security and not letting scripts just copy stuff to the clipboard - // it has to be a user initiated action that is done through the DOM object - const dummy = document.createElement('input'); - dummy.setAttribute('visibility', 'hidden'); - dummy.setAttribute('display', 'none'); - document.body.appendChild(dummy); - // dummy.setAttribute('id', 'copy_id'); - // document.getElementById('copy_id')!.value = text; - dummy.value = text; - dummy.select(); - document.execCommand('copy'); - document.body.removeChild(dummy); + copyToClipboard(text).then(() => { + setLinkCopyTooltip(isVersionLink, 'Copied!'); + setTimeout(() => { + setLinkCopyTooltip(isVersionLink, DEFAULT_COPY_TOOLTIP); + }, 1000); + }).catch((err) => { + $log.warn('failed to copy with the following error:'); + $log.warn(err); + }) } const citationReady = !!citation && citation.isReady; @@ -183,10 +204,10 @@ const ShareCiteModal = ({ ({versionDateRelative}) - + copyToClipboard(versionLink, LogActions.SHARE_VERSIONED_LINK_COPY)} + onClick={() => onCopyToClipboard(true)} /> @@ -195,10 +216,10 @@ const ShareCiteModal = ({ }

Live Link - + copyToClipboard(liveLink, LogActions.SHARE_LIVE_LINK_COPY)} + onClick={() => onCopyToClipboard(false)} />

diff --git a/src/components/record/related-table-actions.tsx b/src/components/record/related-table-actions.tsx index 6e6fa3cf7..b73f87e81 100644 --- a/src/components/record/related-table-actions.tsx +++ b/src/components/record/related-table-actions.tsx @@ -699,6 +699,23 @@ const RelatedTableActions = ({ ); }; + + const renderBulkEditBtnTooltip = () => { + if (relatedModel.recordsetState.page?.length < 1) { + return( + + Unable to edit {currentTable} records until some are created. + + ) + } + + return( + + Edit this page of {currentTable} records related to this {mainTable}. + + ) + } + /* * This function is to render each button. We call the renderButton function with button text, * inner element classname and boolean flag to show tertiary class(for the dropdown buttons) or not @@ -715,8 +732,12 @@ const RelatedTableActions = ({ {relatedModel.isPureBinary && relatedModel.canDelete && renderButton('Unlink records', false)} {allowCustomModeRelated(relatedModel) && renderCustomModeBtn()} - - {relatedModel.canEdit && renderButton('Bulk Edit', false)} + {/* + * if user can edit, also check for create permission + * - if they can't create, allow edit if there are some rows set + * - disable button if can create but no rows + */} + {relatedModel.canEdit && (relatedModel.canCreate || relatedModel.recordsetState.page?.length > 0) && renderButton('Bulk Edit', false)} {renderButton('Explore', false)} @@ -754,21 +775,20 @@ const RelatedTableActions = ({
); case 'Bulk Edit': + const disableBulkEdit = relatedModel.recordsetState.page?.length < 1; return ( - Edit this page of {currentTable} records related to this {mainTable}. - - } + tooltip={renderBulkEditBtnTooltip()} > Bulk Edit diff --git a/src/components/recordset/recordset.tsx b/src/components/recordset/recordset.tsx index 294d98599..fc6778ad5 100644 --- a/src/components/recordset/recordset.tsx +++ b/src/components/recordset/recordset.tsx @@ -18,7 +18,7 @@ import Title from '@isrd-isi-edu/chaise/src/components/title'; import TableHeader from '@isrd-isi-edu/chaise/src/components/recordset/table-header'; // hooks -import { useEffect, useRef, useState } from 'react'; +import React, { AnchorHTMLAttributes, useEffect, useRef, useState } from 'react'; import useError from '@isrd-isi-edu/chaise/src/hooks/error'; import useRecordset from '@isrd-isi-edu/chaise/src/hooks/recordset'; @@ -162,6 +162,8 @@ const RecordsetInner = ({ const [savedQueryUpdated, setSavedQueryUpdated] = useState(false); + const [permalinkTooltip, setPermalinkTooltip] = useState(MESSAGE_MAP.tooltip.permalink); + const mainContainer = useRef(null); const topRightContainer = useRef(null); const topLeftContainer = useRef(null); @@ -529,11 +531,26 @@ const RecordsetInner = ({ const recordsetLink = getRecordsetLink(); - const copyPermalink = () => { + /** + * the callback for when permalink button is clicked + */ + const copyPermalink = (e: React.MouseEvent) => { + // avoid the navigation + e.preventDefault(); + // log the action logRecordsetClientAction(LogActions.PERMALINK_LEFT); - copyToClipboard(recordsetLink); + // copy to the clipboard + copyToClipboard(recordsetLink).then(() => { + setPermalinkTooltip('Copied!'); + setTimeout(() => { + setPermalinkTooltip(MESSAGE_MAP.tooltip.permalink); + }, 1000); + }).catch((err) => { + $log.warn('failed to copy with the following error:') + $log.warn(err); + }) } /** @@ -823,7 +840,7 @@ const RecordsetInner = ({ reference={reference} disabled={isLoading || !page || page.length === 0} /> - +