Browser extension that demonstrates the Web Annotation Discovery mechanism: subscribe to people’s annotation collections/‘feeds’, to see their notes on the web; and create & publish annotations yourself.

web-annotation-discovery-we.../src/popup/ AnnotationStoreSelector.tsx
146 lines
4.4 KiB

  1. import { Component, h, Fragment, createRef } from 'preact';
  2. import cls from 'classnames';
  3. import { AnnotationSource } from '../storage/AnnotationSource';
  4. import classes from './AnnotationStoreSelector.module.scss';
  5. interface AnnotationStoreSelectorProps {}
  6. interface AnnotationStoreSelectorState {
  7. possiblyWritableSources?: AnnotationSource[];
  8. selectedSource?: AnnotationSource | null;
  9. testSuccess?: boolean;
  10. }
  11. export class AnnotationStoreSelector extends Component<
  12. AnnotationStoreSelectorProps,
  13. AnnotationStoreSelectorState
  14. > {
  15. state: AnnotationStoreSelectorState = {};
  16. selectElement = createRef<HTMLSelectElement>();
  17. testButton = createRef<HTMLButtonElement>();
  18. override async componentDidMount() {
  19. await this.loadData();
  20. }
  21. async loadData() {
  22. const possiblyWritableSources =
  23. await AnnotationSource.getPossiblyWritableSources();
  24. const selectedSource = possiblyWritableSources.find(
  25. (source) => source.data.useForNewAnnotations,
  26. );
  27. this.setState({
  28. possiblyWritableSources: possiblyWritableSources,
  29. selectedSource,
  30. });
  31. }
  32. async onChange() {
  33. const optionValue = this.selectElement.current?.value;
  34. const selectedSourceId = optionValue ? +optionValue : null;
  35. const selectedSource = selectedSourceId
  36. ? await AnnotationSource.get(selectedSourceId)
  37. : null;
  38. this.setState({ selectedSource, testSuccess: false });
  39. }
  40. async testCreateAnnotation() {
  41. this.testButton.current?.classList.add(classes.loading);
  42. const { selectedSource } = this.state;
  43. try {
  44. if (!selectedSource) throw new Error('No source selected to test.');
  45. await selectedSource.testWritable();
  46. this.setState({ testSuccess: true });
  47. // Flag the selected source, unflag the others.
  48. const allSources = await AnnotationSource.getAll();
  49. await Promise.all(
  50. allSources.map((source) =>
  51. source.useForNewAnnotations(
  52. source.data._id === selectedSource.data._id,
  53. ),
  54. ),
  55. );
  56. await this.loadData();
  57. } catch (error) {
  58. this.setState({ testSuccess: false });
  59. alert(error);
  60. } finally {
  61. this.testButton.current?.classList.remove(classes.loading);
  62. }
  63. }
  64. render(
  65. {}: AnnotationStoreSelectorProps,
  66. {
  67. possiblyWritableSources,
  68. selectedSource,
  69. testSuccess,
  70. }: AnnotationStoreSelectorState,
  71. ) {
  72. // List sources, sorting previously connected ones first.
  73. const optionsList = possiblyWritableSources
  74. ?.sort((a, b) =>
  75. a.data.writable === b.data.writable ? 0 : b.data.writable ? 1 : -1,
  76. )
  77. .map((source) => (
  78. <option
  79. value={source.data._id}
  80. selected={source.data._id === selectedSource?.data._id}
  81. >
  82. {source.data.writable ? '✓' : '?'} {source.data.title} &lt;
  83. {source.data.url}&gt;
  84. </option>
  85. ));
  86. if (optionsList?.length === 0) {
  87. return (
  88. <p>
  89. To create annotations, first subscribe to a collection (that you have
  90. write access to).
  91. </p>
  92. );
  93. }
  94. return (
  95. <>
  96. <p>Choose the collection to store/publish annotations you create:</p>
  97. <select
  98. ref={this.selectElement}
  99. value={`${selectedSource?.data._id ?? ''}`}
  100. onChange={(e) => this.onChange()}
  101. class={classes.select}
  102. >
  103. <option value="">None</option>
  104. {optionsList}
  105. </select>
  106. {selectedSource && (
  107. <>
  108. <button
  109. onClick={() => this.testCreateAnnotation()}
  110. title="Click to test if you can create an annotation in the chosen collection. Your browser may prompt you to provide login credentials."
  111. ref={this.testButton}
  112. class={cls(classes.button, { [classes.success]: testSuccess })}
  113. >
  114. {testSuccess
  115. ? 'Connected'
  116. : selectedSource.data.writable
  117. ? selectedSource.data.needsAuth
  118. ? 'Reconnect'
  119. : 'Check'
  120. : 'Connect'}
  121. </button>
  122. {selectedSource.data.writable && selectedSource.data.needsAuth && (
  123. <p>
  124. Your browser appears to have lost write access to the
  125. collection. Please click reconnect to try log in again.
  126. </p>
  127. )}
  128. </>
  129. )}
  130. </>
  131. );
  132. }
  133. }