Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules
.vscode
.idea
dist

# Turborepo cache
Expand Down
24 changes: 24 additions & 0 deletions examples/contained/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
3 changes: 3 additions & 0 deletions examples/contained/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `webamp/contained` Example

Example for Webamp fully contained into a DOM element. Uses [Vite](https://vitejs.dev/) for development and bundling.
13 changes: 13 additions & 0 deletions examples/contained/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Webamp (contained)</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
18 changes: 18 additions & 0 deletions examples/contained/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "contained",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"devDependencies": {
"typescript": "~5.8.3",
"vite": "^7.0.4"
},
"dependencies": {
"webamp": "^2.2.0"
}
}
36 changes: 36 additions & 0 deletions examples/contained/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Webamp from "../../../packages/webamp/js/webamp";

const webamp = new Webamp({
initialTracks: [
{
metaData: {
artist: "DJ Mike Llama",
title: "Llama Whippin' Intro",
},
// NOTE: Your audio file must be served from the same domain as your HTML
// file, or served with permissive CORS HTTP headers:
// https://docs.webamp.org/docs/guides/cors
url: "https://cdn.jsdelivr.net/gh/captbaritone/webamp@43434d82cfe0e37286dbbe0666072dc3190a83bc/mp3/llama-2.91.mp3",
duration: 5.322286,
},
],
windowLayout: {
main: { position: { left: 0, top: 0 } },
equalizer: { position: { left: 0, top: 116 } },
playlist: {
position: { left: 0, top: 232 },
size: { extraHeight: 4, extraWidth: 0 },
},
},
});

// Container smaller than body
const container = document.getElementById("app");
container!.style.position = "absolute";
container!.style.left = "20px";
container!.style.top = "20px";
container!.style.right = "120px";
container!.style.bottom = "120px";
container!.style.backgroundColor = "lightyellow";

webamp.renderWhenReady(container!, true);
1 change: 1 addition & 0 deletions examples/contained/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
25 changes: 25 additions & 0 deletions examples/contained/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}
2 changes: 1 addition & 1 deletion packages/skin-database/addSkin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ async function addClassicSkinFromBuffer(
await setHashesForSkin(skin);

// Disable while we figure out our quota
// await Skins.updateSearchIndex(ctx, md5);
await Skins.updateSearchIndex(ctx, md5);

return { md5, status: "ADDED", skinType: "CLASSIC" };
}
Expand Down
2 changes: 1 addition & 1 deletion packages/skin-database/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ program
);
const md5s = rows.map((row) => row.md5);
console.log(md5s.length);
console.log(await Skins.updateSearchIndexs(ctx, md5s));
console.log(await Skins.updateSearchIndexes(ctx, md5s));
}
if (refreshContentHash) {
const ctx = new UserContext();
Expand Down
4 changes: 2 additions & 2 deletions packages/skin-database/data/skins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ async function getSearchIndexes(
);
}

