status.tsx 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { VDomModel, VDomRenderer } from '@jupyterlab/apputils';
  4. import {
  5. ILogger,
  6. ILoggerRegistry,
  7. IContentChange
  8. } from '@jupyterlab/logconsole';
  9. import { GroupItem, TextItem, interactiveItem } from '@jupyterlab/statusbar';
  10. import { listIcon } from '@jupyterlab/ui-components';
  11. import { Signal } from '@lumino/signaling';
  12. import React from 'react';
  13. /**
  14. * A pure functional component for a Log Console status item.
  15. *
  16. * @param props - the props for the component.
  17. *
  18. * @returns a tsx component for rendering the Log Console status.
  19. */
  20. function LogConsoleStatusComponent(
  21. props: LogConsoleStatusComponent.IProps
  22. ): React.ReactElement<LogConsoleStatusComponent.IProps> {
  23. let title = '';
  24. if (props.newMessages > 0) {
  25. title = `${props.newMessages} new messages, `;
  26. }
  27. title += `${props.logEntries} log entries for ${props.source}`;
  28. return (
  29. <GroupItem spacing={0} onClick={props.handleClick} title={title}>
  30. <listIcon.react top={'2px'} kind={'statusBar'} />
  31. {props.newMessages > 0 ? <TextItem source={props.newMessages} /> : <></>}
  32. </GroupItem>
  33. );
  34. }
  35. /*
  36. * A namespace for LogConsoleStatusComponent.
  37. */
  38. namespace LogConsoleStatusComponent {
  39. /**
  40. * The props for the LogConsoleStatusComponent.
  41. */
  42. export interface IProps {
  43. /**
  44. * A click handler for the item. By default
  45. * Log Console panel is launched.
  46. */
  47. handleClick: () => void;
  48. /**
  49. * Number of log entries.
  50. */
  51. logEntries: number;
  52. /**
  53. * Number of new log messages.
  54. */
  55. newMessages: number;
  56. /**
  57. * Log source name
  58. */
  59. source: string | null;
  60. }
  61. }
  62. /**
  63. * A VDomRenderer widget for displaying the status of Log Console logs.
  64. */
  65. export class LogConsoleStatus extends VDomRenderer<LogConsoleStatus.Model> {
  66. /**
  67. * Construct the log console status widget.
  68. *
  69. * @param options - The status widget initialization options.
  70. */
  71. constructor(options: LogConsoleStatus.IOptions) {
  72. super(new LogConsoleStatus.Model(options.loggerRegistry));
  73. this._handleClick = options.handleClick;
  74. this.addClass(interactiveItem);
  75. this.addClass('jp-LogConsoleStatusItem');
  76. }
  77. /**
  78. * Render the log console status item.
  79. */
  80. render() {
  81. if (this.model === null || this.model.version === 0) {
  82. this.hide();
  83. return null;
  84. }
  85. this.show();
  86. let {
  87. flashEnabled,
  88. messages,
  89. source,
  90. version,
  91. versionDisplayed,
  92. versionNotified
  93. } = this.model;
  94. if (source !== null && flashEnabled && version > versionNotified) {
  95. this._flashHighlight();
  96. this.model.sourceNotified(source, version);
  97. } else if (source !== null && flashEnabled && version > versionDisplayed) {
  98. this._showHighlighted();
  99. } else {
  100. this._clearHighlight();
  101. }
  102. return (
  103. <LogConsoleStatusComponent
  104. handleClick={this._handleClick}
  105. logEntries={messages}
  106. newMessages={version - versionDisplayed}
  107. source={this.model.source}
  108. />
  109. );
  110. }
  111. private _flashHighlight() {
  112. this._showHighlighted();
  113. // To make sure the browser triggers the animation, we remove the class,
  114. // wait for an animation frame, then add it back
  115. this.removeClass('jp-LogConsole-flash');
  116. requestAnimationFrame(() => {
  117. this.addClass('jp-LogConsole-flash');
  118. });
  119. }
  120. private _showHighlighted() {
  121. this.addClass('jp-mod-selected');
  122. }
  123. private _clearHighlight() {
  124. this.removeClass('jp-LogConsole-flash');
  125. this.removeClass('jp-mod-selected');
  126. }
  127. private _handleClick: () => void;
  128. }
  129. /**
  130. * A namespace for Log Console log status.
  131. */
  132. export namespace LogConsoleStatus {
  133. /**
  134. * A VDomModel for the LogConsoleStatus item.
  135. */
  136. export class Model extends VDomModel {
  137. /**
  138. * Create a new LogConsoleStatus model.
  139. *
  140. * @param loggerRegistry - The logger registry providing the logs.
  141. */
  142. constructor(loggerRegistry: ILoggerRegistry) {
  143. super();
  144. this._loggerRegistry = loggerRegistry;
  145. this._loggerRegistry.registryChanged.connect(
  146. this._handleLogRegistryChange,
  147. this
  148. );
  149. this._handleLogRegistryChange();
  150. }
  151. /**
  152. * Number of messages currently in the current source.
  153. */
  154. get messages(): number {
  155. if (this._source === null) {
  156. return 0;
  157. }
  158. const logger = this._loggerRegistry.getLogger(this._source);
  159. return logger.length;
  160. }
  161. /**
  162. * The number of messages ever stored by the current source.
  163. */
  164. get version(): number {
  165. if (this._source === null) {
  166. return 0;
  167. }
  168. const logger = this._loggerRegistry.getLogger(this._source);
  169. return logger.version;
  170. }
  171. /**
  172. * The name of the active log source
  173. */
  174. get source(): string | null {
  175. return this._source;
  176. }
  177. set source(name: string | null) {
  178. if (this._source === name) {
  179. return;
  180. }
  181. this._source = name;
  182. // refresh rendering
  183. this.stateChanged.emit();
  184. }
  185. /**
  186. * The last source version that was displayed.
  187. */
  188. get versionDisplayed(): number {
  189. if (this._source === null) {
  190. return 0;
  191. }
  192. return this._sourceVersion.get(this._source)?.lastDisplayed ?? 0;
  193. }
  194. /**
  195. * The last source version we notified the user about.
  196. */
  197. get versionNotified(): number {
  198. if (this._source === null) {
  199. return 0;
  200. }
  201. return this._sourceVersion.get(this._source)?.lastNotified ?? 0;
  202. }
  203. /**
  204. * Flag to toggle flashing when new logs added.
  205. */
  206. get flashEnabled(): boolean {
  207. return this._flashEnabled;
  208. }
  209. set flashEnabled(enabled: boolean) {
  210. if (this._flashEnabled === enabled) {
  211. return;
  212. }
  213. this._flashEnabled = enabled;
  214. this.flashEnabledChanged.emit();
  215. // refresh rendering
  216. this.stateChanged.emit();
  217. }
  218. /**
  219. * Record the last source version displayed to the user.
  220. *
  221. * @param source - The name of the log source.
  222. * @param version - The version of the log that was displayed.
  223. *
  224. * #### Notes
  225. * This will also update the last notified version so that the last
  226. * notified version is always at least the last displayed version.
  227. */
  228. sourceDisplayed(source: string | null, version: number | null) {
  229. if (source === null || version === null) {
  230. return;
  231. }
  232. const versions = this._sourceVersion.get(source)!;
  233. let change = false;
  234. if (versions.lastDisplayed < version) {
  235. versions.lastDisplayed = version;
  236. change = true;
  237. }
  238. if (versions.lastNotified < version) {
  239. versions.lastNotified = version;
  240. change = true;
  241. }
  242. if (change && source === this._source) {
  243. this.stateChanged.emit();
  244. }
  245. }
  246. /**
  247. * Record a source version we notified the user about.
  248. *
  249. * @param source - The name of the log source.
  250. * @param version - The version of the log.
  251. */
  252. sourceNotified(source: string | null, version: number) {
  253. if (source === null) {
  254. return;
  255. }
  256. const versions = this._sourceVersion.get(source);
  257. if (versions!.lastNotified < version) {
  258. versions!.lastNotified = version;
  259. if (source === this._source) {
  260. this.stateChanged.emit();
  261. }
  262. }
  263. }
  264. private _handleLogRegistryChange() {
  265. const loggers = this._loggerRegistry.getLoggers();
  266. for (let logger of loggers) {
  267. if (!this._sourceVersion.has(logger.source)) {
  268. logger.contentChanged.connect(this._handleLogContentChange, this);
  269. this._sourceVersion.set(logger.source, {
  270. lastDisplayed: 0,
  271. lastNotified: 0
  272. });
  273. }
  274. }
  275. }
  276. private _handleLogContentChange(
  277. { source }: ILogger,
  278. change: IContentChange
  279. ) {
  280. if (source === this._source) {
  281. this.stateChanged.emit();
  282. }
  283. }
  284. /**
  285. * A signal emitted when the flash enablement changes.
  286. */
  287. public flashEnabledChanged = new Signal<this, void>(this);
  288. private _flashEnabled: boolean = true;
  289. private _loggerRegistry: ILoggerRegistry;
  290. private _source: string | null = null;
  291. /**
  292. * The view status of each source.
  293. *
  294. * #### Notes
  295. * Keys are source names, value is a list of two numbers. The first
  296. * represents the version of the messages that was last displayed to the
  297. * user, the second represents the version that we last notified the user
  298. * about.
  299. */
  300. private _sourceVersion: Map<string, IVersionInfo> = new Map();
  301. }
  302. interface IVersionInfo {
  303. lastDisplayed: number;
  304. lastNotified: number;
  305. }
  306. /**
  307. * Options for creating a new LogConsoleStatus item
  308. */
  309. export interface IOptions {
  310. /**
  311. * The logger registry providing the logs.
  312. */
  313. loggerRegistry: ILoggerRegistry;
  314. /**
  315. * A click handler for the item. By default
  316. * Log Console panel is launched.
  317. */
  318. handleClick: () => void;
  319. }
  320. }