playlist creation, viewing, integration with other views. still wip for full playlist functionality
This commit is contained in:
@@ -18,6 +18,7 @@ import (
|
|||||||
"yellowjacket/backend/frontendutil"
|
"yellowjacket/backend/frontendutil"
|
||||||
"yellowjacket/backend/library"
|
"yellowjacket/backend/library"
|
||||||
"yellowjacket/backend/player"
|
"yellowjacket/backend/player"
|
||||||
|
"yellowjacket/backend/playlist"
|
||||||
"yellowjacket/backend/queue"
|
"yellowjacket/backend/queue"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ type YellowJacketApp struct {
|
|||||||
database *database.DB
|
database *database.DB
|
||||||
library *library.Library
|
library *library.Library
|
||||||
player *player.Player
|
player *player.Player
|
||||||
|
playlist *playlist.Service
|
||||||
queue *queue.Queue
|
queue *queue.Queue
|
||||||
appContext context.Context
|
appContext context.Context
|
||||||
appConfig *config.Config
|
appConfig *config.Config
|
||||||
@@ -93,9 +95,13 @@ func NewYellowJacketApp(
|
|||||||
|
|
||||||
yjApp.assetHandler.RegisterHandler("/covers/", coverHandler)
|
yjApp.assetHandler.RegisterHandler("/covers/", coverHandler)
|
||||||
|
|
||||||
|
// create playlist service
|
||||||
|
yjApp.playlist = playlist.NewService(yjApp.logger, yjApp.database)
|
||||||
|
|
||||||
yjApp.FEBindings = []any{
|
yjApp.FEBindings = []any{
|
||||||
yjApp.FrontendUtil,
|
yjApp.FrontendUtil,
|
||||||
yjApp.library,
|
yjApp.library,
|
||||||
|
yjApp.playlist,
|
||||||
}
|
}
|
||||||
|
|
||||||
return yjApp, nil
|
return yjApp, nil
|
||||||
@@ -113,6 +119,7 @@ func (yj *YellowJacketApp) OnStartup(ctx context.Context) {
|
|||||||
yj.appConfig.SetContext(ctx)
|
yj.appConfig.SetContext(ctx)
|
||||||
yj.FrontendUtil.SetContext(ctx)
|
yj.FrontendUtil.SetContext(ctx)
|
||||||
yj.library.SetContext(ctx)
|
yj.library.SetContext(ctx)
|
||||||
|
yj.playlist.SetContext(ctx)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
// create player
|
// create player
|
||||||
|
|||||||
@@ -30,3 +30,7 @@ DELETE FROM playlist_tracks WHERE id = ?;
|
|||||||
|
|
||||||
-- name: ClearPlaylistTracks :exec
|
-- name: ClearPlaylistTracks :exec
|
||||||
DELETE FROM playlist_tracks WHERE playlist_id = ?;
|
DELETE FROM playlist_tracks WHERE playlist_id = ?;
|
||||||
|
|
||||||
|
-- name: GetNextPlaylistTrackPosition :one
|
||||||
|
SELECT COALESCE(MAX(position), -1) + 1 AS next_position
|
||||||
|
FROM playlist_tracks WHERE playlist_id = ?;
|
||||||
|
|||||||
@@ -99,6 +99,18 @@ func (q *Queries) GetAllPlaylists(ctx context.Context) ([]Playlist, error) {
|
|||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getNextPlaylistTrackPosition = `-- name: GetNextPlaylistTrackPosition :one
|
||||||
|
SELECT COALESCE(MAX(position), -1) + 1 AS next_position
|
||||||
|
FROM playlist_tracks WHERE playlist_id = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetNextPlaylistTrackPosition(ctx context.Context, playlistID int64) (int64, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getNextPlaylistTrackPosition, playlistID)
|
||||||
|
var next_position int64
|
||||||
|
err := row.Scan(&next_position)
|
||||||
|
return next_position, err
|
||||||
|
}
|
||||||
|
|
||||||
const getPlaylist = `-- name: GetPlaylist :one
|
const getPlaylist = `-- name: GetPlaylist :one
|
||||||
SELECT id, name, created_at, updated_at FROM playlists WHERE id = ? LIMIT 1
|
SELECT id, name, created_at, updated_at FROM playlists WHERE id = ? LIMIT 1
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
// Package playlist provides playlist management functionality.
|
||||||
|
package playlist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"yellowjacket/backend/database"
|
||||||
|
"yellowjacket/backend/database/sql/sqlcgen"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errEmptyName = errors.New("playlist name cannot be empty")
|
||||||
|
errEmptyFilePath = errors.New("file path cannot be empty")
|
||||||
|
errNoFilePaths = errors.New("no file paths provided")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Summary is a lightweight representation of a playlist for the picker UI.
|
||||||
|
type Summary struct {
|
||||||
|
ID int64 `json:"ID"`
|
||||||
|
Name string `json:"Name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service manages playlist operations.
|
||||||
|
type Service struct {
|
||||||
|
ctx context.Context
|
||||||
|
logger *slog.Logger
|
||||||
|
db *database.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new playlist service.
|
||||||
|
func NewService(
|
||||||
|
logger *slog.Logger,
|
||||||
|
db *database.DB,
|
||||||
|
) *Service {
|
||||||
|
return &Service{
|
||||||
|
logger: logger.WithGroup("playlist"),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContext sets the Wails runtime context.
|
||||||
|
func (s *Service) SetContext(ctx context.Context) {
|
||||||
|
s.ctx = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllPlaylists returns all playlists ordered by most recently updated.
|
||||||
|
func (s *Service) GetAllPlaylists() ([]Summary, error) {
|
||||||
|
playlists, err := s.db.Queries.GetAllPlaylists(s.db.Ctx)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get playlists", "err", err)
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("failed to get playlists: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
summaries := make([]Summary, 0, len(playlists))
|
||||||
|
for _, p := range playlists {
|
||||||
|
summaries = append(summaries, Summary{
|
||||||
|
ID: p.ID,
|
||||||
|
Name: p.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return summaries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePlaylist creates a new empty playlist with the given name.
|
||||||
|
func (s *Service) CreatePlaylist(name string) (Summary, error) {
|
||||||
|
trimmed := strings.TrimSpace(name)
|
||||||
|
if trimmed == "" {
|
||||||
|
return Summary{}, errEmptyName
|
||||||
|
}
|
||||||
|
|
||||||
|
created, err := s.db.Queries.CreatePlaylist(s.db.Ctx, trimmed)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to create playlist", "name", trimmed, "err", err)
|
||||||
|
|
||||||
|
return Summary{}, fmt.Errorf("failed to create playlist: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("Playlist created", "id", created.ID, "name", created.Name)
|
||||||
|
|
||||||
|
return Summary{ID: created.ID, Name: created.Name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTracksToPlaylist adds one or more tracks to an existing playlist.
|
||||||
|
func (s *Service) AddTracksToPlaylist(
|
||||||
|
playlistID int64,
|
||||||
|
filePaths []string,
|
||||||
|
) error {
|
||||||
|
if len(filePaths) == 0 {
|
||||||
|
return errNoFilePaths
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPos, err := s.db.Queries.GetNextPlaylistTrackPosition(
|
||||||
|
s.db.Ctx,
|
||||||
|
playlistID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error(
|
||||||
|
"Failed to get next position",
|
||||||
|
"playlistId", playlistID,
|
||||||
|
"err", err,
|
||||||
|
)
|
||||||
|
|
||||||
|
return fmt.Errorf("failed to get next track position: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, fp := range filePaths {
|
||||||
|
if err := s.addSingleTrack(playlistID, fp, nextPos+int64(i)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info(
|
||||||
|
"Tracks added to playlist",
|
||||||
|
"playlistId", playlistID,
|
||||||
|
"count", len(filePaths),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePlaylistWithTracks creates a new playlist and populates it with tracks.
|
||||||
|
func (s *Service) CreatePlaylistWithTracks(
|
||||||
|
name string,
|
||||||
|
filePaths []string,
|
||||||
|
) (Summary, error) {
|
||||||
|
summary, err := s.CreatePlaylist(name)
|
||||||
|
if err != nil {
|
||||||
|
return Summary{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filePaths) > 0 {
|
||||||
|
if err := s.AddTracksToPlaylist(summary.ID, filePaths); err != nil {
|
||||||
|
return Summary{}, fmt.Errorf(
|
||||||
|
"playlist created but failed to add tracks: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return summary, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addSingleTrack looks up the audio file by path and inserts it into the playlist.
|
||||||
|
func (s *Service) addSingleTrack(
|
||||||
|
playlistID int64,
|
||||||
|
filePath string,
|
||||||
|
position int64,
|
||||||
|
) error {
|
||||||
|
if strings.TrimSpace(filePath) == "" {
|
||||||
|
return errEmptyFilePath
|
||||||
|
}
|
||||||
|
|
||||||
|
audioFile, err := s.db.Queries.GetAudioFileByPath(s.db.Ctx, filePath)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error(
|
||||||
|
"Failed to find audio file",
|
||||||
|
"filePath", filePath,
|
||||||
|
"err", err,
|
||||||
|
)
|
||||||
|
|
||||||
|
return fmt.Errorf("failed to find audio file %q: %w", filePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.db.Queries.AddPlaylistTrack(
|
||||||
|
s.db.Ctx,
|
||||||
|
sqlcgen.AddPlaylistTrackParams{
|
||||||
|
PlaylistID: playlistID,
|
||||||
|
AudioFileID: audioFile.ID,
|
||||||
|
Position: position,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error(
|
||||||
|
"Failed to add track to playlist",
|
||||||
|
"playlistId", playlistID,
|
||||||
|
"audioFileId", audioFile.ID,
|
||||||
|
"err", err,
|
||||||
|
)
|
||||||
|
|
||||||
|
return fmt.Errorf("failed to add track to playlist: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+4
-4
@@ -4,6 +4,7 @@ import '@components/cover-grid/cover-grid.ts';
|
|||||||
import '@components/now-playing/now-playing.ts';
|
import '@components/now-playing/now-playing.ts';
|
||||||
import '@components/sidebar/app-sidebar.ts';
|
import '@components/sidebar/app-sidebar.ts';
|
||||||
import '@components/queue-panel/queue-panel.ts';
|
import '@components/queue-panel/queue-panel.ts';
|
||||||
|
import '@components/playlist-view/playlist-view.ts';
|
||||||
import '@awesome.me/webawesome/dist/styles/themes/default.css';
|
import '@awesome.me/webawesome/dist/styles/themes/default.css';
|
||||||
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||||
import { setBasePath } from '@awesome.me/webawesome/dist/webawesome.js';
|
import { setBasePath } from '@awesome.me/webawesome/dist/webawesome.js';
|
||||||
@@ -24,6 +25,9 @@ document.addEventListener('navigate', (e: Event) => {
|
|||||||
case 'tracks':
|
case 'tracks':
|
||||||
mainContent.innerHTML = '<track-list></track-list>';
|
mainContent.innerHTML = '<track-list></track-list>';
|
||||||
break;
|
break;
|
||||||
|
case 'playlists':
|
||||||
|
mainContent.innerHTML = '<playlist-view></playlist-view>';
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
mainContent.innerHTML = `<div style="padding: 1em; color: #b3b3b3;">
|
mainContent.innerHTML = `<div style="padding: 1em; color: #b3b3b3;">
|
||||||
<p>Coming soon: ${view}</p>
|
<p>Coming soon: ${view}</p>
|
||||||
@@ -46,8 +50,4 @@ if (queueButton && queuePanel) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close panel when the component dispatches a close event
|
|
||||||
queuePanel.addEventListener('queue-panel-close', () => {
|
|
||||||
queuePanel.removeAttribute('open');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
74e25cdcdccb20fc50b40dc29ec5f6f9
|
cf76bbfd46ad4447fbfbaa4a1c6845ca
|
||||||
@@ -9,6 +9,8 @@ import { grid } from '@lit-labs/virtualizer/layouts/grid.js';
|
|||||||
import '@awesome.me/webawesome/dist/components/popup/popup.js';
|
import '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||||
import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';
|
import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';
|
||||||
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||||
|
import '@components/playlist-picker/playlist-picker.js';
|
||||||
|
import type { PlaylistPicker } from '@components/playlist-picker/playlist-picker.js';
|
||||||
|
|
||||||
@customElement('cover-grid')
|
@customElement('cover-grid')
|
||||||
export class CoverGrid extends LitElement {
|
export class CoverGrid extends LitElement {
|
||||||
@@ -143,6 +145,20 @@ export class CoverGrid extends LitElement {
|
|||||||
.context-menu-panel wa-dropdown-item:hover {
|
.context-menu-panel wa-dropdown-item:hover {
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.submenu-item {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-arrow {
|
||||||
|
font-size: 10px;
|
||||||
|
margin-left: auto;
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#playlist-submenu {
|
||||||
|
z-index: 210;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
@@ -157,9 +173,18 @@ export class CoverGrid extends LitElement {
|
|||||||
@state()
|
@state()
|
||||||
private contextMenuAlbum: library.Album | null = null;
|
private contextMenuAlbum: library.Album | null = null;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private playlistSubmenuOpen = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private playlistFilePaths: string[] = [];
|
||||||
|
|
||||||
@query('#context-menu')
|
@query('#context-menu')
|
||||||
private contextMenuPopup!: HTMLElement;
|
private contextMenuPopup!: HTMLElement;
|
||||||
|
|
||||||
|
@query('#playlist-submenu')
|
||||||
|
private playlistSubmenuPopup!: HTMLElement;
|
||||||
|
|
||||||
override connectedCallback() {
|
override connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this.loadAlbums();
|
this.loadAlbums();
|
||||||
@@ -253,8 +278,10 @@ export class CoverGrid extends LitElement {
|
|||||||
private closeContextMenu() {
|
private closeContextMenu() {
|
||||||
if (!this.contextMenuOpen) return;
|
if (!this.contextMenuOpen) return;
|
||||||
|
|
||||||
|
this.closePlaylistSubmenu();
|
||||||
this.contextMenuOpen = false;
|
this.contextMenuOpen = false;
|
||||||
this.contextMenuAlbum = null;
|
this.contextMenuAlbum = null;
|
||||||
|
this.playlistFilePaths = [];
|
||||||
|
|
||||||
const popup = this.contextMenuPopup;
|
const popup = this.contextMenuPopup;
|
||||||
|
|
||||||
@@ -263,6 +290,52 @@ export class CoverGrid extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async showPlaylistSubmenu() {
|
||||||
|
if (this.playlistSubmenuOpen) return;
|
||||||
|
|
||||||
|
if (this.contextMenuAlbum) {
|
||||||
|
this.playlistFilePaths = await this.getAlbumFilePaths(
|
||||||
|
this.contextMenuAlbum,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.playlistSubmenuOpen = true;
|
||||||
|
|
||||||
|
await this.updateComplete;
|
||||||
|
|
||||||
|
const submenu = this.playlistSubmenuPopup;
|
||||||
|
const trigger = this.shadowRoot?.querySelector(
|
||||||
|
'.submenu-item',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (submenu && trigger) {
|
||||||
|
(submenu as any).anchor = trigger;
|
||||||
|
(submenu as any).active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const picker = this.shadowRoot?.querySelector(
|
||||||
|
'playlist-picker',
|
||||||
|
) as PlaylistPicker | null;
|
||||||
|
|
||||||
|
picker?.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private closePlaylistSubmenu() {
|
||||||
|
if (!this.playlistSubmenuOpen) return;
|
||||||
|
|
||||||
|
this.playlistSubmenuOpen = false;
|
||||||
|
|
||||||
|
const submenu = this.playlistSubmenuPopup;
|
||||||
|
|
||||||
|
if (submenu) {
|
||||||
|
(submenu as any).active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPlaylistActionComplete = () => {
|
||||||
|
this.closeContextMenu();
|
||||||
|
};
|
||||||
|
|
||||||
private renderAlbumCard = (album: library.Album): unknown => {
|
private renderAlbumCard = (album: library.Album): unknown => {
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
@@ -376,10 +449,38 @@ export class CoverGrid extends LitElement {
|
|||||||
<wa-icon slot="icon" name="forward-step"></wa-icon>
|
<wa-icon slot="icon" name="forward-step"></wa-icon>
|
||||||
Play Next
|
Play Next
|
||||||
</wa-dropdown-item>
|
</wa-dropdown-item>
|
||||||
|
<wa-dropdown-item
|
||||||
|
class="submenu-item"
|
||||||
|
@mouseenter=${() => this.showPlaylistSubmenu()}
|
||||||
|
@click=${(e: Event) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
void this.showPlaylistSubmenu();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<wa-icon slot="icon" name="plus"></wa-icon>
|
||||||
|
Add to Playlist
|
||||||
|
<span class="submenu-arrow">▶</span>
|
||||||
|
</wa-dropdown-item>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
</wa-popup>
|
</wa-popup>
|
||||||
|
|
||||||
|
<wa-popup
|
||||||
|
id="playlist-submenu"
|
||||||
|
placement="right-start"
|
||||||
|
.active=${this.playlistSubmenuOpen}
|
||||||
|
>
|
||||||
|
${this.playlistSubmenuOpen
|
||||||
|
? html`
|
||||||
|
<playlist-picker
|
||||||
|
.filePaths=${this.playlistFilePaths}
|
||||||
|
@playlist-action-complete=${this.onPlaylistActionComplete}
|
||||||
|
@click=${(e: Event) => e.stopPropagation()}
|
||||||
|
></playlist-picker>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</wa-popup>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,299 @@
|
|||||||
|
import { LitElement, html, css, nothing } from 'lit';
|
||||||
|
import { customElement, property, state } from 'lit/decorators.js';
|
||||||
|
|
||||||
|
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||||
|
import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
GetAllPlaylists,
|
||||||
|
AddTracksToPlaylist,
|
||||||
|
CreatePlaylistWithTracks,
|
||||||
|
} from '@go/playlist/Service';
|
||||||
|
import type { playlist } from '@go/models';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reusable playlist picker that displays existing playlists
|
||||||
|
* and allows creating new ones. Accepts file paths and handles
|
||||||
|
* adding tracks to the selected/created playlist.
|
||||||
|
*
|
||||||
|
* @fires playlist-action-complete - When tracks have been added successfully.
|
||||||
|
*/
|
||||||
|
@customElement('playlist-picker')
|
||||||
|
export class PlaylistPicker extends LitElement {
|
||||||
|
/** File paths to add when a playlist is selected or created. */
|
||||||
|
@property({ type: Array }) filePaths: string[] = [];
|
||||||
|
|
||||||
|
@state() private mode: 'list' | 'create' = 'list';
|
||||||
|
@state() private playlists: playlist.Summary[] = [];
|
||||||
|
@state() private newPlaylistName = '';
|
||||||
|
@state() private loading = false;
|
||||||
|
|
||||||
|
static override styles = css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-panel {
|
||||||
|
background-color: #343a40;
|
||||||
|
border: 1px solid #444;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 4px 0;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
|
||||||
|
min-width: 180px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-panel wa-dropdown-item {
|
||||||
|
cursor: pointer;
|
||||||
|
--wa-color-text-normal: #fff;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-panel wa-dropdown-item:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
height: 1px;
|
||||||
|
background: #555;
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-form {
|
||||||
|
padding: 8px 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-form input {
|
||||||
|
background: #2a2d30;
|
||||||
|
border: 1px solid #555;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
padding: 6px 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
outline: none;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-form input:focus {
|
||||||
|
border-color: #ffd43b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-form input::placeholder {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-row button {
|
||||||
|
background: #495057;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-row button:hover {
|
||||||
|
background: #5a6268;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-row button.primary {
|
||||||
|
background: #ffd43b;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-row button.primary:hover {
|
||||||
|
background: #ffe066;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-row button.primary:disabled {
|
||||||
|
background: #665a1e;
|
||||||
|
color: #888;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-message {
|
||||||
|
padding: 8px 12px;
|
||||||
|
color: #888;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
override connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this.loadPlaylists();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadPlaylists() {
|
||||||
|
try {
|
||||||
|
this.playlists = await GetAllPlaylists();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load playlists:', err);
|
||||||
|
this.playlists = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleSelectPlaylist = async (playlistId: number) => {
|
||||||
|
if (this.loading || this.filePaths.length === 0) return;
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await AddTracksToPlaylist(playlistId, this.filePaths);
|
||||||
|
this.dispatchComplete();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to add tracks to playlist:', err);
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleShowCreate = () => {
|
||||||
|
this.mode = 'create';
|
||||||
|
this.newPlaylistName = '';
|
||||||
|
|
||||||
|
void this.updateComplete.then(() => {
|
||||||
|
const input =
|
||||||
|
this.shadowRoot?.querySelector<HTMLInputElement>(
|
||||||
|
'.create-form input',
|
||||||
|
);
|
||||||
|
|
||||||
|
input?.focus();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleCancelCreate = () => {
|
||||||
|
this.mode = 'list';
|
||||||
|
this.newPlaylistName = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleCreatePlaylist = async () => {
|
||||||
|
const name = this.newPlaylistName.trim();
|
||||||
|
if (!name || this.loading) return;
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await CreatePlaylistWithTracks(name, this.filePaths);
|
||||||
|
this.dispatchComplete();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to create playlist:', err);
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleInputChange = (e: Event) => {
|
||||||
|
const input = e.target as HTMLInputElement;
|
||||||
|
this.newPlaylistName = input.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleInputKeydown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
void this.handleCreatePlaylist();
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
this.handleCancelCreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop propagation so parent context menu handlers don't interfere.
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
private dispatchComplete() {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('playlist-action-complete', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resets the picker to its initial list state. */
|
||||||
|
reset() {
|
||||||
|
this.mode = 'list';
|
||||||
|
this.newPlaylistName = '';
|
||||||
|
this.loading = false;
|
||||||
|
this.loadPlaylists();
|
||||||
|
}
|
||||||
|
|
||||||
|
override render() {
|
||||||
|
if (this.mode === 'create') {
|
||||||
|
return this.renderCreateForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.renderPlaylistList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderPlaylistList() {
|
||||||
|
return html`
|
||||||
|
<div class="picker-panel">
|
||||||
|
${this.playlists.length > 0
|
||||||
|
? html`
|
||||||
|
${this.playlists.map(
|
||||||
|
(p) => html`
|
||||||
|
<wa-dropdown-item
|
||||||
|
@click=${() =>
|
||||||
|
this.handleSelectPlaylist(p.ID)}
|
||||||
|
>
|
||||||
|
${p.Name}
|
||||||
|
</wa-dropdown-item>
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
<div class="separator"></div>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
<wa-dropdown-item @click=${this.handleShowCreate}>
|
||||||
|
<wa-icon slot="icon" name="plus"></wa-icon>
|
||||||
|
New Playlist
|
||||||
|
</wa-dropdown-item>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderCreateForm() {
|
||||||
|
const canCreate = this.newPlaylistName.trim().length > 0;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="picker-panel">
|
||||||
|
<div class="create-form">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Playlist name"
|
||||||
|
.value=${this.newPlaylistName}
|
||||||
|
@input=${this.handleInputChange}
|
||||||
|
@keydown=${this.handleInputKeydown}
|
||||||
|
@click=${(e: Event) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
<div class="button-row">
|
||||||
|
<button @click=${this.handleCancelCreate}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="primary"
|
||||||
|
?disabled=${!canCreate || this.loading}
|
||||||
|
@click=${this.handleCreatePlaylist}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'playlist-picker': PlaylistPicker;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,325 @@
|
|||||||
|
import { LitElement, html, css } from 'lit';
|
||||||
|
import { customElement, state } from 'lit/decorators.js';
|
||||||
|
|
||||||
|
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
GetAllPlaylists,
|
||||||
|
CreatePlaylist,
|
||||||
|
} from '@go/playlist/Service';
|
||||||
|
import type { playlist } from '@go/models';
|
||||||
|
|
||||||
|
@customElement('playlist-view')
|
||||||
|
export class PlaylistView extends LitElement {
|
||||||
|
@state() private playlists: playlist.Summary[] = [];
|
||||||
|
@state() private loading = true;
|
||||||
|
@state() private creating = false;
|
||||||
|
@state() private newPlaylistName = '';
|
||||||
|
|
||||||
|
static override styles = css`
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-playlist-button {
|
||||||
|
background: none;
|
||||||
|
border: 1px solid #555;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-playlist-button:hover {
|
||||||
|
border-color: #ffd43b;
|
||||||
|
color: #ffd43b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-form {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-form input {
|
||||||
|
flex: 1;
|
||||||
|
background: #2a2d30;
|
||||||
|
border: 1px solid #555;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
outline: none;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-form input:focus {
|
||||||
|
border-color: #ffd43b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-form input::placeholder {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-form button {
|
||||||
|
background: #495057;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-form button:hover {
|
||||||
|
background: #5a6268;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-form button.primary {
|
||||||
|
background: #ffd43b;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-form button.primary:hover {
|
||||||
|
background: #ffe066;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-form button.primary:disabled {
|
||||||
|
background: #665a1e;
|
||||||
|
color: #888;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlist-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlist-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
gap: 12px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlist-item:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlist-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #888;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlist-name {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #fff;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 32px;
|
||||||
|
color: #b3b3b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 48px 20px;
|
||||||
|
color: #b3b3b3;
|
||||||
|
text-align: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state wa-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state p {
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
override connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this.loadPlaylists();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadPlaylists() {
|
||||||
|
try {
|
||||||
|
this.loading = true;
|
||||||
|
const result = await GetAllPlaylists();
|
||||||
|
this.playlists = result ?? [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load playlists:', err);
|
||||||
|
this.playlists = [];
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleNewPlaylistClick = () => {
|
||||||
|
this.creating = true;
|
||||||
|
this.newPlaylistName = '';
|
||||||
|
|
||||||
|
void this.updateComplete.then(() => {
|
||||||
|
const input =
|
||||||
|
this.shadowRoot?.querySelector<HTMLInputElement>(
|
||||||
|
'.create-form input',
|
||||||
|
);
|
||||||
|
|
||||||
|
input?.focus();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleCancelCreate = () => {
|
||||||
|
this.creating = false;
|
||||||
|
this.newPlaylistName = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleCreatePlaylist = async () => {
|
||||||
|
const name = this.newPlaylistName.trim();
|
||||||
|
if (!name) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await CreatePlaylist(name);
|
||||||
|
this.creating = false;
|
||||||
|
this.newPlaylistName = '';
|
||||||
|
await this.loadPlaylists();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to create playlist:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleInputChange = (e: Event) => {
|
||||||
|
const input = e.target as HTMLInputElement;
|
||||||
|
this.newPlaylistName = input.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleInputKeydown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
void this.handleCreatePlaylist();
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
this.handleCancelCreate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
override render() {
|
||||||
|
return html`
|
||||||
|
<div class="header">
|
||||||
|
<h2>Playlists</h2>
|
||||||
|
<button
|
||||||
|
class="new-playlist-button"
|
||||||
|
@click=${this.handleNewPlaylistClick}
|
||||||
|
>
|
||||||
|
<wa-icon name="plus"></wa-icon>
|
||||||
|
New Playlist
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${this.creating ? this.renderCreateForm() : ''}
|
||||||
|
${this.loading
|
||||||
|
? html`<div class="loading">Loading playlists...</div>`
|
||||||
|
: this.renderPlaylistList()}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderCreateForm() {
|
||||||
|
const canCreate = this.newPlaylistName.trim().length > 0;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="create-form">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Playlist name"
|
||||||
|
.value=${this.newPlaylistName}
|
||||||
|
@input=${this.handleInputChange}
|
||||||
|
@keydown=${this.handleInputKeydown}
|
||||||
|
/>
|
||||||
|
<button @click=${this.handleCancelCreate}>Cancel</button>
|
||||||
|
<button
|
||||||
|
class="primary"
|
||||||
|
?disabled=${!canCreate}
|
||||||
|
@click=${this.handleCreatePlaylist}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderPlaylistList() {
|
||||||
|
if (this.playlists.length === 0) {
|
||||||
|
return html`
|
||||||
|
<div class="empty-state">
|
||||||
|
<wa-icon name="list"></wa-icon>
|
||||||
|
<p>No playlists yet</p>
|
||||||
|
<p style="font-size: 12px;">
|
||||||
|
Create a playlist to get started.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ul class="playlist-list">
|
||||||
|
${this.playlists.map(
|
||||||
|
(p) => html`
|
||||||
|
<li class="playlist-item">
|
||||||
|
<wa-icon
|
||||||
|
class="playlist-icon"
|
||||||
|
name="list"
|
||||||
|
></wa-icon>
|
||||||
|
<span class="playlist-name">${p.Name}</span>
|
||||||
|
</li>
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'playlist-view': PlaylistView;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import { LitElement, html, css, unsafeCSS } from 'lit';
|
import { LitElement, html, css, nothing, unsafeCSS } from 'lit';
|
||||||
import { customElement, property, state } from 'lit/decorators.js';
|
import { customElement, property, state, query } from 'lit/decorators.js';
|
||||||
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||||
|
import '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||||
import { QueueController } from '@store/controllers/queue-controller';
|
import { QueueController } from '@store/controllers/queue-controller';
|
||||||
|
import '@components/playlist-picker/playlist-picker.js';
|
||||||
|
import type { PlaylistPicker } from '@components/playlist-picker/playlist-picker.js';
|
||||||
|
|
||||||
const MIN_WIDTH = 200;
|
const MIN_WIDTH = 200;
|
||||||
const MAX_WIDTH = 500;
|
const MAX_WIDTH = 500;
|
||||||
@@ -17,6 +20,22 @@ export class QueuePanel extends LitElement {
|
|||||||
@state()
|
@state()
|
||||||
private isDragging = false;
|
private isDragging = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private playlistPickerOpen = false;
|
||||||
|
|
||||||
|
@query('#save-playlist-popup')
|
||||||
|
private savePlaylistPopup!: HTMLElement;
|
||||||
|
|
||||||
|
private closePickerHandler = (e: MouseEvent) => {
|
||||||
|
const path = e.composedPath();
|
||||||
|
const popup = this.savePlaylistPopup;
|
||||||
|
const btn = this.shadowRoot?.querySelector('.save-playlist-button');
|
||||||
|
|
||||||
|
if (popup && !path.includes(popup) && (!btn || !path.includes(btn))) {
|
||||||
|
this.closePlaylistPicker();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private panelWidth = DEFAULT_WIDTH;
|
private panelWidth = DEFAULT_WIDTH;
|
||||||
|
|
||||||
static override styles = css`
|
static override styles = css`
|
||||||
@@ -75,7 +94,7 @@ export class QueuePanel extends LitElement {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-button {
|
.save-playlist-button {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
@@ -85,10 +104,19 @@ export class QueuePanel extends LitElement {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-button:hover {
|
.save-playlist-button:hover {
|
||||||
color: #ffd43b;
|
color: #ffd43b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.save-playlist-button:disabled {
|
||||||
|
color: #555;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
#save-playlist-popup {
|
||||||
|
z-index: 210;
|
||||||
|
}
|
||||||
|
|
||||||
.track-list {
|
.track-list {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -193,24 +221,56 @@ export class QueuePanel extends LitElement {
|
|||||||
this.style.setProperty('--queue-width', `${this.panelWidth}px`);
|
this.style.setProperty('--queue-width', `${this.panelWidth}px`);
|
||||||
document.addEventListener('mousemove', this.handleMouseMove);
|
document.addEventListener('mousemove', this.handleMouseMove);
|
||||||
document.addEventListener('mouseup', this.handleMouseUp);
|
document.addEventListener('mouseup', this.handleMouseUp);
|
||||||
|
document.addEventListener('click', this.closePickerHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
override disconnectedCallback() {
|
override disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
document.removeEventListener('mousemove', this.handleMouseMove);
|
document.removeEventListener('mousemove', this.handleMouseMove);
|
||||||
document.removeEventListener('mouseup', this.handleMouseUp);
|
document.removeEventListener('mouseup', this.handleMouseUp);
|
||||||
|
document.removeEventListener('click', this.closePickerHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleClose() {
|
private async handleSaveAsPlaylist() {
|
||||||
this.open = false;
|
if (this.queue.tracks.length === 0) return;
|
||||||
this.dispatchEvent(
|
|
||||||
new CustomEvent('queue-panel-close', {
|
this.playlistPickerOpen = !this.playlistPickerOpen;
|
||||||
bubbles: true,
|
|
||||||
composed: true,
|
await this.updateComplete;
|
||||||
}),
|
|
||||||
);
|
const popup = this.savePlaylistPopup;
|
||||||
|
const btn = this.shadowRoot?.querySelector('.save-playlist-button');
|
||||||
|
|
||||||
|
if (popup && btn) {
|
||||||
|
(popup as any).anchor = btn;
|
||||||
|
(popup as any).active = this.playlistPickerOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.playlistPickerOpen) {
|
||||||
|
const picker = this.shadowRoot?.querySelector(
|
||||||
|
'playlist-picker',
|
||||||
|
) as PlaylistPicker | null;
|
||||||
|
|
||||||
|
picker?.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private closePlaylistPicker() {
|
||||||
|
if (!this.playlistPickerOpen) return;
|
||||||
|
|
||||||
|
this.playlistPickerOpen = false;
|
||||||
|
|
||||||
|
const popup = this.savePlaylistPopup;
|
||||||
|
|
||||||
|
if (popup) {
|
||||||
|
(popup as any).active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPlaylistActionComplete = () => {
|
||||||
|
this.closePlaylistPicker();
|
||||||
|
};
|
||||||
|
|
||||||
private handleRemoveTrack(e: Event, position: number) {
|
private handleRemoveTrack(e: Event, position: number) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.queue.removeFromQueue(position);
|
this.queue.removeFromQueue(position);
|
||||||
@@ -275,11 +335,32 @@ export class QueuePanel extends LitElement {
|
|||||||
></div>
|
></div>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h3>Queue</h3>
|
<h3>Queue</h3>
|
||||||
<button class="close-button" @click=${this.handleClose}>
|
<button
|
||||||
<wa-icon name="xmark"></wa-icon>
|
class="save-playlist-button"
|
||||||
|
@click=${this.handleSaveAsPlaylist}
|
||||||
|
?disabled=${tracks.length === 0}
|
||||||
|
title="Save queue as playlist"
|
||||||
|
>
|
||||||
|
<wa-icon name="plus"></wa-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<wa-popup
|
||||||
|
id="save-playlist-popup"
|
||||||
|
placement="bottom-end"
|
||||||
|
.active=${this.playlistPickerOpen}
|
||||||
|
>
|
||||||
|
${this.playlistPickerOpen
|
||||||
|
? html`
|
||||||
|
<playlist-picker
|
||||||
|
.filePaths=${tracks.map((t) => t.filePath)}
|
||||||
|
@playlist-action-complete=${this.onPlaylistActionComplete}
|
||||||
|
@click=${(e: Event) => e.stopPropagation()}
|
||||||
|
></playlist-picker>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</wa-popup>
|
||||||
|
|
||||||
${tracks.length === 0
|
${tracks.length === 0
|
||||||
? html`
|
? html`
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { flow } from '@lit-labs/virtualizer/layouts/flow.js';
|
|||||||
import '@awesome.me/webawesome/dist/components/popup/popup.js';
|
import '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||||
import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';
|
import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';
|
||||||
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||||
|
import '@components/playlist-picker/playlist-picker.js';
|
||||||
|
import type { PlaylistPicker } from '@components/playlist-picker/playlist-picker.js';
|
||||||
|
|
||||||
@customElement('track-list')
|
@customElement('track-list')
|
||||||
export class TrackList extends LitElement {
|
export class TrackList extends LitElement {
|
||||||
@@ -26,9 +28,15 @@ export class TrackList extends LitElement {
|
|||||||
@state()
|
@state()
|
||||||
private contextMenuTrack: library.Track | null = null;
|
private contextMenuTrack: library.Track | null = null;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private playlistSubmenuOpen = false;
|
||||||
|
|
||||||
@query('#context-menu')
|
@query('#context-menu')
|
||||||
private contextMenuPopup!: HTMLElement;
|
private contextMenuPopup!: HTMLElement;
|
||||||
|
|
||||||
|
@query('#playlist-submenu')
|
||||||
|
private playlistSubmenuPopup!: HTMLElement;
|
||||||
|
|
||||||
private closeHandler = () => this.closeContextMenu();
|
private closeHandler = () => this.closeContextMenu();
|
||||||
|
|
||||||
static override styles = css`
|
static override styles = css`
|
||||||
@@ -128,6 +136,20 @@ export class TrackList extends LitElement {
|
|||||||
.context-menu-panel wa-dropdown-item:hover {
|
.context-menu-panel wa-dropdown-item:hover {
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.submenu-item {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-arrow {
|
||||||
|
font-size: 10px;
|
||||||
|
margin-left: auto;
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#playlist-submenu {
|
||||||
|
z-index: 210;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
override connectedCallback() {
|
override connectedCallback() {
|
||||||
@@ -214,6 +236,7 @@ export class TrackList extends LitElement {
|
|||||||
private closeContextMenu() {
|
private closeContextMenu() {
|
||||||
if (!this.contextMenuOpen) return;
|
if (!this.contextMenuOpen) return;
|
||||||
|
|
||||||
|
this.closePlaylistSubmenu();
|
||||||
this.contextMenuOpen = false;
|
this.contextMenuOpen = false;
|
||||||
this.contextMenuTrack = null;
|
this.contextMenuTrack = null;
|
||||||
|
|
||||||
@@ -224,6 +247,44 @@ export class TrackList extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async showPlaylistSubmenu() {
|
||||||
|
if (this.playlistSubmenuOpen) return;
|
||||||
|
|
||||||
|
this.playlistSubmenuOpen = true;
|
||||||
|
|
||||||
|
await this.updateComplete;
|
||||||
|
|
||||||
|
const submenu = this.playlistSubmenuPopup;
|
||||||
|
const trigger = this.shadowRoot?.querySelector('.submenu-item');
|
||||||
|
|
||||||
|
if (submenu && trigger) {
|
||||||
|
(submenu as any).anchor = trigger;
|
||||||
|
(submenu as any).active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const picker = this.shadowRoot?.querySelector(
|
||||||
|
'playlist-picker',
|
||||||
|
) as PlaylistPicker | null;
|
||||||
|
|
||||||
|
picker?.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private closePlaylistSubmenu() {
|
||||||
|
if (!this.playlistSubmenuOpen) return;
|
||||||
|
|
||||||
|
this.playlistSubmenuOpen = false;
|
||||||
|
|
||||||
|
const submenu = this.playlistSubmenuPopup;
|
||||||
|
|
||||||
|
if (submenu) {
|
||||||
|
(submenu as any).active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPlaylistActionComplete = () => {
|
||||||
|
this.closeContextMenu();
|
||||||
|
};
|
||||||
|
|
||||||
private isActiveTrack(track: library.Track): boolean {
|
private isActiveTrack(track: library.Track): boolean {
|
||||||
const currentTrack = this.player.currentTrack;
|
const currentTrack = this.player.currentTrack;
|
||||||
|
|
||||||
@@ -298,10 +359,38 @@ export class TrackList extends LitElement {
|
|||||||
<wa-icon slot="icon" name="forward-step"></wa-icon>
|
<wa-icon slot="icon" name="forward-step"></wa-icon>
|
||||||
Play Next
|
Play Next
|
||||||
</wa-dropdown-item>
|
</wa-dropdown-item>
|
||||||
|
<wa-dropdown-item
|
||||||
|
class="submenu-item"
|
||||||
|
@mouseenter=${() => this.showPlaylistSubmenu()}
|
||||||
|
@click=${(e: Event) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
void this.showPlaylistSubmenu();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<wa-icon slot="icon" name="plus"></wa-icon>
|
||||||
|
Add to Playlist
|
||||||
|
<span class="submenu-arrow">▶</span>
|
||||||
|
</wa-dropdown-item>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
</wa-popup>
|
</wa-popup>
|
||||||
|
|
||||||
|
<wa-popup
|
||||||
|
id="playlist-submenu"
|
||||||
|
placement="right-start"
|
||||||
|
.active=${this.playlistSubmenuOpen}
|
||||||
|
>
|
||||||
|
${this.playlistSubmenuOpen && this.contextMenuTrack
|
||||||
|
? html`
|
||||||
|
<playlist-picker
|
||||||
|
.filePaths=${[this.contextMenuTrack.FilePath]}
|
||||||
|
@playlist-action-complete=${this.onPlaylistActionComplete}
|
||||||
|
@click=${(e: Event) => e.stopPropagation()}
|
||||||
|
></playlist-picker>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</wa-popup>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,3 +43,22 @@ export namespace library {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export namespace playlist {
|
||||||
|
|
||||||
|
export class Summary {
|
||||||
|
ID: number;
|
||||||
|
Name: string;
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new Summary(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.ID = source["ID"];
|
||||||
|
this.Name = source["Name"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
import {playlist} from '../models';
|
||||||
|
import {context} from '../models';
|
||||||
|
|
||||||
|
export function AddTracksToPlaylist(arg1:number,arg2:Array<string>):Promise<void>;
|
||||||
|
|
||||||
|
export function CreatePlaylist(arg1:string):Promise<playlist.Summary>;
|
||||||
|
|
||||||
|
export function CreatePlaylistWithTracks(arg1:string,arg2:Array<string>):Promise<playlist.Summary>;
|
||||||
|
|
||||||
|
export function GetAllPlaylists():Promise<Array<playlist.Summary>>;
|
||||||
|
|
||||||
|
export function SetContext(arg1:context.Context):Promise<void>;
|
||||||
Executable
+23
@@ -0,0 +1,23 @@
|
|||||||
|
// @ts-check
|
||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
export function AddTracksToPlaylist(arg1, arg2) {
|
||||||
|
return window['go']['playlist']['Service']['AddTracksToPlaylist'](arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CreatePlaylist(arg1) {
|
||||||
|
return window['go']['playlist']['Service']['CreatePlaylist'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CreatePlaylistWithTracks(arg1, arg2) {
|
||||||
|
return window['go']['playlist']['Service']['CreatePlaylistWithTracks'](arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetAllPlaylists() {
|
||||||
|
return window['go']['playlist']['Service']['GetAllPlaylists']();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SetContext(arg1) {
|
||||||
|
return window['go']['playlist']['Service']['SetContext'](arg1);
|
||||||
|
}
|
||||||
Regular → Executable
Regular → Executable
@@ -85,6 +85,8 @@ require (
|
|||||||
github.com/ckaznocha/intrange v0.3.1 // indirect
|
github.com/ckaznocha/intrange v0.3.1 // indirect
|
||||||
github.com/cli/browser v1.3.0 // indirect
|
github.com/cli/browser v1.3.0 // indirect
|
||||||
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
|
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
|
||||||
|
github.com/cloudflare/circl v1.3.7 // indirect
|
||||||
|
github.com/containerd/console v1.0.3 // indirect
|
||||||
github.com/creack/pty v1.1.24 // indirect
|
github.com/creack/pty v1.1.24 // indirect
|
||||||
github.com/cubicdaiya/gonp v1.0.4 // indirect
|
github.com/cubicdaiya/gonp v1.0.4 // indirect
|
||||||
github.com/curioswitch/go-reassign v0.3.0 // indirect
|
github.com/curioswitch/go-reassign v0.3.0 // indirect
|
||||||
@@ -96,7 +98,9 @@ require (
|
|||||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/ebitengine/oto/v3 v3.3.3 // indirect
|
github.com/ebitengine/oto/v3 v3.3.3 // indirect
|
||||||
github.com/ebitengine/purego v0.8.4 // indirect
|
github.com/ebitengine/purego v0.9.1 // indirect
|
||||||
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
|
github.com/ettle/strcase v0.2.0 // indirect
|
||||||
github.com/evilmartians/lefthook v1.13.6 // indirect
|
github.com/evilmartians/lefthook v1.13.6 // indirect
|
||||||
github.com/fatih/color v1.18.0 // indirect
|
github.com/fatih/color v1.18.0 // indirect
|
||||||
github.com/fatih/structtag v1.2.0 // indirect
|
github.com/fatih/structtag v1.2.0 // indirect
|
||||||
@@ -341,7 +345,6 @@ require (
|
|||||||
golang.org/x/crypto v0.48.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||||
golang.org/x/exp/typeparams v0.0.0-20251125195548-87e1e737ad39 // indirect
|
golang.org/x/exp/typeparams v0.0.0-20251125195548-87e1e737ad39 // indirect
|
||||||
golang.org/x/image v0.12.0 // indirect
|
|
||||||
golang.org/x/mod v0.33.0 // indirect
|
golang.org/x/mod v0.33.0 // indirect
|
||||||
golang.org/x/net v0.50.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
@@ -377,6 +380,7 @@ tool (
|
|||||||
github.com/sqlc-dev/sqlc/cmd/sqlc
|
github.com/sqlc-dev/sqlc/cmd/sqlc
|
||||||
github.com/wailsapp/wails/v2/cmd/wails
|
github.com/wailsapp/wails/v2/cmd/wails
|
||||||
golang.org/x/vuln/cmd/govulncheck
|
golang.org/x/vuln/cmd/govulncheck
|
||||||
|
yellowjacket
|
||||||
)
|
)
|
||||||
|
|
||||||
// replace github.com/TheCodeOfCaleb/beep/v2 => /mnt/vault/dev/golang/beep/
|
// replace github.com/TheCodeOfCaleb/beep/v2 => /mnt/vault/dev/golang/beep/
|
||||||
|
|||||||
@@ -216,8 +216,14 @@ github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7Lsp
|
|||||||
github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk=
|
github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk=
|
||||||
github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo=
|
github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo=
|
||||||
github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk=
|
github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
|
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
|
||||||
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||||
|
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||||
|
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
|
||||||
|
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||||
@@ -274,8 +280,27 @@ github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3
|
|||||||
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
||||||
|
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
|
github.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1f+MzD0=
|
||||||
|
github.com/ghostiam/protogetter v0.3.20/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI=
|
||||||
|
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||||
|
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||||
|
github.com/go-critic/go-critic v0.14.3 h1:5R1qH2iFeo4I/RJU8vTezdqs08Egi4u5p6vOESA0pog=
|
||||||
|
github.com/go-critic/go-critic v0.14.3/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ=
|
||||||
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||||
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||||
|
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
||||||
|
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||||
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||||
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||||
|
github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0=
|
||||||
|
github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A=
|
||||||
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU=
|
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU=
|
||||||
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
|
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
@@ -516,6 +541,10 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
|
|||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
github.com/jszwec/csvutil v1.5.1/go.mod h1:Rpu7Uu9giO9subDyMCIQfHVDuLrcaC36UA4YcJjGBkg=
|
github.com/jszwec/csvutil v1.5.1/go.mod h1:Rpu7Uu9giO9subDyMCIQfHVDuLrcaC36UA4YcJjGBkg=
|
||||||
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
|
github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ=
|
||||||
|
github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY=
|
||||||
github.com/kaptinlin/go-i18n v0.2.2 h1:kebVCZme/BrCTqonh/J+VYCl1+Of5C18bvyn3DRPl5M=
|
github.com/kaptinlin/go-i18n v0.2.2 h1:kebVCZme/BrCTqonh/J+VYCl1+Of5C18bvyn3DRPl5M=
|
||||||
github.com/kaptinlin/go-i18n v0.2.2/go.mod h1:MiwkeHryBopAhC/M3zEwIM/2IN8TvTqJQswPw6kceqM=
|
github.com/kaptinlin/go-i18n v0.2.2/go.mod h1:MiwkeHryBopAhC/M3zEwIM/2IN8TvTqJQswPw6kceqM=
|
||||||
github.com/kaptinlin/jsonpointer v0.4.8 h1:HocHcXrOBfP/nUJw0YYjed/TlQvuCAY6uRs3Qok7F6g=
|
github.com/kaptinlin/jsonpointer v0.4.8 h1:HocHcXrOBfP/nUJw0YYjed/TlQvuCAY6uRs3Qok7F6g=
|
||||||
@@ -625,6 +654,8 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP
|
|||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||||
github.com/mattn/go-tty v0.0.7 h1:KJ486B6qI8+wBO7kQxYgmmEFDaFEE96JMBQ7h400N8Q=
|
github.com/mattn/go-tty v0.0.7 h1:KJ486B6qI8+wBO7kQxYgmmEFDaFEE96JMBQ7h400N8Q=
|
||||||
@@ -778,6 +809,14 @@ github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDj
|
|||||||
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=
|
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=
|
||||||
github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY=
|
github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY=
|
||||||
github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc=
|
github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc=
|
||||||
|
github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0=
|
||||||
|
github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4=
|
||||||
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
||||||
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||||
|
github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw=
|
||||||
|
github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ=
|
||||||
|
github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ=
|
||||||
|
github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8=
|
||||||
github.com/schollz/progressbar/v3 v3.19.0 h1:Ea18xuIRQXLAUidVDox3AbwfUhD0/1IvohyTutOIFoc=
|
github.com/schollz/progressbar/v3 v3.19.0 h1:Ea18xuIRQXLAUidVDox3AbwfUhD0/1IvohyTutOIFoc=
|
||||||
github.com/schollz/progressbar/v3 v3.19.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
|
github.com/schollz/progressbar/v3 v3.19.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
|
||||||
github.com/securego/gosec/v2 v2.22.11 h1:tW+weM/hCM/GX3iaCV91d5I6hqaRT2TPsFM1+USPXwg=
|
github.com/securego/gosec/v2 v2.22.11 h1:tW+weM/hCM/GX3iaCV91d5I6hqaRT2TPsFM1+USPXwg=
|
||||||
@@ -971,8 +1010,21 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||||
|
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||||
|
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||||
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||||
@@ -985,6 +1037,12 @@ golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+o
|
|||||||
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||||
golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ=
|
golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ=
|
||||||
golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk=
|
golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
@@ -1002,8 +1060,15 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||||
|
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
@@ -1037,8 +1102,21 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
|
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -1048,6 +1126,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
|
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -1105,15 +1185,24 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 h1:bTLqdHv7xrGlFbvf5/TXNxy/iUwwdkjhqQTJDjW7aj0=
|
||||||
|
golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
|
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||||
|
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||||
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
@@ -1121,9 +1210,13 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
@@ -1174,8 +1267,16 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|||||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||||
|
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||||
|
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||||
|
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
|
||||||
|
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
||||||
|
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
|
||||||
|
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
|
||||||
|
golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I=
|
||||||
|
golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
Reference in New Issue
Block a user