export async function updateSearchIndexs(
export async function updateSearchIndexes(
ctx: UserContext,
md5s: string[]
): Promise<any> {
Expand All @@ -276,7 +276,7 @@ export async function updateSearchIndex(
ctx: UserContext,
md5: string
): Promise<any | null> {
return updateSearchIndexs(ctx, [md5]);
return updateSearchIndexes(ctx, [md5]);
}

export async function hideSkin(md5: string): Promise<void> {
Expand Down
2 changes: 2 additions & 0 deletions packages/webamp-docs/docs/03_initialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Create a DOM element somewhere in your HTML document. This will be used by Webam

:::tip
**Webamp will not actually insert itself as a child of this element.** It will will insert itself as a child of the body element, and will attempt to center itself within this element. This is needed to allow the various Webamp windows to dragged around the page unencumbered.

If you want Webamp to be a child of a specific element, use the [`renderInto(domNode)`](./06_API/03_instance-methods.md#renderintodomnode-htmlelement-promisevoid) method instead.
:::

## Initialize Webamp instance
Expand Down
13 changes: 13 additions & 0 deletions packages/webamp-docs/docs/06_API/03_instance-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,19 @@ Toggle repeat mode between enabled and disabled.
webamp.toggleRepeat();
```

### `renderInto(domNode: HTMLElement): Promise<void>`

Webamp will wait until it has fetched the skin and fully parsed it, and then render itself as a child of the provided `domNode` and position itself in the center of that DOM node.

A promise is returned which will resolve after the render is complete.

```ts
const container = document.getElementById("webamp-container");
webamp.renderWhenReady(container).then(() => {
console.log("rendered webamp!");
});
```

### `renderWhenReady(domNode: HTMLElement): Promise<void>`

Webamp will wait until it has fetched the skin and fully parsed it, and then render itself into a new DOM node at the end of the `<body>` tag.
Expand Down
1 change: 1 addition & 0 deletions packages/webamp-docs/docs/12_changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ If you want access to the changes in this section before they are officially rel

### Improvements

- Added a new Webamp instance method: [`webamp.renderInto(domNode)`](./06_API/03_instance-methods.md#renderintodomnode-htmlelement-promisevoid). Which renders Webamp as a _child_ of the provided DOM node, rather than within its own top-level container.
- Added new [`requireButterchurnPresets`](./06_API/02_webamp-constructor.md#requirebutterchurnpresets---promisepreset) option when constructing a Webamp instance. This allows you to specify which Butterchurn presets to use for the Milkdrop visualizer. If you don't specify this option, Webamp will use the default Butterchurn presets.

### Bug Fixes
Expand Down
39 changes: 26 additions & 13 deletions packages/webamp/js/actionCreators/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,20 +104,27 @@ export function updateWindowPositions(
return { type: "UPDATE_WINDOW_POSITIONS", positions, absolute };
}

export function centerWindowsInContainer(container: HTMLElement): Thunk {
export function centerWindowsInContainer(
container: HTMLElement,
contained: boolean
): Thunk {
return (dispatch, getState) => {
if (!Selectors.getPositionsAreRelative(getState())) {
return;
}
const { left, top } = container.getBoundingClientRect();
const { left, top } = contained
? { left: 0, top: 0 }
: container.getBoundingClientRect();
const { scrollWidth: width, scrollHeight: height } = container;
dispatch(centerWindows({ left, top, width, height }));
};
}

export function centerWindowsInView(): Thunk {
const height = window.innerHeight;
const width = window.innerWidth;
export function centerWindowsInView(parentDomNode?: HTMLElement): Thunk {
const { width, height } =
parentDomNode === document.body || !parentDomNode
? { width: window.innerWidth, height: window.innerHeight }
: Utils.getElementSize(parentDomNode);
return centerWindows({ left: 0, top: 0, width, height });
}

Expand Down Expand Up @@ -168,13 +175,16 @@ export function centerWindows({ left, top, width, height }: Box): Thunk {
};
}

export function browserWindowSizeChanged(size: {
height: number;
width: number;
}): Thunk {
export function browserWindowSizeChanged(
size: {
height: number;
width: number;
},
parentDomNode?: HTMLElement
): Thunk {
return (dispatch: Dispatch) => {
dispatch({ type: "BROWSER_WINDOW_SIZE_CHANGED", ...size });
dispatch(ensureWindowsAreOnScreen());
dispatch(ensureWindowsAreOnScreen(parentDomNode));
};
}

Expand Down Expand Up @@ -234,13 +244,16 @@ export function setWindowLayout(layout?: WindowLayout): Thunk {
};
}

export function ensureWindowsAreOnScreen(): Thunk {
export function ensureWindowsAreOnScreen(parentDomNode?: HTMLElement): Thunk {
return (dispatch, getState) => {
const state = getState();

const windowsInfo = Selectors.getWindowsInfo(state);
const getOpen = Selectors.getWindowOpen(state);
const { height, width } = Utils.getWindowSize();
const { height, width } =
parentDomNode === document.body || !parentDomNode
? Utils.getWindowSize()
: Utils.getElementSize(parentDomNode);
const bounding = Utils.calculateBoundingBox(
windowsInfo.filter((w) => getOpen(w.key))
);
Expand Down Expand Up @@ -294,6 +307,6 @@ export function ensureWindowsAreOnScreen(): Thunk {
// I give up. Just reset everything.
dispatch(resetWindowSizes());
dispatch(stackWindows());
dispatch(centerWindowsInView());
dispatch(centerWindowsInView(parentDomNode));
};
}
9 changes: 6 additions & 3 deletions packages/webamp/js/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export default function App({
webampNode.style.right = "0";
webampNode.style.bottom = "0";
webampNode.style.overflow = "hidden";
browserWindowSizeChanged(Utils.getWindowSize());
browserWindowSizeChanged(Utils.getWindowSize(), parentDomNode);
webampNode.style.right = "auto";
webampNode.style.bottom = "auto";
webampNode.style.overflow = "visible";
Expand All @@ -100,7 +100,7 @@ export default function App({
return () => {
window.removeEventListener("resize", handleWindowResize);
};
}, [browserWindowSizeChanged, webampNode]);
}, [parentDomNode, browserWindowSizeChanged, webampNode]);

useEffect(() => {
if (onMount != null) {
Expand Down Expand Up @@ -151,7 +151,10 @@ export default function App({
<ContextMenuWrapper
renderContents={() => <MainContextMenu filePickers={filePickers} />}
>
<WindowManager windows={renderWindows()} />
<WindowManager
windows={renderWindows()}
parentDomNode={parentDomNode}
/>
</ContextMenuWrapper>
</div>
</StrictMode>,
Expand Down
23 changes: 16 additions & 7 deletions packages/webamp/js/components/WindowManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const abuts = (a: Box, b: Box) => {

interface Props {
windows: { [windowId: string]: ReactNode };
parentDomNode: HTMLElement;
}

type DraggingState = {
Expand All @@ -25,9 +26,12 @@ type DraggingState = {
mouseStart: Point;
};

function useHandleMouseDown(propsWindows: {
[windowId: string]: ReactNode;
}): (
function useHandleMouseDown(
propsWindows: {
[windowId: string]: ReactNode;
},
parentDomNode: HTMLElement
): (
key: WindowId,
e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>
) => void {
Expand Down Expand Up @@ -69,7 +73,9 @@ function useHandleMouseDown(propsWindows: {

const withinDiff = SnapUtils.snapWithinDiff(
proposedBox,
browserWindowSize
parentDomNode === document.body || !parentDomNode
? browserWindowSize
: Utils.getElementSize(parentDomNode)
);

const finalDiff = SnapUtils.applyMultipleDiffs(
Expand Down Expand Up @@ -102,7 +108,7 @@ function useHandleMouseDown(propsWindows: {
window.removeEventListener("mouseup", handleMouseUp);
window.removeEventListener("touchend", handleMouseUp);
};
}, [browserWindowSize, draggingState, updateWindowPositions]);
}, [parentDomNode, browserWindowSize, draggingState, updateWindowPositions]);

// Mouse down handler
return useCallback(
Expand Down Expand Up @@ -149,10 +155,13 @@ function useHandleMouseDown(propsWindows: {
);
}

export default function WindowManager({ windows: propsWindows }: Props) {
export default function WindowManager({
windows: propsWindows,
parentDomNode,
}: Props) {
const windowsInfo = useTypedSelector(Selectors.getWindowsInfo);
const setFocusedWindow = useActionCreator(Actions.setFocusedWindow);
const handleMouseDown = useHandleMouseDown(propsWindows);
const handleMouseDown = useHandleMouseDown(propsWindows, parentDomNode);

const windows = windowsInfo.filter((w) => propsWindows[w.key]);

Expand Down
10 changes: 10 additions & 0 deletions packages/webamp/js/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,16 @@ export function findLastIndex<T>(arr: T[], cb: (val: T) => boolean) {
return -1;
}

export function getElementSize(domNode: HTMLElement): {
width: number;
height: number;
} {
return {
width: Math.max(domNode.scrollWidth, domNode.offsetWidth),
height: Math.max(domNode.scrollHeight, domNode.offsetHeight),
};
}

export function getWindowSize(): { width: number; height: number } {
// Apparently this is crazy across browsers.
return {
Expand Down
Loading